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