From 288d64be5402f2f24f6345ad4e55c982b868ba21 Mon Sep 17 00:00:00 2001 From: Vasilis Pantelis Date: Mon, 6 Jan 2025 19:45:52 +0200 Subject: [PATCH] [dscalarm] Process Bypassed Zones Bitfield Dump (616) command Signed-off-by: Vasilis Pantelis --- .../dscalarm/internal/DSCAlarmCode.java | 1 + .../dscalarm/internal/DSCAlarmMessage.java | 347 +++++++++--------- .../handler/DSCAlarmBaseBridgeHandler.java | 160 ++++---- .../internal/handler/ZoneThingHandler.java | 39 +- 4 files changed, 305 insertions(+), 242 deletions(-) diff --git a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmCode.java b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmCode.java index 7e644f39ee1..e130ff04a8f 100644 --- a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmCode.java +++ b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmCode.java @@ -88,6 +88,7 @@ public enum DSCAlarmCode { ZoneRestored("610", "Zone Restored", "610: General status of the zone - restored."), EnvisalinkZoneTimerDump("615", "Envisalink Zone Timer Dump", "615: The raw zone timers used inside the Envisalink."), + BypassedZonesBitfield("616", "Bypassed Zones Bitfield", "616: Bypassed zones bitfield."), DuressAlarm("620", "Duress Alarm", "620: A duress code has been entered on a system keypad."), FireKeyAlarm("621", "Fire Key Alarm", "621: A Fire key alarm has been activated."), FireKeyRestored("622", "Fire Key Alarm Restore", "622: A Fire key alarm has been restored."), diff --git a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmMessage.java b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmMessage.java index ae2bc0dade8..a7e7c7f661f 100644 --- a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmMessage.java +++ b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/DSCAlarmMessage.java @@ -79,187 +79,180 @@ public class DSCAlarmMessage { * Processes the incoming DSC Alarm message and extracts the information. */ private void processDSCAlarmMessage() { - DSCAlarmCode dscAlarmCode; - - if (message.length() > 3) { - try { - if (message.length() >= 8 && message.charAt(2) == ':' && message.charAt(5) == ':') { - timeStamp = message.substring(0, 8); - message = message.substring(9, message.length() - 2); - } else { - message = message.substring(0, message.length() - 2); - } - - codeReceived = message.substring(0, 3); - - if (message.length() >= 4) { - data = message.substring(3); - } - } catch (Exception e) { - logger.warn("processDSCAlarmMessage(): Error processing message: ({}) ", message, e); - return; - } - - dscAlarmCode = DSCAlarmCode.getDSCAlarmCodeValue(codeReceived); - - if (dscAlarmCode != null) { - name = dscAlarmCode.getName(); - description = dscAlarmCode.getDescription(); - - MessageParameters messageParms = DSCALARM_MESSAGE_PARAMETERS.get(dscAlarmCode); - - if (messageParms != null) { - boolean hasPartition = messageParms.hasPartition(); - boolean hasZone = messageParms.hasZone(); - - if (hasPartition) { - partition = message.substring(3, 4); - } - - if (hasZone) { - if (hasPartition) { - zone = message.substring(4); - } else { - zone = message.substring(3); - } - } - - messageType = messageParms.getType(); - } - - switch (dscAlarmCode) { - case SystemError: /* 502 */ - int systemErrorCode = 0; - systemErrorCode = Integer.parseInt(data); - switch (systemErrorCode) { - case 1: - error = "Receive Buffer Overrun"; - break; - case 2: - error = "Receive Buffer Overflow"; - break; - case 3: - error = "Transmit Buffer Overflow"; - break; - case 10: - error = "Keybus Transmit Buffer Overrun"; - break; - case 11: - error = "Keybus Transmit Time Timeout"; - break; - case 12: - error = "Keybus Transmit Mode Timeout"; - break; - case 13: - error = "Keybus Transmit Keystring Timeout"; - break; - case 14: - error = "Keybus Interface Not Functioning"; - break; - case 15: - error = "Keybus Busy - Attempting to Disarm or Arm with user code"; - break; - case 16: - error = "Keybus Busy – Lockout"; - break; - case 17: - error = "Keybus Busy – Installers Mode"; - break; - case 18: - error = "Keybus Busy - General Busy"; - break; - case 20: - error = "API Command Syntax Error"; - break; - case 21: - error = "API Command Partition Error - Requested Partition is out of bounds"; - break; - case 22: - error = "API Command Not Supported"; - break; - case 23: - error = "API System Not Armed - Sent in response to a disarm command"; - break; - case 24: - error = "API System Not Ready to Arm - System is either not-secure, in exit-delay, or already armed"; - break; - case 25: - error = "API Command Invalid Length"; - break; - case 26: - error = "API User Code not Required"; - break; - case 27: - error = "API Invalid Characters in Command - No alpha characters are allowed except for checksum"; - break; - case 28: - error = "API Virtual Keypad is Disabled"; - break; - case 29: - error = "API Not Valid Parameter"; - break; - case 30: - error = "API Keypad Does Not Come Out of Blank Mode"; - break; - case 31: - error = "API IT-100 is Already in Thermostat Menu"; - break; - case 32: - error = "API IT-100 is NOT in Thermostat Menu"; - break; - case 33: - error = "API No Response From Thermostat or Escort Module"; - break; - case 0: - default: - error = "No Error"; - break; - } - break; - - case PartitionArmed: /* 652 */ - mode = message.substring(4); - if ("0".equals(mode)) { - name += " (Away)"; - } else if ("1".equals(mode)) { - name += " (Stay)"; - } else if ("2".equals(mode)) { - name += " (ZEA)"; - } else if ("3".equals(mode)) { - name += " (ZES)"; - } - messageType = DSCAlarmMessageType.PARTITION_EVENT; - break; - case UserClosing: /* 700 */ - user = message.substring(4); - name = name.concat(": " + user); - description = codeReceived + ": Partition " + partition + " has been armed by user " + user - + "."; - messageType = DSCAlarmMessageType.PARTITION_EVENT; - break; - case UserOpening: /* 750 */ - user = message.substring(4); - name = name.concat(": " + user); - description = codeReceived + ": Partition " + partition + " has been disarmed by user " + user - + "."; - messageType = DSCAlarmMessageType.PARTITION_EVENT; - break; - - default: - break; - } - - logger.debug( - "parseAPIMessage(): Message Received ({}) - Code: {}, Name: {}, Description: {}, Data: {}\r\n", - message, codeReceived, name, description, data); - } - } else { + if (message.length() <= 3) { codeReceived = "-1"; data = ""; - dscAlarmCode = DSCAlarmCode.getDSCAlarmCodeValue(codeReceived); + DSCAlarmCode dscAlarmCode = DSCAlarmCode.UnknownCode; name = dscAlarmCode.getName(); description = dscAlarmCode.getDescription(); logger.debug("parseAPIMessage(): Invalid Message Received"); + return; + } + + try { + if (message.length() >= 8 && message.charAt(2) == ':' && message.charAt(5) == ':') { + timeStamp = message.substring(0, 8); + message = message.substring(9, message.length() - 2); + } else { + message = message.substring(0, message.length() - 2); + } + + codeReceived = message.substring(0, 3); + + if (message.length() >= 4) { + data = message.substring(3); + } + } catch (Exception e) { + logger.warn("processDSCAlarmMessage(): Error processing message: ({}) ", message, e); + return; + } + + DSCAlarmCode dscAlarmCode = DSCAlarmCode.getDSCAlarmCodeValue(codeReceived); + + if (dscAlarmCode != null) { + name = dscAlarmCode.getName(); + description = dscAlarmCode.getDescription(); + + MessageParameters messageParams = DSCALARM_MESSAGE_PARAMETERS.get(dscAlarmCode); + + if (messageParams != null) { + boolean hasPartition = messageParams.hasPartition(); + boolean hasZone = messageParams.hasZone(); + + if (hasPartition) { + partition = message.substring(3, 4); + } + + if (hasZone) { + int zoneIndex = hasPartition ? 4 : 3; + zone = message.substring(zoneIndex); + } + + messageType = messageParams.getType(); + } + + switch (dscAlarmCode) { + case SystemError: /* 502 */ + int systemErrorCode = 0; + systemErrorCode = Integer.parseInt(data); + switch (systemErrorCode) { + case 1: + error = "Receive Buffer Overrun"; + break; + case 2: + error = "Receive Buffer Overflow"; + break; + case 3: + error = "Transmit Buffer Overflow"; + break; + case 10: + error = "Keybus Transmit Buffer Overrun"; + break; + case 11: + error = "Keybus Transmit Time Timeout"; + break; + case 12: + error = "Keybus Transmit Mode Timeout"; + break; + case 13: + error = "Keybus Transmit Keystring Timeout"; + break; + case 14: + error = "Keybus Interface Not Functioning"; + break; + case 15: + error = "Keybus Busy - Attempting to Disarm or Arm with user code"; + break; + case 16: + error = "Keybus Busy – Lockout"; + break; + case 17: + error = "Keybus Busy – Installers Mode"; + break; + case 18: + error = "Keybus Busy - General Busy"; + break; + case 20: + error = "API Command Syntax Error"; + break; + case 21: + error = "API Command Partition Error - Requested Partition is out of bounds"; + break; + case 22: + error = "API Command Not Supported"; + break; + case 23: + error = "API System Not Armed - Sent in response to a disarm command"; + break; + case 24: + error = "API System Not Ready to Arm - System is either not-secure, in exit-delay, or already armed"; + break; + case 25: + error = "API Command Invalid Length"; + break; + case 26: + error = "API User Code not Required"; + break; + case 27: + error = "API Invalid Characters in Command - No alpha characters are allowed except for checksum"; + break; + case 28: + error = "API Virtual Keypad is Disabled"; + break; + case 29: + error = "API Not Valid Parameter"; + break; + case 30: + error = "API Keypad Does Not Come Out of Blank Mode"; + break; + case 31: + error = "API IT-100 is Already in Thermostat Menu"; + break; + case 32: + error = "API IT-100 is NOT in Thermostat Menu"; + break; + case 33: + error = "API No Response From Thermostat or Escort Module"; + break; + case 0: + default: + error = "No Error"; + break; + } + break; + case PartitionArmed: /* 652 */ + mode = message.substring(4); + if ("0".equals(mode)) { + name += " (Away)"; + } else if ("1".equals(mode)) { + name += " (Stay)"; + } else if ("2".equals(mode)) { + name += " (ZEA)"; + } else if ("3".equals(mode)) { + name += " (ZES)"; + } + messageType = DSCAlarmMessageType.PARTITION_EVENT; + break; + case UserClosing: /* 700 */ + user = message.substring(4); + name = name.concat(": " + user); + description = codeReceived + ": Partition " + partition + " has been armed by user " + user + "."; + messageType = DSCAlarmMessageType.PARTITION_EVENT; + break; + case UserOpening: /* 750 */ + user = message.substring(4); + name = name.concat(": " + user); + description = codeReceived + ": Partition " + partition + " has been disarmed by user " + user + + "."; + messageType = DSCAlarmMessageType.PARTITION_EVENT; + break; + + default: + break; + } + + logger.debug("parseAPIMessage(): Message Received ({}) - Code: {}, Name: {}, Description: {}, Data: {}\r\n", + message, codeReceived, name, description, data); } } @@ -451,6 +444,8 @@ public class DSCAlarmMessage { new MessageParameters(DSCAlarmMessageType.ZONE_EVENT, false, true)); DSCALARM_MESSAGE_PARAMETERS.put(DSCAlarmCode.EnvisalinkZoneTimerDump, new MessageParameters(DSCAlarmMessageType.PANEL_EVENT, false, false)); + DSCALARM_MESSAGE_PARAMETERS.put(DSCAlarmCode.BypassedZonesBitfield, + new MessageParameters(DSCAlarmMessageType.ZONE_EVENT, false, false)); DSCALARM_MESSAGE_PARAMETERS.put(DSCAlarmCode.DuressAlarm, new MessageParameters(DSCAlarmMessageType.PANEL_EVENT, false, false)); DSCALARM_MESSAGE_PARAMETERS.put(DSCAlarmCode.FireKeyAlarm, diff --git a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/DSCAlarmBaseBridgeHandler.java b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/DSCAlarmBaseBridgeHandler.java index e38d3c85dbd..c3d25827f34 100644 --- a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/DSCAlarmBaseBridgeHandler.java +++ b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/DSCAlarmBaseBridgeHandler.java @@ -37,6 +37,7 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -449,94 +450,123 @@ public abstract class DSCAlarmBaseBridgeHandler extends BaseBridgeHandler { return thing; } + public List findAllZoneThings() { + List things = getThing().getThings(); + return things.stream().filter(this::isZoneThing).toList(); + } + + private boolean isZoneThing(Thing thing) { + ThingHandler thingHandler = thing.getHandler(); + if (thingHandler == null) { + return false; + } + + DSCAlarmBaseThingHandler handler = (DSCAlarmBaseThingHandler) thingHandler; + return DSCAlarmThingType.ZONE.equals(handler.getDSCAlarmThingType()); + } + /** * Handles an incoming message from the DSC Alarm System. * * @param incomingMessage */ public synchronized void handleIncomingMessage(String incomingMessage) { - if (incomingMessage != null && !incomingMessage.isEmpty()) { - DSCAlarmMessage dscAlarmMessage = new DSCAlarmMessage(incomingMessage); - DSCAlarmMessageType dscAlarmMessageType = dscAlarmMessage.getDSCAlarmMessageType(); + if (incomingMessage == null || incomingMessage.isEmpty()) { + logger.debug("handleIncomingMessage(): No Message Received!"); + return; + } - logger.debug("handleIncomingMessage(): Message received: {} - {}", incomingMessage, - dscAlarmMessage.toString()); + DSCAlarmMessage dscAlarmMessage = new DSCAlarmMessage(incomingMessage); + DSCAlarmMessageType dscAlarmMessageType = dscAlarmMessage.getDSCAlarmMessageType(); - DSCAlarmEvent event = new DSCAlarmEvent(this); - event.dscAlarmEventMessage(dscAlarmMessage); - DSCAlarmThingType dscAlarmThingType = null; - int partitionId = 0; - int zoneId = 0; + logger.debug("handleIncomingMessage(): Message received: {} - {}", incomingMessage, dscAlarmMessage); - DSCAlarmCode dscAlarmCode = DSCAlarmCode - .getDSCAlarmCodeValue(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.CODE)); + DSCAlarmCode dscAlarmCode = DSCAlarmCode + .getDSCAlarmCodeValue(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.CODE)); - if (panelThingHandler != null) { - panelThingHandler.setPanelMessage(dscAlarmMessage); + if (panelThingHandler != null) { + panelThingHandler.setPanelMessage(dscAlarmMessage); + } + + if (dscAlarmCode == DSCAlarmCode.LoginResponse) { + String dscAlarmMessageData = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA); + if ("3".equals(dscAlarmMessageData)) { + sendCommand(DSCAlarmCode.NetworkLogin); + // onConnected(); + } else if ("1".equals(dscAlarmMessageData)) { + onConnected(); } - - if (dscAlarmCode == DSCAlarmCode.LoginResponse) { - String dscAlarmMessageData = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA); - if ("3".equals(dscAlarmMessageData)) { - sendCommand(DSCAlarmCode.NetworkLogin); - // onConnected(); - } else if ("1".equals(dscAlarmMessageData)) { - onConnected(); - } - return; - } else if (dscAlarmCode == DSCAlarmCode.CommandAcknowledge) { - String dscAlarmMessageData = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA); - if ("000".equals(dscAlarmMessageData)) { - setBridgeStatus(true); - } + return; + } else if (dscAlarmCode == DSCAlarmCode.CommandAcknowledge) { + String dscAlarmMessageData = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA); + if ("000".equals(dscAlarmMessageData)) { + setBridgeStatus(true); } + } - switch (dscAlarmMessageType) { - case PANEL_EVENT: - dscAlarmThingType = DSCAlarmThingType.PANEL; - break; - case PARTITION_EVENT: - dscAlarmThingType = DSCAlarmThingType.PARTITION; - partitionId = Integer - .parseInt(event.getDSCAlarmMessage().getMessageInfo(DSCAlarmMessageInfoType.PARTITION)); - break; - case ZONE_EVENT: - dscAlarmThingType = DSCAlarmThingType.ZONE; - zoneId = Integer.parseInt(event.getDSCAlarmMessage().getMessageInfo(DSCAlarmMessageInfoType.ZONE)); - break; - case KEYPAD_EVENT: - dscAlarmThingType = DSCAlarmThingType.KEYPAD; - break; - default: - break; + DSCAlarmEvent event = new DSCAlarmEvent(this); + event.dscAlarmEventMessage(dscAlarmMessage); + + DSCAlarmThingType dscAlarmThingType = null; + int partitionId = 0; + int zoneId = 0; + + switch (dscAlarmMessageType) { + case PANEL_EVENT: + dscAlarmThingType = DSCAlarmThingType.PANEL; + break; + case PARTITION_EVENT: + dscAlarmThingType = DSCAlarmThingType.PARTITION; + partitionId = Integer + .parseInt(event.getDSCAlarmMessage().getMessageInfo(DSCAlarmMessageInfoType.PARTITION)); + break; + case ZONE_EVENT: + dscAlarmThingType = DSCAlarmThingType.ZONE; + zoneId = Integer.parseInt(event.getDSCAlarmMessage().getMessageInfo(DSCAlarmMessageInfoType.ZONE)); + break; + case KEYPAD_EVENT: + dscAlarmThingType = DSCAlarmThingType.KEYPAD; + break; + default: + break; + } + + if (dscAlarmThingType == null) { + return; + } + + if (DSCAlarmCode.BypassedZonesBitfield.equals(dscAlarmCode)) { + List allZoneThings = findAllZoneThings(); + for (Thing zone : allZoneThings) { + handleIncomingMessage(event, zone, dscAlarmThingType); } + } else { + Thing thing = findThing(dscAlarmThingType, partitionId, zoneId); + handleIncomingMessage(event, thing, dscAlarmThingType); + } + } - if (dscAlarmThingType != null) { - Thing thing = findThing(dscAlarmThingType, partitionId, zoneId); + private void handleIncomingMessage(DSCAlarmEvent event, Thing thing, DSCAlarmThingType dscAlarmThingType) { - logger.debug("handleIncomingMessage(): Thing Search - '{}'", thing); + logger.debug("handleIncomingMessage(): Thing Search - '{}'", thing); - if (thing != null) { - DSCAlarmBaseThingHandler thingHandler = (DSCAlarmBaseThingHandler) thing.getHandler(); + if (thing != null) { + DSCAlarmBaseThingHandler thingHandler = (DSCAlarmBaseThingHandler) thing.getHandler(); - if (thingHandler != null) { - if (thingHandler.isThingHandlerInitialized() && thing.getStatus() == ThingStatus.ONLINE) { - thingHandler.dscAlarmEventReceived(event, thing); + if (thingHandler != null) { + if (thingHandler.isThingHandlerInitialized() && thing.getStatus() == ThingStatus.ONLINE) { + thingHandler.dscAlarmEventReceived(event, thing); - } else { - logger.debug("handleIncomingMessage(): Thing '{}' Not Refreshed!", thing.getUID()); - } - } } else { - logger.debug("handleIncomingMessage(): Thing Not Found! Send to Discovery Service!"); - - if (dscAlarmDiscoveryService != null) { - dscAlarmDiscoveryService.addThing(getThing(), dscAlarmThingType, event); - } + logger.debug("handleIncomingMessage(): Thing '{}' Not Refreshed!", thing.getUID()); } } } else { - logger.debug("handleIncomingMessage(): No Message Received!"); + logger.debug("handleIncomingMessage(): Thing Not Found! Send to Discovery Service!"); + + if (dscAlarmDiscoveryService != null) { + dscAlarmDiscoveryService.addThing(getThing(), dscAlarmThingType, event); + } } } diff --git a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/ZoneThingHandler.java b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/ZoneThingHandler.java index c1aad8b0c00..28de5654f08 100644 --- a/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/ZoneThingHandler.java +++ b/bundles/org.openhab.binding.dscalarm/src/main/java/org/openhab/binding/dscalarm/internal/handler/ZoneThingHandler.java @@ -14,7 +14,9 @@ package org.openhab.binding.dscalarm.internal.handler; import static org.openhab.binding.dscalarm.internal.DSCAlarmBindingConstants.*; +import java.util.ArrayList; import java.util.EventObject; +import java.util.List; import org.openhab.binding.dscalarm.internal.DSCAlarmCode; import org.openhab.binding.dscalarm.internal.DSCAlarmEvent; @@ -129,11 +131,11 @@ public class ZoneThingHandler extends DSCAlarmBaseThingHandler { DSCAlarmEvent dscAlarmEvent = (DSCAlarmEvent) event; DSCAlarmMessage dscAlarmMessage = dscAlarmEvent.getDSCAlarmMessage(); - ChannelUID channelUID = null; DSCAlarmCode dscAlarmCode = DSCAlarmCode .getDSCAlarmCodeValue(dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.CODE)); logger.debug("dscAlarmEventRecieved(): Thing - {} Command - {}", thing.getUID(), dscAlarmCode); + ChannelUID channelUID; int state = 0; String status = ""; @@ -173,10 +175,45 @@ public class ZoneThingHandler extends DSCAlarmBaseThingHandler { updateChannel(channelUID, state, ""); zoneMessage(status); break; + case BypassedZonesBitfield: + state = isZoneByPassed(dscAlarmMessage) ? 1 : 0; + channelUID = new ChannelUID(getThing().getUID(), ZONE_BYPASS_MODE); + updateChannel(channelUID, state, ""); + break; default: break; } } } } + + private boolean isZoneByPassed(DSCAlarmMessage dscAlarmMessage) { + String data = dscAlarmMessage.getMessageInfo(DSCAlarmMessageInfoType.DATA); + List bypassedZones = parseZoneIdsFromHex(data); + return bypassedZones.contains(getZoneNumber()); + } + + private List parseZoneIdsFromHex(String data) { + // List to store bypassed zones + List bypassedZones = new ArrayList<>(); + + // Process each byte in the HEX string + for (int byteIndex = 0; byteIndex < data.length() / 2; byteIndex++) { + // Get two characters representing the byte (2 HEX characters = 1 byte) + String byteHex = data.substring(byteIndex * 2, byteIndex * 2 + 2); + + // Convert the HEX string to an integer + int byteValue = Integer.parseInt(byteHex, 16); + + // Process each bit in the byte (8 bits per byte) + for (int bit = 0; bit < 8; bit++) { + if ((byteValue & (1 << bit)) != 0) { + // Calculate the zone number (1-based) + int zoneNumber = byteIndex * 8 + bit + 1; + bypassedZones.add(zoneNumber); + } + } + } + return bypassedZones; + } }