]> git.basschouten.com Git - openhab-addons.git/commitdiff
[qolsysiq] Initial contribution of the Qolsys IQ Binding (#13699)
authorDan Cunningham <dan@digitaldan.com>
Sun, 27 Nov 2022 18:25:31 +0000 (10:25 -0800)
committerGitHub <noreply@github.com>
Sun, 27 Nov 2022 18:25:31 +0000 (19:25 +0100)
* [qolsysiq] Initial contribution of the Qolsys IQ Binding

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
55 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.qolsysiq/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/README.md [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/doc/qolsysiq4.png [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysIQClientListener.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysiqClient.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/Action.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ActionType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmAction.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmActionType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmAwayArmingAction.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingAction.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingActionType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoAction.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoActionType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/AlarmEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ArmingEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ErrorEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/Event.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/EventType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEventType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SecureArmInfoEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SummaryInfoEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneActiveEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneAddEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEventType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneUpdateEvent.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/AlarmType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Partition.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/PartitionStatus.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Zone.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneActiveState.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneStatus.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneType.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPanelConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPartitionConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQZoneConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/discovery/QolsysIQChildDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQChildDiscoveryHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPanelHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPartitionHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQZoneHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/i18n/qolsysiq.properties [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/panel.xml [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/partition.xml [new file with mode: 0644]
bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/zone.xml [new file with mode: 0644]
bundles/pom.xml

index 1d0ebb2377e8b8028176f88ab30959f078b10f64..2cd894f963887d5e87b436cacdf3970d7e0d4934 100644 (file)
 /bundles/org.openhab.binding.pushover/ @cweitkamp
 /bundles/org.openhab.binding.pushsafer/ @appzer @cweitkamp
 /bundles/org.openhab.binding.qbus/ @QbusKoen
+/bundles/org.openhab.binding.qolsysiq/ @digitaldan
 /bundles/org.openhab.binding.radiothermostat/ @mlobstein
 /bundles/org.openhab.binding.regoheatpump/ @crnjan
 /bundles/org.openhab.binding.revogi/ @andibraeu
index d5178fa3bc06d7fa2d63973fda6cda6c2c2fdc29..eb7f0257249801d85023c574e72e4cdf880456d5 100644 (file)
       <artifactId>org.openhab.binding.qbus</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.qolsysiq</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.radiothermostat</artifactId>
diff --git a/bundles/org.openhab.binding.qolsysiq/NOTICE b/bundles/org.openhab.binding.qolsysiq/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.qolsysiq/README.md b/bundles/org.openhab.binding.qolsysiq/README.md
new file mode 100644 (file)
index 0000000..7be0e7f
--- /dev/null
@@ -0,0 +1,125 @@
+# Qolsys IQ Binding
+
+This binding directly controls a [Qolsys IQ](https://qolsys.com/security/) security panel.
+This allows for local monitoring of alarm and zone statuses as well as arming, disarming and triggering alarms.
+
+Qolsys (a division of Johnson Controls) is a popular manufacturer of alarm systems.
+The Qolsys IQ line of panels supports both wireless and hard wire sensors and features built in Cellular and Wi-Fi dual path communication that natively integrates with Alarm.com monitoring and supervision. 
+
+This binding directly interfaces with the panel and does not require cloud access. 
+
+![Qolsys IQ 4](doc/qolsysiq4.png)
+
+## Supported Things
+
+| Thing               | Description                                                                               | Thing Type | Thing UID |
+|---------------------|-------------------------------------------------------------------------------------------|------------|-----------|
+| Qolsys IQ Panel     | A Qolsys IQ security panel (all current models, which is 2+ and 4 at the time of writing) | Bridge     | panel     |
+| Qolsys IQ Partition | A logical partition which can be armed, disarmed, and is responsible for managing zones   | Bridge     | partition |
+| Qolsys IQ Zone      | A generic zone sensor                                                                     | Thing      | zone      |
+
+## Discovery
+
+### Qolsys IQ Panel (Bridge)
+
+The Qolsys IQ Panel must be manually added using a host name or ip address along with a secure access token from the panel settings.
+To enable 3rd party control and retrieve the access token follow the following steps on the security panel touch screen:
+
+`Settings` --> `Advanced Settings` --> `Installation` --> `Dealer Settings` -> `6 Digit User Code` (set to enabled)
+
+`Settings` --> `Advanced Settings` --> `Installation` --> `Devices` --> `Wi-Fi Devices` --> `Control4` (set to enabled)
+
+ *Panel will reboot*
+
+`Settings` --> `Advanced Settings` --> `Installation` --> `Devices` --> `Wi-Fi Devices` --> `Reveal Secure Token` (copy token to use in panel configuration)
+
+At this point you may add the panel thing in openHAB using the secure token along with the IP or host name of the panel.  
+
+### Partition (Bridge)
+
+Once a panel is added, partitions will be automatically discovered and appear in the inbox.
+
+### Zone (Thing)
+
+Once a partition is added, zones will be automatically discovered and appear in the inbox.
+
+## Thing Configuration
+
+### `panel` Thing Configuration
+
+| Name              | Type    | Description                                         | Default | Required | Advanced |
+|-------------------|---------|-----------------------------------------------------|---------|----------|----------|
+| hostname          | text    | Hostname or IP address of the device                | N/A     | yes      | no       |
+| port              | integer | Port the device is listening on                     | 12345   | no       | no       |
+| key               | text    | Access token / key found in the panel settings menu | N/A     | yes      | no       |
+
+### `partition` Thing Configuration
+
+| Name       | Type    | Description                                                                                                                                                                                                 | Default | Required | Advanced |
+|------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------|
+| id         | integer | Partition id of the panel, staring with '0' for the first partition                                                                                                                                         | N/A     | yes      | no       |
+| disarmCode | text    | Optional disarm code to use when receiving a disarm command without a code. Required for integrations like Alexa and Homekit who do not provide codes when disarming.  Leave blank to always require a code | blank   | no       | no       |
+| armCode    | text    | Optional arm code to use when receiving arm commands without a code.  Only required if the panel has been configured to require arm codes.  Leave blank to always require a code                            | blank   | no       | yes      |
+
+### `zone` Thing Configuration
+
+| Name    | Type    | Description                                                                                             | Default | Required | Advanced |
+|---------|---------|---------------------------------------------------------------------------------------------------------|---------|----------|----------|
+| id      | integer | Id of the zone, staring with '1' for the first zone                                                | N/A     | yes      | no       |
+
+## Channels
+
+### Panel Channels
+
+None.
+
+### Partition Channels
+
+| Channel     | Type   | Read/Write | Description                                                                                                                                                                                                                                                                                | State Options                                              | Command Options            |
+|-------------|--------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|----------------------------|
+| armState    | String | RW         | Reports the current partition arm state or sends an arm or disarm command to the system. Security codes can be appended to the command using a colon delimiter (e.g. 'DISARM:123456'). Codes appended to the command will be used in place of the `armCode` configuration property if set. | ALARM, ARM_AWAY, ARM_STAY, DISARM, ENTRY_DELAY, EXIT_DELAY | ARM_AWAY, ARM_STAY, DISARM |
+| alarmState  | String | RW         | Reports on the current alarm state, or triggers an instant alarm                                                                                                                                                                                                                           | AUXILIARY, FIRE, POLICE, ZONEOPEN, NONE                    | AUXILIARY, FIRE, POLICE    |
+| armingDelay | Number | R          | The arming delay countdown currently in progress                                                                                                                                                                                                                                           | Seconds remaining                                          | N/A                        |
+| errorEvent  | String | R          | Last error event message reported by the partition. Clears after 30 seconds                                                                                                                                                                                                                | Error text                                                 | N/A                        |
+
+### Zone Channels
+
+| Channel | Type    | Read/Write | Description            | State Options                               | 
+|---------|---------|------------|------------------------|---------------------------------------------|
+| status  | String  | R          | The zone status        | ACTIVE, CLOSED, OPEN, FAILURE, IDLE, TAMPER |
+| state   | Number  | R          | The zone state         | Number                                      |
+| contact | Contact | R          | The zone contact state | OPEN, CLOSED                                |
+
+## Full Example
+
+### qolsysiq.things
+
+```
+Bridge qolsysiq:panel:home "Home Security Panel" [ hostname="192.168.3.123", port=12345, key="AAABBB00" ] {
+    Bridge partition 0 "Partition Main" [ id=0, armCode="123456" ] {
+        Thing zone 1 "Window" [ id=1 ]
+        Thing zone 2 "Motion" [ id=2 ]
+    }
+}
+```
+
+### qolsysiq.items
+
+Sample items file with both Alexa and Homekit voice control
+
+```
+Group      PartitionMain                         "Alarm System"                                                           ["Equipment"]    {alexa="SecurityPanel", homekit = "SecuritySystem"}
+String     PartitionMain_PartitionArmState       "Partition Arm State"                <Alarm>    (PartitionMain)          ["Point"]        {channel="qolsysiq:partition:home:0:armState", alexa="ArmState" [DISARMED="DISARM",ARMED_STAY="ARM_STAY",ARMED_AWAY="ARM_AWAY:EXIT_DELAY"], homekit = "SecuritySystem.CurrentSecuritySystemState,SecuritySystem.TargetSecuritySystemState" [STAY_ARM="ARM_STAY", AWAY_ARM="ARM_AWAY", DISARM="DISARM", DISARMED="DISARM", TRIGGERED="ALARM"]}
+String     PartitionMain_PartitionAlarmState     "Partition Alarm State"              <Alarm>    (PartitionMain)          ["Point"]        {channel="qolsysiq:partition:home:0:alarmState"}
+Number     PartitionMain_PartitionArmingDelay    "Partition Arming Delay"                        (PartitionMain)          ["Point"]        {channel="qolsysiq:partition:home:0:armingDelay"}
+String     PartitionMain_ErrorEvent              "Error Event"                                   (PartitionMain)          ["Point"]        {channel="qolsysiq:partition:home:0:errorEvent" }
+
+Group      ZoneKitchenWindows                    "Qolsys IQ Zone: Kitchen Windows"                                        ["Equipment"]
+Number     ZoneKitchenWindows_ZoneState          "Kitchen Windows Zone State"                    (ZoneKitchenWindows)     ["Point"]        {channel="qolsysiq:zone:home:0:1:state"}
+String     ZoneKitchenWindows_ZoneStatus         "Kitchen Windows Zone Status"                   (ZoneKitchenWindows)     ["Point"]        {channel="qolsysiq:zone:home:0:1:status"}
+Contact    ZoneKitchenWindows_ZoneContact        "Kitchen Windows Zone Contact"                  (ZoneKitchenWindows)     ["Point"]        {channel="qolsysiq:zone:home:0:1:contact"}
+
+Group      ZoneMotionDetector1                   "Motion Detector 1"                                                      ["Equipment"]
+Number     ZoneMotionDetector_ZoneState1         "Motion Detector 1 Zone State"                  (ZoneMotionDetector1)    ["Point"]        {channel="qolsysiq:zone:home:0:2:state"}
+String     ZoneMotionDetector_ZoneStatus1        "Motion Detector 1 Zone Status"                 (ZoneMotionDetector1)    ["Point"]        {channel="qolsysiq:zone:home:0:2:status"} 
+```
diff --git a/bundles/org.openhab.binding.qolsysiq/doc/qolsysiq4.png b/bundles/org.openhab.binding.qolsysiq/doc/qolsysiq4.png
new file mode 100644 (file)
index 0000000..35fbb25
Binary files /dev/null and b/bundles/org.openhab.binding.qolsysiq/doc/qolsysiq4.png differ
diff --git a/bundles/org.openhab.binding.qolsysiq/pom.xml b/bundles/org.openhab.binding.qolsysiq/pom.xml
new file mode 100644 (file)
index 0000000..91c7b73
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>3.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.qolsysiq</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: QolsysIQ Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/feature/feature.xml b/bundles/org.openhab.binding.qolsysiq/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..b02bdd6
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.qolsysiq-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-qolsysiq" description="QolsysIQ Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.qolsysiq/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQBindingConstants.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQBindingConstants.java
new file mode 100644 (file)
index 0000000..4028893
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link QolsysIQBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQBindingConstants {
+
+    public static final String BINDING_ID = "qolsysiq";
+
+    public static final ThingTypeUID THING_TYPE_PANEL = new ThingTypeUID(BINDING_ID, "panel");
+    public static final ThingTypeUID THING_TYPE_PARTITION = new ThingTypeUID(BINDING_ID, "partition");
+    public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
+
+    public static final String CHANNEL_PARTITION_ARM_STATE = "armState";
+    public static final String CHANNEL_PARTITION_ALARM_STATE = "alarmState";
+    public static final String CHANNEL_PARTITION_COMMAND_DELAY = "armingDelay";
+    public static final String CHANNEL_PARTITION_ERROR_EVENT = "errorEvent";
+
+    public static final String CHANNEL_ZONE_STATE = "state";
+    public static final String CHANNEL_ZONE_STATUS = "status";
+    public static final String CHANNEL_ZONE_CONTACT = "contact";
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQHandlerFactory.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQHandlerFactory.java
new file mode 100644 (file)
index 0000000..ae1edf8
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal;
+
+import static org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.handler.QolsysIQPanelHandler;
+import org.openhab.binding.qolsysiq.internal.handler.QolsysIQPartitionHandler;
+import org.openhab.binding.qolsysiq.internal.handler.QolsysIQZoneHandler;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link QolsysIQHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.qolsysiq", service = ThingHandlerFactory.class)
+public class QolsysIQHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PANEL, THING_TYPE_PARTITION,
+            THING_TYPE_ZONE);
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (THING_TYPE_PANEL.equals(thingTypeUID)) {
+            return new QolsysIQPanelHandler((Bridge) thing);
+        }
+
+        if (THING_TYPE_PARTITION.equals(thingTypeUID)) {
+            return new QolsysIQPartitionHandler((Bridge) thing);
+        }
+
+        if (THING_TYPE_ZONE.equals(thingTypeUID)) {
+            return new QolsysIQZoneHandler(thing);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysIQClientListener.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysIQClientListener.java
new file mode 100644 (file)
index 0000000..61e7566
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
+
+/**
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public interface QolsysIQClientListener {
+    /**
+     * Callback when the connection has been disconnected
+     *
+     * @param reason
+     */
+    void disconnected(Exception reason);
+
+    /**
+     * {@link AlarmEvent} message callback
+     *
+     * @param event
+     */
+    void alarmEvent(AlarmEvent event);
+
+    /**
+     * {@link ArmingEvent} message callback
+     *
+     * @param event
+     */
+    void armingEvent(ArmingEvent event);
+
+    /**
+     * {@link ErrorEvent} message callback
+     *
+     * @param event
+     */
+    void errorEvent(ErrorEvent event);
+
+    /**
+     * {@link SummaryInfoEvent} message callback
+     *
+     * @param event
+     */
+    void summaryInfoEvent(SummaryInfoEvent event);
+
+    /**
+     * {@link SecureArmInfoEvent} message callback
+     *
+     * @param event
+     */
+    void secureArmInfoEvent(SecureArmInfoEvent event);
+
+    /**
+     * {@link ZoneActiveEvent} message callback
+     *
+     * @param event
+     */
+    void zoneActiveEvent(ZoneActiveEvent event);
+
+    /**
+     * {@link ZoneUpdateEvent} message callback
+     *
+     * @param event
+     */
+    void zoneUpdateEvent(ZoneUpdateEvent event);
+
+    /**
+     * {@link ZoneAddEvent} message callback
+     *
+     * @param event
+     */
+    void zoneAddEvent(ZoneAddEvent event);
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysiqClient.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysiqClient.java
new file mode 100644 (file)
index 0000000..ae73896
--- /dev/null
@@ -0,0 +1,390 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Type;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.Action;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.Event;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.EventType;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.InfoEventType;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneEventType;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * A client that can communicate with a Qolsys IQ Panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysiqClient {
+    private static final String MESSAGE_ACK = "ACK";
+    private final Logger logger = LoggerFactory.getLogger(QolsysiqClient.class);
+    private final Gson gson = new GsonBuilder().registerTypeAdapter(Event.class, new EventDeserializer())
+            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+    private List<QolsysIQClientListener> listeners = Collections.synchronizedList(new ArrayList<>());
+    private @Nullable SSLSocket socket;
+    private @Nullable BufferedReader reader;
+    private @Nullable BufferedWriter writer;
+    private @Nullable Thread readerThread;
+    private @Nullable ScheduledFuture<?> heartBeatFuture;
+    private ScheduledExecutorService scheduler;
+    private Object writeLock = new Object();
+    private long lastResponseTime;
+    private boolean hasACK = false;
+    private boolean connected;
+    private String host;
+    private int port;
+    private int heartbeatSeconds;
+    private String threadName;
+    private SSLSocketFactory sslsocketfactory;
+
+    /**
+     * Creates a new QolsysiqClient
+     *
+     * @param host
+     * @param port
+     * @param heartbeatSeconds
+     * @param scheduler for the heart beat task
+     * @param threadName
+     */
+    public QolsysiqClient(String host, int port, int heartbeatSeconds, ScheduledExecutorService scheduler,
+            String threadName) throws IOException {
+        this.host = host;
+        this.port = port;
+        this.heartbeatSeconds = heartbeatSeconds;
+        this.scheduler = scheduler;
+        this.threadName = threadName;
+
+        try {
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, acceptAlltrustManagers(), null);
+            sslsocketfactory = sslContext.getSocketFactory();
+        } catch (KeyManagementException | NoSuchAlgorithmException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Connects to the panel
+     *
+     * @throws IOException
+     */
+    public synchronized void connect() throws IOException {
+        logger.debug("connect");
+        if (connected) {
+            logger.debug("connect: already connected, ignoring");
+            return;
+        }
+
+        SSLSocket socket = (SSLSocket) sslsocketfactory.createSocket(host, port);
+        socket.startHandshake();
+        writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
+        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+        this.socket = socket;
+
+        Thread readerThread = new Thread(this::readEvents, threadName);
+        readerThread.setDaemon(true);
+        readerThread.start();
+        this.readerThread = readerThread;
+        connected = true;
+        try {
+            // send an initial message to confirm a connection and record a response time
+            writeMessage("");
+        } catch (IOException e) {
+            // clean up before bubbling up exception
+            disconnect();
+            throw e;
+        }
+        heartBeatFuture = scheduler.scheduleWithFixedDelay(() -> {
+            if (connected) {
+                try {
+                    if (System.currentTimeMillis() - lastResponseTime > (heartbeatSeconds + 5) * 1000) {
+                        throw new IOException("No responses received");
+                    }
+                    writeMessage("");
+                } catch (IOException e) {
+                    logger.debug("Problem sending heartbeat", e);
+                    disconnectAndNotify(e);
+                }
+            }
+        }, heartbeatSeconds, heartbeatSeconds, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Disconnects from the panel
+     */
+    public void disconnect() {
+        connected = false;
+
+        ScheduledFuture<?> heartbeatFuture = this.heartBeatFuture;
+        if (heartbeatFuture != null) {
+            heartbeatFuture.cancel(true);
+        }
+
+        Thread readerThread = this.readerThread;
+        if (readerThread != null && readerThread.isAlive()) {
+            readerThread.interrupt();
+        }
+
+        SSLSocket socket = this.socket;
+        if (socket != null) {
+            try {
+                socket.close();
+            } catch (IOException e) {
+                logger.debug("Error closing SSL socket: {}", e.getMessage());
+            }
+            this.socket = null;
+        }
+        BufferedReader reader = this.reader;
+        if (reader != null) {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                logger.debug("Error closing reader: {}", e.getMessage());
+            }
+            this.reader = null;
+        }
+        BufferedWriter writer = this.writer;
+        if (writer != null) {
+            try {
+                writer.close();
+            } catch (IOException e) {
+                logger.debug("Error closing writer: {}", e.getMessage());
+            }
+            this.writer = null;
+        }
+    }
+
+    /**
+     * Sends an Action message to the panel
+     *
+     * @param action
+     * @throws IOException
+     */
+    public void sendAction(Action action) throws IOException {
+        logger.debug("sendAction {}", action.type);
+        writeMessage(gson.toJson(action));
+    }
+
+    /**
+     * Adds a QolsysIQClientListener
+     *
+     * @param listener
+     */
+    public void addListener(QolsysIQClientListener listener) {
+        synchronized (listeners) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a QolsysIQClientListener
+     *
+     * @param listener
+     */
+    public void removeListener(QolsysIQClientListener listener) {
+        synchronized (listeners) {
+            listeners.remove(listener);
+        }
+    }
+
+    private synchronized void writeMessage(String message) throws IOException {
+        if (!connected) {
+            logger.debug("writeMessage: not connected, ignoring {}", message);
+            return;
+        }
+        synchronized (writeLock) {
+            hasACK = false;
+            logger.trace("writeMessage: {}", message);
+            BufferedWriter writer = this.writer;
+            if (writer != null) {
+                writer.write(message);
+                writer.newLine();
+                writer.flush();
+                try {
+                    writeLock.wait(5000);
+                } catch (InterruptedException e) {
+                    logger.debug("write lock interupted");
+                }
+                if (!hasACK) {
+                    logger.trace("writeMessage: no ACK for {}", message);
+                    throw new IOException("No response to message: " + message);
+                }
+            }
+        }
+    }
+
+    private void readEvents() {
+        String message;
+        BufferedReader reader = this.reader;
+        try {
+            while (connected && reader != null && (message = reader.readLine()) != null) {
+                logger.trace("Message: {}", message);
+                lastResponseTime = System.currentTimeMillis();
+                if (MESSAGE_ACK.equals(message)) {
+                    synchronized (writeLock) {
+                        hasACK = true;
+                        writeLock.notify();
+                    }
+                    continue;
+                }
+                try {
+                    Event event = gson.fromJson(message, Event.class);
+                    if (event == null) {
+                        logger.debug("Could not deserialize message: {}", message);
+                        continue;
+                    }
+                    synchronized (listeners) {
+                        if (event instanceof AlarmEvent) {
+                            listeners.forEach(listener -> listener.alarmEvent((AlarmEvent) event));
+                        } else if (event instanceof ArmingEvent) {
+                            listeners.forEach(listener -> listener.armingEvent((ArmingEvent) event));
+                        } else if (event instanceof ErrorEvent) {
+                            listeners.forEach(listener -> listener.errorEvent((ErrorEvent) event));
+                        } else if (event instanceof SecureArmInfoEvent) {
+                            listeners.forEach(listener -> listener.secureArmInfoEvent((SecureArmInfoEvent) event));
+                        } else if (event instanceof SummaryInfoEvent) {
+                            listeners.forEach(listener -> listener.summaryInfoEvent((SummaryInfoEvent) event));
+                        } else if (event instanceof ZoneActiveEvent) {
+                            listeners.forEach(listener -> listener.zoneActiveEvent((ZoneActiveEvent) event));
+                        } else if (event instanceof ZoneUpdateEvent) {
+                            listeners.forEach(listener -> listener.zoneUpdateEvent((ZoneUpdateEvent) event));
+                        } else if (event instanceof ZoneAddEvent) {
+                            listeners.forEach(listener -> listener.zoneAddEvent((ZoneAddEvent) event));
+                        }
+                    }
+                } catch (JsonSyntaxException e) {
+                    logger.debug("Could not parse messge", e);
+                }
+            }
+            if (connected) {
+                throw new IOException("socket disconencted");
+            }
+        } catch (IOException e) {
+            disconnectAndNotify(e);
+        }
+    }
+
+    private void disconnectAndNotify(Exception e) {
+        if (connected) {
+            disconnect();
+            synchronized (listeners) {
+                listeners.forEach(listener -> listener.disconnected(e));
+            }
+        }
+    }
+
+    private TrustManager[] acceptAlltrustManagers() {
+        return new TrustManager[] { new X509TrustManager() {
+            @Override
+            public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
+            }
+
+            @Override
+            public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
+            }
+
+            @Override
+            public X509Certificate @Nullable [] getAcceptedIssuers() {
+                return null;
+            }
+        } };
+    }
+
+    class EventDeserializer implements JsonDeserializer<Event> {
+        @Override
+        public @Nullable Event deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+                throws JsonParseException {
+            JsonObject jsonObject = json.getAsJsonObject();
+            JsonElement event = jsonObject.get("event");
+            if (event != null) {
+                switch (EventType.valueOf(event.getAsString())) {
+                    case ALARM:
+                        return context.deserialize(jsonObject, AlarmEvent.class);
+                    case ARMING:
+                        return context.deserialize(jsonObject, ArmingEvent.class);
+                    case ERROR:
+                        return context.deserialize(jsonObject, ErrorEvent.class);
+                    case INFO:
+                        JsonElement infoType = jsonObject.get("info_type");
+                        if (infoType != null) {
+                            switch (InfoEventType.valueOf(infoType.getAsString())) {
+                                case SECURE_ARM:
+                                    return context.deserialize(jsonObject, SecureArmInfoEvent.class);
+                                case SUMMARY:
+                                    return context.deserialize(jsonObject, SummaryInfoEvent.class);
+                            }
+                        }
+                        break;
+                    case ZONE_EVENT:
+                        JsonElement zoneEventType = jsonObject.get("zone_event_type");
+                        if (zoneEventType != null) {
+                            switch (ZoneEventType.valueOf(zoneEventType.getAsString())) {
+                                case ZONE_ACTIVE:
+                                    return context.deserialize(jsonObject, ZoneActiveEvent.class);
+                                case ZONE_UPDATE:
+                                    return context.deserialize(jsonObject, ZoneUpdateEvent.class);
+                                case ZONE_ADD:
+                                    return context.deserialize(jsonObject, ZoneAddEvent.class);
+                                default:
+                                    break;
+                            }
+                        }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/Action.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/Action.java
new file mode 100644 (file)
index 0000000..2f71427
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The base type for various action messages sent to a panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public abstract class Action {
+    @SerializedName("action")
+    public ActionType type;
+    public Integer version = 0;
+    public String source = "C4";
+    public String token;
+
+    public Action(ActionType type) {
+        this(type, "");
+    }
+
+    public Action(ActionType type, String token) {
+        this.type = type;
+        this.token = token;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ActionType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ActionType.java
new file mode 100644 (file)
index 0000000..af9184a
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * The type of {@link Action} sent to a panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum ActionType {
+    ALARM,
+    ARMING,
+    INFO
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmAction.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmAction.java
new file mode 100644 (file)
index 0000000..b2b6e19
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * An {@link ActionType.ALARM} type of {@link Action} message sent to the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class AlarmAction extends Action {
+    public AlarmActionType alarmType;
+
+    public AlarmAction(AlarmActionType alarmType) {
+        this(alarmType, "");
+    }
+
+    public AlarmAction(AlarmActionType alarmType, String token) {
+        super(ActionType.ALARM, token);
+        this.alarmType = alarmType;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmActionType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmActionType.java
new file mode 100644 (file)
index 0000000..dc8c577
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * The type of {@link AlarmAction} sent to a panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum AlarmActionType {
+    AUXILIARY,
+    FIRE,
+    POLCIE
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmAwayArmingAction.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmAwayArmingAction.java
new file mode 100644 (file)
index 0000000..3149b57
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * An {@link ArmingActionType.ARM_AWAY} type of {@link ArmingAction} message sent to the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ArmAwayArmingAction extends ArmingAction {
+    public Integer delay;
+
+    public ArmAwayArmingAction(String token, Integer partitionId, Integer delay) {
+        super(ArmingActionType.ARM_AWAY, token, partitionId);
+        this.delay = delay;
+    }
+
+    public ArmAwayArmingAction(String token, Integer partitionId) {
+        this(token, partitionId, null);
+    }
+
+    public ArmAwayArmingAction(Integer partitionId) {
+        this("", partitionId, null);
+    }
+
+    public ArmAwayArmingAction(Integer partitionId, Integer delay) {
+        this("", partitionId, delay);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingAction.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingAction.java
new file mode 100644 (file)
index 0000000..fdf0bb8
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * An {@link ActionType.ARMING} type of {@link ArmingAction} message sent to the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ArmingAction extends Action {
+    public ArmingActionType armingType;
+    public Integer partitionId;
+    public String usercode;
+
+    public ArmingAction(ArmingActionType armingType, Integer partitionId) {
+        this(armingType, "", partitionId, null);
+    }
+
+    public ArmingAction(ArmingActionType armingType, Integer partitionId, String usercode) {
+        this(armingType, "", partitionId, usercode);
+    }
+
+    public ArmingAction(ArmingActionType armingType, String token, Integer partitionId) {
+        this(armingType, token, partitionId, null);
+    }
+
+    public ArmingAction(ArmingActionType armingType, String token, Integer partitionId, String usercode) {
+        super(ActionType.ARMING, token);
+        this.armingType = armingType;
+        this.partitionId = partitionId;
+        this.usercode = usercode;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingActionType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingActionType.java
new file mode 100644 (file)
index 0000000..9951b68
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * The type of {@link ArmingAction} sent to a panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum ArmingActionType {
+    ARM_AWAY,
+    ARM_STAY,
+    DISARM;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoAction.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoAction.java
new file mode 100644 (file)
index 0000000..4d1e6a5
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * An {@link ActionType.INFO} type of {@link InfoAction} message sent to the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class InfoAction extends Action {
+    public InfoActionType infoType;
+
+    public InfoAction(InfoActionType infoType) {
+        this(infoType, "");
+    }
+
+    public InfoAction(InfoActionType infoType, String token) {
+        super(ActionType.INFO, token);
+        this.infoType = infoType;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoActionType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoActionType.java
new file mode 100644 (file)
index 0000000..a62a7a3
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.action;
+
+/**
+ * The type of {@link InfoAction} sent to a panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum InfoActionType {
+    SUMMARY,
+    SECURE_ARM
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/AlarmEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/AlarmEvent.java
new file mode 100644 (file)
index 0000000..0570e8d
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.AlarmType;
+
+/**
+ * An {@link EventType.ALARM} type of {@link Event} message sent from the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class AlarmEvent extends Event {
+    public AlarmType alarmType;
+    public Integer partitionId;
+
+    public AlarmEvent() {
+        super(EventType.ALARM);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ArmingEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ArmingEvent.java
new file mode 100644 (file)
index 0000000..42bc3c0
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.PartitionStatus;
+
+/**
+ * An {@link EventType.ARMING} type of {@link Event} message sent from the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ArmingEvent extends Event {
+    public PartitionStatus armingType;
+    public Integer partitionId;
+    public Integer delay;
+
+    public ArmingEvent() {
+        super(EventType.ARMING);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ErrorEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ErrorEvent.java
new file mode 100644 (file)
index 0000000..1c04abf
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * An {@link EventType.ERROR} type of {@link Event} message sent from the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ErrorEvent extends Event {
+    public String errorType;
+    public String description;
+    public Integer partitionId;
+
+    public ErrorEvent() {
+        super(EventType.ERROR);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/Event.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/Event.java
new file mode 100644 (file)
index 0000000..77bc1da
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The base type for various event messages sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public abstract class Event {
+    @SerializedName("event")
+    public EventType eventType;
+    public String nonce;
+    @SerializedName("requestID")
+    public String requestID;
+
+    public Event(EventType eventType) {
+        this.eventType = eventType;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/EventType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/EventType.java
new file mode 100644 (file)
index 0000000..ba2621d
--- /dev/null
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * The type of {@link Event} sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum EventType {
+    ALARM,
+    ARMING,
+    ERROR,
+    INFO,
+    ZONE_EVENT;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEvent.java
new file mode 100644 (file)
index 0000000..775a2f0
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * An {@link EventType.INFO} type of {@link Event} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public abstract class InfoEvent extends Event {
+    public InfoEventType infoType;
+
+    public InfoEvent(InfoEventType infoType) {
+        super(EventType.INFO);
+        this.infoType = infoType;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEventType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEventType.java
new file mode 100644 (file)
index 0000000..b6afce9
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * The type of {@link InfoEvent} sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum InfoEventType {
+    SUMMARY,
+    SECURE_ARM;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SecureArmInfoEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SecureArmInfoEvent.java
new file mode 100644 (file)
index 0000000..dee4d30
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * A {@link InfoEventType.SECURE_ARM} type of {@link InfoEvent} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class SecureArmInfoEvent extends InfoEvent {
+    public Integer partitionId;
+    public Boolean value;
+
+    public SecureArmInfoEvent() {
+        super(InfoEventType.SECURE_ARM);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SummaryInfoEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SummaryInfoEvent.java
new file mode 100644 (file)
index 0000000..f80b854
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import java.util.List;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
+
+/**
+ * A {@link InfoEventType.SUMMARY} type of {@link InfoEvent} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class SummaryInfoEvent extends InfoEvent {
+    public List<Partition> partitionList;
+
+    public SummaryInfoEvent() {
+        super(InfoEventType.SUMMARY);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneActiveEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneActiveEvent.java
new file mode 100644 (file)
index 0000000..1b7d749
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.ZoneActiveState;
+
+/**
+ * A {@link ZoneEventType.ZONE_ACTIVE} type of {@link ZoneEvent} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ZoneActiveEvent extends ZoneEvent {
+    public ZoneActiveState zone;
+
+    public ZoneActiveEvent() {
+        super(ZoneEventType.ZONE_ACTIVE);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneAddEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneAddEvent.java
new file mode 100644 (file)
index 0000000..999b8a4
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
+
+/**
+ * A {@link ZoneEventType.ZONE_ADD} type of {@link ZoneEvent} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ZoneAddEvent extends ZoneEvent {
+    public Zone zone;
+
+    public ZoneAddEvent() {
+        super(ZoneEventType.ZONE_ADD);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEvent.java
new file mode 100644 (file)
index 0000000..26599f2
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * A Zone {@link Event} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public abstract class ZoneEvent extends Event {
+    @SerializedName("zone_event_type")
+    public ZoneEventType type;
+
+    public ZoneEvent(ZoneEventType type) {
+        super(EventType.ZONE_EVENT);
+        this.type = type;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEventType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEventType.java
new file mode 100644 (file)
index 0000000..d40a5be
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+/**
+ * The type of {@link ZoneEvent} sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum ZoneEventType {
+    ZONE_ACTIVE,
+    ZONE_ADD,
+    ZONE_UPDATE;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneUpdateEvent.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneUpdateEvent.java
new file mode 100644 (file)
index 0000000..b01ca09
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.event;
+
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
+
+/**
+ * A {@link ZoneEventType.ZONE_UPDATE} type of {@link ZoneEvent} message sent by the panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ZoneUpdateEvent extends ZoneEvent {
+    public Zone zone;
+
+    public ZoneUpdateEvent() {
+        super(ZoneEventType.ZONE_UPDATE);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/AlarmType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/AlarmType.java
new file mode 100644 (file)
index 0000000..9535846
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The type of alarm
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum AlarmType {
+    AUXILIARY,
+    FIRE,
+    POLICE,
+    @SerializedName("")
+    ZONEOPEN,
+    NONE;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Partition.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Partition.java
new file mode 100644 (file)
index 0000000..fc02d71
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+import java.util.List;
+
+/**
+ * A logical alarm partition that can be armed, report state and contain zones
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class Partition {
+    public Integer partitionId;
+    public String name;
+    public PartitionStatus status;
+    public Boolean secureArm;
+    public List<Zone> zoneList;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/PartitionStatus.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/PartitionStatus.java
new file mode 100644 (file)
index 0000000..cc38edd
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+/**
+ * The current status of an alarm panel
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum PartitionStatus {
+    ALARM,
+    ARM_AWAY,
+    ARM_STAY,
+    DISARM,
+    ENTRY_DELAY,
+    EXIT_DELAY;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Zone.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Zone.java
new file mode 100644 (file)
index 0000000..3901030
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+/**
+ * A zone sensor
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class Zone {
+    public String id;
+    public String type;
+    public String name;
+    public String group;
+    public ZoneStatus status;
+    public Integer state;
+    public Integer zoneId;
+    public Integer zonePhysicalType;
+    public Integer zoneAlarmType;
+    public ZoneType zoneType;
+    public Integer partitionId;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneActiveState.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneActiveState.java
new file mode 100644 (file)
index 0000000..4a05d52
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+/**
+ * The active state of a zone
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ZoneActiveState {
+    public Integer zoneId;
+    public ZoneStatus status;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneStatus.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneStatus.java
new file mode 100644 (file)
index 0000000..f37a441
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Represents the status of a zone
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum ZoneStatus {
+    @SerializedName("Active")
+    ACTIVE,
+    @SerializedName("Closed")
+    CLOSED,
+    @SerializedName("Open")
+    OPEN,
+    @SerializedName("Failure")
+    FAILURE,
+    @SerializedName("Idle")
+    IDlE,
+    @SerializedName("Tamper")
+    TAMPER;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneType.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneType.java
new file mode 100644 (file)
index 0000000..e78f838
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.client.dto.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The zone physical type
+ *
+ * Big thanks to the folks at https://community.home-assistant.io/t/qolsys-iq-panel-2-and-3rd-party-integration/231405
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public enum ZoneType {
+    @SerializedName("0")
+    UNKNOWN,
+    @SerializedName("1")
+    CONTACT,
+    @SerializedName("2")
+    MOTION,
+    @SerializedName("3")
+    SOUND,
+    @SerializedName("4")
+    BREAKAGE,
+    @SerializedName("5")
+    SMOKE_HEAT,
+    @SerializedName("6")
+    CARBON_MONOXIDE,
+    @SerializedName("7")
+    RADON,
+    @SerializedName("8")
+    TEMPERATURE,
+    @SerializedName("9")
+    PANIC_BUTTON,
+    @SerializedName("10")
+    CONTROL,
+    @SerializedName("11")
+    CAMERA,
+    @SerializedName("12")
+    LIGHT,
+    @SerializedName("13")
+    GPS,
+    @SerializedName("14")
+    SIREN,
+    @SerializedName("15")
+    WATER,
+    @SerializedName("16")
+    TILT,
+    @SerializedName("17")
+    FREEZE,
+    @SerializedName("18")
+    TAKEOVER_MODULE,
+    @SerializedName("19")
+    GLASSBREAK,
+    @SerializedName("20")
+    TRANSLATOR,
+    @SerializedName("21")
+    MEDICAL_PENDANT,
+    @SerializedName("22")
+    WATER_IQ_FLOOD,
+    @SerializedName("23")
+    WATER_OTHER_FLOOD,
+    @SerializedName("30")
+    IMAGE_SENSOR,
+    @SerializedName("100")
+    WIRED_SENSOR,
+    @SerializedName("101")
+    RF_SENSOR,
+    @SerializedName("102")
+    KEYFOB,
+    @SerializedName("103")
+    WALLFOB,
+    @SerializedName("104")
+    RF_KEYPAD,
+    @SerializedName("105")
+    PANEL,
+    @SerializedName("106")
+    WTTS_OR_SECONDARY,
+    @SerializedName("107")
+    SHOCK,
+    @SerializedName("108")
+    SHOCK_SENSOR_MULTI_FUNCTION,
+    @SerializedName("109")
+    DOOR_BELL,
+    @SerializedName("110")
+    CONTACT_MULTI_FUNCTION,
+    @SerializedName("111")
+    SMOKE_MULTI_FUNCTION,
+    @SerializedName("112")
+    TEMPERATURE_MULTI_FUNCTION,
+    @SerializedName("113")
+    SHOCK_OTHERS,
+    @SerializedName("114")
+    OCCUPANCY_SENSOR,
+    @SerializedName("115")
+    BLUETOOTH,
+    @SerializedName("116")
+    PANEL_GLASS_BREAK,
+    @SerializedName("117")
+    POWERG_SIREN,
+    @SerializedName("118")
+    BLUETOOTH_SPEAKER,
+    @SerializedName("119")
+    PANEL_MOTION,
+    @SerializedName("120")
+    ZWAVE_SIREN,
+    @SerializedName("121")
+    COUNT;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPanelConfiguration.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPanelConfiguration.java
new file mode 100644 (file)
index 0000000..8d09f3b
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link QolsysIQPanelConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQPanelConfiguration {
+    public String hostname = "";
+    public int port = 12345;
+    public String key = "";
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPartitionConfiguration.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPartitionConfiguration.java
new file mode 100644 (file)
index 0000000..06107dc
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link QolsysIQPartitionConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQPartitionConfiguration {
+    public int id = 0;
+    public String armCode = "";
+    public String disarmCode = "";
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQZoneConfiguration.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQZoneConfiguration.java
new file mode 100644 (file)
index 0000000..14800fe
--- /dev/null
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link QolsysIQZoneConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQZoneConfiguration {
+    public int id = 0;
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/discovery/QolsysIQChildDiscoveryService.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/discovery/QolsysIQChildDiscoveryService.java
new file mode 100644 (file)
index 0000000..796c6c4
--- /dev/null
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.discovery;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
+import org.openhab.binding.qolsysiq.internal.handler.QolsysIQChildDiscoveryHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple discovery service that can be used by Partition and Zone Handlers
+ *
+ * @author Dan Cunningham - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class QolsysIQChildDiscoveryService extends AbstractDiscoveryService
+        implements DiscoveryService, ThingHandlerService {
+    private final Logger logger = LoggerFactory.getLogger(QolsysIQChildDiscoveryService.class);
+
+    private static final Set<ThingTypeUID> SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set
+            .of(QolsysIQBindingConstants.THING_TYPE_PARTITION, QolsysIQBindingConstants.THING_TYPE_ZONE);
+
+    private @Nullable ThingHandler thingHandler;
+
+    public QolsysIQChildDiscoveryService() throws IllegalArgumentException {
+        super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 5, false);
+    }
+
+    @Override
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof QolsysIQChildDiscoveryHandler) {
+            ((QolsysIQChildDiscoveryHandler) handler).setDiscoveryService(this);
+            this.thingHandler = handler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return thingHandler;
+    }
+
+    @Override
+    protected void startScan() {
+        ThingHandler handler = this.thingHandler;
+        if (handler != null) {
+            ((QolsysIQChildDiscoveryHandler) handler).startDiscovery();
+        }
+    }
+
+    @Override
+    public void activate() {
+        super.activate(null);
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+    }
+
+    public void discoverQolsysIQChildThing(ThingUID thingUID, ThingUID bridgeUID, Integer id, String label) {
+        logger.trace("discoverQolsysIQChildThing: {} {} {} {}", thingUID, bridgeUID, id, label);
+        DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(label).withProperty("id", id)
+                .withRepresentationProperty("id").withBridge(bridgeUID).build();
+        thingDiscovered(result);
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQChildDiscoveryHandler.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQChildDiscoveryHandler.java
new file mode 100644 (file)
index 0000000..f9b818f
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
+
+/**
+ * Callback for our custom discovery service
+ *
+ * @author Dan Cunningham - Initial contribution
+ *
+ */
+@NonNullByDefault
+public interface QolsysIQChildDiscoveryHandler {
+    /**
+     * Sets a {@link QolsysIQChildDiscoveryService} to call when device information is received
+     *
+     * @param service
+     */
+    public void setDiscoveryService(QolsysIQChildDiscoveryService service);
+
+    /**
+     * Initiates the discovery process
+     */
+    public void startDiscovery();
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPanelHandler.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPanelHandler.java
new file mode 100644 (file)
index 0000000..c0c736f
--- /dev/null
@@ -0,0 +1,327 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.handler;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
+import org.openhab.binding.qolsysiq.internal.client.QolsysIQClientListener;
+import org.openhab.binding.qolsysiq.internal.client.QolsysiqClient;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.Action;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoAction;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoActionType;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
+import org.openhab.binding.qolsysiq.internal.config.QolsysIQPanelConfiguration;
+import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QolsysIQPanelHandler} connects to a security panel and routes messages to child partitions.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQPanelHandler extends BaseBridgeHandler
+        implements QolsysIQClientListener, QolsysIQChildDiscoveryHandler {
+    private final Logger logger = LoggerFactory.getLogger(QolsysIQPanelHandler.class);
+    private static final int QUICK_RETRY_SECONDS = 1;
+    private static final int LONG_RETRY_SECONDS = 30;
+    private static final int HEARTBEAT_SECONDS = 30;
+    private @Nullable QolsysiqClient apiClient;
+    private @Nullable ScheduledFuture<?> retryFuture;
+    private @Nullable QolsysIQChildDiscoveryService discoveryService;
+    private List<Partition> partitions = Collections.synchronizedList(new LinkedList<Partition>());
+    private String key = "";
+
+    public QolsysIQPanelHandler(Bridge bridge) {
+        super(bridge);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("handleCommand {}", command);
+        if (command instanceof RefreshType) {
+            refresh();
+        }
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("initialize");
+        updateStatus(ThingStatus.UNKNOWN);
+        scheduler.execute(() -> {
+            connect();
+        });
+    }
+
+    @Override
+    public void dispose() {
+        stopRetryFuture();
+        disconnect();
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(QolsysIQChildDiscoveryService.class);
+    }
+
+    @Override
+    public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
+        this.discoveryService = service;
+    }
+
+    @Override
+    public void startDiscovery() {
+        refresh();
+    }
+
+    @Override
+    public void disconnected(Exception reason) {
+        logger.debug("disconnected", reason);
+        setOfflineAndReconnect(reason, QUICK_RETRY_SECONDS);
+    }
+
+    @Override
+    public void alarmEvent(AlarmEvent event) {
+        logger.debug("AlarmEvent {}", event.partitionId);
+        QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
+        if (handler != null) {
+            handler.alarmEvent(event);
+        }
+    }
+
+    @Override
+    public void armingEvent(ArmingEvent event) {
+        logger.debug("ArmingEvent {}", event.partitionId);
+        QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
+        if (handler != null) {
+            handler.armingEvent(event);
+        }
+    }
+
+    @Override
+    public void errorEvent(ErrorEvent event) {
+        logger.debug("ErrorEvent {}", event.partitionId);
+        QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
+        if (handler != null) {
+            handler.errorEvent(event);
+        }
+    }
+
+    @Override
+    public void summaryInfoEvent(SummaryInfoEvent event) {
+        logger.debug("SummaryInfoEvent");
+        synchronized (partitions) {
+            partitions.clear();
+            partitions.addAll(event.partitionList);
+        }
+        updatePartitions();
+        discoverChildDevices();
+    }
+
+    @Override
+    public void secureArmInfoEvent(SecureArmInfoEvent event) {
+        logger.debug("ArmingEvent {}", event.value);
+        QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
+        if (handler != null) {
+            handler.secureArmInfoEvent(event);
+        }
+    }
+
+    @Override
+    public void zoneActiveEvent(ZoneActiveEvent event) {
+        logger.debug("ZoneActiveEvent {} {}", event.zone.zoneId, event.zone.status);
+        partitions.forEach(p -> {
+            if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
+                QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
+                if (handler != null) {
+                    handler.zoneActiveEvent(event);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void zoneUpdateEvent(ZoneUpdateEvent event) {
+        logger.debug("ZoneUpdateEvent {}", event.zone.name);
+        partitions.forEach(p -> {
+            if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
+                QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
+                if (handler != null) {
+                    handler.zoneUpdateEvent(event);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void zoneAddEvent(ZoneAddEvent event) {
+        logger.debug("ZoneAddEvent {}", event.zone.name);
+        partitions.forEach(p -> {
+            if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
+                QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
+                if (handler != null) {
+                    handler.zoneAddEvent(event);
+                }
+            }
+        });
+    }
+
+    /**
+     * Sends the action to the panel. This will replace the token of the action passed in with the one configured here
+     *
+     * @param action
+     */
+    protected void sendAction(Action action) {
+        action.token = key;
+        QolsysiqClient client = this.apiClient;
+        if (client != null) {
+            try {
+                client.sendAction(action);
+            } catch (IOException e) {
+                logger.debug("Could not send action", e);
+                setOfflineAndReconnect(e, QUICK_RETRY_SECONDS);
+            }
+        }
+    }
+
+    protected synchronized void refresh() {
+        sendAction(new InfoAction(InfoActionType.SUMMARY));
+    }
+
+    /**
+     * Connect the client
+     */
+    private synchronized void connect() {
+        if (getThing().getStatus() == ThingStatus.ONLINE) {
+            logger.debug("connect: Bridge is already connected");
+            return;
+        }
+        QolsysIQPanelConfiguration config = getConfigAs(QolsysIQPanelConfiguration.class);
+        key = config.key;
+
+        try {
+            QolsysiqClient apiClient = new QolsysiqClient(config.hostname, config.port, HEARTBEAT_SECONDS, scheduler,
+                    "OH-binding-" + getThing().getUID().getAsString());
+            apiClient.connect();
+            apiClient.addListener(this);
+            this.apiClient = apiClient;
+            refresh();
+            updateStatus(ThingStatus.ONLINE);
+        } catch (IOException e) {
+            logger.debug("Could not connect");
+            setOfflineAndReconnect(e, LONG_RETRY_SECONDS);
+        }
+    }
+
+    /**
+     * Disconnects the client and removes listeners
+     */
+    private void disconnect() {
+        logger.debug("disconnect");
+        QolsysiqClient apiClient = this.apiClient;
+        if (apiClient != null) {
+            apiClient.removeListener(this);
+            apiClient.disconnect();
+            this.apiClient = null;
+        }
+    }
+
+    private void startRetryFuture(int seconds) {
+        stopRetryFuture();
+        logger.debug("startRetryFuture");
+        this.retryFuture = scheduler.schedule(this::connect, seconds, TimeUnit.SECONDS);
+    }
+
+    private void stopRetryFuture() {
+        logger.debug("stopRetryFuture");
+        ScheduledFuture<?> retryFuture = this.retryFuture;
+        if (retryFuture != null) {
+            retryFuture.cancel(true);
+            this.retryFuture = null;
+        }
+    }
+
+    private void setOfflineAndReconnect(Exception reason, int seconds) {
+        logger.debug("setOfflineAndReconnect");
+        disconnect();
+        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
+        startRetryFuture(seconds);
+    }
+
+    private void updatePartitions() {
+        synchronized (partitions) {
+            partitions.forEach(p -> {
+                QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
+                if (handler != null) {
+                    handler.updatePartition(p);
+                }
+            });
+        }
+    }
+
+    private void discoverChildDevices() {
+        synchronized (partitions) {
+            QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
+            if (discoveryService != null) {
+                partitions.forEach(p -> {
+                    ThingUID bridgeUID = getThing().getUID();
+                    ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_PARTITION, bridgeUID,
+                            String.valueOf(p.partitionId));
+                    discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, p.partitionId,
+                            "Qolsys IQ Partition: " + p.name);
+                });
+            }
+        }
+    }
+
+    private @Nullable QolsysIQPartitionHandler partitionHandler(int partitionId) {
+        for (Thing thing : getThing().getThings()) {
+            ThingHandler handler = thing.getHandler();
+            if (handler instanceof QolsysIQPartitionHandler) {
+                if (((QolsysIQPartitionHandler) handler).getPartitionId() == partitionId) {
+                    return (QolsysIQPartitionHandler) handler;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPartitionHandler.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPartitionHandler.java
new file mode 100644 (file)
index 0000000..4dca6fd
--- /dev/null
@@ -0,0 +1,369 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.handler;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmAction;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmActionType;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingAction;
+import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingActionType;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.AlarmType;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.PartitionStatus;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
+import org.openhab.binding.qolsysiq.internal.config.QolsysIQPartitionConfiguration;
+import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
+import org.openhab.core.library.types.DecimalType;
+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.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QolsysIQPartitionHandler} manages security partitions
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQPartitionHandler extends BaseBridgeHandler implements QolsysIQChildDiscoveryHandler {
+    private final Logger logger = LoggerFactory.getLogger(QolsysIQPartitionHandler.class);
+    private static final int CLEAR_ERROR_MESSSAGE_TIME = 30;
+    private @Nullable QolsysIQChildDiscoveryService discoveryService;
+    private @Nullable ScheduledFuture<?> delayFuture;
+    private @Nullable ScheduledFuture<?> errorFuture;
+    private @Nullable String armCode;
+    private @Nullable String disarmCode;
+    private List<Zone> zones = Collections.synchronizedList(new LinkedList<Zone>());
+    private int partitionId;
+
+    public QolsysIQPartitionHandler(Bridge bridge) {
+        super(bridge);
+    }
+
+    @Override
+    public void initialize() {
+        QolsysIQPartitionConfiguration config = getConfigAs(QolsysIQPartitionConfiguration.class);
+        partitionId = config.id;
+        armCode = config.armCode.isBlank() ? null : config.armCode;
+        disarmCode = config.disarmCode.isBlank() ? null : config.disarmCode;
+        logger.debug("initialize partition {}", partitionId);
+        initializePartition();
+    }
+
+    @Override
+    public void dispose() {
+        cancelExitDelayJob();
+        cancelErrorDelayJob();
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
+            cancelExitDelayJob();
+            cancelErrorDelayJob();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+        } else {
+            initializePartition();
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            refresh();
+            return;
+        }
+
+        QolsysIQPanelHandler panel = panelHandler();
+        if (panel != null) {
+            if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE)) {
+                try {
+                    panel.sendAction(new AlarmAction(AlarmActionType.valueOf(command.toString())));
+                } catch (IllegalArgumentException e) {
+                    logger.debug("Unknown alarm type {} to channel {}", command, channelUID);
+                }
+                return;
+            }
+
+            // support ARM_AWAY and ARM_AWAY:123456 , same for other arm / disarm modes
+            if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE)) {
+                String armingTypeName = command.toString();
+                String code = null;
+                if (armingTypeName.contains(":")) {
+                    String[] split = armingTypeName.split(":");
+                    armingTypeName = split[0];
+                    if (split.length > 1 && split[1].length() > 0) {
+                        code = split[1];
+                    }
+                }
+                try {
+                    ArmingActionType armingType = ArmingActionType.valueOf(armingTypeName);
+                    if (code == null) {
+                        if (armingType == ArmingActionType.DISARM) {
+                            code = disarmCode;
+                        } else {
+                            code = armCode;
+                        }
+                    }
+                    panel.sendAction(new ArmingAction(armingType, getPartitionId(), code));
+                } catch (IllegalArgumentException e) {
+                    logger.debug("Unknown arm type {} to channel {}", armingTypeName, channelUID);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(QolsysIQChildDiscoveryService.class);
+    }
+
+    @Override
+    public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
+        this.discoveryService = service;
+    }
+
+    @Override
+    public void startDiscovery() {
+        refresh();
+    }
+
+    /**
+     * The partition id
+     *
+     * @return
+     */
+    public int getPartitionId() {
+        return partitionId;
+    }
+
+    public void zoneActiveEvent(ZoneActiveEvent event) {
+        QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
+        if (handler != null) {
+            handler.zoneActiveEvent(event);
+        }
+    }
+
+    public void zoneUpdateEvent(ZoneUpdateEvent event) {
+        QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
+        if (handler != null) {
+            handler.zoneUpdateEvent(event);
+        }
+    }
+
+    protected void alarmEvent(AlarmEvent event) {
+        if (event.alarmType != AlarmType.NONE && event.alarmType != AlarmType.ZONEOPEN) {
+            updatePartitionStatus(PartitionStatus.ALARM);
+        }
+        updateAlarmState(event.alarmType);
+    }
+
+    protected void armingEvent(ArmingEvent event) {
+        updatePartitionStatus(event.armingType);
+        updateDelay(event.delay == null ? 0 : event.delay);
+    }
+
+    protected void errorEvent(ErrorEvent event) {
+        cancelErrorDelayJob();
+        updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, new StringType(event.description));
+        errorFuture = scheduler.schedule(this::clearErrorEvent, CLEAR_ERROR_MESSSAGE_TIME, TimeUnit.SECONDS);
+    }
+
+    protected void secureArmInfoEvent(SecureArmInfoEvent event) {
+        setSecureArm(event.value);
+    }
+
+    public void zoneAddEvent(ZoneAddEvent event) {
+        discoverZone(event.zone);
+    }
+
+    protected void updatePartition(Partition partition) {
+        updatePartitionStatus(partition.status);
+        setSecureArm(partition.secureArm);
+        if (partition.status != PartitionStatus.ALARM) {
+            updateAlarmState(AlarmType.NONE);
+        }
+        synchronized (zones) {
+            zones.clear();
+            zones.addAll(partition.zoneList);
+            zones.forEach(z -> {
+                QolsysIQZoneHandler zoneHandler = zoneHandler(z.zoneId);
+                if (zoneHandler != null) {
+                    zoneHandler.updateZone(z);
+                }
+            });
+        }
+        if (getThing().getStatus() != ThingStatus.ONLINE) {
+            updateStatus(ThingStatus.ONLINE);
+        }
+        discoverChildDevices();
+    }
+
+    protected @Nullable Zone getZone(Integer zoneId) {
+        synchronized (zones) {
+            return zones.stream().filter(z -> z.zoneId.equals(zoneId)).findAny().orElse(null);
+        }
+    }
+
+    private void initializePartition() {
+        QolsysIQPanelHandler panel = panelHandler();
+        if (panel == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
+        } else if (panel.getThing().getStatus() != ThingStatus.ONLINE) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+        } else {
+            updateStatus(ThingStatus.UNKNOWN);
+            scheduler.execute(() -> {
+                panel.refresh();
+            });
+        }
+    }
+
+    private void refresh() {
+        QolsysIQPanelHandler panel = panelHandler();
+        if (panel != null) {
+            panel.refresh();
+        }
+    }
+
+    private void updatePartitionStatus(PartitionStatus status) {
+        updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE, new StringType(status.toString()));
+        cancelErrorDelayJob();
+        if (status == PartitionStatus.DISARM) {
+            updateAlarmState(AlarmType.NONE);
+            updateDelay(0);
+        }
+    }
+
+    private void setSecureArm(Boolean secure) {
+        Map<String, String> props = new HashMap<String, String>();
+        props.put("secureArm", String.valueOf(secure));
+        getThing().setProperties(props);
+    }
+
+    private void updateDelay(Integer delay) {
+        cancelExitDelayJob();
+        if (delay <= 0) {
+            updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
+            return;
+        }
+
+        final long endTime = System.currentTimeMillis() + (delay * 1000);
+        delayFuture = scheduler.scheduleAtFixedRate(() -> {
+            long remaining = endTime - System.currentTimeMillis();
+            logger.debug("updateDelay remaining {}", remaining / 1000);
+            if (remaining <= 0) {
+                cancelExitDelayJob();
+            } else {
+                updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY,
+                        new DecimalType(remaining / 1000));
+            }
+        }, 1, 1, TimeUnit.SECONDS);
+    }
+
+    private void updateAlarmState(AlarmType alarmType) {
+        updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE, new StringType(alarmType.toString()));
+    }
+
+    private void clearErrorEvent() {
+        updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, UnDefType.NULL);
+    }
+
+    private void cancelExitDelayJob() {
+        ScheduledFuture<?> delayFuture = this.delayFuture;
+        if (delayFuture != null) {
+            delayFuture.cancel(true);
+            this.delayFuture = null;
+        }
+        updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
+    }
+
+    private void cancelErrorDelayJob() {
+        ScheduledFuture<?> errorFuture = this.errorFuture;
+        if (errorFuture != null) {
+            errorFuture.cancel(true);
+            this.errorFuture = null;
+        }
+        clearErrorEvent();
+    }
+
+    private void discoverChildDevices() {
+        synchronized (zones) {
+            zones.forEach(z -> discoverZone(z));
+        }
+    }
+
+    private void discoverZone(Zone z) {
+        QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
+        if (discoveryService != null) {
+            ThingUID bridgeUID = getThing().getUID();
+            ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_ZONE, bridgeUID,
+                    String.valueOf(z.zoneId));
+            discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, z.zoneId, "Qolsys IQ Zone: " + z.name);
+        }
+    }
+
+    private @Nullable QolsysIQZoneHandler zoneHandler(int zoneId) {
+        for (Thing thing : getThing().getThings()) {
+            ThingHandler handler = thing.getHandler();
+            if (handler instanceof QolsysIQZoneHandler) {
+                if (((QolsysIQZoneHandler) handler).getZoneId() == zoneId) {
+                    return (QolsysIQZoneHandler) handler;
+                }
+            }
+        }
+        return null;
+    }
+
+    private @Nullable QolsysIQPanelHandler panelHandler() {
+        Bridge bridge = getBridge();
+        if (bridge != null) {
+            BridgeHandler handler = bridge.getHandler();
+            if (handler instanceof QolsysIQPanelHandler) {
+                return (QolsysIQPanelHandler) handler;
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQZoneHandler.java b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQZoneHandler.java
new file mode 100644 (file)
index 0000000..fbcc442
--- /dev/null
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2010-2022 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.qolsysiq.internal.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
+import org.openhab.binding.qolsysiq.internal.client.dto.model.ZoneStatus;
+import org.openhab.binding.qolsysiq.internal.config.QolsysIQZoneConfiguration;
+import org.openhab.core.library.types.DecimalType;
+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.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link QolsysIQZoneHandler} manages security zones.
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+@NonNullByDefault
+public class QolsysIQZoneHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(QolsysIQZoneHandler.class);
+
+    private int zoneId;
+
+    public QolsysIQZoneHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("initialize");
+        zoneId = getConfigAs(QolsysIQZoneConfiguration.class).id;
+        initializeZone();
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusChanged) {
+        logger.debug("bridgeStatusChanged {}", bridgeStatusChanged);
+        initializeZone();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+    }
+
+    public int getZoneId() {
+        return zoneId;
+    }
+
+    protected void updateZone(Zone zone) {
+        logger.debug("updateZone {}", zone.zoneId);
+        updateState(QolsysIQBindingConstants.CHANNEL_ZONE_STATE, new DecimalType(zone.state));
+        updateZoneStatus(zone.status);
+        Map<String, String> props = new HashMap<String, String>();
+        props.put("type", zone.type);
+        props.put("name", zone.name);
+        props.put("group", zone.group);
+        props.put("zoneID", zone.id);
+        props.put("zonePhysicalType", String.valueOf(zone.zonePhysicalType));
+        props.put("zoneAlarmType", String.valueOf(zone.zoneAlarmType));
+        props.put("zoneType", zone.zoneType.toString());
+        props.put("partitionId", String.valueOf(zone.partitionId));
+        getThing().setProperties(props);
+    }
+
+    protected void zoneActiveEvent(ZoneActiveEvent event) {
+        if (event.zone.zoneId == getZoneId()) {
+            updateZoneStatus(event.zone.status);
+        }
+    }
+
+    protected void zoneUpdateEvent(ZoneUpdateEvent event) {
+        if (event.zone.zoneId == getZoneId()) {
+            updateZone(event.zone);
+        }
+    }
+
+    private void initializeZone() {
+        Bridge bridge = getBridge();
+        BridgeHandler handler = bridge == null ? null : bridge.getHandler();
+        if (bridge != null && handler instanceof QolsysIQPartitionHandler) {
+            if (handler.getThing().getStatus() != ThingStatus.ONLINE) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+                return;
+            }
+            Zone z = ((QolsysIQPartitionHandler) handler).getZone(getZoneId());
+            if (z == null) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Zone not found in partition");
+                return;
+            }
+            updateZone(z);
+            updateStatus(ThingStatus.ONLINE);
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
+        }
+    }
+
+    private void updateZoneStatus(@Nullable ZoneStatus status) {
+        if (status != null) {
+            updateState(QolsysIQBindingConstants.CHANNEL_ZONE_STATUS, new StringType(status.toString()));
+            updateState(QolsysIQBindingConstants.CHANNEL_ZONE_CONTACT,
+                    status == ZoneStatus.CLOSED || status == ZoneStatus.IDlE ? OpenClosedType.CLOSED
+                            : OpenClosedType.OPEN);
+        } else {
+            logger.debug("updateZoneStatus: null status");
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..1b73454
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="qolsysiq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+       <name>QolsysIQ Binding</name>
+       <description>This is the binding for Qolsys IQ Alarm Systems.</description>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/i18n/qolsysiq.properties b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/i18n/qolsysiq.properties
new file mode 100644 (file)
index 0000000..ad5424e
--- /dev/null
@@ -0,0 +1,72 @@
+## mvn i18n:generate-default-translations
+
+# binding
+
+binding.qolsysiq.name = QolsysIQ Binding
+binding.qolsysiq.description = This is the binding for Qolsys IQ Alarm Systems.
+
+# thing types
+
+thing-type.qolsysiq.panel.label = Qolsys IQ Panel
+thing-type.qolsysiq.panel.description = A Qolsys IQ Panel Bridge
+thing-type.qolsysiq.partition.label = Partition
+thing-type.qolsysiq.partition.description = A Qolsys IQ Partition
+thing-type.qolsysiq.zone.label = Zone
+thing-type.qolsysiq.zone.description = A Qolsys IQ Zone
+
+# thing types config
+
+thing-type.config.qolsysiq.panel.hostname.label = Hostname
+thing-type.config.qolsysiq.panel.hostname.description = Hostname or IP address of the panel
+thing-type.config.qolsysiq.panel.key.label = key
+thing-type.config.qolsysiq.panel.key.description = Key to access the device
+thing-type.config.qolsysiq.panel.port.label = Port
+thing-type.config.qolsysiq.panel.port.description = The port to connect to on the panel.
+thing-type.config.qolsysiq.partition.armCode.label = Arm Code
+thing-type.config.qolsysiq.partition.armCode.description = Optional arm code to use when receiving arm commands without a code. Only required if the panel has been configured to require arm codes. Leave blank to always require a code
+thing-type.config.qolsysiq.partition.disarmCode.label = Disarm Code
+thing-type.config.qolsysiq.partition.disarmCode.description = Optional disarm code to use when receiving a disarm command without a code. Required for integrations like Alexa and Homekit who do not provide codes when disarming. Leave blank to always require a code
+thing-type.config.qolsysiq.partition.id.label = Partition ID
+thing-type.config.qolsysiq.partition.id.description = The Partition ID.
+thing-type.config.qolsysiq.zone.id.label = Zone ID
+thing-type.config.qolsysiq.zone.id.description = The Zone ID.
+
+# channel types
+
+channel-type.qolsysiq.alarmState.label = Partition Alarm State
+channel-type.qolsysiq.alarmState.description = Reports on the current alarm state, or triggers an instant alarm.
+channel-type.qolsysiq.alarmState.state.option.AUXILIARY = Auxiliary
+channel-type.qolsysiq.alarmState.state.option.FIRE = Fire
+channel-type.qolsysiq.alarmState.state.option.POLICE = Police
+channel-type.qolsysiq.alarmState.state.option.ZONEOPEN = Zone Open
+channel-type.qolsysiq.alarmState.state.option.NONE = None
+channel-type.qolsysiq.alarmState.command.option.AUXILIARY = Auxiliary
+channel-type.qolsysiq.alarmState.command.option.FIRE = Fire
+channel-type.qolsysiq.alarmState.command.option.POLICE = Police
+channel-type.qolsysiq.armState.label = Partition Arm State
+channel-type.qolsysiq.armState.description = Reports the current partition arm state or sends a arm or disarm command to the system. For security codes, append the 6 digit code to the command separated by a colon (e.g. 'DISARM:123456')
+channel-type.qolsysiq.armState.state.option.ALARM = In Alarm
+channel-type.qolsysiq.armState.state.option.ARM_AWAY = Armed Away
+channel-type.qolsysiq.armState.state.option.ARM_STAY = Armed Stay
+channel-type.qolsysiq.armState.state.option.DISARM = Disarmed
+channel-type.qolsysiq.armState.state.option.ENTRY_DELAY = Entry Delay
+channel-type.qolsysiq.armState.state.option.EXIT_DELAY = Exit Delay
+channel-type.qolsysiq.armState.command.option.ARM_AWAY = Arm Away
+channel-type.qolsysiq.armState.command.option.ARM_STAY = Arm Stay
+channel-type.qolsysiq.armState.command.option.DISARM = Disarm
+channel-type.qolsysiq.armingDelay.label = Partition Arming Delay
+channel-type.qolsysiq.armingDelay.description = The arming delay currently in progress
+channel-type.qolsysiq.contact.label = Zone Contact
+channel-type.qolsysiq.contact.description = The zone contact state.
+channel-type.qolsysiq.errorEvent.label = Error Event
+channel-type.qolsysiq.errorEvent.description = Last error event message reported by the partition. Clears after 30 seconds
+channel-type.qolsysiq.zoneState.label = Zone State
+channel-type.qolsysiq.zoneState.description = The zone state.
+channel-type.qolsysiq.zoneStatus.label = Zone Status
+channel-type.qolsysiq.zoneStatus.description = The zone status.
+channel-type.qolsysiq.zoneStatus.state.option.ACTIVE = Active
+channel-type.qolsysiq.zoneStatus.state.option.CLOSED = Closed
+channel-type.qolsysiq.zoneStatus.state.option.OPEN = Open
+channel-type.qolsysiq.zoneStatus.state.option.FAILURE = Failure
+channel-type.qolsysiq.zoneStatus.state.option.IDlE = Idle
+channel-type.qolsysiq.zoneStatus.state.option.TAMPER = Tamper
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/panel.xml b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/panel.xml
new file mode 100644 (file)
index 0000000..65ba243
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="qolsysiq"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <bridge-type id="panel">
+               <label>Qolsys IQ Panel</label>
+               <description>A Qolsys IQ Panel Bridge</description>
+               <config-description>
+                       <parameter name="hostname" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Hostname</label>
+                               <description>Hostname or IP address of the panel</description>
+                       </parameter>
+                       <parameter name="port" type="integer">
+                               <label>Port</label>
+                               <description>The port to connect to on the panel.</description>
+                               <default>12345</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="key" type="text" required="true">
+                               <context>password</context>
+                               <label>key</label>
+                               <description>Key to access the device</description>
+                       </parameter>
+               </config-description>
+       </bridge-type>
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/partition.xml b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/partition.xml
new file mode 100644 (file)
index 0000000..cb56226
--- /dev/null
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="qolsysiq"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <bridge-type id="partition">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="panel"/>
+               </supported-bridge-type-refs>
+               <label>Partition</label>
+               <description>A Qolsys IQ Partition</description>
+               <channels>
+                       <channel id="armState" typeId="armState"/>
+                       <channel id="alarmState" typeId="alarmState"/>
+                       <channel id="armingDelay" typeId="armingDelay"/>
+                       <channel id="errorEvent" typeId="errorEvent"/>
+               </channels>
+               <properties>
+                       <property name="secureArm">false</property>
+               </properties>
+               <representation-property>id</representation-property>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>Partition ID</label>
+                               <description>The Partition ID.</description>
+                       </parameter>
+                       <parameter name="disarmCode" type="text" required="false">
+                               <default></default>
+                               <label>Disarm Code</label>
+                               <description>Optional disarm code to use when receiving a disarm command without a code. Required for integrations
+                                       like Alexa and Homekit who do not provide codes when disarming. Leave blank to always require a code</description>
+                       </parameter>
+                       <parameter name="armCode" type="text" required="false">
+                               <default></default>
+                               <label>Arm Code</label>
+                               <description>Optional arm code to use when receiving arm commands without a code. Only required if the panel has
+                                       been configured to require arm codes. Leave blank to always require a code</description>
+                               <advanced>true</advanced>
+                       </parameter>
+               </config-description>
+       </bridge-type>
+       <channel-type id="armState">
+               <item-type>String</item-type>
+               <label>Partition Arm State</label>
+               <description>Reports the current partition arm state or sends a arm or disarm command to the system. For security
+                       codes, append the 6 digit code to the command separated by a colon (e.g. 'DISARM:123456')</description>
+               <category>Alarm</category>
+               <state>
+                       <options>
+                               <option value="ALARM">In Alarm</option>
+                               <option value="ARM_AWAY">Armed Away</option>
+                               <option value="ARM_STAY">Armed Stay</option>
+                               <option value="DISARM">Disarmed</option>
+                               <option value="ENTRY_DELAY">Entry Delay</option>
+                               <option value="EXIT_DELAY">Exit Delay</option>
+                       </options>
+               </state>
+               <command>
+                       <options>
+                               <option value="ARM_AWAY">Arm Away</option>
+                               <option value="ARM_STAY">Arm Stay</option>
+                               <option value="DISARM">Disarm</option>
+                       </options>
+               </command>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="alarmState">
+               <item-type>String</item-type>
+               <label>Partition Alarm State</label>
+               <description>Reports on the current alarm state, or triggers an instant alarm.</description>
+               <category>Alarm</category>
+               <state>
+                       <options>
+                               <option value="AUXILIARY">Auxiliary</option>
+                               <option value="FIRE">Fire</option>
+                               <option value="POLICE">Police</option>
+                               <option value="ZONEOPEN">Zone Open</option>
+                               <option value="NONE">None</option>
+                       </options>
+               </state>
+               <command>
+                       <options>
+                               <option value="AUXILIARY">Auxiliary</option>
+                               <option value="FIRE">Fire</option>
+                               <option value="POLICE">Police</option>
+                       </options>
+               </command>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="armingDelay">
+               <item-type>Number</item-type>
+               <label>Partition Arming Delay</label>
+               <description>The arming delay currently in progress</description>
+               <category>Alarm</category>
+               <state readOnly="true"></state>
+       </channel-type>
+       <channel-type id="errorEvent" advanced="true">
+               <item-type>String</item-type>
+               <label>Error Event</label>
+               <description>Last error event message reported by the partition. Clears after 30 seconds</description>
+               <state readOnly="true"></state>
+       </channel-type>
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/zone.xml b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/zone.xml
new file mode 100644 (file)
index 0000000..b53a9d5
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="qolsysiq"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <thing-type id="zone">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="partition"/>
+               </supported-bridge-type-refs>
+               <label>Zone</label>
+               <description>A Qolsys IQ Zone</description>
+               <channels>
+                       <channel id="state" typeId="zoneState"/>
+                       <channel id="status" typeId="zoneStatus"/>
+                       <channel id="contact" typeId="contact"/>
+               </channels>
+               <properties>
+                       <property name="type"></property>
+                       <property name="name"></property>
+                       <property name="group"></property>
+                       <property name="zoneId"></property>
+                       <property name="zonePhysicalType"></property>
+                       <property name="zoneAlarmType"></property>
+                       <property name="zoneType"></property>
+                       <property name="partitionId"></property>
+               </properties>
+               <representation-property>id</representation-property>
+               <config-description>
+                       <parameter name="id" type="integer" required="true">
+                               <label>Zone ID</label>
+                               <description>The Zone ID.</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+       <channel-type id="zoneStatus">
+               <item-type>String</item-type>
+               <label>Zone Status</label>
+               <description>The zone status.</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="ACTIVE">Active</option>
+                               <option value="CLOSED">Closed</option>
+                               <option value="OPEN">Open</option>
+                               <option value="FAILURE">Failure</option>
+                               <option value="IDlE">Idle</option>
+                               <option value="TAMPER">Tamper</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="zoneState" advanced="true">
+               <item-type>Number</item-type>
+               <label>Zone State</label>
+               <description>The zone state.</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="contact">
+               <item-type>Contact</item-type>
+               <label>Zone Contact</label>
+               <description>The zone contact state.</description>
+               <state readOnly="true"/>
+       </channel-type>
+</thing:thing-descriptions>
index 316b6a9ad2d788a94d90c72140552a1f1714395a..08386b72ddfaaa6c30002c5dfe40e3d4be5b0dd8 100644 (file)
     <module>org.openhab.binding.pushover</module>
     <module>org.openhab.binding.pushsafer</module>
     <module>org.openhab.binding.qbus</module>
+    <module>org.openhab.binding.qolsysiq</module>
     <module>org.openhab.binding.radiothermostat</module>
     <module>org.openhab.binding.regoheatpump</module>
     <module>org.openhab.binding.revogi</module>