]> git.basschouten.com Git - openhab-addons.git/commitdiff
[paradoxalarm] Implement zone bypass command and additional zone states (#14557)
authorKonstantin Polihronov <polychronov@gmail.com>
Tue, 25 Jul 2023 17:53:09 +0000 (20:53 +0300)
committerGitHub <noreply@github.com>
Tue, 25 Jul 2023 17:53:09 +0000 (19:53 +0200)
* Reduce warnings 1

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* 2

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* 3

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Initial files and package refactoring

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Implemented zone commands without checksum calculation

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* More stuff

* Added the checksum functionality
* Added more examples to the test

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* 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 <polychronov@gmail.com>
* Fully implement the test for creating zone command payload

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Fix build / add headers and author to the new files

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add command handling to the zone handler

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add command channel to the Zone thing

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Research of zone states and some TODO notes

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Retrieval of zone special states from the panel

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Fix build

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add the new channels to the metadata file

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add new channels to zone handler

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Fix indexing in memory map and add more logging

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Refactoring and potential NPE access fixes

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add new property "label" to the discovered zones and partitions

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Fix zone command issues

* Fix checksum creation
* Fix the parse and confirmation of the response

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add the new channels to the README.md

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Fixed issue with not updating new channels in the zones

* A silly copy/paste mistake

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* 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 <polychronov@gmail.com>
* Fix issue that the channel label is always NULL

* For both zone and partitions

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
* Add new types and channels to the i18n

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
---------

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
31 files changed:
bundles/org.openhab.binding.paradoxalarm/README.md
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/EvoCommunicator.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/MemoryMap.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/RequestType.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/Response.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/ZoneCommandRequest.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/crypto/EncryptionHandler.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/Command.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/CommandPayload.java [deleted file]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/PartitionCommand.java [deleted file]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/partition/PartitionCommandPayload.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommand.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/communication/messages/zone/ZoneCommandPayload.java [new file with mode: 0644]
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/discovery/ParadoxDiscoveryService.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxAlarmBindingConstants.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPanelHandler.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxPartitionHandler.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/handlers/ParadoxZoneHandler.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Partition.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/Zone.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneState.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/model/ZoneStateFlags.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/parsers/EvoParser.java
bundles/org.openhab.binding.paradoxalarm/src/main/java/org/openhab/binding/paradoxalarm/internal/util/ParadoxUtil.java
bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/i18n/paradoxalarm.properties
bundles/org.openhab.binding.paradoxalarm/src/main/resources/OH-INF/thing/zone.xml
bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestCreateCommandPayload.java
bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestEncryptionHandler.java
bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestGetBytes.java
bundles/org.openhab.binding.paradoxalarm/src/test/java/org/openhab/binding/paradoxalarm/internal/TestParadoxUtil.java

index 8f449875ef8fbaf32249e1effff2507337f2f576..55ceb2f95b68dddd8a5a676c44ea763a190409f5 100644 (file)
@@ -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
 
index 1bb9c7385ea29e6624c2cfa1dd4b8fe30c8bca86..5c6c0b3e05ce05836ddbd9e8e137685d29c269b9 100644 (file)
@@ -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() {
index de086e06a402ff8106d263c856e9535962c6965e..47520858eca56cbafe746795ea5a64d4290d743d 100644 (file)
@@ -41,6 +41,6 @@ public class MemoryMap {
     }
 
     public synchronized void updateElement(int index, byte[] elementValue) {
-        ramCache.set(index - 1, elementValue);
+        ramCache.set(index, elementValue);
     }
 }
index 7d68fd8c10b9ee132694b7cbb4bbbb4aa741dfd6..0e3f9cece2ef455d55b06879772cc24e64c3c135 100644 (file)
@@ -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 (file)
index 0000000..6973410
--- /dev/null
@@ -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);
+    }
+}
index e43ee2dbaa089474bf9e526496edd058cc0777fa..a87bacd46be855e5b2c13d87acbed4a4afa710df 100644 (file)
@@ -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 (file)
index 0000000..0519dff
--- /dev/null
@@ -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 (file)
index 4b87326..0000000
+++ /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 (file)
index 14c8d05..0000000
+++ /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 (file)
index 0000000..4b49e47
--- /dev/null
@@ -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 (file)
index 0000000..37c18cb
--- /dev/null
@@ -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 (file)
index 0000000..7433ae8
--- /dev/null
@@ -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 (file)
index 0000000..406a7bb
--- /dev/null
@@ -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)<br>
+     *
+     * 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),<br>
+     * for zone 5 - 0x10, for zone 6 - 0x20, for zone 7 - 0x40, for zone 8 - 0x80.<br>
+     * 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;
+    }
+}
index a3f62f22c6c6efdc2c38b0db79d01062c91ccf65..ba014c821e6ccd74e05662b7a478eadcbd73c864 100644 (file)
@@ -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);
index 83b68fda8d1c38e07c58a054a7cb04b0b7bebe13..a082d7c8085fdcd8520c65bbfe76415b9b937a45 100644 (file)
@@ -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");
index ffc390f5e82755c11921b7fe5c8f1ada7b3e100e..c3a932a257f24d82d45282a68168542477dced3b 100644 (file)
@@ -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);
     }
