[qolsysiq] Initial contribution of the Qolsys IQ Binding (#13699)

* [qolsysiq] Initial contribution of the Qolsys IQ Binding

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
This commit is contained in:
Dan Cunningham 2022-11-27 10:25:31 -08:00 committed by GitHub
parent 22ea587d20
commit 1d9bf63d5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 3033 additions and 0 deletions

View File

@ -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

View File

@ -1356,6 +1356,11 @@
<artifactId>org.openhab.binding.qbus</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.qolsysiq</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.radiothermostat</artifactId>

View File

@ -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

View File

@ -0,0 +1,125 @@
# Qolsys IQ Binding
This binding directly controls a [Qolsys IQ](https://qolsys.com/security/) security panel.
This allows for local monitoring of alarm and zone statuses as well as arming, disarming and triggering alarms.
Qolsys (a division of Johnson Controls) is a popular manufacturer of alarm systems.
The Qolsys IQ line of panels supports both wireless and hard wire sensors and features built in Cellular and Wi-Fi dual path communication that natively integrates with Alarm.com monitoring and supervision.
This binding directly interfaces with the panel and does not require cloud access.
![Qolsys IQ 4](doc/qolsysiq4.png)
## Supported Things
| Thing | Description | Thing Type | Thing UID |
|---------------------|-------------------------------------------------------------------------------------------|------------|-----------|
| Qolsys IQ Panel | A Qolsys IQ security panel (all current models, which is 2+ and 4 at the time of writing) | Bridge | panel |
| Qolsys IQ Partition | A logical partition which can be armed, disarmed, and is responsible for managing zones | Bridge | partition |
| Qolsys IQ Zone | A generic zone sensor | Thing | zone |
## Discovery
### Qolsys IQ Panel (Bridge)
The Qolsys IQ Panel must be manually added using a host name or ip address along with a secure access token from the panel settings.
To enable 3rd party control and retrieve the access token follow the following steps on the security panel touch screen:
`Settings` --> `Advanced Settings` --> `Installation` --> `Dealer Settings` -> `6 Digit User Code` (set to enabled)
`Settings` --> `Advanced Settings` --> `Installation` --> `Devices` --> `Wi-Fi Devices` --> `Control4` (set to enabled)
*Panel will reboot*
`Settings` --> `Advanced Settings` --> `Installation` --> `Devices` --> `Wi-Fi Devices` --> `Reveal Secure Token` (copy token to use in panel configuration)
At this point you may add the panel thing in openHAB using the secure token along with the IP or host name of the panel.
### Partition (Bridge)
Once a panel is added, partitions will be automatically discovered and appear in the inbox.
### Zone (Thing)
Once a partition is added, zones will be automatically discovered and appear in the inbox.
## Thing Configuration
### `panel` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|-------------------|---------|-----------------------------------------------------|---------|----------|----------|
| hostname | text | Hostname or IP address of the device | N/A | yes | no |
| port | integer | Port the device is listening on | 12345 | no | no |
| key | text | Access token / key found in the panel settings menu | N/A | yes | no |
### `partition` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------|
| id | integer | Partition id of the panel, staring with '0' for the first partition | N/A | yes | no |
| disarmCode | text | Optional disarm code to use when receiving a disarm command without a code. Required for integrations like Alexa and Homekit who do not provide codes when disarming. Leave blank to always require a code | blank | no | no |
| armCode | text | Optional arm code to use when receiving arm commands without a code. Only required if the panel has been configured to require arm codes. Leave blank to always require a code | blank | no | yes |
### `zone` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|---------|---------|---------------------------------------------------------------------------------------------------------|---------|----------|----------|
| id | integer | Id of the zone, staring with '1' for the first zone | N/A | yes | no |
## Channels
### Panel Channels
None.
### Partition Channels
| Channel | Type | Read/Write | Description | State Options | Command Options |
|-------------|--------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|----------------------------|
| armState | String | RW | Reports the current partition arm state or sends an arm or disarm command to the system. Security codes can be appended to the command using a colon delimiter (e.g. 'DISARM:123456'). Codes appended to the command will be used in place of the `armCode` configuration property if set. | ALARM, ARM_AWAY, ARM_STAY, DISARM, ENTRY_DELAY, EXIT_DELAY | ARM_AWAY, ARM_STAY, DISARM |
| alarmState | String | RW | Reports on the current alarm state, or triggers an instant alarm | AUXILIARY, FIRE, POLICE, ZONEOPEN, NONE | AUXILIARY, FIRE, POLICE |
| armingDelay | Number | R | The arming delay countdown currently in progress | Seconds remaining | N/A |
| errorEvent | String | R | Last error event message reported by the partition. Clears after 30 seconds | Error text | N/A |
### Zone Channels
| Channel | Type | Read/Write | Description | State Options |
|---------|---------|------------|------------------------|---------------------------------------------|
| status | String | R | The zone status | ACTIVE, CLOSED, OPEN, FAILURE, IDLE, TAMPER |
| state | Number | R | The zone state | Number |
| contact | Contact | R | The zone contact state | OPEN, CLOSED |
## Full Example
### qolsysiq.things
```
Bridge qolsysiq:panel:home "Home Security Panel" [ hostname="192.168.3.123", port=12345, key="AAABBB00" ] {
Bridge partition 0 "Partition Main" [ id=0, armCode="123456" ] {
Thing zone 1 "Window" [ id=1 ]
Thing zone 2 "Motion" [ id=2 ]
}
}
```
### qolsysiq.items
Sample items file with both Alexa and Homekit voice control
```
Group PartitionMain "Alarm System" ["Equipment"] {alexa="SecurityPanel", homekit = "SecuritySystem"}
String PartitionMain_PartitionArmState "Partition Arm State" <Alarm> (PartitionMain) ["Point"] {channel="qolsysiq:partition:home:0:armState", alexa="ArmState" [DISARMED="DISARM",ARMED_STAY="ARM_STAY",ARMED_AWAY="ARM_AWAY:EXIT_DELAY"], homekit = "SecuritySystem.CurrentSecuritySystemState,SecuritySystem.TargetSecuritySystemState" [STAY_ARM="ARM_STAY", AWAY_ARM="ARM_AWAY", DISARM="DISARM", DISARMED="DISARM", TRIGGERED="ALARM"]}
String PartitionMain_PartitionAlarmState "Partition Alarm State" <Alarm> (PartitionMain) ["Point"] {channel="qolsysiq:partition:home:0:alarmState"}
Number PartitionMain_PartitionArmingDelay "Partition Arming Delay" (PartitionMain) ["Point"] {channel="qolsysiq:partition:home:0:armingDelay"}
String PartitionMain_ErrorEvent "Error Event" (PartitionMain) ["Point"] {channel="qolsysiq:partition:home:0:errorEvent" }
Group ZoneKitchenWindows "Qolsys IQ Zone: Kitchen Windows" ["Equipment"]
Number ZoneKitchenWindows_ZoneState "Kitchen Windows Zone State" (ZoneKitchenWindows) ["Point"] {channel="qolsysiq:zone:home:0:1:state"}
String ZoneKitchenWindows_ZoneStatus "Kitchen Windows Zone Status" (ZoneKitchenWindows) ["Point"] {channel="qolsysiq:zone:home:0:1:status"}
Contact ZoneKitchenWindows_ZoneContact "Kitchen Windows Zone Contact" (ZoneKitchenWindows) ["Point"] {channel="qolsysiq:zone:home:0:1:contact"}
Group ZoneMotionDetector1 "Motion Detector 1" ["Equipment"]
Number ZoneMotionDetector_ZoneState1 "Motion Detector 1 Zone State" (ZoneMotionDetector1) ["Point"] {channel="qolsysiq:zone:home:0:2:state"}
String ZoneMotionDetector_ZoneStatus1 "Motion Detector 1 Zone Status" (ZoneMotionDetector1) ["Point"] {channel="qolsysiq:zone:home:0:2:status"}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.4.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.qolsysiq</artifactId>
<name>openHAB Add-ons :: Bundles :: QolsysIQ Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.qolsysiq-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-qolsysiq" description="QolsysIQ Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.qolsysiq/${project.version}</bundle>
</feature>
</features>

View File

@ -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";
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal;
import static org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.handler.QolsysIQPanelHandler;
import org.openhab.binding.qolsysiq.internal.handler.QolsysIQPartitionHandler;
import org.openhab.binding.qolsysiq.internal.handler.QolsysIQZoneHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link QolsysIQHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.qolsysiq", service = ThingHandlerFactory.class)
public class QolsysIQHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PANEL, THING_TYPE_PARTITION,
THING_TYPE_ZONE);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_PANEL.equals(thingTypeUID)) {
return new QolsysIQPanelHandler((Bridge) thing);
}
if (THING_TYPE_PARTITION.equals(thingTypeUID)) {
return new QolsysIQPartitionHandler((Bridge) thing);
}
if (THING_TYPE_ZONE.equals(thingTypeUID)) {
return new QolsysIQZoneHandler(thing);
}
return null;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,390 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.client.dto.action.Action;
import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.Event;
import org.openhab.binding.qolsysiq.internal.client.dto.event.EventType;
import org.openhab.binding.qolsysiq.internal.client.dto.event.InfoEventType;
import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneEventType;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
/**
* A client that can communicate with a Qolsys IQ Panel
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class QolsysiqClient {
private static final String MESSAGE_ACK = "ACK";
private final Logger logger = LoggerFactory.getLogger(QolsysiqClient.class);
private final Gson gson = new GsonBuilder().registerTypeAdapter(Event.class, new EventDeserializer())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
private List<QolsysIQClientListener> listeners = Collections.synchronizedList(new ArrayList<>());
private @Nullable SSLSocket socket;
private @Nullable BufferedReader reader;
private @Nullable BufferedWriter writer;
private @Nullable Thread readerThread;
private @Nullable ScheduledFuture<?> heartBeatFuture;
private ScheduledExecutorService scheduler;
private Object writeLock = new Object();
private long lastResponseTime;
private boolean hasACK = false;
private boolean connected;
private String host;
private int port;
private int heartbeatSeconds;
private String threadName;
private SSLSocketFactory sslsocketfactory;
/**
* Creates a new QolsysiqClient
*
* @param host
* @param port
* @param heartbeatSeconds
* @param scheduler for the heart beat task
* @param threadName
*/
public QolsysiqClient(String host, int port, int heartbeatSeconds, ScheduledExecutorService scheduler,
String threadName) throws IOException {
this.host = host;
this.port = port;
this.heartbeatSeconds = heartbeatSeconds;
this.scheduler = scheduler;
this.threadName = threadName;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, acceptAlltrustManagers(), null);
sslsocketfactory = sslContext.getSocketFactory();
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new IOException(e);
}
}
/**
* Connects to the panel
*
* @throws IOException
*/
public synchronized void connect() throws IOException {
logger.debug("connect");
if (connected) {
logger.debug("connect: already connected, ignoring");
return;
}
SSLSocket socket = (SSLSocket) sslsocketfactory.createSocket(host, port);
socket.startHandshake();
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.socket = socket;
Thread readerThread = new Thread(this::readEvents, threadName);
readerThread.setDaemon(true);
readerThread.start();
this.readerThread = readerThread;
connected = true;
try {
// send an initial message to confirm a connection and record a response time
writeMessage("");
} catch (IOException e) {
// clean up before bubbling up exception
disconnect();
throw e;
}
heartBeatFuture = scheduler.scheduleWithFixedDelay(() -> {
if (connected) {
try {
if (System.currentTimeMillis() - lastResponseTime > (heartbeatSeconds + 5) * 1000) {
throw new IOException("No responses received");
}
writeMessage("");
} catch (IOException e) {
logger.debug("Problem sending heartbeat", e);
disconnectAndNotify(e);
}
}
}, heartbeatSeconds, heartbeatSeconds, TimeUnit.SECONDS);
}
/**
* Disconnects from the panel
*/
public void disconnect() {
connected = false;
ScheduledFuture<?> heartbeatFuture = this.heartBeatFuture;
if (heartbeatFuture != null) {
heartbeatFuture.cancel(true);
}
Thread readerThread = this.readerThread;
if (readerThread != null && readerThread.isAlive()) {
readerThread.interrupt();
}
SSLSocket socket = this.socket;
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
logger.debug("Error closing SSL socket: {}", e.getMessage());
}
this.socket = null;
}
BufferedReader reader = this.reader;
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.debug("Error closing reader: {}", e.getMessage());
}
this.reader = null;
}
BufferedWriter writer = this.writer;
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
logger.debug("Error closing writer: {}", e.getMessage());
}
this.writer = null;
}
}
/**
* Sends an Action message to the panel
*
* @param action
* @throws IOException
*/
public void sendAction(Action action) throws IOException {
logger.debug("sendAction {}", action.type);
writeMessage(gson.toJson(action));
}
/**
* Adds a QolsysIQClientListener
*
* @param listener
*/
public void addListener(QolsysIQClientListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Removes a QolsysIQClientListener
*
* @param listener
*/
public void removeListener(QolsysIQClientListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
private synchronized void writeMessage(String message) throws IOException {
if (!connected) {
logger.debug("writeMessage: not connected, ignoring {}", message);
return;
}
synchronized (writeLock) {
hasACK = false;
logger.trace("writeMessage: {}", message);
BufferedWriter writer = this.writer;
if (writer != null) {
writer.write(message);
writer.newLine();
writer.flush();
try {
writeLock.wait(5000);
} catch (InterruptedException e) {
logger.debug("write lock interupted");
}
if (!hasACK) {
logger.trace("writeMessage: no ACK for {}", message);
throw new IOException("No response to message: " + message);
}
}
}
}
private void readEvents() {
String message;
BufferedReader reader = this.reader;
try {
while (connected && reader != null && (message = reader.readLine()) != null) {
logger.trace("Message: {}", message);
lastResponseTime = System.currentTimeMillis();
if (MESSAGE_ACK.equals(message)) {
synchronized (writeLock) {
hasACK = true;
writeLock.notify();
}
continue;
}
try {
Event event = gson.fromJson(message, Event.class);
if (event == null) {
logger.debug("Could not deserialize message: {}", message);
continue;
}
synchronized (listeners) {
if (event instanceof AlarmEvent) {
listeners.forEach(listener -> listener.alarmEvent((AlarmEvent) event));
} else if (event instanceof ArmingEvent) {
listeners.forEach(listener -> listener.armingEvent((ArmingEvent) event));
} else if (event instanceof ErrorEvent) {
listeners.forEach(listener -> listener.errorEvent((ErrorEvent) event));
} else if (event instanceof SecureArmInfoEvent) {
listeners.forEach(listener -> listener.secureArmInfoEvent((SecureArmInfoEvent) event));
} else if (event instanceof SummaryInfoEvent) {
listeners.forEach(listener -> listener.summaryInfoEvent((SummaryInfoEvent) event));
} else if (event instanceof ZoneActiveEvent) {
listeners.forEach(listener -> listener.zoneActiveEvent((ZoneActiveEvent) event));
} else if (event instanceof ZoneUpdateEvent) {
listeners.forEach(listener -> listener.zoneUpdateEvent((ZoneUpdateEvent) event));
} else if (event instanceof ZoneAddEvent) {
listeners.forEach(listener -> listener.zoneAddEvent((ZoneAddEvent) event));
}
}
} catch (JsonSyntaxException e) {
logger.debug("Could not parse messge", e);
}
}
if (connected) {
throw new IOException("socket disconencted");
}
} catch (IOException e) {
disconnectAndNotify(e);
}
}
private void disconnectAndNotify(Exception e) {
if (connected) {
disconnect();
synchronized (listeners) {
listeners.forEach(listener -> listener.disconnected(e));
}
}
}
private TrustManager[] acceptAlltrustManagers() {
return new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
}
@Override
public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
}
@Override
public X509Certificate @Nullable [] getAcceptedIssuers() {
return null;
}
} };
}
class EventDeserializer implements JsonDeserializer<Event> {
@Override
public @Nullable Event deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement event = jsonObject.get("event");
if (event != null) {
switch (EventType.valueOf(event.getAsString())) {
case ALARM:
return context.deserialize(jsonObject, AlarmEvent.class);
case ARMING:
return context.deserialize(jsonObject, ArmingEvent.class);
case ERROR:
return context.deserialize(jsonObject, ErrorEvent.class);
case INFO:
JsonElement infoType = jsonObject.get("info_type");
if (infoType != null) {
switch (InfoEventType.valueOf(infoType.getAsString())) {
case SECURE_ARM:
return context.deserialize(jsonObject, SecureArmInfoEvent.class);
case SUMMARY:
return context.deserialize(jsonObject, SummaryInfoEvent.class);
}
}
break;
case ZONE_EVENT:
JsonElement zoneEventType = jsonObject.get("zone_event_type");
if (zoneEventType != null) {
switch (ZoneEventType.valueOf(zoneEventType.getAsString())) {
case ZONE_ACTIVE:
return context.deserialize(jsonObject, ZoneActiveEvent.class);
case ZONE_UPDATE:
return context.deserialize(jsonObject, ZoneUpdateEvent.class);
case ZONE_ADD:
return context.deserialize(jsonObject, ZoneAddEvent.class);
default:
break;
}
}
}
}
return null;
}
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.client.dto.event;
import java.util.List;
import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
/**
* A {@link InfoEventType.SUMMARY} type of {@link InfoEvent} message sent by the panel
*
* @author Dan Cunningham - Initial contribution
*/
public class SummaryInfoEvent extends InfoEvent {
public List<Partition> partitionList;
public SummaryInfoEvent() {
super(InfoEventType.SUMMARY);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.client.dto.model;
import java.util.List;
/**
* A logical alarm partition that can be armed, report state and contain zones
*
* @author Dan Cunningham - Initial contribution
*/
public class Partition {
public Integer partitionId;
public String name;
public PartitionStatus status;
public Boolean secureArm;
public List<Zone> zoneList;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 = "";
}

View File

@ -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 = "";
}

View File

@ -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;
}

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.discovery;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
import org.openhab.binding.qolsysiq.internal.handler.QolsysIQChildDiscoveryHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple discovery service that can be used by Partition and Zone Handlers
*
* @author Dan Cunningham - Initial contribution
*
*/
@NonNullByDefault
public class QolsysIQChildDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(QolsysIQChildDiscoveryService.class);
private static final Set<ThingTypeUID> SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set
.of(QolsysIQBindingConstants.THING_TYPE_PARTITION, QolsysIQBindingConstants.THING_TYPE_ZONE);
private @Nullable ThingHandler thingHandler;
public QolsysIQChildDiscoveryService() throws IllegalArgumentException {
super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 5, false);
}
@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof QolsysIQChildDiscoveryHandler) {
((QolsysIQChildDiscoveryHandler) handler).setDiscoveryService(this);
this.thingHandler = handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return thingHandler;
}
@Override
protected void startScan() {
ThingHandler handler = this.thingHandler;
if (handler != null) {
((QolsysIQChildDiscoveryHandler) handler).startDiscovery();
}
}
@Override
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
public void discoverQolsysIQChildThing(ThingUID thingUID, ThingUID bridgeUID, Integer id, String label) {
logger.trace("discoverQolsysIQChildThing: {} {} {} {}", thingUID, bridgeUID, id, label);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(label).withProperty("id", id)
.withRepresentationProperty("id").withBridge(bridgeUID).build();
thingDiscovered(result);
}
}

View File

@ -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();
}

View File

@ -0,0 +1,327 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.handler;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
import org.openhab.binding.qolsysiq.internal.client.QolsysIQClientListener;
import org.openhab.binding.qolsysiq.internal.client.QolsysiqClient;
import org.openhab.binding.qolsysiq.internal.client.dto.action.Action;
import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoAction;
import org.openhab.binding.qolsysiq.internal.client.dto.action.InfoActionType;
import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.SummaryInfoEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
import org.openhab.binding.qolsysiq.internal.config.QolsysIQPanelConfiguration;
import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link QolsysIQPanelHandler} connects to a security panel and routes messages to child partitions.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class QolsysIQPanelHandler extends BaseBridgeHandler
implements QolsysIQClientListener, QolsysIQChildDiscoveryHandler {
private final Logger logger = LoggerFactory.getLogger(QolsysIQPanelHandler.class);
private static final int QUICK_RETRY_SECONDS = 1;
private static final int LONG_RETRY_SECONDS = 30;
private static final int HEARTBEAT_SECONDS = 30;
private @Nullable QolsysiqClient apiClient;
private @Nullable ScheduledFuture<?> retryFuture;
private @Nullable QolsysIQChildDiscoveryService discoveryService;
private List<Partition> partitions = Collections.synchronizedList(new LinkedList<Partition>());
private String key = "";
public QolsysIQPanelHandler(Bridge bridge) {
super(bridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand {}", command);
if (command instanceof RefreshType) {
refresh();
}
}
@Override
public void initialize() {
logger.debug("initialize");
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> {
connect();
});
}
@Override
public void dispose() {
stopRetryFuture();
disconnect();
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(QolsysIQChildDiscoveryService.class);
}
@Override
public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
this.discoveryService = service;
}
@Override
public void startDiscovery() {
refresh();
}
@Override
public void disconnected(Exception reason) {
logger.debug("disconnected", reason);
setOfflineAndReconnect(reason, QUICK_RETRY_SECONDS);
}
@Override
public void alarmEvent(AlarmEvent event) {
logger.debug("AlarmEvent {}", event.partitionId);
QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
if (handler != null) {
handler.alarmEvent(event);
}
}
@Override
public void armingEvent(ArmingEvent event) {
logger.debug("ArmingEvent {}", event.partitionId);
QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
if (handler != null) {
handler.armingEvent(event);
}
}
@Override
public void errorEvent(ErrorEvent event) {
logger.debug("ErrorEvent {}", event.partitionId);
QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
if (handler != null) {
handler.errorEvent(event);
}
}
@Override
public void summaryInfoEvent(SummaryInfoEvent event) {
logger.debug("SummaryInfoEvent");
synchronized (partitions) {
partitions.clear();
partitions.addAll(event.partitionList);
}
updatePartitions();
discoverChildDevices();
}
@Override
public void secureArmInfoEvent(SecureArmInfoEvent event) {
logger.debug("ArmingEvent {}", event.value);
QolsysIQPartitionHandler handler = partitionHandler(event.partitionId);
if (handler != null) {
handler.secureArmInfoEvent(event);
}
}
@Override
public void zoneActiveEvent(ZoneActiveEvent event) {
logger.debug("ZoneActiveEvent {} {}", event.zone.zoneId, event.zone.status);
partitions.forEach(p -> {
if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
if (handler != null) {
handler.zoneActiveEvent(event);
}
}
});
}
@Override
public void zoneUpdateEvent(ZoneUpdateEvent event) {
logger.debug("ZoneUpdateEvent {}", event.zone.name);
partitions.forEach(p -> {
if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
if (handler != null) {
handler.zoneUpdateEvent(event);
}
}
});
}
@Override
public void zoneAddEvent(ZoneAddEvent event) {
logger.debug("ZoneAddEvent {}", event.zone.name);
partitions.forEach(p -> {
if (p.zoneList.stream().filter(z -> z.zoneId.equals(event.zone.zoneId)).findAny().isPresent()) {
QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
if (handler != null) {
handler.zoneAddEvent(event);
}
}
});
}
/**
* Sends the action to the panel. This will replace the token of the action passed in with the one configured here
*
* @param action
*/
protected void sendAction(Action action) {
action.token = key;
QolsysiqClient client = this.apiClient;
if (client != null) {
try {
client.sendAction(action);
} catch (IOException e) {
logger.debug("Could not send action", e);
setOfflineAndReconnect(e, QUICK_RETRY_SECONDS);
}
}
}
protected synchronized void refresh() {
sendAction(new InfoAction(InfoActionType.SUMMARY));
}
/**
* Connect the client
*/
private synchronized void connect() {
if (getThing().getStatus() == ThingStatus.ONLINE) {
logger.debug("connect: Bridge is already connected");
return;
}
QolsysIQPanelConfiguration config = getConfigAs(QolsysIQPanelConfiguration.class);
key = config.key;
try {
QolsysiqClient apiClient = new QolsysiqClient(config.hostname, config.port, HEARTBEAT_SECONDS, scheduler,
"OH-binding-" + getThing().getUID().getAsString());
apiClient.connect();
apiClient.addListener(this);
this.apiClient = apiClient;
refresh();
updateStatus(ThingStatus.ONLINE);
} catch (IOException e) {
logger.debug("Could not connect");
setOfflineAndReconnect(e, LONG_RETRY_SECONDS);
}
}
/**
* Disconnects the client and removes listeners
*/
private void disconnect() {
logger.debug("disconnect");
QolsysiqClient apiClient = this.apiClient;
if (apiClient != null) {
apiClient.removeListener(this);
apiClient.disconnect();
this.apiClient = null;
}
}
private void startRetryFuture(int seconds) {
stopRetryFuture();
logger.debug("startRetryFuture");
this.retryFuture = scheduler.schedule(this::connect, seconds, TimeUnit.SECONDS);
}
private void stopRetryFuture() {
logger.debug("stopRetryFuture");
ScheduledFuture<?> retryFuture = this.retryFuture;
if (retryFuture != null) {
retryFuture.cancel(true);
this.retryFuture = null;
}
}
private void setOfflineAndReconnect(Exception reason, int seconds) {
logger.debug("setOfflineAndReconnect");
disconnect();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason.getMessage());
startRetryFuture(seconds);
}
private void updatePartitions() {
synchronized (partitions) {
partitions.forEach(p -> {
QolsysIQPartitionHandler handler = partitionHandler(p.partitionId);
if (handler != null) {
handler.updatePartition(p);
}
});
}
}
private void discoverChildDevices() {
synchronized (partitions) {
QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
partitions.forEach(p -> {
ThingUID bridgeUID = getThing().getUID();
ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_PARTITION, bridgeUID,
String.valueOf(p.partitionId));
discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, p.partitionId,
"Qolsys IQ Partition: " + p.name);
});
}
}
}
private @Nullable QolsysIQPartitionHandler partitionHandler(int partitionId) {
for (Thing thing : getThing().getThings()) {
ThingHandler handler = thing.getHandler();
if (handler instanceof QolsysIQPartitionHandler) {
if (((QolsysIQPartitionHandler) handler).getPartitionId() == partitionId) {
return (QolsysIQPartitionHandler) handler;
}
}
}
return null;
}
}

View File

@ -0,0 +1,369 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.handler;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmAction;
import org.openhab.binding.qolsysiq.internal.client.dto.action.AlarmActionType;
import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingAction;
import org.openhab.binding.qolsysiq.internal.client.dto.action.ArmingActionType;
import org.openhab.binding.qolsysiq.internal.client.dto.event.AlarmEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ArmingEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ErrorEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.SecureArmInfoEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneAddEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.model.AlarmType;
import org.openhab.binding.qolsysiq.internal.client.dto.model.Partition;
import org.openhab.binding.qolsysiq.internal.client.dto.model.PartitionStatus;
import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
import org.openhab.binding.qolsysiq.internal.config.QolsysIQPartitionConfiguration;
import org.openhab.binding.qolsysiq.internal.discovery.QolsysIQChildDiscoveryService;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link QolsysIQPartitionHandler} manages security partitions
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class QolsysIQPartitionHandler extends BaseBridgeHandler implements QolsysIQChildDiscoveryHandler {
private final Logger logger = LoggerFactory.getLogger(QolsysIQPartitionHandler.class);
private static final int CLEAR_ERROR_MESSSAGE_TIME = 30;
private @Nullable QolsysIQChildDiscoveryService discoveryService;
private @Nullable ScheduledFuture<?> delayFuture;
private @Nullable ScheduledFuture<?> errorFuture;
private @Nullable String armCode;
private @Nullable String disarmCode;
private List<Zone> zones = Collections.synchronizedList(new LinkedList<Zone>());
private int partitionId;
public QolsysIQPartitionHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
QolsysIQPartitionConfiguration config = getConfigAs(QolsysIQPartitionConfiguration.class);
partitionId = config.id;
armCode = config.armCode.isBlank() ? null : config.armCode;
disarmCode = config.disarmCode.isBlank() ? null : config.disarmCode;
logger.debug("initialize partition {}", partitionId);
initializePartition();
}
@Override
public void dispose() {
cancelExitDelayJob();
cancelErrorDelayJob();
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
cancelExitDelayJob();
cancelErrorDelayJob();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
initializePartition();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refresh();
return;
}
QolsysIQPanelHandler panel = panelHandler();
if (panel != null) {
if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE)) {
try {
panel.sendAction(new AlarmAction(AlarmActionType.valueOf(command.toString())));
} catch (IllegalArgumentException e) {
logger.debug("Unknown alarm type {} to channel {}", command, channelUID);
}
return;
}
// support ARM_AWAY and ARM_AWAY:123456 , same for other arm / disarm modes
if (channelUID.getId().equals(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE)) {
String armingTypeName = command.toString();
String code = null;
if (armingTypeName.contains(":")) {
String[] split = armingTypeName.split(":");
armingTypeName = split[0];
if (split.length > 1 && split[1].length() > 0) {
code = split[1];
}
}
try {
ArmingActionType armingType = ArmingActionType.valueOf(armingTypeName);
if (code == null) {
if (armingType == ArmingActionType.DISARM) {
code = disarmCode;
} else {
code = armCode;
}
}
panel.sendAction(new ArmingAction(armingType, getPartitionId(), code));
} catch (IllegalArgumentException e) {
logger.debug("Unknown arm type {} to channel {}", armingTypeName, channelUID);
}
}
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(QolsysIQChildDiscoveryService.class);
}
@Override
public void setDiscoveryService(QolsysIQChildDiscoveryService service) {
this.discoveryService = service;
}
@Override
public void startDiscovery() {
refresh();
}
/**
* The partition id
*
* @return
*/
public int getPartitionId() {
return partitionId;
}
public void zoneActiveEvent(ZoneActiveEvent event) {
QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
if (handler != null) {
handler.zoneActiveEvent(event);
}
}
public void zoneUpdateEvent(ZoneUpdateEvent event) {
QolsysIQZoneHandler handler = zoneHandler(event.zone.zoneId);
if (handler != null) {
handler.zoneUpdateEvent(event);
}
}
protected void alarmEvent(AlarmEvent event) {
if (event.alarmType != AlarmType.NONE && event.alarmType != AlarmType.ZONEOPEN) {
updatePartitionStatus(PartitionStatus.ALARM);
}
updateAlarmState(event.alarmType);
}
protected void armingEvent(ArmingEvent event) {
updatePartitionStatus(event.armingType);
updateDelay(event.delay == null ? 0 : event.delay);
}
protected void errorEvent(ErrorEvent event) {
cancelErrorDelayJob();
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, new StringType(event.description));
errorFuture = scheduler.schedule(this::clearErrorEvent, CLEAR_ERROR_MESSSAGE_TIME, TimeUnit.SECONDS);
}
protected void secureArmInfoEvent(SecureArmInfoEvent event) {
setSecureArm(event.value);
}
public void zoneAddEvent(ZoneAddEvent event) {
discoverZone(event.zone);
}
protected void updatePartition(Partition partition) {
updatePartitionStatus(partition.status);
setSecureArm(partition.secureArm);
if (partition.status != PartitionStatus.ALARM) {
updateAlarmState(AlarmType.NONE);
}
synchronized (zones) {
zones.clear();
zones.addAll(partition.zoneList);
zones.forEach(z -> {
QolsysIQZoneHandler zoneHandler = zoneHandler(z.zoneId);
if (zoneHandler != null) {
zoneHandler.updateZone(z);
}
});
}
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
discoverChildDevices();
}
protected @Nullable Zone getZone(Integer zoneId) {
synchronized (zones) {
return zones.stream().filter(z -> z.zoneId.equals(zoneId)).findAny().orElse(null);
}
}
private void initializePartition() {
QolsysIQPanelHandler panel = panelHandler();
if (panel == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
} else if (panel.getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> {
panel.refresh();
});
}
}
private void refresh() {
QolsysIQPanelHandler panel = panelHandler();
if (panel != null) {
panel.refresh();
}
}
private void updatePartitionStatus(PartitionStatus status) {
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ARM_STATE, new StringType(status.toString()));
cancelErrorDelayJob();
if (status == PartitionStatus.DISARM) {
updateAlarmState(AlarmType.NONE);
updateDelay(0);
}
}
private void setSecureArm(Boolean secure) {
Map<String, String> props = new HashMap<String, String>();
props.put("secureArm", String.valueOf(secure));
getThing().setProperties(props);
}
private void updateDelay(Integer delay) {
cancelExitDelayJob();
if (delay <= 0) {
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
return;
}
final long endTime = System.currentTimeMillis() + (delay * 1000);
delayFuture = scheduler.scheduleAtFixedRate(() -> {
long remaining = endTime - System.currentTimeMillis();
logger.debug("updateDelay remaining {}", remaining / 1000);
if (remaining <= 0) {
cancelExitDelayJob();
} else {
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY,
new DecimalType(remaining / 1000));
}
}, 1, 1, TimeUnit.SECONDS);
}
private void updateAlarmState(AlarmType alarmType) {
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ALARM_STATE, new StringType(alarmType.toString()));
}
private void clearErrorEvent() {
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_ERROR_EVENT, UnDefType.NULL);
}
private void cancelExitDelayJob() {
ScheduledFuture<?> delayFuture = this.delayFuture;
if (delayFuture != null) {
delayFuture.cancel(true);
this.delayFuture = null;
}
updateState(QolsysIQBindingConstants.CHANNEL_PARTITION_COMMAND_DELAY, new DecimalType(0));
}
private void cancelErrorDelayJob() {
ScheduledFuture<?> errorFuture = this.errorFuture;
if (errorFuture != null) {
errorFuture.cancel(true);
this.errorFuture = null;
}
clearErrorEvent();
}
private void discoverChildDevices() {
synchronized (zones) {
zones.forEach(z -> discoverZone(z));
}
}
private void discoverZone(Zone z) {
QolsysIQChildDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
ThingUID bridgeUID = getThing().getUID();
ThingUID thingUID = new ThingUID(QolsysIQBindingConstants.THING_TYPE_ZONE, bridgeUID,
String.valueOf(z.zoneId));
discoveryService.discoverQolsysIQChildThing(thingUID, bridgeUID, z.zoneId, "Qolsys IQ Zone: " + z.name);
}
}
private @Nullable QolsysIQZoneHandler zoneHandler(int zoneId) {
for (Thing thing : getThing().getThings()) {
ThingHandler handler = thing.getHandler();
if (handler instanceof QolsysIQZoneHandler) {
if (((QolsysIQZoneHandler) handler).getZoneId() == zoneId) {
return (QolsysIQZoneHandler) handler;
}
}
}
return null;
}
private @Nullable QolsysIQPanelHandler panelHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler instanceof QolsysIQPanelHandler) {
return (QolsysIQPanelHandler) handler;
}
}
return null;
}
}

View File

@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.qolsysiq.internal.handler;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.qolsysiq.internal.QolsysIQBindingConstants;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneActiveEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.event.ZoneUpdateEvent;
import org.openhab.binding.qolsysiq.internal.client.dto.model.Zone;
import org.openhab.binding.qolsysiq.internal.client.dto.model.ZoneStatus;
import org.openhab.binding.qolsysiq.internal.config.QolsysIQZoneConfiguration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link QolsysIQZoneHandler} manages security zones.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class QolsysIQZoneHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(QolsysIQZoneHandler.class);
private int zoneId;
public QolsysIQZoneHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("initialize");
zoneId = getConfigAs(QolsysIQZoneConfiguration.class).id;
initializeZone();
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusChanged) {
logger.debug("bridgeStatusChanged {}", bridgeStatusChanged);
initializeZone();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
public int getZoneId() {
return zoneId;
}
protected void updateZone(Zone zone) {
logger.debug("updateZone {}", zone.zoneId);
updateState(QolsysIQBindingConstants.CHANNEL_ZONE_STATE, new DecimalType(zone.state));
updateZoneStatus(zone.status);
Map<String, String> props = new HashMap<String, String>();
props.put("type", zone.type);
props.put("name", zone.name);
props.put("group", zone.group);
props.put("zoneID", zone.id);
props.put("zonePhysicalType", String.valueOf(zone.zonePhysicalType));
props.put("zoneAlarmType", String.valueOf(zone.zoneAlarmType));
props.put("zoneType", zone.zoneType.toString());
props.put("partitionId", String.valueOf(zone.partitionId));
getThing().setProperties(props);
}
protected void zoneActiveEvent(ZoneActiveEvent event) {
if (event.zone.zoneId == getZoneId()) {
updateZoneStatus(event.zone.status);
}
}
protected void zoneUpdateEvent(ZoneUpdateEvent event) {
if (event.zone.zoneId == getZoneId()) {
updateZone(event.zone);
}
}
private void initializeZone() {
Bridge bridge = getBridge();
BridgeHandler handler = bridge == null ? null : bridge.getHandler();
if (bridge != null && handler instanceof QolsysIQPartitionHandler) {
if (handler.getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
Zone z = ((QolsysIQPartitionHandler) handler).getZone(getZoneId());
if (z == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Zone not found in partition");
return;
}
updateZone(z);
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
}
}
private void updateZoneStatus(@Nullable ZoneStatus status) {
if (status != null) {
updateState(QolsysIQBindingConstants.CHANNEL_ZONE_STATUS, new StringType(status.toString()));
updateState(QolsysIQBindingConstants.CHANNEL_ZONE_CONTACT,
status == ZoneStatus.CLOSED || status == ZoneStatus.IDlE ? OpenClosedType.CLOSED
: OpenClosedType.OPEN);
} else {
logger.debug("updateZoneStatus: null status");
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="qolsysiq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>QolsysIQ Binding</name>
<description>This is the binding for Qolsys IQ Alarm Systems.</description>
</binding:binding>

View File

@ -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

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="qolsysiq"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="panel">
<label>Qolsys IQ Panel</label>
<description>A Qolsys IQ Panel Bridge</description>
<config-description>
<parameter name="hostname" type="text" required="true">
<context>network-address</context>
<label>Hostname</label>
<description>Hostname or IP address of the panel</description>
</parameter>
<parameter name="port" type="integer">
<label>Port</label>
<description>The port to connect to on the panel.</description>
<default>12345</default>
<advanced>true</advanced>
</parameter>
<parameter name="key" type="text" required="true">
<context>password</context>
<label>key</label>
<description>Key to access the device</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="qolsysiq"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="partition">
<supported-bridge-type-refs>
<bridge-type-ref id="panel"/>
</supported-bridge-type-refs>
<label>Partition</label>
<description>A Qolsys IQ Partition</description>
<channels>
<channel id="armState" typeId="armState"/>
<channel id="alarmState" typeId="alarmState"/>
<channel id="armingDelay" typeId="armingDelay"/>
<channel id="errorEvent" typeId="errorEvent"/>
</channels>
<properties>
<property name="secureArm">false</property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Partition ID</label>
<description>The Partition ID.</description>
</parameter>
<parameter name="disarmCode" type="text" required="false">
<default></default>
<label>Disarm Code</label>
<description>Optional disarm code to use when receiving a disarm command without a code. Required for integrations
like Alexa and Homekit who do not provide codes when disarming. Leave blank to always require a code</description>
</parameter>
<parameter name="armCode" type="text" required="false">
<default></default>
<label>Arm Code</label>
<description>Optional arm code to use when receiving arm commands without a code. Only required if the panel has
been configured to require arm codes. Leave blank to always require a code</description>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<channel-type id="armState">
<item-type>String</item-type>
<label>Partition Arm State</label>
<description>Reports the current partition arm state or sends a arm or disarm command to the system. For security
codes, append the 6 digit code to the command separated by a colon (e.g. 'DISARM:123456')</description>
<category>Alarm</category>
<state>
<options>
<option value="ALARM">In Alarm</option>
<option value="ARM_AWAY">Armed Away</option>
<option value="ARM_STAY">Armed Stay</option>
<option value="DISARM">Disarmed</option>
<option value="ENTRY_DELAY">Entry Delay</option>
<option value="EXIT_DELAY">Exit Delay</option>
</options>
</state>
<command>
<options>
<option value="ARM_AWAY">Arm Away</option>
<option value="ARM_STAY">Arm Stay</option>
<option value="DISARM">Disarm</option>
</options>
</command>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="alarmState">
<item-type>String</item-type>
<label>Partition Alarm State</label>
<description>Reports on the current alarm state, or triggers an instant alarm.</description>
<category>Alarm</category>
<state>
<options>
<option value="AUXILIARY">Auxiliary</option>
<option value="FIRE">Fire</option>
<option value="POLICE">Police</option>
<option value="ZONEOPEN">Zone Open</option>
<option value="NONE">None</option>
</options>
</state>
<command>
<options>
<option value="AUXILIARY">Auxiliary</option>
<option value="FIRE">Fire</option>
<option value="POLICE">Police</option>
</options>
</command>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="armingDelay">
<item-type>Number</item-type>
<label>Partition Arming Delay</label>
<description>The arming delay currently in progress</description>
<category>Alarm</category>
<state readOnly="true"></state>
</channel-type>
<channel-type id="errorEvent" advanced="true">
<item-type>String</item-type>
<label>Error Event</label>
<description>Last error event message reported by the partition. Clears after 30 seconds</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="qolsysiq"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="zone">
<supported-bridge-type-refs>
<bridge-type-ref id="partition"/>
</supported-bridge-type-refs>
<label>Zone</label>
<description>A Qolsys IQ Zone</description>
<channels>
<channel id="state" typeId="zoneState"/>
<channel id="status" typeId="zoneStatus"/>
<channel id="contact" typeId="contact"/>
</channels>
<properties>
<property name="type"></property>
<property name="name"></property>
<property name="group"></property>
<property name="zoneId"></property>
<property name="zonePhysicalType"></property>
<property name="zoneAlarmType"></property>
<property name="zoneType"></property>
<property name="partitionId"></property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="integer" required="true">
<label>Zone ID</label>
<description>The Zone ID.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="zoneStatus">
<item-type>String</item-type>
<label>Zone Status</label>
<description>The zone status.</description>
<state readOnly="true">
<options>
<option value="ACTIVE">Active</option>
<option value="CLOSED">Closed</option>
<option value="OPEN">Open</option>
<option value="FAILURE">Failure</option>
<option value="IDlE">Idle</option>
<option value="TAMPER">Tamper</option>
</options>
</state>
</channel-type>
<channel-type id="zoneState" advanced="true">
<item-type>Number</item-type>
<label>Zone State</label>
<description>The zone state.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="contact">
<item-type>Contact</item-type>
<label>Zone Contact</label>
<description>The zone contact state.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -305,6 +305,7 @@
<module>org.openhab.binding.pushover</module>
<module>org.openhab.binding.pushsafer</module>
<module>org.openhab.binding.qbus</module>
<module>org.openhab.binding.qolsysiq</module>
<module>org.openhab.binding.radiothermostat</module>
<module>org.openhab.binding.regoheatpump</module>
<module>org.openhab.binding.revogi</module>