diff --git a/CODEOWNERS b/CODEOWNERS
index 1d0ebb2377e..2cd894f9638 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -272,6 +272,7 @@
/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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index d5178fa3bc0..eb7f0257249 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1356,6 +1356,11 @@
org.openhab.binding.qbus
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.qolsysiq
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.radiothermostat
diff --git a/bundles/org.openhab.binding.qolsysiq/NOTICE b/bundles/org.openhab.binding.qolsysiq/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/NOTICE
@@ -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
index 00000000000..7be0e7fe23e
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/README.md
@@ -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" (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" (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
index 00000000000..35fbb25c1d7
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
index 00000000000..91c7b73da3b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.4.0-SNAPSHOT
+
+
+ org.openhab.binding.qolsysiq
+
+ openHAB Add-ons :: Bundles :: QolsysIQ Binding
+
+
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
index 00000000000..b02bdd6f50b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.qolsysiq/${project.version}
+
+
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
index 00000000000..4028893edb6
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQBindingConstants.java
@@ -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
index 00000000000..ae1edf8b30b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/QolsysIQHandlerFactory.java
@@ -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 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
index 00000000000..61e7566efed
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysIQClientListener.java
@@ -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
index 00000000000..ae738962845
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/QolsysiqClient.java
@@ -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 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 {
+ @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
index 00000000000..2f7142798b8
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/Action.java
@@ -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
index 00000000000..af9184ac29a
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ActionType.java
@@ -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
index 00000000000..b2b6e194054
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmAction.java
@@ -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
index 00000000000..dc8c57721a8
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/AlarmActionType.java
@@ -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
index 00000000000..3149b577aaa
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmAwayArmingAction.java
@@ -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
index 00000000000..fdf0bb88b6d
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingAction.java
@@ -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
index 00000000000..9951b68184d
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/ArmingActionType.java
@@ -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
index 00000000000..4d1e6a5ad0b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoAction.java
@@ -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
index 00000000000..a62a7a39119
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/action/InfoActionType.java
@@ -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
index 00000000000..0570e8dcc43
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/AlarmEvent.java
@@ -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
index 00000000000..42bc3c0af26
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ArmingEvent.java
@@ -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
index 00000000000..1c04abf9e97
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ErrorEvent.java
@@ -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
index 00000000000..77bc1daa4a8
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/Event.java
@@ -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
index 00000000000..ba2621d937b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/EventType.java
@@ -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
index 00000000000..775a2f0808c
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEvent.java
@@ -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
index 00000000000..b6afce9fe7b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/InfoEventType.java
@@ -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
index 00000000000..dee4d302506
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SecureArmInfoEvent.java
@@ -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
index 00000000000..f80b854a3af
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/SummaryInfoEvent.java
@@ -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 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
index 00000000000..1b7d74971ed
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneActiveEvent.java
@@ -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
index 00000000000..999b8a4458e
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneAddEvent.java
@@ -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
index 00000000000..26599f216af
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEvent.java
@@ -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
index 00000000000..d40a5be5692
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneEventType.java
@@ -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
index 00000000000..b01ca09699d
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/event/ZoneUpdateEvent.java
@@ -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
index 00000000000..9535846775a
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/AlarmType.java
@@ -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
index 00000000000..fc02d71b97d
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Partition.java
@@ -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 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
index 00000000000..cc38eddab9b
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/PartitionStatus.java
@@ -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
index 00000000000..39010306c58
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/Zone.java
@@ -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
index 00000000000..4a05d525cba
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneActiveState.java
@@ -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
index 00000000000..f37a44119e0
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneStatus.java
@@ -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
index 00000000000..e78f838b442
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/client/dto/model/ZoneType.java
@@ -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
index 00000000000..8d09f3b3749
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPanelConfiguration.java
@@ -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
index 00000000000..06107dc3261
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQPartitionConfiguration.java
@@ -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
index 00000000000..14800fe158d
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/config/QolsysIQZoneConfiguration.java
@@ -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
index 00000000000..796c6c480cd
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/discovery/QolsysIQChildDiscoveryService.java
@@ -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 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
index 00000000000..f9b818fc090
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQChildDiscoveryHandler.java
@@ -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
index 00000000000..c0c736f0f6a
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPanelHandler.java
@@ -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 partitions = Collections.synchronizedList(new LinkedList());
+ 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> 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
index 00000000000..4dca6fde854
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQPartitionHandler.java
@@ -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 zones = Collections.synchronizedList(new LinkedList());
+ 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> 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 props = new HashMap();
+ 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
index 00000000000..fbcc4420945
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/java/org/openhab/binding/qolsysiq/internal/handler/QolsysIQZoneHandler.java
@@ -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 props = new HashMap();
+ 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
index 00000000000..1b734542c64
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ QolsysIQ Binding
+ This is the binding for Qolsys IQ Alarm Systems.
+
+
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
index 00000000000..ad5424eb206
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/i18n/qolsysiq.properties
@@ -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
index 00000000000..65ba24377b4
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/panel.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ A Qolsys IQ Panel Bridge
+
+
+ network-address
+
+ Hostname or IP address of the panel
+
+
+
+ The port to connect to on the panel.
+ 12345
+ true
+
+
+ password
+
+ Key to access the device
+
+
+
+
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
index 00000000000..cb56226e544
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/partition.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ A Qolsys IQ Partition
+
+
+
+
+
+
+
+ false
+
+ id
+
+
+
+ The Partition ID.
+
+
+
+
+ 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
+
+
+
+
+ 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
+ true
+
+
+
+
+ String
+
+ 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')
+ Alarm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ veto
+
+
+ String
+
+ Reports on the current alarm state, or triggers an instant alarm.
+ Alarm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ veto
+
+
+ Number
+
+ The arming delay currently in progress
+ Alarm
+
+
+
+ String
+
+ Last error event message reported by the partition. Clears after 30 seconds
+
+
+
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
index 00000000000..b53a9d5e76f
--- /dev/null
+++ b/bundles/org.openhab.binding.qolsysiq/src/main/resources/OH-INF/thing/zone.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ A Qolsys IQ Zone
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+ The Zone ID.
+
+
+
+
+ String
+
+ The zone status.
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ The zone state.
+
+
+
+
+ Contact
+
+ The zone contact state.
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 316b6a9ad2d..08386b72ddf 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -305,6 +305,7 @@
org.openhab.binding.pushover
org.openhab.binding.pushsafer
org.openhab.binding.qbus
+ org.openhab.binding.qolsysiq
org.openhab.binding.radiothermostat
org.openhab.binding.regoheatpump
org.openhab.binding.revogi