index f7debaeb0828404908f6e3dd382b12f9e2c79273..99855a25fbb4fbc8e306d3ffd92b901e246a4c8f 100644 (file)
@@ -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<Partition> 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<Partition> 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;
+    }
 }
index a9027650699cf1fbc43972204794de7d0b56f2fa..c2ab9c15007f14a12a50bc5dcf749bf69a7cff15 100644 (file)
@@ -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<Zone> 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<Zone> 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();
+    }
 }
index d845217e91d2decaf6fdce111dcb41c8b5ad9858..91dae6baf3bd05275102caa849a68011694595e3 100644 (file)
  */
 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);
     }
 }
index 3bf1faa8c8f0916903738c5fa7bb710737dda81f..1fa12d36ecfac993df74df868c34bb4f78383ab6 100644 (file)
  */
 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);
     }
 }
index 80687626957d6330872a8b1fdeb237cacad20cb9..c058a5fa12bd69085be0d03e7bdfae783d9dd129 100644 (file)
@@ -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 + "]";
+    }
 }
index bc2ad5ebdac8f1ad1804e527931564d31e80821a..4a920e19efa7233a5d0f3d5516c05282a38e7438 100644 (file)
@@ -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;
+    }
 }
index 7a95e1a4285ea9eb1137f89ba545a054164ad9c3..8b3c9251c978e1e2540ac03b099ebf73a612faa5 100644 (file)
@@ -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));
     }
 }
index 0f6768f2c4562419a74f9d4ff91258cb236c0997..1624b9d2447c3c9b6772d0b351c2a0b6ffa56031 100644 (file)
@@ -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];
         }
     }
index e347f372d2549fccd356fcd5efbf9d4c07471a69..b59bfef8add48cf27165820759609d0deff2daf1 100644 (file)
@@ -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.
index 67c3b62fc1d0d9ad8fe84eb54ffeffedc19ab806..7c35ff6c896448592eb6252c406d0e00a2461e8c 100644 (file)
                        <channel id="opened" typeId="openedState"/>
                        <channel id="tampered" typeId="tamperedState"/>
                        <channel id="lowBattery" typeId="system.low-battery"/>
+                       <channel id="command" typeId="command"/>
+
+                       <channel id="supervisionTrouble" typeId="supervisionTrouble"/>
+                       <channel id="inTxDelay" typeId="inTxDelay"/>
+                       <channel id="shutdown" typeId="shutdown"/>
+                       <channel id="bypassed" typeId="bypassed"/>
+                       <channel id="hasActivatedIntellizoneDelay" typeId="hasActivatedIntellizoneDelay"/>
+                       <channel id="hasActivatedEntryDelay" typeId="hasActivatedEntryDelay"/>
+                       <channel id="presentlyInAlarm" typeId="presentlyInAlarm"/>
+                       <channel id="generatedAlarm" typeId="generatedAlarm"/>
                </channels>
 
                <config-description>
                <description>State of zone</description>
                <state readOnly="true" pattern="%s"/>
        </channel-type>
+       <channel-type id="command">
+               <item-type>String</item-type>
+               <label>Zone Command</label>
+               <description>Send command for a zone</description>
+               <state>
+                       <options>
+                               <option value="BYPASS">Bypass</option>
+                               <option value="CLEAR_BYPASS">Clear Bypass</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="supervisionTrouble">
+               <item-type>Switch</item-type>
+               <label>Supervision Trouble</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="inTxDelay">
+               <item-type>Switch</item-type>
+               <label>In TX Delay</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="shutdown">
+               <item-type>Switch</item-type>
+               <label>Shutdown</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="bypassed">
+               <item-type>Switch</item-type>
+               <label>Bypassed</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="hasActivatedIntellizoneDelay">
+               <item-type>Switch</item-type>
+               <label>Has Activated Intellizone Delay</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="hasActivatedEntryDelay">
+               <item-type>Switch</item-type>
+               <label>Has Activated Entry Delay</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="presentlyInAlarm">
+               <item-type>Switch</item-type>
+               <label>Currently in Alarm</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="generatedAlarm">
+               <item-type>Switch</item-type>
+               <label>Generated an Alarm</label>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
 </thing:thing-descriptions>
index 4cc24f8315b56e51a4a0a81a14e4364271ecc4f5..ce2920f546f0d9b6b0fbc4477297a5ee7a8e8358 100644 (file)
  */
 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];
index 475554b24ece38c7fdaa911933cde6f63792eb82..8b106f1431b38f3094a25544556cd0387448259c 100644 (file)
@@ -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.";
index 5d571a17ec5459dd99a2042abc3f38ee65d82d55..22a1f0b21d2792b6e65b06ebe5c368c23ab9ff4b 100644 (file)
  */
 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));
+    }
 }
index 38b804b22d9dc725be526bbc0903f318cc5bb1a5..e0926b21d92c70f5f73696217599424e7f2995ec 100644 (file)
@@ -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);
     }
 }