From: Konstantin Polihronov Date: Tue, 25 Jul 2023 17:53:09 +0000 (+0300) Subject: [paradoxalarm] Implement zone bypass command and additional zone states (#14557) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=33dd5e7f7010116146cc60cfecd16d854f41e69c;p=openhab-addons.git [paradoxalarm] Implement zone bypass command and additional zone states (#14557) * Reduce warnings 1 Signed-off-by: Konstantin Polihronov * 2 Signed-off-by: Konstantin Polihronov * 3 Signed-off-by: Konstantin Polihronov * Initial files and package refactoring Signed-off-by: Konstantin Polihronov * Implemented zone commands without checksum calculation Signed-off-by: Konstantin Polihronov * More stuff * Added the checksum functionality * Added more examples to the test Signed-off-by: Konstantin Polihronov * Implement ZoneCommand and necessary classes * Refactor the common logic * Extract interface Command * CHange the Response class to use Switch/case Signed-off-by: Konstantin Polihronov * Fully implement the test for creating zone command payload Signed-off-by: Konstantin Polihronov * Fix build / add headers and author to the new files Signed-off-by: Konstantin Polihronov * Add command handling to the zone handler Signed-off-by: Konstantin Polihronov * Add command channel to the Zone thing Signed-off-by: Konstantin Polihronov * Research of zone states and some TODO notes Signed-off-by: Konstantin Polihronov * Retrieval of zone special states from the panel Signed-off-by: Konstantin Polihronov * Fix build Signed-off-by: Konstantin Polihronov * Add the new channels to the metadata file Signed-off-by: Konstantin Polihronov * Add new channels to zone handler Signed-off-by: Konstantin Polihronov * Fix indexing in memory map and add more logging Signed-off-by: Konstantin Polihronov * Refactoring and potential NPE access fixes Signed-off-by: Konstantin Polihronov * Add new property "label" to the discovered zones and partitions Signed-off-by: Konstantin Polihronov * Fix zone command issues * Fix checksum creation * Fix the parse and confirmation of the response Signed-off-by: Konstantin Polihronov * Add the new channels to the README.md Signed-off-by: Konstantin Polihronov * Fixed issue with not updating new channels in the zones * A silly copy/paste mistake Signed-off-by: Konstantin Polihronov * Change the type of the new channels from contact to switch As per community discussion this makes more sense and will be more intuitive - when something is true -> make it ON, when it's false -> make it OFF. OPEN and CLOSED are not fitting so well here... Signed-off-by: Konstantin Polihronov * Fix issue that the channel label is always NULL * For both zone and partitions Signed-off-by: Konstantin Polihronov * Add new types and channels to the i18n Signed-off-by: Konstantin Polihronov --------- Signed-off-by: Konstantin Polihronov --- diff --git a/bundles/org.openhab.binding.paradoxalarm/README.md b/bundles/org.openhab.binding.paradoxalarm/README.md index 8f449875ef..55ceb2f95b 100644 --- a/bundles/org.openhab.binding.paradoxalarm/README.md +++ b/bundles/org.openhab.binding.paradoxalarm/README.md @@ -97,11 +97,21 @@ Currently binding supports the following panels: EVO192, EVO48(not tested), EVO9 ### Zone channels: -| Channel | Type | Description | -|-----------------|---------|--------------------------------------------------------------------------------| -| zoneLabel | String | Label of zone inside Paradox configuration | -| openedState | Contact | Zone opened / closed | -| tamperedState | Switch | Zone is tampered / not tampered | +| Channel | Type | Description | +|------------------------------------|---------|--------------------------------------------------------------------------------| +| zoneLabel | String | Label of zone inside Paradox configuration | +| openedState | Contact | Zone opened / closed | +| tamperedState | Switch | Zone is tampered | +| supervisionTrouble | Switch | Zone is in supervision trouble | +| inTxDelay | Switch | Zone is in txDelay | +| shutdown | Switch | Zone is shutdown | +| bypassed | Switch | Zone is bypassed | +| hasActivatedIntellizoneDelay | Switch | Zone is has an activated Intellizone delay | +| hasActivatedEntryDelay | Switch | Zone is has an activated entry delay | +| presentlyInAlarm | Switch | Zone is currently in alarm | +| generatedAlarm | Switch | Zone has generated an alarm | +| command | String | Command for zone. Can be (BYPASS, CLEAR_BYPASS) | + ## Example things configuration diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/EvoCommunicator.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/EvoCommunicator.java index 1bb9c7385e..5c6c0b3e05 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/EvoCommunicator.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/EvoCommunicator.java @@ -87,7 +87,7 @@ public class EvoCommunicator extends GenericCommunicator implements IParadoxComm if (payload != null && payload.length >= RAM_BLOCK_SIZE) { RamRequest request = (RamRequest) response.getRequest(); int ramBlockNumber = request.getRamBlockNumber(); - memoryMap.updateElement(ramBlockNumber, payload); + memoryMap.updateElement(ramBlockNumber - 1, payload); if (logger.isTraceEnabled()) { logger.trace("Result for ramBlock={} is [{}]", ramBlockNumber, ParadoxUtil.byteArrayToString(payload)); } @@ -176,7 +176,21 @@ public class EvoCommunicator extends GenericCommunicator implements IParadoxComm byte[] firstPage = memoryMap.getElement(0); byte[] secondPage = memoryMap.getElement(8); + createZoneOpenedFlags(result, firstPage, secondPage); + createZoneTamperedFlags(result, firstPage, secondPage); + createZoneLowbatteryFlags(result, firstPage, secondPage); + createSpecialZoneFlags(result, memoryMap); + + ParadoxUtil.printByteArray("Zone opened flags", result.getZonesOpened()); + ParadoxUtil.printByteArray("Zone tampered flags", result.getZonesTampered()); + ParadoxUtil.printByteArray("Zone low battery flags", result.getZonesLowBattery()); + ParadoxUtil.printByteArray("Zone special flags", result.getZoneSpecialFlags()); + + return result; + } + + private void createZoneOpenedFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) { int pageOffset = panelType == PanelType.EVO48 ? 34 : 40; byte[] firstBlock = Arrays.copyOfRange(firstPage, 28, pageOffset); if (panelType != PanelType.EVO192) { @@ -186,9 +200,11 @@ public class EvoCommunicator extends GenericCommunicator implements IParadoxComm byte[] zonesOpened = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock); result.setZonesOpened(zonesOpened); } + } - pageOffset = panelType == PanelType.EVO48 ? 46 : 52; - firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset); + private void createZoneTamperedFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) { + int pageOffset = panelType == PanelType.EVO48 ? 46 : 52; + byte[] firstBlock = Arrays.copyOfRange(firstPage, 40, pageOffset); if (panelType != PanelType.EVO192) { result.setZonesTampered(firstBlock); } else { @@ -196,18 +212,51 @@ public class EvoCommunicator extends GenericCommunicator implements IParadoxComm byte[] zonesTampered = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock); result.setZonesTampered(zonesTampered); } + } - pageOffset = panelType == PanelType.EVO48 ? 58 : 64; - firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset); + private void createZoneLowbatteryFlags(ZoneStateFlags result, byte[] firstPage, byte[] secondPage) { + int pageOffset = panelType == PanelType.EVO48 ? 58 : 64; + byte[] firstBlock = Arrays.copyOfRange(firstPage, 52, pageOffset); if (panelType != PanelType.EVO192) { - result.setZonesTampered(firstBlock); + result.setZonesLowBattery(firstBlock); } else { byte[] secondBlock = Arrays.copyOfRange(secondPage, 24, 36); byte[] zonesLowBattery = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock); result.setZonesLowBattery(zonesLowBattery); } + } - return result; + @SuppressWarnings("incomplete-switch") + private void createSpecialZoneFlags(ZoneStateFlags result, MemoryMap memoryMap) { + byte[] page2 = memoryMap.getElement(1); + byte[] page3 = memoryMap.getElement(2); + byte[] page7 = memoryMap.getElement(8); + byte[] page8 = memoryMap.getElement(9); + byte[] page9 = memoryMap.getElement(10); + + switch (panelType) { + case EVO48: + byte[] firstBlock = Arrays.copyOfRange(page2, 0, 48); + result.setZoneSpecialFlags(firstBlock); + break; + case EVO96: + firstBlock = Arrays.copyOf(page2, 64); + byte[] secondBlock = Arrays.copyOfRange(page3, 0, 32); + byte[] specialZoneFlags = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock); + result.setZoneSpecialFlags(specialZoneFlags); + break; + case EVO192: + case EVOHD: + firstBlock = Arrays.copyOf(page2, 64); + secondBlock = Arrays.copyOfRange(page3, 0, 32); + byte[] thirdBlock = Arrays.copyOfRange(page7, 36, 64); + byte[] fourthBlock = Arrays.copyOf(page8, 64); + byte[] fifthBlock = Arrays.copyOfRange(page9, 0, 4); + specialZoneFlags = ParadoxUtil.mergeByteArrays(firstBlock, secondBlock, thirdBlock, fourthBlock, + fifthBlock); + result.setZoneSpecialFlags(specialZoneFlags); + break; + } } public void initializeMemoryMap() { diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/MemoryMap.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/MemoryMap.java index de086e06a4..47520858ec 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/MemoryMap.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/MemoryMap.java @@ -41,6 +41,6 @@ public class MemoryMap { } public synchronized void updateElement(int index, byte[] elementValue) { - ramCache.set(index - 1, elementValue); + ramCache.set(index, elementValue); } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/RequestType.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/RequestType.java index 65fa014250..a6daec7ecf 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/RequestType.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/RequestType.java @@ -21,5 +21,6 @@ public enum RequestType { LOGON_SEQUENCE, RAM, EPROM, - PARTITION_COMMAND + PARTITION_COMMAND, + ZONE_COMMAND } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/Response.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/Response.java index 7d68fd8c10..0e3f9cece2 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/Response.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/Response.java @@ -103,36 +103,52 @@ public class Response implements IResponse { byte highNibble = ParadoxUtil.getHighNibble(receivedCommand); RequestType requestType = request.getType(); - // For EPROM and RAM messages received command must be 0x5x - if (requestType == RequestType.EPROM || requestType == RequestType.RAM) { - if (highNibble == 0x5) { - header = Arrays.copyOfRange(packetBytes, 0, 22); - payload = Arrays.copyOfRange(packetBytes, 22, packetBytes.length - 1); - return; - } + switch (requestType) { + // For EPROM and RAM messages received command must be 0x5x + case EPROM: + case RAM: + if (highNibble == 0x5) { + header = Arrays.copyOfRange(packetBytes, 0, 22); + payload = Arrays.copyOfRange(packetBytes, 22, packetBytes.length - 1); + return; + } + break; + // For logon sequence packets there are various commands but their high nibbles should be either 0x0, 0x1 or // 0x7 - } else if (requestType == RequestType.LOGON_SEQUENCE) { - switch (highNibble) { - case 0x0: - case 0x1: - case 0x7: + case LOGON_SEQUENCE: + switch (highNibble) { + case 0x0: + case 0x1: + case 0x7: + header = Arrays.copyOfRange(packetBytes, 0, 16); + payload = Arrays.copyOfRange(packetBytes, 16, packetBytes.length); + return; + } + break; + + case PARTITION_COMMAND: + if (highNibble == 0x4) { + header = Arrays.copyOfRange(packetBytes, 0, 16); + payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]); + logger.debug("Received a valid response for partition command"); + return; + } + break; + + case ZONE_COMMAND: + if (highNibble == 0xD) { header = Arrays.copyOfRange(packetBytes, 0, 16); - payload = Arrays.copyOfRange(packetBytes, 16, packetBytes.length); + payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]); + logger.debug("Received a valid response for zone command"); return; - } - } else if (requestType == RequestType.PARTITION_COMMAND) { - if (highNibble == 0x4) { - header = Arrays.copyOfRange(packetBytes, 0, 16); - payload = Arrays.copyOfRange(packetBytes, 16, 16 + packetBytes[1]); - logger.debug("Received valid response for partition command"); - return; - } + } + break; } // All other cases are considered wrong results for the parser and are probably live events which cannot be // parsed currently - logger.debug("Message command not expected. Received command={}", receivedCommand); + logger.debug("Message command not expected. Received command={}", String.format("0x%08X", receivedCommand)); header = null; payload = null; } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/ZoneCommandRequest.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/ZoneCommandRequest.java new file mode 100644 index 0000000000..6973410fb3 --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/ZoneCommandRequest.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication; + +import org.openhab.binding.paradoxalarm.internal.communication.messages.IPPacket; + +/** + * @author Konstantin Polihronov - Initial contribution + */ +public class ZoneCommandRequest extends Request { + + public ZoneCommandRequest(RequestType type, IPPacket packet, IResponseReceiver receiver) { + super(type, packet, receiver); + } +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/crypto/EncryptionHandler.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/crypto/EncryptionHandler.java index e43ee2dbaa..a87bacd46b 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/crypto/EncryptionHandler.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/crypto/EncryptionHandler.java @@ -50,8 +50,8 @@ public class EncryptionHandler { private static final int PAYLOAD_RATE_LENGTH = 16; private static final int ROUNDS = 14; - private static final int[] lTable = new int[TABLE_SIZE]; - private static final int[] aTable = new int[TABLE_SIZE]; + private static final int[] L_TABLE = new int[TABLE_SIZE]; + private static final int[] A_TABLE = new int[TABLE_SIZE]; private static EncryptionHandler instance = new EncryptionHandler(new byte[] {}); static { @@ -62,7 +62,7 @@ public class EncryptionHandler { int a = 1; int d; for (int index = 0; index < 255; index++) { - aTable[index] = a & 0xFF; + A_TABLE[index] = a & 0xFF; /* Multiply by three */ d = (a & 0x80) & 0xFF; a <<= 1; @@ -70,13 +70,13 @@ public class EncryptionHandler { a ^= 0x1b; a &= 0xFF; } - a ^= aTable[index]; + a ^= A_TABLE[index]; a &= 0xFF; /* Set the log table value */ - lTable[aTable[index]] = index & 0xFF; + L_TABLE[A_TABLE[index]] = index & 0xFF; } - aTable[255] = aTable[0]; - lTable[0] = 0; + A_TABLE[255] = A_TABLE[0]; + L_TABLE[0] = 0; } private final int[] expandedKey = new int[KEY_LENGTH]; @@ -196,9 +196,7 @@ public class EncryptionHandler { if (i % 8 == 0) { int tmp = temp[0]; - for (int j = 1; j < 4; j++) { - temp[j - 1] = temp[j]; - } + System.arraycopy(temp, 1, temp, 0, temp.length - 1); temp[3] = tmp; temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)]; @@ -212,9 +210,9 @@ public class EncryptionHandler { } private int gmul(int c, int b) { - int s = lTable[c] + lTable[b]; + int s = L_TABLE[c] + L_TABLE[b]; s %= 255; - s = aTable[s]; + s = A_TABLE[s]; if (b == 0 || c == 0) { s = 0; } @@ -267,8 +265,7 @@ public class EncryptionHandler { int[] tmpArray = new int[] { 0, 0, 0, 0 }; for (int i = 1; i < 4; i++) { for (int j = 0; j < 4; j++) { - int[][][] shifts = EncryptionHandlerConstants.SHIFTS; - int index = i * 4 + (j + shifts[0][i][d]) % 4; + int index = i * 4 + (j + EncryptionHandlerConstants.SHIFTS[0][i][d]) % 4; tmpArray[j] = a[index]; } for (int j = 0; j < 4; j++) { diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/Command.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/Command.java new file mode 100644 index 0000000000..0519dffd3d --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/Command.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication.messages; + +import org.openhab.binding.paradoxalarm.internal.communication.IRequest; + +/** + * More generic interface for creating command requests + * + * @author Konstantin Polihronov - Initial contribution + */ +public interface Command { + IRequest getRequest(int id); +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/CommandPayload.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/CommandPayload.java deleted file mode 100644 index 4b87326b34..0000000000 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/CommandPayload.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.paradoxalarm.internal.communication.messages; - -import java.nio.ByteBuffer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CommandPayload} Class that structures the payload for partition commands. - * - * @author Konstantin Polihronov - Initial contribution - */ -@NonNullByDefault -public class CommandPayload implements IPayload { - - private static final int BYTES_LENGTH = 15; - - private final byte MESSAGE_START = 0x40; - private final byte PAYLOAD_SIZE = 0x0f; - private final byte[] EMPTY_FOUR_BYTES = { 0, 0, 0, 0 }; - private final byte CHECKSUM = 0; - - private final int partitionNumber; - private final PartitionCommand command; - - public CommandPayload(int partitionNumber, PartitionCommand command) { - this.partitionNumber = partitionNumber; - this.command = command; - } - - @Override - public byte[] getBytes() { - byte[] bufferArray = new byte[BYTES_LENGTH]; - ByteBuffer buf = ByteBuffer.wrap(bufferArray); - buf.put(MESSAGE_START); - buf.put(PAYLOAD_SIZE); - buf.put(EMPTY_FOUR_BYTES); - buf.put(calculateMessageBytes()); - buf.put(EMPTY_FOUR_BYTES); - buf.put(CHECKSUM); - return bufferArray; - } - - /* - * The message bytes contain nibbles of command information. First byte, first nibble is partition 1, first byte, - * second nibble is partition 2, second byte, first nibble is partition 3, etc... - * - * For command values that are set in byte nibbles, see PartitionCommand enum - */ - private byte[] calculateMessageBytes() { - byte[] result = { 0, 0, 0, 0 }; - int index = (partitionNumber - 1) / 2; - result[index] = (byte) (calculateNibbleToSet() & 0xff); - return result; - } - - private int calculateNibbleToSet() { - if ((partitionNumber - 1) % 2 == 0) { - return (command.getCommand() << 4) & 0xF0; - } else { - return command.getCommand() & 0x0F; - } - } -} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/PartitionCommand.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/PartitionCommand.java deleted file mode 100644 index 14c8d0551b..0000000000 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/PartitionCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.paradoxalarm.internal.communication.messages; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link PartitionCommand} Enum representing the possible commands for a partition with the respective integer - * values that are sent as nibbles in the packet. - * - * @author Konstantin Polihronov - Initial contribution - */ -@NonNullByDefault -public enum PartitionCommand { - UNKNOWN(0), - ARM(2), - STAY_ARM(3), - INSTANT_ARM(4), - FORCE_ARM(5), - DISARM(6), - BEEP(8); - - private static final Logger logger = LoggerFactory.getLogger(PartitionCommand.class); - - private int command; - - PartitionCommand(int command) { - this.command = command; - } - - public int getCommand() { - return command; - } - - public static PartitionCommand parse(String command) { - try { - return PartitionCommand.valueOf(command); - } catch (IllegalArgumentException e) { - logger.debug("Unable to parse command={}. Fallback to UNKNOWN.", command); - return PartitionCommand.UNKNOWN; - } - } -} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommand.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommand.java new file mode 100644 index 0000000000..4b49e47673 --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommand.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication.messages.partition; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.paradoxalarm.internal.communication.IRequest; +import org.openhab.binding.paradoxalarm.internal.communication.PartitionCommandRequest; +import org.openhab.binding.paradoxalarm.internal.communication.RequestType; +import org.openhab.binding.paradoxalarm.internal.communication.messages.Command; +import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType; +import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PartitionCommand} Enum representing the possible commands for a partition with the respective integer + * values that are sent as nibbles in the packet. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public enum PartitionCommand implements Command { + ARM(2), + STAY_ARM(3), + INSTANT_ARM(4), + FORCE_ARM(5), + DISARM(6), + BEEP(8); + + private static final Logger LOGGER = LoggerFactory.getLogger(PartitionCommand.class); + + private int command; + + PartitionCommand(int command) { + this.command = command; + } + + public int getCommand() { + return command; + } + + public static @Nullable PartitionCommand parse(String command) { + try { + return PartitionCommand.valueOf(command); + } catch (IllegalArgumentException e) { + LOGGER.debug("Unable to parse command={}. Fallback to UNKNOWN.", command); + return null; + } + } + + @Override + public IRequest getRequest(int partitionId) { + PartitionCommandPayload payload = new PartitionCommandPayload(partitionId, this); + ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes()) + .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST); + return new PartitionCommandRequest(RequestType.PARTITION_COMMAND, packet, null); + } +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommandPayload.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommandPayload.java new file mode 100644 index 0000000000..37c18cbcdc --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommandPayload.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication.messages.partition; + +import java.nio.ByteBuffer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload; + +/** + * The {@link PartitionCommandPayload} Class that structures the payload for partition commands. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class PartitionCommandPayload implements IPayload { + + private static final int BYTES_LENGTH = 15; + + private static final byte MESSAGE_START = 0x40; + private static final byte PAYLOAD_SIZE = 0x0f; + private static final byte[] EMPTY_FOUR_BYTES = { 0, 0, 0, 0 }; + private static final byte CHECKSUM = 0; + + private final int partitionNumber; + private final PartitionCommand command; + + public PartitionCommandPayload(int partitionNumber, PartitionCommand command) { + this.partitionNumber = partitionNumber; + this.command = command; + } + + @Override + public byte[] getBytes() { + byte[] bufferArray = new byte[BYTES_LENGTH]; + ByteBuffer buf = ByteBuffer.wrap(bufferArray); + buf.put(MESSAGE_START); + buf.put(PAYLOAD_SIZE); + buf.put(EMPTY_FOUR_BYTES); + buf.put(calculateMessageBytes()); + buf.put(EMPTY_FOUR_BYTES); + buf.put(CHECKSUM); + return bufferArray; + } + + /* + * The message bytes contain nibbles of command information. First byte, first nibble is partition 1, first byte, + * second nibble is partition 2, second byte, first nibble is partition 3, etc... + * + * For command values that are set in byte nibbles, see PartitionCommand enum + */ + private byte[] calculateMessageBytes() { + byte[] result = { 0, 0, 0, 0 }; + int index = (partitionNumber - 1) / 2; + result[index] = (byte) (calculateNibbleToSet() & 0xff); + return result; + } + + private int calculateNibbleToSet() { + if ((partitionNumber - 1) % 2 == 0) { + return (command.getCommand() << 4) & 0xF0; + } else { + return command.getCommand() & 0x0F; + } + } +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommand.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommand.java new file mode 100644 index 0000000000..7433ae8542 --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommand.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication.messages.zone; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.paradoxalarm.internal.communication.IRequest; +import org.openhab.binding.paradoxalarm.internal.communication.RequestType; +import org.openhab.binding.paradoxalarm.internal.communication.ZoneCommandRequest; +import org.openhab.binding.paradoxalarm.internal.communication.messages.Command; +import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType; +import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public enum ZoneCommand implements Command { + CLEAR_BYPASS(0), + BYPASS(8); + + private static final Logger LOGGER = LoggerFactory.getLogger(ZoneCommand.class); + + private byte command; + + ZoneCommand(int command) { + this.command = (byte) command; + } + + public byte getCommand() { + return command; + } + + public static @Nullable ZoneCommand parse(@Nullable String command) { + if (command == null) { + return null; + } + + try { + return ZoneCommand.valueOf(command); + } catch (IllegalArgumentException e) { + LOGGER.debug("Unable to parse command={}. Fallback to UNKNOWN.", command); + return null; + } + } + + @Override + public IRequest getRequest(int zoneId) { + ZoneCommandPayload payload = new ZoneCommandPayload(zoneId, this); + ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes(), false) + .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST); + return new ZoneCommandRequest(RequestType.ZONE_COMMAND, packet, null); + } +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommandPayload.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommandPayload.java new file mode 100644 index 0000000000..406a7bb044 --- /dev/null +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommandPayload.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.paradoxalarm.internal.communication.messages.zone; + +import java.nio.ByteBuffer; + +import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload; +import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; + +/** + * @author Konstantin Polihronov - Initial contribution + */ +public class ZoneCommandPayload implements IPayload { + + private static final int BYTES_LENGTH = 31; + + private static final byte MESSAGE_START = (byte) 0xD0; + private static final byte PAYLOAD_SIZE = 0x1f; + private static final byte ZONE_FLAG = 0x08; // "bypassed" flag (5th bit) + private static final byte[] EMPTY_TWO_BYTES = { 0, 0 }; + private static final byte CHECKSUM = 0; + + private int zoneNumber; + private ZoneCommand command; + + public ZoneCommandPayload(int zoneNumber, ZoneCommand command) { + this.zoneNumber = zoneNumber; + this.command = command; + } + + @Override + public byte[] getBytes() { + byte[] bufferArray = new byte[BYTES_LENGTH]; + ByteBuffer buf = ByteBuffer.wrap(bufferArray); + buf.put(MESSAGE_START); + buf.put(PAYLOAD_SIZE); + buf.put(ZONE_FLAG); + buf.put(command.getCommand()); + buf.put(EMPTY_TWO_BYTES); + buf.put(calculateMessageBytes()); + buf.put(ParadoxUtil.calculateChecksum(bufferArray)); + return bufferArray; + } + + /** + * The total zone message consists of 24 bytes (8 bits each) which results of 192 bits for each zone in case of + * Evo192 (i.e. 24x8). + * The low nible of each byte represents the first 4 zones of each group as a bit, the high nible is + * the second 4 zones. The zone groups are considered every 8 zones represented by a byte in this array (1-8,9-16, + * 17-24, etc)
+ * + * Example: So if we address zone 1 for example the value of first byte will be 0x01, for zone 2 - 0x02, for zone 3 + * - 0x04 + * (third bit set to 1), for zone 4 - 0x08(fourth bit set to 1),
+ * for zone 5 - 0x10, for zone 6 - 0x20, for zone 7 - 0x40, for zone 8 - 0x80.
+ * For examples see TestGetBytes.java + * + * @return 24 bytes array with the needed zone to be set to bypass/clear bypass + */ + private byte[] calculateMessageBytes() { + byte[] zoneMessage = new byte[24]; + int byteIndex = (zoneNumber - 1) / 8; + byte zoneByteGroup = zoneMessage[byteIndex]; + int bitNumber = calculateBitNumber(); + zoneMessage[byteIndex] = ParadoxUtil.setBit(zoneByteGroup, bitNumber - 1, 1); + return zoneMessage; + } + + private int calculateBitNumber() { + int residual = zoneNumber % 8; + return residual != 0 ? residual : 8; + } +} diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/discovery/ParadoxDiscoveryService.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/discovery/ParadoxDiscoveryService.java index a3f62f22c6..ba014c821e 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/discovery/ParadoxDiscoveryService.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/discovery/ParadoxDiscoveryService.java @@ -89,7 +89,7 @@ public class ParadoxDiscoveryService extends AbstractDiscoveryService { ThingUID thingUID = new ThingUID(PARTITION_THING_TYPE_UID, bridgeUid, thingId); DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUid) .withLabel("Partition " + label).withProperty(PARTITION_THING_TYPE_ID, thingId) - .withProperty("id", partition.getId()).build(); + .withProperty("label", label).withProperty("id", partition.getId()).build(); logger.debug("Partition DiscoveryResult={}", result); thingDiscovered(result); @@ -105,7 +105,7 @@ public class ParadoxDiscoveryService extends AbstractDiscoveryService { ThingUID thingUID = new ThingUID(ZONE_THING_TYPE_UID, bridgeUid, thingId); DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUid) .withLabel("Zone " + label).withProperty(ZONE_THING_TYPE_ID, thingId) - .withProperty("id", zone.getId()).build(); + .withProperty("id", zone.getId()).withProperty("label", label).build(); logger.debug("Zone DiscoveryResult={}", result); thingDiscovered(result); diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java index 83b68fda8d..a082d7c808 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java @@ -67,7 +67,7 @@ public class ParadoxAlarmBindingConstants { public static final String PANEL_BOARD_VOLTAGE = "boardVoltage"; public static final String PANEL_BATTERY_VOLTAGE = "batteryVoltage"; - public static final String PARTITION_LABEL_CHANNEL_UID = "partitionLabel"; + public static final String PARTITION_LABEL_CHANNEL_UID = "label"; public static final String PARTITION_STATE_CHANNEL_UID = "state"; @Deprecated // After implementation of channels for every possible state, the summarized additional states is no // longer needed. We'll keep it for backward compatibility @@ -88,11 +88,20 @@ public class ParadoxAlarmBindingConstants { public static final String PARTITION_INHIBIT_READY_CHANNEL_UID = "inhibitReady"; public static final String PARTITION_ALL_ZONES_CLOSED_CHANNEL_UID = "allZonesClosed"; - public static final String ZONE_LABEL_CHANNEL_UID = "zoneLabel"; + public static final String ZONE_LABEL_CHANNEL_UID = "label"; public static final String ZONE_OPENED_CHANNEL_UID = "opened"; public static final String ZONE_TAMPERED_CHANNEL_UID = "tampered"; public static final String ZONE_LOW_BATTERY_CHANNEL_UID = "lowBattery"; + public static final String ZONE_SUPERVISION_TROUBLE_UID = "supervisionTrouble"; + public static final String ZONE_IN_TX_DELAY_UID = "inTxDelay"; + public static final String ZONE_SHUTDOWN_UID = "shutdown"; + public static final String ZONE_BYPASSED_UID = "bypassed"; + public static final String ZONE_HAS_ACTIVATED_INTELLIZONE_DELAY_UID = "hasActivatedIntellizoneDelay"; + public static final String ZONE_HAS_ACTIVATED_ENTRY_DELAY_UID = "hasActivatedEntryDelay"; + public static final String ZONE_PRESENTLY_IN_ALARM_UID = "presentlyInAlarm"; + public static final String ZONE_GENERATED_ALARM_UID = "generatedAlarm"; + // Misc constants public static final StringType STATE_OFFLINE = new StringType("Offline"); public static final StringType STATE_ONLINE = new StringType("Online"); diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java index ffc390f5e8..c3a932a257 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java @@ -24,8 +24,6 @@ import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Thing; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link ParadoxPanelHandler} This is the handler that takes care of the panel related stuff. @@ -35,8 +33,6 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class ParadoxPanelHandler extends EntityBaseHandler { - private final Logger logger = LoggerFactory.getLogger(ParadoxPanelHandler.class); - public ParadoxPanelHandler(Thing thing) { super(thing); } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPartitionHandler.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPartitionHandler.java index f7debaeb08..99855a25fb 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPartitionHandler.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPartitionHandler.java @@ -17,7 +17,7 @@ import static org.openhab.binding.paradoxalarm.internal.handlers.ParadoxAlarmBin import java.util.List; import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand; import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel; import org.openhab.binding.paradoxalarm.internal.model.Partition; import org.openhab.core.library.types.OnOffType; @@ -78,26 +78,6 @@ public class ParadoxPartitionHandler extends EntityBaseHandler { } } - protected Partition getPartition() { - int index = calculateEntityIndex(); - ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler(); - ParadoxPanel panel = bridge.getPanel(); - List partitions = panel.getPartitions(); - if (partitions == null) { - logger.debug( - "Partitions collection of Paradox Panel object is null. Probably not yet initialized. Skipping update."); - return null; - } - if (partitions.size() <= index) { - logger.debug("Attempted to access partition out of bounds of current partitions list. Index: {}, List: {}", - index, partitions); - return null; - } - - Partition partition = partitions.get(index); - return partition; - } - private OpenClosedType booleanToContactState(boolean value) { return value ? OpenClosedType.OPEN : OpenClosedType.CLOSED; } @@ -124,4 +104,24 @@ public class ParadoxPartitionHandler extends EntityBaseHandler { super.handleCommand(channelUID, command); } } + + protected Partition getPartition() { + int index = calculateEntityIndex(); + ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler(); + ParadoxPanel panel = bridge.getPanel(); + List partitions = panel.getPartitions(); + if (partitions == null) { + logger.debug( + "Partitions collection of Paradox Panel object is null. Probably not yet initialized. Skipping update."); + return null; + } + if (partitions.size() <= index) { + logger.debug("Attempted to access partition out of bounds of current partitions list. Index: {}, List: {}", + index, partitions); + return null; + } + + Partition partition = partitions.get(index); + return partition; + } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxZoneHandler.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxZoneHandler.java index a902765069..c2ab9c1500 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxZoneHandler.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxZoneHandler.java @@ -19,10 +19,14 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.paradoxalarm.internal.model.ParadoxPanel; import org.openhab.binding.paradoxalarm.internal.model.Zone; +import org.openhab.binding.paradoxalarm.internal.model.ZoneState; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,15 +45,21 @@ public class ParadoxZoneHandler extends EntityBaseHandler { @Override protected void updateEntity() { - int index = calculateEntityIndex(); - ParadoxIP150BridgeHandler bridge = (ParadoxIP150BridgeHandler) getBridge().getHandler(); - ParadoxPanel panel = bridge.getPanel(); + ParadoxIP150BridgeHandler bridgeHandler = getBridgeHandler(); + if (bridgeHandler == null) { + logger.debug("Paradox bridge handler is null. Skipping update."); + return; + } + + ParadoxPanel panel = bridgeHandler.getPanel(); List zones = panel.getZones(); if (zones == null) { logger.debug( "Zones collection of Paradox Panel object is null. Probably not yet initialized. Skipping update."); return; } + + int index = calculateEntityIndex(); if (zones.size() <= index) { logger.debug("Attempted to access zone out of bounds of current zone list. Index: {}, List: {}", index, zones); @@ -59,9 +69,23 @@ public class ParadoxZoneHandler extends EntityBaseHandler { Zone zone = zones.get(index); if (zone != null) { updateState(ZONE_LABEL_CHANNEL_UID, new StringType(zone.getLabel())); - updateState(ZONE_OPENED_CHANNEL_UID, booleanToContactState(zone.getZoneState().isOpened())); - updateState(ZONE_TAMPERED_CHANNEL_UID, booleanToSwitchState(zone.getZoneState().isTampered())); - updateState(ZONE_LOW_BATTERY_CHANNEL_UID, booleanToSwitchState(zone.getZoneState().hasLowBattery())); + ZoneState zoneState = zone.getZoneState(); + if (zoneState != null) { + updateState(ZONE_OPENED_CHANNEL_UID, booleanToContactState(zoneState.isOpened())); + updateState(ZONE_TAMPERED_CHANNEL_UID, booleanToSwitchState(zoneState.isTampered())); + updateState(ZONE_LOW_BATTERY_CHANNEL_UID, booleanToSwitchState(zoneState.hasLowBattery())); + + updateState(ZONE_SUPERVISION_TROUBLE_UID, booleanToSwitchState(zoneState.isSupervisionTrouble())); + updateState(ZONE_IN_TX_DELAY_UID, booleanToSwitchState(zoneState.isInTxDelay())); + updateState(ZONE_SHUTDOWN_UID, booleanToSwitchState(zoneState.isShutdown())); + updateState(ZONE_BYPASSED_UID, booleanToSwitchState(zoneState.isBypassed())); + updateState(ZONE_HAS_ACTIVATED_INTELLIZONE_DELAY_UID, + booleanToSwitchState(zoneState.isHasActivatedIntellizoneDelay())); + updateState(ZONE_HAS_ACTIVATED_ENTRY_DELAY_UID, + booleanToSwitchState(zoneState.isHasActivatedEntryDelay())); + updateState(ZONE_PRESENTLY_IN_ALARM_UID, booleanToSwitchState(zoneState.isPresentlyInAlarm())); + updateState(ZONE_GENERATED_ALARM_UID, booleanToSwitchState(zoneState.isGeneratedAlarm())); + } } } @@ -72,4 +96,52 @@ public class ParadoxZoneHandler extends EntityBaseHandler { private OnOffType booleanToSwitchState(boolean value) { return value ? OnOffType.ON : OnOffType.OFF; } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + if (command instanceof StringType) { + Zone zone = getZone(); + if (zone != null) { + zone.handleCommand(command.toString()); + } + } else { + super.handleCommand(channelUID, command); + } + } + + protected Zone getZone() { + ParadoxIP150BridgeHandler bridgeHandler = getBridgeHandler(); + if (bridgeHandler == null) { + logger.debug("Paradox bridge handler is null. Skipping update."); + return null; + } + + ParadoxPanel panel = bridgeHandler.getPanel(); + List zones = panel.getZones(); + if (zones == null) { + logger.debug( + "Zones collection of Paradox Panel object is null. Probably not yet initialized. Skipping update."); + return null; + } + + int index = calculateEntityIndex(); + if (zones.size() <= index) { + logger.debug("Attempted to access a zone out of bounds of current zone list. Index: {}, List: {}", index, + zones); + return null; + } + + Zone zone = zones.get(index); + return zone; + } + + private ParadoxIP150BridgeHandler getBridgeHandler() { + Bridge bridge = getBridge(); + if (bridge == null) { + logger.debug("Paradox bridge is null. Skipping update."); + return null; + } + + return (ParadoxIP150BridgeHandler) bridge.getHandler(); + } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Partition.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Partition.java index d845217e91..91dae6baf3 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Partition.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Partition.java @@ -12,12 +12,8 @@ */ package org.openhab.binding.paradoxalarm.internal.model; -import org.openhab.binding.paradoxalarm.internal.communication.PartitionCommandRequest; -import org.openhab.binding.paradoxalarm.internal.communication.RequestType; -import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload; -import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderMessageType; -import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket; -import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.IRequest; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand; import org.openhab.binding.paradoxalarm.internal.handlers.Commandable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,16 +47,13 @@ public class Partition extends Entity implements Commandable { @Override public void handleCommand(String command) { PartitionCommand partitionCommand = PartitionCommand.parse(command); - if (partitionCommand == PartitionCommand.UNKNOWN) { - logger.debug("Command UNKNOWN will be ignored."); + if (partitionCommand == null) { + logger.debug("Command {} is parsed to null. Skipping it", command); return; } logger.debug("Submitting command={} for partition=[{}]", partitionCommand, this); - CommandPayload payload = new CommandPayload(getId(), partitionCommand); - ParadoxIPPacket packet = new ParadoxIPPacket(payload.getBytes()) - .setMessageType(HeaderMessageType.SERIAL_PASSTHRU_REQUEST); - PartitionCommandRequest request = new PartitionCommandRequest(RequestType.PARTITION_COMMAND, packet, null); + IRequest request = partitionCommand.getRequest(getId()); getPanel().getCommunicator().submitRequest(request); } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Zone.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Zone.java index 3bf1faa8c8..1fa12d36ec 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Zone.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Zone.java @@ -12,6 +12,11 @@ */ package org.openhab.binding.paradoxalarm.internal.model; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.paradoxalarm.internal.communication.IRequest; +import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommand; +import org.openhab.binding.paradoxalarm.internal.handlers.Commandable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,23 +27,36 @@ import org.slf4j.LoggerFactory; * * @author Konstantin Polihronov - Initial contribution */ -public class Zone extends Entity { +@NonNullByDefault +public class Zone extends Entity implements Commandable { private final Logger logger = LoggerFactory.getLogger(Zone.class); - private ZoneState zoneState; + private @Nullable ZoneState zoneState; - public Zone(ParadoxPanel panel, int id, String label) { + public Zone(ParadoxPanel panel, int id, @Nullable String label) { super(panel, id, label); } - public ZoneState getZoneState() { + public @Nullable ZoneState getZoneState() { return zoneState; } public void setZoneState(ZoneState zoneState) { this.zoneState = zoneState; - logger.debug("Zone {} state updated to:\tOpened: {}, Tampered: {}, LowBattery: {}", getLabel(), - zoneState.isOpened(), zoneState.isTampered(), zoneState.hasLowBattery()); + logger.debug("Zone {} state updated to: {}", getLabel(), zoneState); + } + + @Override + public void handleCommand(@Nullable String command) { + ZoneCommand zoneCommand = ZoneCommand.parse(command); + if (zoneCommand == null) { + logger.debug("Command {} is parsed to null. Skipping it", command); + return; + } + + logger.debug("Submitting command={} for partition=[{}]", zoneCommand, this); + IRequest request = zoneCommand.getRequest(getId()); + getPanel().getCommunicator().submitRequest(request); } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneState.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneState.java index 8068762695..c058a5fa12 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneState.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneState.java @@ -18,10 +18,21 @@ package org.openhab.binding.paradoxalarm.internal.model; * @author Konstantin Polihronov - Initial contribution */ public class ZoneState { + // Regular states private boolean isOpened; private boolean isTampered; private boolean hasLowBattery; + // Special flag states + private boolean supervisionTrouble; + private boolean inTxDelay; + private boolean shuttedDown; + private boolean bypassed; + private boolean hasActivatedIntellizoneDelay; + private boolean hasActivatedEntryDelay; + private boolean presentlyInAlarm; + private boolean generatedAlarm; + public ZoneState(boolean isOpened, boolean isTampered, boolean hasLowBattery) { this.isOpened = isOpened; this.isTampered = isTampered; @@ -51,4 +62,77 @@ public class ZoneState { public void setHasLowBattery(boolean hasLowBattery) { this.hasLowBattery = hasLowBattery; } + + public void setSupervisionTrouble(boolean supervisionTrouble) { + this.supervisionTrouble = supervisionTrouble; + } + + public boolean isSupervisionTrouble() { + return supervisionTrouble; + } + + public boolean isInTxDelay() { + return inTxDelay; + } + + public void setInTxDelay(boolean inTxDelay) { + this.inTxDelay = inTxDelay; + } + + public boolean isShutdown() { + return shuttedDown; + } + + public void setShuttedDown(boolean shuttedDown) { + this.shuttedDown = shuttedDown; + } + + public boolean isBypassed() { + return bypassed; + } + + public void setBypassed(boolean bypassed) { + this.bypassed = bypassed; + } + + public boolean isHasActivatedIntellizoneDelay() { + return hasActivatedIntellizoneDelay; + } + + public void setHasActivatedIntellizoneDelay(boolean hasActivatedIntellizoneDelay) { + this.hasActivatedIntellizoneDelay = hasActivatedIntellizoneDelay; + } + + public boolean isHasActivatedEntryDelay() { + return hasActivatedEntryDelay; + } + + public void setHasActivatedEntryDelay(boolean hasActivatedEntryDelay) { + this.hasActivatedEntryDelay = hasActivatedEntryDelay; + } + + public boolean isPresentlyInAlarm() { + return presentlyInAlarm; + } + + public void setPresentlyInAlarm(boolean presentlyInAlarm) { + this.presentlyInAlarm = presentlyInAlarm; + } + + public boolean isGeneratedAlarm() { + return generatedAlarm; + } + + public void setGeneratedAlarm(boolean generatedAlarm) { + this.generatedAlarm = generatedAlarm; + } + + @Override + public String toString() { + return "ZoneState [isOpened=" + isOpened + ", isTampered=" + isTampered + ", hasLowBattery=" + hasLowBattery + + ", supervisionTrouble=" + supervisionTrouble + ", inTxDelay=" + inTxDelay + ", shuttedDown=" + + shuttedDown + ", bypassed=" + bypassed + ", hasActivatedIntellizoneDelay=" + + hasActivatedIntellizoneDelay + ", hasActivatedEntryDelay=" + hasActivatedEntryDelay + + ", presentlyInAlarm=" + presentlyInAlarm + ", generatedAlarm=" + generatedAlarm + "]"; + } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneStateFlags.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneStateFlags.java index bc2ad5ebda..4a920e19ef 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneStateFlags.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneStateFlags.java @@ -21,6 +21,7 @@ public class ZoneStateFlags { private byte[] zonesOpened; private byte[] zonesTampered; private byte[] zonesLowBattery; + private byte[] zoneSpecialFlags; public byte[] getZonesOpened() { return zonesOpened; @@ -45,4 +46,12 @@ public class ZoneStateFlags { public void setZonesLowBattery(byte[] zonesLowBattery) { this.zonesLowBattery = zonesLowBattery; } + + public byte[] getZoneSpecialFlags() { + return zoneSpecialFlags; + } + + public void setZoneSpecialFlags(byte[] zoneSpecialFlags) { + this.zoneSpecialFlags = zoneSpecialFlags; + } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/parsers/EvoParser.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/parsers/EvoParser.java index 7a95e1a428..8b3c9251c9 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/parsers/EvoParser.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/parsers/EvoParser.java @@ -64,10 +64,11 @@ public class EvoParser extends AbstractParser { @Override public ZoneState calculateZoneState(int id, ZoneStateFlags zoneStateFlags) { - int index = (id - 1) / 8; int bitNumber = id % 8 - 1; + // Every zone state is represented by a bit set/unset in the big byte array retrieved from the memory of the + // panel byte[] zonesOpened = zoneStateFlags.getZonesOpened(); boolean isOpened = ParadoxUtil.isBitSet(zonesOpened[index], bitNumber); @@ -77,6 +78,38 @@ public class EvoParser extends AbstractParser { byte[] zonesLowBattery = zoneStateFlags.getZonesLowBattery(); boolean hasLowBattery = ParadoxUtil.isBitSet(zonesLowBattery[index], bitNumber); - return new ZoneState(isOpened, isTampered, hasLowBattery); + ZoneState zoneState = new ZoneState(isOpened, isTampered, hasLowBattery); + + calculateSpecialFlags(zoneStateFlags, id, zoneState); + + return zoneState; + } + + private void calculateSpecialFlags(ZoneStateFlags zoneStateFlags, int index, ZoneState zoneState) { + // Each byte is filled with 8 special zone flags. + // Each bit of the byte represents a specific flag. + // Zone Flags: + // 0 = Zone supervision trouble + // 1 = Zone in TX delay + // 2 = Zone shutted down + // 3 = Zone bypassed + // 4 = Zone activated intellizone delay + // 5 = Zone activated entry delay + // 6 = Zone presently in alarm + // 7 = Zone generated an alarm + + // The index of the actual zones and partitions enumerates from 1-N. In the arrays we need to index it from 0. + int specialFlagsIndex = index - 1; + byte[] zoneSpecialFlags = zoneStateFlags.getZoneSpecialFlags(); + byte currentZoneFlags = zoneSpecialFlags[specialFlagsIndex]; + + zoneState.setSupervisionTrouble(ParadoxUtil.isBitSet(currentZoneFlags, 0)); + zoneState.setInTxDelay(ParadoxUtil.isBitSet(currentZoneFlags, 1)); + zoneState.setShuttedDown(ParadoxUtil.isBitSet(currentZoneFlags, 2)); + zoneState.setBypassed(ParadoxUtil.isBitSet(currentZoneFlags, 3)); + zoneState.setHasActivatedIntellizoneDelay(ParadoxUtil.isBitSet(currentZoneFlags, 4)); + zoneState.setHasActivatedEntryDelay(ParadoxUtil.isBitSet(currentZoneFlags, 5)); + zoneState.setPresentlyInAlarm(ParadoxUtil.isBitSet(currentZoneFlags, 6)); + zoneState.setGeneratedAlarm(ParadoxUtil.isBitSet(currentZoneFlags, 7)); } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/util/ParadoxUtil.java b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/util/ParadoxUtil.java index 0f6768f2c4..1624b9d244 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/util/ParadoxUtil.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/util/ParadoxUtil.java @@ -30,7 +30,8 @@ import org.slf4j.LoggerFactory; public class ParadoxUtil { private static final String SPACE_DELIMITER = " "; - private static final Logger logger = LoggerFactory.getLogger(ParadoxUtil.class); + + private static final Logger LOGGER = LoggerFactory.getLogger(ParadoxUtil.class); public static byte calculateChecksum(byte[] payload) { int result = 0; @@ -50,28 +51,28 @@ public class ParadoxUtil { } public static void printPacket(String description, byte[] array) { - if (logger.isTraceEnabled()) { - logger.trace("Packet payload size: {}", array[1]); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Packet payload size: {}", array[1]); printByteArray(description, array, array[1] + 16); } } public static void printByteArray(String description, byte[] array) { if (array == null) { - logger.trace("Array is null"); + LOGGER.trace("Array is null"); return; } printByteArray(description, array, array.length); } public static void printByteArray(String description, byte[] array, int length) { - if (!logger.isTraceEnabled()) { + if (!LOGGER.isTraceEnabled()) { return; } String result = byteArrayToString(array, length); if (!result.isEmpty()) { - logger.trace("{}", description + SPACE_DELIMITER + result); + LOGGER.trace("{}", description + SPACE_DELIMITER + result); } } @@ -131,7 +132,7 @@ public class ParadoxUtil { byte[] byteArray = outputStream.toByteArray(); return byteArray; } catch (IOException e) { - logger.warn("Exception merging arrays:", e); + LOGGER.warn("Exception merging arrays:", e); return new byte[0]; } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/i18n/paradoxalarm.properties b/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/i18n/paradoxalarm.properties index e347f372d2..b59bfef8ad 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/i18n/paradoxalarm.properties +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/i18n/paradoxalarm.properties @@ -1,8 +1,3 @@ -# add-on - -addon.paradoxalarm.name = ParadoxAlarm Binding -addon.paradoxalarm.description = This is the binding for ParadoxAlarm. - # thing types thing-type.paradoxalarm.ip150.label = Paradox IP150 Module Connector @@ -57,6 +52,9 @@ channel-type.paradoxalarm.bootloaderVersion.label = Boot Loader Version channel-type.paradoxalarm.bootloaderVersion.description = Boot loader version channel-type.paradoxalarm.bypassReady.label = Partition Is Bypass Ready channel-type.paradoxalarm.bypassReady.description = Partition is Bypass Ready +channel-type.paradoxalarm.bypassed.label = Bypassed +channel-type.paradoxalarm.command.label = Communicator Command +channel-type.paradoxalarm.command.description = Send Command channel-type.paradoxalarm.command.label = Partition Command channel-type.paradoxalarm.command.description = Send command channel-type.paradoxalarm.command.state.option.ARM = Arm @@ -65,20 +63,26 @@ channel-type.paradoxalarm.command.state.option.INSTANT_ARM = Instant Arm channel-type.paradoxalarm.command.state.option.FORCE_ARM = Force Arm channel-type.paradoxalarm.command.state.option.DISARM = Disarm channel-type.paradoxalarm.command.state.option.BEEP = Keyboard Beep -channel-type.paradoxalarm.command.label = Communicator Command -channel-type.paradoxalarm.command.description = Send Command +channel-type.paradoxalarm.command.label = Zone Command +channel-type.paradoxalarm.command.description = Send command for a zone +channel-type.paradoxalarm.command.state.option.BYPASS = Bypass +channel-type.paradoxalarm.command.state.option.CLEAR_BYPASS = Clear Bypass channel-type.paradoxalarm.communicationState.label = Bridge Communication State channel-type.paradoxalarm.communicationState.description = Status of connection to Paradox system channel-type.paradoxalarm.forceReady.label = Partition Is Force Ready channel-type.paradoxalarm.forceReady.description = Partition is Force Ready +channel-type.paradoxalarm.generatedAlarm.label = Generated an Alarm channel-type.paradoxalarm.hardwareVersion.label = Hardware Version channel-type.paradoxalarm.hardwareVersion.description = Panel hardware version +channel-type.paradoxalarm.hasActivatedEntryDelay.label = Has Activated Entry Delay +channel-type.paradoxalarm.hasActivatedIntellizoneDelay.label = Has Activated Intellizone Delay channel-type.paradoxalarm.inEntryDelay.label = Partition In Entry Delay channel-type.paradoxalarm.inEntryDelay.description = Partition in Entry Delay channel-type.paradoxalarm.inExitDelay.label = Partition In Exit Delay channel-type.paradoxalarm.inExitDelay.description = Partition in Exit Delay channel-type.paradoxalarm.inTrouble.label = Partition In Trouble channel-type.paradoxalarm.inTrouble.description = Partition in Trouble +channel-type.paradoxalarm.inTxDelay.label = In TX Delay channel-type.paradoxalarm.inhibitReady.label = Partition Is Inhibit Ready channel-type.paradoxalarm.inhibitReady.description = Partition is Inhibit Ready channel-type.paradoxalarm.openedState.label = Zone State @@ -91,14 +95,17 @@ channel-type.paradoxalarm.panelType.label = Panel Type channel-type.paradoxalarm.panelType.description = Panel type (Evo, SP, etc) channel-type.paradoxalarm.partitionLabel.label = Partition Label channel-type.paradoxalarm.partitionLabel.description = Label of partition +channel-type.paradoxalarm.presentlyInAlarm.label = Currently in Alarm channel-type.paradoxalarm.readyToArm.label = Partition Ready To Arm channel-type.paradoxalarm.readyToArm.description = Partition ready to arm channel-type.paradoxalarm.serialNumber.label = Serial Number channel-type.paradoxalarm.serialNumber.description = Panel serial number +channel-type.paradoxalarm.shutdown.label = Shutdown channel-type.paradoxalarm.state.label = Partition State channel-type.paradoxalarm.state.description = State of partition channel-type.paradoxalarm.stayInstantReady.label = Partition Is Stay Instant Ready channel-type.paradoxalarm.stayInstantReady.description = Partition is Stay Instant Ready +channel-type.paradoxalarm.supervisionTrouble.label = Supervision Trouble channel-type.paradoxalarm.tamperedState.label = Tampered channel-type.paradoxalarm.tamperedState.description = State of zone channel-type.paradoxalarm.voltage.label = Voltage @@ -115,3 +122,8 @@ channel-type.paradoxalarm.zoneInTamperTrouble.label = Partition Has Zone In Tamp channel-type.paradoxalarm.zoneInTamperTrouble.description = Partition has in Tamper Trouble channel-type.paradoxalarm.zoneLabel.label = Zone Label channel-type.paradoxalarm.zoneLabel.description = Label of zone + +# add-on + +addon.paradoxalarm.name = ParadoxAlarm Binding +addon.paradoxalarm.description = This is the binding for ParadoxAlarm. diff --git a/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/zone.xml b/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/zone.xml index 67c3b62fc1..7c35ff6c89 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/zone.xml +++ b/bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/zone.xml @@ -16,6 +16,16 @@ + + + + + + + + + + @@ -44,4 +54,55 @@ State of zone + + String + + Send command for a zone + + + + + + + + + Switch + + + + + Switch + + + + + Switch + + + + + Switch + + + + + Switch + + + + + Switch + + + + + Switch + + + + + Switch + + + diff --git a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestCreateCommandPayload.java b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestCreateCommandPayload.java index 4cc24f8315..ce2920f546 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestCreateCommandPayload.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestCreateCommandPayload.java @@ -12,11 +12,12 @@ */ package org.openhab.binding.paradoxalarm.internal; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload; -import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommandPayload; import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; /** @@ -25,19 +26,20 @@ import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; * * @author Konstantin Polihronov - Initial contribution */ +@NonNullByDefault public class TestCreateCommandPayload { @Test public void testCreatePayload() { for (PartitionCommand command : PartitionCommand.values()) { for (int partitionNumber = 1; partitionNumber <= 8; partitionNumber++) { - CommandPayload payload = new CommandPayload(partitionNumber, command); + PartitionCommandPayload payload = new PartitionCommandPayload(partitionNumber, command); assertNibble(partitionNumber, command, payload); } } } - private void assertNibble(int partitionNumber, PartitionCommand command, CommandPayload payload) { + private void assertNibble(int partitionNumber, PartitionCommand command, PartitionCommandPayload payload) { byte[] bytes = payload.getBytes(); int payloadIndexOfByteToCheck = 6 + (partitionNumber - 1) / 2; byte byteValue = bytes[payloadIndexOfByteToCheck]; diff --git a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestEncryptionHandler.java b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestEncryptionHandler.java index 475554b24e..8b106f1431 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestEncryptionHandler.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestEncryptionHandler.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.Arrays; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.paradoxalarm.internal.communication.crypto.EncryptionHandler; import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand; @@ -27,6 +28,7 @@ import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; * * @author Konstantin Polihronov - Initial contribution */ +@NonNullByDefault public class TestEncryptionHandler { private static final String INPUT_STRING = "My test string for encryption."; diff --git a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestGetBytes.java b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestGetBytes.java index 5d571a17ec..22a1f0b21d 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestGetBytes.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestGetBytes.java @@ -12,17 +12,20 @@ */ package org.openhab.binding.paradoxalarm.internal; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.paradoxalarm.internal.communication.messages.CommandPayload; import org.openhab.binding.paradoxalarm.internal.communication.messages.EpromRequestPayload; import org.openhab.binding.paradoxalarm.internal.communication.messages.HeaderCommand; import org.openhab.binding.paradoxalarm.internal.communication.messages.IPayload; import org.openhab.binding.paradoxalarm.internal.communication.messages.ParadoxIPPacket; -import org.openhab.binding.paradoxalarm.internal.communication.messages.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.partition.PartitionCommandPayload; +import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommand; +import org.openhab.binding.paradoxalarm.internal.communication.messages.zone.ZoneCommandPayload; import org.openhab.binding.paradoxalarm.internal.exceptions.ParadoxException; import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; import org.slf4j.Logger; @@ -33,6 +36,7 @@ import org.slf4j.LoggerFactory; * * @author Konstantin Polihronov - Initial contribution */ +@NonNullByDefault public class TestGetBytes { private static final int PARTITION_NUMBER = 1; @@ -61,18 +65,18 @@ public class TestGetBytes { assertTrue(Arrays.equals(packetBytes, EXPECTED1)); } - private static final byte[] EXPECTED_COMMAND_PAYLOAD = { 0x40, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static final byte[] EXPECTED_PARTITION_COMMAND_PAYLOAD = { 0x40, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; @Test - public void testCommandPayload() { - CommandPayload payload = new CommandPayload(PARTITION_NUMBER, PartitionCommand.ARM); + public void testPartitionCommandPayload() { + PartitionCommandPayload payload = new PartitionCommandPayload(PARTITION_NUMBER, PartitionCommand.ARM); final byte[] packetBytes = payload.getBytes(); - ParadoxUtil.printByteArray("Expected =", EXPECTED_COMMAND_PAYLOAD); + ParadoxUtil.printByteArray("Expected =", EXPECTED_PARTITION_COMMAND_PAYLOAD); ParadoxUtil.printByteArray("Result =", packetBytes); - assertTrue(Arrays.equals(packetBytes, EXPECTED_COMMAND_PAYLOAD)); + assertTrue(Arrays.equals(packetBytes, EXPECTED_PARTITION_COMMAND_PAYLOAD)); } private static final byte[] EXPECTED_MEMORY_PAYLOAD = { (byte) 0xAA, 0x0A, 0x00, 0x03, 0x08, (byte) 0xF0, 0x00, @@ -88,4 +92,39 @@ public class TestGetBytes { ParadoxUtil.printByteArray("Expected =", EXPECTED_MEMORY_PAYLOAD); ParadoxUtil.printByteArray("Result =", bytes); } + + private static final byte[] EXPECTED_ZONE_5_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f }; + private static final byte[] EXPECTED_ZONE_5_CLEAR_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + private static final byte[] EXPECTED_ZONE_20_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + private static final byte[] EXPECTED_ZONE_23_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f }; + private static final byte[] EXPECTED_ZONE_23_CLEAR_BYPASS_COMMAND = { (byte) 0xd0, 0x1f, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37 }; + + @Test + public void testZoneCommandPayload() { + testZoneCommandPayload(5, ZoneCommand.BYPASS, EXPECTED_ZONE_5_BYPASS_COMMAND); + testZoneCommandPayload(5, ZoneCommand.CLEAR_BYPASS, EXPECTED_ZONE_5_CLEAR_BYPASS_COMMAND); + testZoneCommandPayload(20, ZoneCommand.BYPASS, EXPECTED_ZONE_20_BYPASS_COMMAND); + testZoneCommandPayload(23, ZoneCommand.BYPASS, EXPECTED_ZONE_23_BYPASS_COMMAND); + testZoneCommandPayload(23, ZoneCommand.CLEAR_BYPASS, EXPECTED_ZONE_23_CLEAR_BYPASS_COMMAND); + } + + private void testZoneCommandPayload(int zoneNumber, ZoneCommand command, byte[] expected) { + ZoneCommandPayload payload = new ZoneCommandPayload(zoneNumber, command); + final byte[] packetBytes = payload.getBytes(); + + ParadoxUtil.printByteArray("Expected " + zoneNumber + " =", expected); + ParadoxUtil.printByteArray("Result " + zoneNumber + " =", packetBytes); + + assertTrue(Arrays.equals(packetBytes, expected)); + } } diff --git a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestParadoxUtil.java b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestParadoxUtil.java index 38b804b22d..e0926b21d9 100644 --- a/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestParadoxUtil.java +++ b/bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestParadoxUtil.java @@ -12,8 +12,9 @@ */ package org.openhab.binding.paradoxalarm.internal; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; @@ -22,6 +23,7 @@ import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil; * * @author Konstantin Polihronov - Initial contribution */ +@NonNullByDefault public class TestParadoxUtil { @Test @@ -30,19 +32,19 @@ public class TestParadoxUtil { final int rate = 16; byte[] extendedArray = ParadoxUtil.extendArray(arrayToExtend, rate); - final byte[] EXPECTED_RESULT = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59, (byte) 0xEE, (byte) 0xEE, + final byte[] expectedResult = { 0x0A, 0x50, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x59, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE, (byte) 0xEE }; - assertArrayEquals(EXPECTED_RESULT, extendedArray); // + assertArrayEquals(expectedResult, extendedArray); // } @Test public void testMergeArrays() { - final byte[] ARR1 = { 0x01, 0x02, 0x03 }; - final byte[] ARR2 = { 0x04, 0x05, 0x06 }; - final byte[] ARR3 = { 0x07, 0x08, 0x09 }; - byte[] mergedArrays = ParadoxUtil.mergeByteArrays(ARR1, ARR2, ARR3); + final byte[] arr1 = { 0x01, 0x02, 0x03 }; + final byte[] arr2 = { 0x04, 0x05, 0x06 }; + final byte[] arr3 = { 0x07, 0x08, 0x09 }; + byte[] mergedArrays = ParadoxUtil.mergeByteArrays(arr1, arr2, arr3); - final byte[] EXPECTED_RESULT = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; - assertArrayEquals(EXPECTED_RESULT, mergedArrays); + final byte[] expectedResult = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; + assertArrayEquals(expectedResult, mergedArrays); } }