]> git.basschouten.com Git - openhab-addons.git/blob
24c5e65eabe890d1013e6821f1b5edb2f143f227
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.powermax.internal.message;
14
15 import java.math.BigInteger;
16 import java.util.ArrayList;
17 import java.util.List;
18
19 import org.openhab.binding.powermax.internal.state.PowermaxArmMode;
20 import org.openhab.binding.powermax.internal.state.PowermaxPanelSettings;
21 import org.openhab.binding.powermax.internal.state.PowermaxSensorType;
22 import org.openhab.binding.powermax.internal.state.PowermaxState;
23 import org.openhab.binding.powermax.internal.state.PowermaxZoneSettings;
24
25 /**
26  * A class for STATUS message handling
27  *
28  * @author Laurent Garnier - Initial contribution
29  */
30 public class PowermaxStatusMessage extends PowermaxBaseMessage {
31
32     private static byte[] zoneBytes(byte zones1, byte zones9, byte zones17, byte zones25) {
33         return new byte[] { zones25, zones17, zones9, zones1 };
34     }
35
36     private static boolean[] zoneBits(byte[] zoneBytes) {
37         boolean[] zones = new boolean[33];
38         BigInteger bigint = new BigInteger(1, zoneBytes);
39
40         for (int i = 1; i <= 32; i++) {
41             zones[i] = bigint.testBit(i - 1);
42         }
43
44         return zones;
45     }
46
47     private static String zoneList(byte[] zoneBytes) {
48         boolean[] zones = zoneBits(zoneBytes);
49         List<String> names = new ArrayList<>();
50
51         for (int i = 1; i < zones.length; i++) {
52             if (zones[i]) {
53                 names.add(String.format("Zone %d", i));
54             }
55         }
56
57         return String.join(", ", names);
58     }
59
60     /**
61      * Constructor
62      *
63      * @param message
64      *            the received message as a buffer of bytes
65      */
66     public PowermaxStatusMessage(byte[] message) {
67         super(message);
68     }
69
70     @Override
71     protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) {
72         if (commManager == null) {
73             return null;
74         }
75
76         PowermaxPanelSettings panelSettings = commManager.getPanelSettings();
77         PowermaxState updatedState = commManager.createNewState();
78
79         byte[] message = getRawData();
80         byte eventType = message[3];
81         String eventTypeStr = PowermaxMessageConstants.getZoneEventType(eventType & 0x000000FF);
82
83         debug("Event type", eventType, eventTypeStr);
84
85         // Each event type except 0x04 contains two sets of zone bitmasks.
86         // Each set is four bytes (32 bits) where each bit indicates the state
87         // of the corresponding zone (1 = set, 0 = unset).
88
89         if (eventType == 0x01) {
90             // These bits are set when a zone causes an alarm
91             //
92             // Set 1: Alarm caused by zone being open/tripped
93             // Set 2: Alarm caused by a tamper
94             //
95             // Note: active alarms are cleared when the Memory flag is turned off
96             // (the panel won't send a follow-up event with these bits set to zero)
97
98             byte[] alarmStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
99             byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
100
101             boolean[] alarmStatus = zoneBits(alarmStatusBytes);
102             boolean[] tamperStatus = zoneBits(tamperStatusBytes);
103
104             String alarmStatusStr = zoneList(alarmStatusBytes);
105             String tamperStatusStr = zoneList(tamperStatusBytes);
106
107             panelSettings.getZoneRange().forEach(i -> {
108                 updatedState.getZone(i).alarmed.setValue(alarmStatus[i]);
109                 updatedState.getZone(i).tamperAlarm.setValue(tamperStatus[i]);
110             });
111
112             debug("Alarm status", alarmStatusBytes, alarmStatusStr);
113             debug("Tamper alarm status", tamperStatusBytes, tamperStatusStr);
114         } else if (eventType == 0x02) {
115             // Set 1: List of zones that are open/tripped
116             // Set 2: List of zones that have a low-battery condition
117
118             byte[] zoneStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
119             byte[] batteryStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
120
121             boolean[] zoneStatus = zoneBits(zoneStatusBytes);
122             boolean[] batteryStatus = zoneBits(batteryStatusBytes);
123
124             String zoneStatusStr = zoneList(zoneStatusBytes);
125             String batteryStatusStr = zoneList(batteryStatusBytes);
126
127             panelSettings.getZoneRange().forEach(i -> {
128                 updatedState.getZone(i).tripped.setValue(zoneStatus[i]);
129                 updatedState.getZone(i).lowBattery.setValue(batteryStatus[i]);
130             });
131
132             debug("Zone status", zoneStatusBytes, zoneStatusStr);
133             debug("Battery status", batteryStatusBytes, batteryStatusStr);
134         } else if (eventType == 0x03) {
135             // Set 1: Inactivity / loss of supervision
136             // Set 2: Zone has an active tamper condition
137
138             byte[] inactiveStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
139             byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
140
141             boolean[] inactiveStatus = zoneBits(inactiveStatusBytes);
142             boolean[] tamperStatus = zoneBits(tamperStatusBytes);
143
144             String inactiveStatusStr = zoneList(inactiveStatusBytes);
145             String tamperStatusStr = zoneList(tamperStatusBytes);
146
147             panelSettings.getZoneRange().forEach(i -> {
148                 updatedState.getZone(i).inactive.setValue(inactiveStatus[i]);
149                 updatedState.getZone(i).tampered.setValue(tamperStatus[i]);
150             });
151
152             debug("Inactive status", inactiveStatusBytes, inactiveStatusStr);
153             debug("Tamper status", tamperStatusBytes, tamperStatusStr);
154         } else if (eventType == 0x04) {
155             // System & zone status message (not like the other event types)
156
157             byte sysStatus = message[4];
158             byte sysFlags = message[5];
159             int eventZone = message[6] & 0x000000FF;
160             int zoneEType = message[7] & 0x000000FF;
161             int x10Status = (message[10] & 0x000000FF) | ((message[11] << 8) & 0x0000FF00);
162
163             String eventZoneStr = panelSettings.getZoneOrUserName(eventZone);
164             String zoneETypeStr = PowermaxMessageConstants.getZoneEvent(zoneEType);
165
166             if (zoneEType != 0x00 && eventZone > 0 && eventZone <= panelSettings.getNbZones()) {
167                 updatedState.getZone(eventZone).lastMessage.setValue(zoneETypeStr);
168                 updatedState.getZone(eventZone).lastMessageTime.setValue(System.currentTimeMillis());
169             }
170
171             if (zoneEType == 0x03) {
172                 // Open
173                 updatedState.getZone(eventZone).tripped.setValue(true);
174                 updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
175             } else if (zoneEType == 0x04) {
176                 // Closed
177                 updatedState.getZone(eventZone).tripped.setValue(false);
178             } else if (zoneEType == 0x05) {
179                 // Violated (Motion)
180                 PowermaxZoneSettings zone = panelSettings.getZoneSettings(eventZone);
181                 if ((zone != null) && zone.getSensorType().equalsIgnoreCase("unknown")) {
182                     zone.setSensorType(PowermaxSensorType.MOTION_SENSOR_1.getLabel());
183                 }
184                 updatedState.getZone(eventZone).tripped.setValue(true);
185                 updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
186             }
187
188             // PGM & X10 devices
189             for (int i = 0; i < panelSettings.getNbPGMX10Devices(); i++) {
190                 updatedState.setPGMX10DeviceStatus(i, ((x10Status >> i) & 0x1) > 0);
191             }
192
193             String sysStatusStr = "";
194             if ((sysFlags & 0x1) == 1) {
195                 sysStatusStr = sysStatusStr + "Ready, ";
196                 updatedState.ready.setValue(true);
197             } else {
198                 sysStatusStr = sysStatusStr + "Not ready, ";
199                 updatedState.ready.setValue(false);
200             }
201             if (((sysFlags >> 1) & 0x1) == 1) {
202                 sysStatusStr = sysStatusStr + "Alert in memory, ";
203                 updatedState.alertInMemory.setValue(true);
204             } else {
205                 updatedState.alertInMemory.setValue(false);
206
207                 // When the memory flag is cleared, also clear all zone alarms and tamper alarms
208                 panelSettings.getZoneRange().forEach(i -> {
209                     updatedState.getZone(i).alarmed.setValue(false);
210                     updatedState.getZone(i).tamperAlarm.setValue(false);
211                 });
212             }
213             if (((sysFlags >> 2) & 0x1) == 1) {
214                 sysStatusStr = sysStatusStr + "Trouble, ";
215                 updatedState.trouble.setValue(true);
216             } else {
217                 updatedState.trouble.setValue(false);
218             }
219             if (((sysFlags >> 3) & 0x1) == 1) {
220                 sysStatusStr = sysStatusStr + "Bypass on, ";
221                 updatedState.bypass.setValue(true);
222             } else {
223                 updatedState.bypass.setValue(false);
224                 panelSettings.getZoneRange().forEach(i -> {
225                     updatedState.getZone(i).bypassed.setValue(false);
226                 });
227             }
228             if (((sysFlags >> 4) & 0x1) == 1) {
229                 sysStatusStr = sysStatusStr + "Last 10 seconds, ";
230             }
231             if (((sysFlags >> 5) & 0x1) == 1) {
232                 sysStatusStr = sysStatusStr + zoneETypeStr;
233                 if (eventZone == 0xFF) {
234                     sysStatusStr = sysStatusStr + " from Panel, ";
235                 } else if (eventZone > 0) {
236                     sysStatusStr = sysStatusStr + String.format(" in %s, ", eventZoneStr);
237                 } else {
238                     sysStatusStr = sysStatusStr + ", ";
239                 }
240             }
241             if (((sysFlags >> 6) & 0x1) == 1) {
242                 sysStatusStr = sysStatusStr + "Status changed, ";
243             }
244             if (((sysFlags >> 7) & 0x1) == 1) {
245                 sysStatusStr = sysStatusStr + "Alarm event, ";
246                 updatedState.alarmActive.setValue(true);
247             } else {
248                 updatedState.alarmActive.setValue(false);
249             }
250             sysStatusStr = sysStatusStr.substring(0, sysStatusStr.length() - 2);
251             String statusStr;
252             try {
253                 PowermaxArmMode armMode = PowermaxArmMode.fromCode(sysStatus & 0x000000FF);
254                 statusStr = armMode.getName();
255             } catch (IllegalArgumentException e) {
256                 statusStr = "UNKNOWN";
257             }
258             updatedState.armMode.setValue(statusStr);
259             updatedState.statusStr.setValue(statusStr + ", " + sysStatusStr);
260
261             debug("System status", sysStatus, statusStr);
262             debug("System flags", sysFlags, sysStatusStr);
263             debug("Event zone", eventZone, eventZoneStr);
264             debug("Zone event type", zoneEType, zoneETypeStr);
265             debug("X10 status", x10Status);
266
267             panelSettings.getZoneRange().forEach(i -> {
268                 PowermaxZoneSettings zone = panelSettings.getZoneSettings(i);
269                 if (zone != null) {
270                     // mode: armed or not
271                     int mode = sysStatus & 0x0000000F;
272                     // Zone is shown as armed if
273                     // the sensor type always triggers an alarm
274                     // or the system is armed away
275                     // or the system is armed home and the zone is not interior(-follow)
276                     boolean armed = (!zone.getType().equalsIgnoreCase("Non-Alarm")
277                             && (zone.isAlwaysInAlarm() || (mode == PowermaxArmMode.ARMED_AWAY.getCode())
278                                     || ((mode == PowermaxArmMode.ARMED_HOME.getCode())
279                                             && !zone.getType().equalsIgnoreCase("Interior-Follow")
280                                             && !zone.getType().equalsIgnoreCase("Interior"))));
281                     updatedState.getZone(i).armed.setValue(armed);
282                 }
283             });
284         } else if (eventType == 0x06) {
285             // Set 1: List of zones that are enrolled (we don't currently use this)
286             // Set 2: List of zones that are bypassed
287
288             byte[] zoneBypassBytes = zoneBytes(message[8], message[9], message[10], message[11]);
289             boolean[] zoneBypass = zoneBits(zoneBypassBytes);
290             String zoneBypassStr = zoneList(zoneBypassBytes);
291
292             panelSettings.getZoneRange().forEach(i -> {
293                 updatedState.getZone(i).bypassed.setValue(zoneBypass[i]);
294             });
295
296             debug("Zone bypass", zoneBypassBytes, zoneBypassStr);
297         }
298
299         // Note: in response to a STATUS request, the panel will also send
300         // messages with eventType = 0x05, 0x07, 0x08, and 0x09 but these
301         // haven't been decoded yet
302
303         return updatedState;
304     }
305 }