2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.powermax.internal.message;
15 import java.math.BigInteger;
16 import java.util.ArrayList;
17 import java.util.List;
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;
26 * A class for STATUS message handling
28 * @author Laurent Garnier - Initial contribution
30 public class PowermaxStatusMessage extends PowermaxBaseMessage {
32 private static byte[] zoneBytes(byte zones1, byte zones9, byte zones17, byte zones25) {
33 return new byte[] { zones25, zones17, zones9, zones1 };
36 private static boolean[] zoneBits(byte[] zoneBytes) {
37 boolean[] zones = new boolean[33];
38 BigInteger bigint = new BigInteger(1, zoneBytes);
40 for (int i = 1; i <= 32; i++) {
41 zones[i] = bigint.testBit(i - 1);
47 private static String zoneList(byte[] zoneBytes) {
48 boolean[] zones = zoneBits(zoneBytes);
49 List<String> names = new ArrayList<>();
51 for (int i = 1; i < zones.length; i++) {
53 names.add(String.format("Zone %d", i));
57 return String.join(", ", names);
64 * the received message as a buffer of bytes
66 public PowermaxStatusMessage(byte[] message) {
71 protected PowermaxState handleMessageInternal(PowermaxCommManager commManager) {
72 if (commManager == null) {
76 PowermaxPanelSettings panelSettings = commManager.getPanelSettings();
77 PowermaxState updatedState = commManager.createNewState();
79 byte[] message = getRawData();
80 byte eventType = message[3];
81 String eventTypeStr = PowermaxMessageConstants.getZoneEventType(eventType & 0x000000FF);
83 debug("Event type", eventType, eventTypeStr);
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).
89 if (eventType == 0x01) {
90 // These bits are set when a zone causes an alarm
92 // Set 1: Alarm caused by zone being open/tripped
93 // Set 2: Alarm caused by a tamper
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)
98 byte[] alarmStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
99 byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
101 boolean[] alarmStatus = zoneBits(alarmStatusBytes);
102 boolean[] tamperStatus = zoneBits(tamperStatusBytes);
104 String alarmStatusStr = zoneList(alarmStatusBytes);
105 String tamperStatusStr = zoneList(tamperStatusBytes);
107 panelSettings.getZoneRange().forEach(i -> {
108 updatedState.getZone(i).alarmed.setValue(alarmStatus[i]);
109 updatedState.getZone(i).tamperAlarm.setValue(tamperStatus[i]);
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
118 byte[] zoneStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
119 byte[] batteryStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
121 boolean[] zoneStatus = zoneBits(zoneStatusBytes);
122 boolean[] batteryStatus = zoneBits(batteryStatusBytes);
124 String zoneStatusStr = zoneList(zoneStatusBytes);
125 String batteryStatusStr = zoneList(batteryStatusBytes);
127 panelSettings.getZoneRange().forEach(i -> {
128 updatedState.getZone(i).tripped.setValue(zoneStatus[i]);
129 updatedState.getZone(i).lowBattery.setValue(batteryStatus[i]);
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
138 byte[] inactiveStatusBytes = zoneBytes(message[4], message[5], message[6], message[7]);
139 byte[] tamperStatusBytes = zoneBytes(message[8], message[9], message[10], message[11]);
141 boolean[] inactiveStatus = zoneBits(inactiveStatusBytes);
142 boolean[] tamperStatus = zoneBits(tamperStatusBytes);
144 String inactiveStatusStr = zoneList(inactiveStatusBytes);
145 String tamperStatusStr = zoneList(tamperStatusBytes);
147 panelSettings.getZoneRange().forEach(i -> {
148 updatedState.getZone(i).inactive.setValue(inactiveStatus[i]);
149 updatedState.getZone(i).tampered.setValue(tamperStatus[i]);
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)
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);
163 String eventZoneStr = panelSettings.getZoneOrUserName(eventZone);
164 String zoneETypeStr = PowermaxMessageConstants.getZoneEvent(zoneEType);
166 if (zoneEType != 0x00 && eventZone > 0 && eventZone <= panelSettings.getNbZones()) {
167 updatedState.getZone(eventZone).lastMessage.setValue(zoneETypeStr);
168 updatedState.getZone(eventZone).lastMessageTime.setValue(System.currentTimeMillis());
171 if (zoneEType == 0x03) {
173 updatedState.getZone(eventZone).tripped.setValue(true);
174 updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
175 } else if (zoneEType == 0x04) {
177 updatedState.getZone(eventZone).tripped.setValue(false);
178 } else if (zoneEType == 0x05) {
180 PowermaxZoneSettings zone = panelSettings.getZoneSettings(eventZone);
181 if ((zone != null) && zone.getSensorType().equalsIgnoreCase("unknown")) {
182 zone.setSensorType(PowermaxSensorType.MOTION_SENSOR_1.getLabel());
184 updatedState.getZone(eventZone).tripped.setValue(true);
185 updatedState.getZone(eventZone).lastTripped.setValue(System.currentTimeMillis());
189 for (int i = 0; i < panelSettings.getNbPGMX10Devices(); i++) {
190 updatedState.setPGMX10DeviceStatus(i, ((x10Status >> i) & 0x1) > 0);
193 String sysStatusStr = "";
194 if ((sysFlags & 0x1) == 1) {
195 sysStatusStr = sysStatusStr + "Ready, ";
196 updatedState.ready.setValue(true);
198 sysStatusStr = sysStatusStr + "Not ready, ";
199 updatedState.ready.setValue(false);
201 if (((sysFlags >> 1) & 0x1) == 1) {
202 sysStatusStr = sysStatusStr + "Alert in memory, ";
203 updatedState.alertInMemory.setValue(true);
205 updatedState.alertInMemory.setValue(false);
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);
213 if (((sysFlags >> 2) & 0x1) == 1) {
214 sysStatusStr = sysStatusStr + "Trouble, ";
215 updatedState.trouble.setValue(true);
217 updatedState.trouble.setValue(false);
219 if (((sysFlags >> 3) & 0x1) == 1) {
220 sysStatusStr = sysStatusStr + "Bypass on, ";
221 updatedState.bypass.setValue(true);
223 updatedState.bypass.setValue(false);
224 panelSettings.getZoneRange().forEach(i -> {
225 updatedState.getZone(i).bypassed.setValue(false);
228 if (((sysFlags >> 4) & 0x1) == 1) {
229 sysStatusStr = sysStatusStr + "Last 10 seconds, ";
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);
238 sysStatusStr = sysStatusStr + ", ";
241 if (((sysFlags >> 6) & 0x1) == 1) {
242 sysStatusStr = sysStatusStr + "Status changed, ";
244 if (((sysFlags >> 7) & 0x1) == 1) {
245 sysStatusStr = sysStatusStr + "Alarm event, ";
246 updatedState.alarmActive.setValue(true);
248 updatedState.alarmActive.setValue(false);
250 sysStatusStr = sysStatusStr.substring(0, sysStatusStr.length() - 2);
253 PowermaxArmMode armMode = PowermaxArmMode.fromCode(sysStatus & 0x000000FF);
254 statusStr = armMode.getName();
255 } catch (IllegalArgumentException e) {
256 statusStr = "UNKNOWN";
258 updatedState.armMode.setValue(statusStr);
259 updatedState.statusStr.setValue(statusStr + ", " + sysStatusStr);
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);
267 panelSettings.getZoneRange().forEach(i -> {
268 PowermaxZoneSettings zone = panelSettings.getZoneSettings(i);
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);
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
288 byte[] zoneBypassBytes = zoneBytes(message[8], message[9], message[10], message[11]);
289 boolean[] zoneBypass = zoneBits(zoneBypassBytes);
290 String zoneBypassStr = zoneList(zoneBypassBytes);
292 panelSettings.getZoneRange().forEach(i -> {
293 updatedState.getZone(i).bypassed.setValue(zoneBypass[i]);
296 debug("Zone bypass", zoneBypassBytes, zoneBypassStr);
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