added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.alarmdecoder/.classpath
Normal file
32
bundles/org.openhab.binding.alarmdecoder/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.alarmdecoder/.project
Normal file
23
bundles/org.openhab.binding.alarmdecoder/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.alarmdecoder</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.alarmdecoder/NOTICE
Normal file
13
bundles/org.openhab.binding.alarmdecoder/NOTICE
Normal 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
|
||||
313
bundles/org.openhab.binding.alarmdecoder/README.md
Normal file
313
bundles/org.openhab.binding.alarmdecoder/README.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# Alarm Decoder Binding
|
||||
|
||||
The [Alarm Decoder](http://www.alarmdecoder.com) from Nu Tech Software Solutions is a hardware adapter that interfaces with Ademco/Honeywell and DSC alarm panels.
|
||||
It acts essentially like a keypad, reading and writing messages on the serial bus that connects keypads with the main panel.
|
||||
|
||||
There are several versions of the adapter available:
|
||||
|
||||
* *AD2PI* or *AD2PHAT* - A board that plugs into a Raspberry Pi and offers network-based TCP connectivity
|
||||
* *AD2SERIAL* - Attaches to a host via a serial port
|
||||
* *AD2USB* - Attaches to a host via USB
|
||||
|
||||
This binding allows openHAB to access the state of wired or wireless contacts and motion detectors connected to supported alarm panels, as well as the state of attached keypads and the messages send to attached LRR devices.
|
||||
Support is also available for sending keypad commands, including special/programmable keys supported by your panel.
|
||||
|
||||
For those upgrading from the OH1 version of the binding, the [original OH1 README](https://www.openhab.org/v2.5/addons/bindings/alarmdecoder1/) file is available for reference.
|
||||
|
||||
## Supported Things
|
||||
|
||||
The binding supports the following thing types:
|
||||
|
||||
* `ipbridge` - Supports TCP connection to the AD.
|
||||
* `serialbridge` - Supports serial/USB connection to the AD.
|
||||
* `keypad` - Reports keypad status and optionally sends keypad messages.
|
||||
* `zone` - Reports status from zone expanders and relay expanders, and also from built-in zones via emulation.
|
||||
* `rfzone` - Reports status from RF zones.
|
||||
* `vzone` - Sends commands to virtual zones.
|
||||
* `lrr` - Reports messages sent from the panel to a Long Range Radio (LRR) or emulated LRR device.
|
||||
|
||||
## Discovery
|
||||
|
||||
Background discovery is currently supported for `zone` and `rfzone` things.
|
||||
If the bridge `discovery` parameter is set to *true*, the first time a status message is seen from each zone or RF zone a corresponding thing will appear in the inbox.
|
||||
Leaving the `discovery` parameter set to *false* during normal operation is recommended, as it will slightly reduce resource consumption by the binding.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The process for wiring the Alarm Decoder into the alarm panel and configuring it is described in the Alarm Decoder Quick Start guide for your model.
|
||||
Before working on the main panel, it is advisable to put the alarm system in test mode, and un-plug the phone connection to it for good measure.
|
||||
Don't forget to plug it back in when you are finished!
|
||||
|
||||
Understanding exactly what expansion boards are connected to the main panel is crucial for a successful setup of the AlarmDecoder and also helpful in interpreting the messages from the alarmdecoder.
|
||||
While many of the expansion devices don't have labels on the outside, inserting a flat screwdriver into the right slot and prying gently will usually uncover a circuit board with numbers on it that can be looked up via web search.
|
||||
|
||||
Although not mentioned in the Quick Start guide, configuring virtual relay boards is absolutely necessary on panels like the Honeywell Vista 20p and similar, or else all of the eight on-board zones will not be visible!
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Alarm Decoder things can be configured through openHAB's management UI, or manually via configuration files.
|
||||
When first configuring the binding it is probably easiest to configure it via the management UI, even if you plan to use configuration files later.
|
||||
If you enable the *discovery* option on the bridge, as you fault zones (e.g. open doors and windows, trigger motion detectors, etc.) they should appear in the discovery inbox.
|
||||
|
||||
### ipbridge
|
||||
|
||||
The `ipbridge` thing supports a TCP/IP connection to an Alarm Decoder device such as *AD2PI* or *AD2PHAT*.
|
||||
|
||||
* `hostname` (required) The hostname or IP address of the Alarm Decoder device
|
||||
* `tcpPort` (default = 10000) TCP port number for the Alarm Decoder connection
|
||||
* `discovery` (default = false) Enable automatic discovery of zones and RF zones
|
||||
* `reconnect` (1-60, default = 2) The period in minutes that the handler will wait between connection checks and connection attempts
|
||||
* `timeout` (0-60, default = 5) The period in minutes after which the connection will be reset if no valid messages have been received. Set to 0 to disable.
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Bridge alarmdecoder:ipbridge:ad1 [ hostname="cerberus.home", tcpPort=10000, discovery=true ] {
|
||||
Thing ...
|
||||
Thing ...
|
||||
}
|
||||
```
|
||||
|
||||
### serialbridge
|
||||
|
||||
The `serialbridge` thing supports a serial or USB connection to an Alarm Decoder device such as *AD2SERIAL* or *AD2USB*.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `serialPort` (required) The name of the serial port used to connect to the Alarm Decoder device
|
||||
* `bitrate` Speed of the serial connection
|
||||
* `discovery` (default=false) Enable automatic discovery of zones and RF zones
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Bridge alarmdecoder:serialbridge:ad1 [ serialPort="/dev/ttyS1", bitrate=115200, discovery=true ] {
|
||||
Thing ...
|
||||
Thing ...
|
||||
}
|
||||
```
|
||||
|
||||
### keypad
|
||||
|
||||
The `keypad` thing reports keypad status and optionally sends keypad messages.
|
||||
For panels that support multiple keypad addresses, it can be configured with an address mask of one or more keypad(s) for which it will receive messages.
|
||||
When sending messages, it will send from the configured keypad address if only one is configured.
|
||||
If a mask containing multiple addresses or 0 (all) is configured, it will send messages from the Alarm Decoder's configured address.
|
||||
|
||||
Commands sent from the keypad thing are limited to the set of valid keypad command characters supported by the Alarm Decoder (0-9,*,#,<,>).
|
||||
In addition, the characters A-H will be translated to special keys 1-8.
|
||||
Command strings containing invalid characters will be ignored.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `addressMask` (default = 0) String containing the mask in hex of addresses for which the keypad thing will receive messages (0 = all addresses).
|
||||
* `sendCommands` (default = false) Allow keypad commands to be sent to the alarm system from openHAB. Enabling this means the alarm system will be only as secure as your openHAB system.
|
||||
* `sendStar` (default = false) When disarmed/faulted, automatically send the * character to obtain zone fault information.
|
||||
* `commandMapping` (optional) Comma separated list of key/value pairs mapping integers to command strings for `intcommand` channel.
|
||||
|
||||
Address masks
|
||||
|
||||
Each bit in the 4 bytes of the address mask represents a device address, ranging from device 0 to device 31.
|
||||
The first byte (left to right) represents devices 0-7, the second 8-15, the third 16-23, and the fourth 24-31.
|
||||
The mask itself is represented as a string containing a hexadecimal number.
|
||||
For example, a mask of 03000000 would indicate devices 0 and 1 as follows:
|
||||
|
||||
```
|
||||
Mask: 03000000
|
||||
Bytes: 03 00 00 00
|
||||
Bits: 00000011 00000000 00000000 00000000
|
||||
-------- -------- -------- --------
|
||||
Device# 111111 22221111 33222222
|
||||
76543210 54321098 32109876 10987654
|
||||
```
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Thing keypad keypad1 [ addressMask=0, sendCommands=true ]
|
||||
```
|
||||
|
||||
### zone
|
||||
|
||||
The `zone` thing reports status from zone expanders and relay expanders, and also from built-in zones via emulation.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `address` (required) Zone address
|
||||
* `channel` (required) Zone channel
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Thing zone frontdoor [ address=10, channel=1 ]
|
||||
```
|
||||
|
||||
### rfzone
|
||||
|
||||
The `rfzone` thing reports status from wireless zones, such as 5800 series RF devices, if your alarm panel has an RF receiver.
|
||||
|
||||
Parameters:
|
||||
|
||||
* `serial` (required) Serial number of the RF zone
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Thing rfzone motion1 [ serial=0180010 ]
|
||||
```
|
||||
|
||||
### vzone
|
||||
|
||||
The `vzone` thing sends open/close commands a virtual zone.
|
||||
After enabling zone expander emulation on both the alarm panel and the Alarm Decoder device, it can be used to control the state of a virtual zone.
|
||||
The `command` channel is write-only, and accepts either the string "OPEN" or the string "CLOSED".
|
||||
The `state` channel is a switch type channel that reflects the current state of the virtual zone (ON=closed/OFF=open).
|
||||
|
||||
Parameters:
|
||||
|
||||
* `address` (required) Virtual zone number (0-99)
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Thing vzone watersensor [ address=41 ]
|
||||
```
|
||||
|
||||
### lrr
|
||||
|
||||
The `lrr` thing reports messages sent to a Long Range Radio (LRR) or emulated LRR device.
|
||||
These are normally specifically formatted messages as described in the [SIA DC-05-1999.09](http://www.alarmdecoder.com/wiki/index.php/File:SIA-ContactIDCodes_Protocol.pdf) standard for Contact ID reporting.
|
||||
They can also, depending on configuration, be other types of messages as described [here](http://www.alarmdecoder.com/wiki/index.php/LRR_Support).
|
||||
For panels that support multiple partitions, the partition for which a given lrr thing will receive messages can be defined.
|
||||
|
||||
* `partition` (default = 0) Partition for which to receive LRR events (0 = All)
|
||||
|
||||
Thing config file example:
|
||||
|
||||
```
|
||||
Thing lrr lrr [ partition=0 ]
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
The alarmdecoder things expose the following channels:
|
||||
|
||||
**zone**
|
||||
|
||||
| channel | type |RO/RW| description |
|
||||
|--------------|---------|-----|------------------------------|
|
||||
| contact | Contact |RO |Zone contact state |
|
||||
|
||||
**rfzone**
|
||||
|
||||
| channel | type |RO/RW| description |
|
||||
|--------------|---------|-----|------------------------------|
|
||||
| lowbat | Switch | RO |Low battery |
|
||||
| supervision | Switch | RO |Supervision warning |
|
||||
| loop1 | Contact | RO |Loop 1 state |
|
||||
| loop2 | Contact | RO |Loop 2 state |
|
||||
| loop3 | Contact | RO |Loop 3 state |
|
||||
| loop4 | Contact | RO |Loop 4 state |
|
||||
|
||||
**vzone**
|
||||
|
||||
| channel | type |RO/RW| description |
|
||||
|--------------|---------|-----|------------------------------|
|
||||
| command | String | WO |"OPEN" or "CLOSED" command |
|
||||
| state | Switch | RW |Zone state (ON = closed) |
|
||||
|
||||
**keypad**
|
||||
|
||||
| channel | type |RO/RW| description |
|
||||
|--------------|---------|-----|------------------------------|
|
||||
| zone | Number | RO |Zone number for status |
|
||||
| text | String | RO |Keypad message text |
|
||||
| ready | Switch | RO |Panel ready |
|
||||
| armedaway | Switch | RO |Armed/Away Indicator |
|
||||
| armedhome | Switch | RO |Armed/Stay Indicator |
|
||||
| backlight | Switch | RO |Keypad backlight on |
|
||||
| program | Switch | RO |Programming mode |
|
||||
| beeps | Number | RO |Number of beeps for message |
|
||||
| bypassed | Switch | RO |Zone bypassed |
|
||||
| acpower | Switch | RO |Panel on AC power |
|
||||
| chime | Switch | RO |Chime enabled |
|
||||
| alarmoccurred| Switch | RO |Alarm occurred in the past |
|
||||
| alarm | Switch | RO |Alarm is currently sounding |
|
||||
| lowbat | Switch | RO |Low battery warning |
|
||||
| delayoff | Switch | RO |Entry delay off |
|
||||
| fire | Switch | RO |Fire detected |
|
||||
| sysfault | Switch | RO |System fault |
|
||||
| perimeter | Switch | RO |Perimeter only |
|
||||
| command | String | RW |Keypad command |
|
||||
| intcommand | Number | RW |Integer keypad command |
|
||||
|
||||
*Note* - The `intcommand` channel is provided for backward compatibility with the OH1 version of the binding.
|
||||
The integer to command string mappings are provided by the optional keypad `commandMapping` parameter.
|
||||
The default mapping is "0=0,1=1,2=2,3=3,4=4,5=5,6=6,7=7,8=8,9=9,10=*,11=#".
|
||||
|
||||
**lrr**
|
||||
|
||||
| channel | type |RO/RW| description |
|
||||
|--------------|---------|-----|------------------------------|
|
||||
| partition | Number | RO |Partition number (0=system) |
|
||||
| eventdata | Number | RO |CID event data (user or zone) |
|
||||
| cidmessage | String | RO |SIA Contact ID Protocol msg. |
|
||||
| reportcode | String | RO |CID report code |
|
||||
|
||||
## Full Example
|
||||
|
||||
Example ad.things file:
|
||||
|
||||
```
|
||||
Bridge alarmdecoder:ipbridge:ad1 [ hostname="cerberus.home", tcpPort=10000, discovery=true ] {
|
||||
Thing zone frontdoor [ address=10, channel=1 ]
|
||||
Thing zone backdoor [ address=11, channel=1 ]
|
||||
Thing rfzone motion1 [ serial=0180010 ]
|
||||
Thing vzone watersensor [ address=41 ]
|
||||
Thing keypad keypad1 [ addressMask=0, sendCommands=true ]
|
||||
Thing lrr lrr [ partition=0 ]
|
||||
}
|
||||
```
|
||||
|
||||
Example ad.items file:
|
||||
|
||||
```
|
||||
Number KeypadZone "Zone [%d]" {channel="alarmdecoder:keypad:ad1:keypad1:zone"}
|
||||
String KeypadText "Message" {channel="alarmdecoder:keypad:ad1:keypad1:text"}
|
||||
Switch KeypadArmedAway "Armed Away" {channel="alarmdecoder:keypad:ad1:keypad1:armedaway"}
|
||||
Switch KeypadArmedHome "Armed Home" {channel="alarmdecoder:keypad:ad1:keypad1:armedhome"}
|
||||
Switch KeypadAlarm "Alarm" {channel="alarmdecoder:keypad:ad1:keypad1:alarm"}
|
||||
Switch KeypadFire "Fire" {channel="alarmdecoder:keypad:ad1:keypad1:fire"}
|
||||
String KeypadCmd "Command" {channel="alarmdecoder:keypad:ad1:keypad1:command"}
|
||||
|
||||
Contact FrontDoorContact "Front Door Zone" {channel="alarmdecoder:zone:ad1:frontdoor:contact"}
|
||||
|
||||
Switch Motion1Lowbat "Low Battery" {channel="alarmdecoder:rfzone:ad1:motion1:lowbat"}
|
||||
Switch Motion1Supervision "Supervision Warning" {channel="alarmdecoder:rfzone:ad1:motion1:supervision"}
|
||||
Contact Motion1Loop1 "Loop 1" {channel="alarmdecoder:rfzone:ad1:motion1:loop1"}
|
||||
Contact Motion1Loop2 "Loop 2" {channel="alarmdecoder:rfzone:ad1:motion1:loop2"}
|
||||
Contact Motion1Loop3 "Loop 3" {channel="alarmdecoder:rfzone:ad1:motion1:loop3"}
|
||||
Contact Motion1Loop4 "Loop 4" {channel="alarmdecoder:rfzone:ad1:motion1:loop4"}
|
||||
|
||||
String WaterSensorCmd "Virtual Zone Command" {channel="alarmdecoder:vzone:ad1:watersensor:command"}
|
||||
|
||||
Number LrrPartition "Partition Number [%d]" {channel="alarmdecoder:lrr:ad1:lrr:partition"}
|
||||
Number LrrEventData "CID Event Data [%d]" {channel="alarmdecoder:lrr:ad1:lrr:eventdata"}
|
||||
String LrrMessage "CID Message" {channel="alarmdecoder:lrr:ad1:lrr:cidmessage"}
|
||||
String LrrReportCode "CID Report Code" {channel="alarmdecoder:lrr:ad1:lrr:reportcode"}
|
||||
```
|
||||
|
||||
*Note: For brevity, not every possible keypad channel is linked to an item in the above example.*
|
||||
|
||||
## Thing Actions
|
||||
|
||||
The `ipbridge` and `serialbridge` things expose the following action to the automation engine:
|
||||
|
||||
*reboot* - Send command to reboot the Alarm Decoder device. Accepts no parameters.
|
||||
|
||||
## Quirks
|
||||
|
||||
The alarmdecoder device cannot query the panel for the state of individual zones.
|
||||
For this reason, the binding puts contacts into the "unknown" state (UNDEF), *until the panel goes into the READY state*.
|
||||
At that point, all contacts for which no update messages have arrived are presumed to be in the CLOSED state.
|
||||
In other words: to get to a clean slate after an openHAB restart, close all doors/windows such that the panel is READY.
|
||||
17
bundles/org.openhab.binding.alarmdecoder/pom.xml
Normal file
17
bundles/org.openhab.binding.alarmdecoder/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.alarmdecoder</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Alarm Decoder Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.alarmdecoder-${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-alarmdecoder" description="alarmdecoder Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.alarmdecoder/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link AlarmDecoderBindingConstants} class defines common constants, which are
|
||||
* used throughout the binding.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
* @author Bill Forsyth - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AlarmDecoderBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "alarmdecoder";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_IPBRIDGE = new ThingTypeUID(BINDING_ID, "ipbridge");
|
||||
public static final ThingTypeUID THING_TYPE_SERIALBRIDGE = new ThingTypeUID(BINDING_ID, "serialbridge");
|
||||
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
|
||||
public static final ThingTypeUID THING_TYPE_RFZONE = new ThingTypeUID(BINDING_ID, "rfzone");
|
||||
public static final ThingTypeUID THING_TYPE_VZONE = new ThingTypeUID(BINDING_ID, "vzone");
|
||||
public static final ThingTypeUID THING_TYPE_KEYPAD = new ThingTypeUID(BINDING_ID, "keypad");
|
||||
public static final ThingTypeUID THING_TYPE_LRR = new ThingTypeUID(BINDING_ID, "lrr");
|
||||
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPE_UIDS = Collections.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_ZONE, THING_TYPE_RFZONE, THING_TYPE_KEYPAD, THING_TYPE_LRR).collect(Collectors.toSet()));
|
||||
|
||||
// Bridge properties
|
||||
public static final String PROPERTY_SERIALNUM = "serialNumber";
|
||||
public static final String PROPERTY_VERSION = "firmwareVersion";
|
||||
public static final String PROPERTY_CAPABILITIES = "capabilities";
|
||||
|
||||
// Channel IDs for ZoneHandler
|
||||
public static final String PROPERTY_ADDRESS = "address";
|
||||
public static final String PROPERTY_CHANNEL = "channel";
|
||||
public static final String PROPERTY_ID = "id";
|
||||
|
||||
public static final String CHANNEL_CONTACT = "contact";
|
||||
public static final String CHANNEL_STATE = "state";
|
||||
|
||||
// Channel IDs for VZoneHandler
|
||||
public static final String CHANNEL_COMMAND = "command";
|
||||
|
||||
// Channel IDs for RFZoneHandler
|
||||
public static final String PROPERTY_SERIAL = "serial";
|
||||
|
||||
public static final String CHANNEL_RF_LOWBAT = "lowbat";
|
||||
public static final String CHANNEL_RF_SUPERVISION = "supervision";
|
||||
public static final String CHANNEL_RF_LOOP1 = "loop1";
|
||||
public static final String CHANNEL_RF_LOOP2 = "loop2";
|
||||
public static final String CHANNEL_RF_LOOP3 = "loop3";
|
||||
public static final String CHANNEL_RF_LOOP4 = "loop4";
|
||||
|
||||
// Channel IDs for KeypadHandler
|
||||
public static final String CHANNEL_KP_ZONE = "zone";
|
||||
public static final String CHANNEL_KP_TEXT = "text";
|
||||
public static final String CHANNEL_KP_READY = "ready";
|
||||
public static final String CHANNEL_KP_ARMEDAWAY = "armedaway";
|
||||
public static final String CHANNEL_KP_ARMEDHOME = "armedhome";
|
||||
public static final String CHANNEL_KP_BACKLIGHT = "backlight";
|
||||
public static final String CHANNEL_KP_PRORGAM = "program";
|
||||
public static final String CHANNEL_KP_BEEPS = "beeps";
|
||||
public static final String CHANNEL_KP_BYPASSED = "bypassed";
|
||||
public static final String CHANNEL_KP_ACPOWER = "acpower";
|
||||
public static final String CHANNEL_KP_CHIME = "chime";
|
||||
public static final String CHANNEL_KP_ALARMOCCURRED = "alarmoccurred";
|
||||
public static final String CHANNEL_KP_ALARM = "alarm";
|
||||
public static final String CHANNEL_KP_LOWBAT = "lowbat";
|
||||
public static final String CHANNEL_KP_DELAYOFF = "delayoff";
|
||||
public static final String CHANNEL_KP_FIRE = "fire";
|
||||
public static final String CHANNEL_KP_SYSFAULT = "sysfault";
|
||||
public static final String CHANNEL_KP_PERIMETER = "perimeter";
|
||||
public static final String CHANNEL_KP_COMMAND = "command";
|
||||
public static final String CHANNEL_KP_INTCOMMAND = "intcommand";
|
||||
public static final String DEFAULT_MAPPING = "0=0,1=1,2=2,3=3,4=4,5=5,6=6,7=7,8=8,9=9,10=*,11=#";
|
||||
|
||||
// Channel IDs for LRRHandler
|
||||
public static final String CHANNEL_LRR_PARTITION = "partition";
|
||||
public static final String CHANNEL_LRR_EVENTDATA = "eventdata";
|
||||
public static final String CHANNEL_LRR_CIDMESSAGE = "cidmessage";
|
||||
public static final String CHANNEL_LRR_REPORTCODE = "reportcode";
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.ADBridgeHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.ZoneHandler;
|
||||
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.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AlarmDecoderDiscoveryService} handles discovery of devices as they are identified by the bridge handler.
|
||||
* Requests from the framework to startScan() are ignored, since no active scanning is possible.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AlarmDecoderDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AlarmDecoderDiscoveryService.class);
|
||||
|
||||
private ADBridgeHandler bridgeHandler;
|
||||
private final Set<String> discoveredZoneSet = new HashSet<>();
|
||||
private final Set<Integer> discoveredRFZoneSet = new HashSet<>();
|
||||
|
||||
public AlarmDecoderDiscoveryService(ADBridgeHandler bridgeHandler) throws IllegalArgumentException {
|
||||
super(DISCOVERABLE_DEVICE_TYPE_UIDS, 0, false);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
// Ignore start scan requests
|
||||
}
|
||||
|
||||
public void processZone(int address, int channel) {
|
||||
String token = ZoneHandler.zoneID(address, channel);
|
||||
if (!discoveredZoneSet.contains(token)) {
|
||||
notifyDiscoveryOfZone(address, channel, token);
|
||||
discoveredZoneSet.add(token);
|
||||
}
|
||||
}
|
||||
|
||||
public void processRFZone(int serial) {
|
||||
if (!discoveredRFZoneSet.contains(serial)) {
|
||||
notifyDiscoveryOfRFZone(serial);
|
||||
discoveredRFZoneSet.add(serial);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDiscoveryOfZone(int address, int channel, String idString) {
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(THING_TYPE_ZONE, bridgeUID, idString);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(PROPERTY_ADDRESS, address);
|
||||
properties.put(PROPERTY_CHANNEL, channel);
|
||||
properties.put(PROPERTY_ID, idString);
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperties(properties)
|
||||
.withRepresentationProperty(PROPERTY_ID).build();
|
||||
thingDiscovered(result);
|
||||
logger.debug("Discovered Zone {}", uid);
|
||||
}
|
||||
|
||||
private void notifyDiscoveryOfRFZone(Integer serial) {
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(THING_TYPE_RFZONE, bridgeUID, serial.toString());
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(PROPERTY_SERIAL, serial);
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperties(properties)
|
||||
.withRepresentationProperty(PROPERTY_SERIAL).build();
|
||||
thingDiscovered(result);
|
||||
logger.debug("Discovered RF Zone{}", uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.ADBridgeHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.IPBridgeHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.KeypadHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.LRRHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.RFZoneHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.SerialBridgeHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.VZoneHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.ZoneHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AlarmDecoderHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.alarmdecoder", service = ThingHandlerFactory.class)
|
||||
public class AlarmDecoderHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_IPBRIDGE, THING_TYPE_SERIALBRIDGE, THING_TYPE_ZONE, THING_TYPE_RFZONE,
|
||||
THING_TYPE_VZONE, THING_TYPE_KEYPAD, THING_TYPE_LRR).collect(Collectors.toSet()));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AlarmDecoderHandlerFactory.class);
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Activate
|
||||
public AlarmDecoderHandlerFactory(final @Reference SerialPortManager serialPortManager) {
|
||||
// Obtain the serial port manager service using an OSGi reference
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegMap = new HashMap<>();
|
||||
// Marked as Nullable only to fix incorrect redundant null check complaints from null annotations
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_IPBRIDGE.equals(thingTypeUID)) {
|
||||
IPBridgeHandler bridgeHandler = new IPBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (THING_TYPE_SERIALBRIDGE.equals(thingTypeUID)) {
|
||||
SerialBridgeHandler bridgeHandler = new SerialBridgeHandler((Bridge) thing, serialPortManager);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (THING_TYPE_ZONE.equals(thingTypeUID)) {
|
||||
return new ZoneHandler(thing);
|
||||
} else if (THING_TYPE_RFZONE.equals(thingTypeUID)) {
|
||||
return new RFZoneHandler(thing);
|
||||
} else if (THING_TYPE_VZONE.equals(thingTypeUID)) {
|
||||
return new VZoneHandler(thing);
|
||||
} else if (THING_TYPE_KEYPAD.equals(thingTypeUID)) {
|
||||
return new KeypadHandler(thing);
|
||||
} else if (THING_TYPE_LRR.equals(thingTypeUID)) {
|
||||
return new LRRHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof ADBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegMap.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
logger.debug("Unregistering discovery service.");
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a discovery service for a bridge handler.
|
||||
*
|
||||
* @param bridgeHandler bridge handler for which to register the discovery service
|
||||
*/
|
||||
private synchronized void registerDiscoveryService(ADBridgeHandler bridgeHandler) {
|
||||
logger.debug("Registering discovery service.");
|
||||
AlarmDecoderDiscoveryService discoveryService = new AlarmDecoderDiscoveryService(bridgeHandler);
|
||||
bridgeHandler.setDiscoveryService(discoveryService);
|
||||
discoveryServiceRegMap.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.actions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.alarmdecoder.internal.handler.ADBridgeHandler;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADCommand;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link BridgeActions} class defines thing actions for alarmdecoder bridges.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "alarmdecoder")
|
||||
@NonNullByDefault
|
||||
public class BridgeActions implements ThingActions, IBridgeActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BridgeActions.class);
|
||||
|
||||
private @Nullable ADBridgeHandler bridge;
|
||||
|
||||
public BridgeActions() {
|
||||
logger.trace("Alarm Decoder bridge actions service created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof ADBridgeHandler) {
|
||||
this.bridge = (ADBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot thing action
|
||||
*/
|
||||
@Override
|
||||
@RuleAction(label = "Reboot", description = "Reboot the Alarm Decoder device")
|
||||
public void reboot() {
|
||||
ADBridgeHandler bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.sendADCommand(ADCommand.reboot());
|
||||
logger.debug("Sending reboot command.");
|
||||
} else {
|
||||
logger.debug("Request for reboot action, but bridge is undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
// Static method for Rules DSL backward compatibility
|
||||
public static void reboot(@Nullable ThingActions actions) {
|
||||
// if (actions instanceof BridgeActions) {
|
||||
// ((BridgeActions) actions).reboot();
|
||||
// } else {
|
||||
// throw new IllegalArgumentException("Instance is not a BridgeActions class.");
|
||||
// }
|
||||
invokeMethodOf(actions).reboot(); // Remove and uncomment above when core issue #1536 is fixed
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
|
||||
* resolved.
|
||||
*/
|
||||
private static IBridgeActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(BridgeActions.class.getName())) {
|
||||
if (actions instanceof IBridgeActions) {
|
||||
return (IBridgeActions) actions;
|
||||
} else {
|
||||
return (IBridgeActions) Proxy.newProxyInstance(IBridgeActions.class.getClassLoader(),
|
||||
new Class[] { IBridgeActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of BridgeActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.actions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link IBridgeActions} defines the interface for all thing actions supported by the bridges.
|
||||
* This is only necessary to work around a bug in openhab-core (issue #1536). It should be removed once that is
|
||||
* resolved.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IBridgeActions {
|
||||
|
||||
public void reboot();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IPBridgeConfig} class contains fields mapping thing configuration parameters for IPBridgeHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IPBridgeConfig {
|
||||
public @Nullable String hostname;
|
||||
public int tcpPort = 10000;
|
||||
public boolean discovery = false;
|
||||
public int reconnect = 2;
|
||||
public int timeout = 5;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.DEFAULT_MAPPING;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link KeypadConfig} class contains fields mapping thing configuration parameters for KeypadHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KeypadConfig {
|
||||
public String addressMask = "0";
|
||||
public boolean sendCommands = false;
|
||||
public boolean sendStar = false;
|
||||
public String commandMapping = DEFAULT_MAPPING;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link LRRConfig} class contains fields mapping thing configuration parameters for LRRHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LRRConfig {
|
||||
public int partition = 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RFZoneConfig} class contains fields mapping thing configuration parameters for RFZoneHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RFZoneConfig {
|
||||
public int serial = -1;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SerialBridgeConfig} class contains fields mapping thing configuration parameters for SerialBridgeHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SerialBridgeConfig {
|
||||
public String serialPort = "";
|
||||
public int bitrate = 115200;
|
||||
public boolean discovery = false;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VZoneConfig} class contains fields mapping thing configuration parameters for VZoneHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VZoneConfig {
|
||||
public int address = -1;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ZoneConfig} class contains fields mapping thing configuration parameters for ZoneHandler.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneConfig {
|
||||
public int address = -1;
|
||||
public int channel = -1;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
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.alarmdecoder.internal.AlarmDecoderDiscoveryService;
|
||||
import org.openhab.binding.alarmdecoder.internal.actions.BridgeActions;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADCommand;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMsgType;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.EXPMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.KeypadMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.LRRMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.RFXMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.VersionMessage;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for bridge handlers responsible for communicating with the Nu Tech Alarm Decoder devices.
|
||||
* Based partly on and including code from the original OH1 alarmdecoder binding by Bernd Pfrommer.
|
||||
*
|
||||
* @author Bernd Pfrommer - Initial contribution (OH1 version)
|
||||
* @author Bob Adair - Re-factored into OH2 binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ADBridgeHandler extends BaseBridgeHandler {
|
||||
protected static final Charset AD_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ADBridgeHandler.class);
|
||||
|
||||
protected @Nullable BufferedReader reader = null;
|
||||
protected @Nullable BufferedWriter writer = null;
|
||||
protected @Nullable Thread msgReaderThread = null;
|
||||
private final Object msgReaderThreadLock = new Object();
|
||||
protected @Nullable AlarmDecoderDiscoveryService discoveryService;
|
||||
protected boolean discovery;
|
||||
protected boolean panelReadyReceived = false;
|
||||
protected volatile @Nullable Date lastReceivedTime;
|
||||
protected volatile boolean writeException;
|
||||
|
||||
protected @Nullable ScheduledFuture<?> connectionCheckJob;
|
||||
protected @Nullable ScheduledFuture<?> connectRetryJob;
|
||||
|
||||
public ADBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.trace("dispose called");
|
||||
disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(BridgeActions.class);
|
||||
}
|
||||
|
||||
public void setDiscoveryService(AlarmDecoderDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Accepts no commands, so do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the alarm decoder using a buffered writer. This could block if the buffer is full, so it should
|
||||
* eventually be replaced with a queuing mechanism and a separate writer thread.
|
||||
*
|
||||
* @param command Command string to send including terminator
|
||||
*/
|
||||
public void sendADCommand(ADCommand command) {
|
||||
logger.debug("Sending AD command: {}", command);
|
||||
try {
|
||||
BufferedWriter bw = writer;
|
||||
if (bw != null) {
|
||||
bw.write(command.toString());
|
||||
bw.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.info("Exception while sending command: {}", e.getMessage());
|
||||
writeException = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void connect();
|
||||
|
||||
protected abstract void disconnect();
|
||||
|
||||
protected void scheduleConnectRetry(long waitMinutes) {
|
||||
logger.debug("Scheduling connection retry in {} minutes", waitMinutes);
|
||||
connectRetryJob = scheduler.schedule(this::connect, waitMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
protected void startMsgReader() {
|
||||
synchronized (msgReaderThreadLock) {
|
||||
Thread mrt = new Thread(this::readerThread, "AD Reader");
|
||||
mrt.setDaemon(true);
|
||||
mrt.start();
|
||||
msgReaderThread = mrt;
|
||||
}
|
||||
}
|
||||
|
||||
protected void stopMsgReader() {
|
||||
synchronized (msgReaderThreadLock) {
|
||||
Thread mrt = msgReaderThread;
|
||||
if (mrt != null) {
|
||||
logger.trace("Stopping reader thread.");
|
||||
mrt.interrupt();
|
||||
msgReaderThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed by message reader thread
|
||||
*/
|
||||
private void readerThread() {
|
||||
logger.debug("Message reader thread started");
|
||||
String message = null;
|
||||
try {
|
||||
// Send version command to get device to respond with VER message.
|
||||
sendADCommand(ADCommand.getVersion());
|
||||
BufferedReader reader = this.reader;
|
||||
while (!Thread.interrupted() && reader != null && (message = reader.readLine()) != null) {
|
||||
logger.trace("Received msg: {}", message);
|
||||
ADMsgType msgType = ADMsgType.getMsgType(message);
|
||||
if (msgType != ADMsgType.INVALID) {
|
||||
lastReceivedTime = new Date();
|
||||
}
|
||||
try {
|
||||
switch (msgType) {
|
||||
case KPM:
|
||||
parseKeypadMessage(message);
|
||||
break;
|
||||
case REL:
|
||||
case EXP:
|
||||
parseRelayOrExpanderMessage(msgType, message);
|
||||
break;
|
||||
case RFX:
|
||||
parseRFMessage(message);
|
||||
break;
|
||||
case LRR:
|
||||
parseLRRMessage(message);
|
||||
break;
|
||||
case VER:
|
||||
parseVersionMessage(message);
|
||||
break;
|
||||
case INVALID:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (MessageParseException e) {
|
||||
logger.warn("Error {} while parsing message {}. Please report bug.", e.getMessage(), message);
|
||||
}
|
||||
}
|
||||
if (message == null) {
|
||||
logger.info("End of input stream detected");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("I/O error while reading from stream: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception in reader thread", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
logger.debug("Message reader thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle keypad messages
|
||||
*
|
||||
* @param msg string containing incoming message payload
|
||||
* @throws MessageParseException
|
||||
*/
|
||||
private void parseKeypadMessage(String msg) throws MessageParseException {
|
||||
KeypadMessage kpMsg;
|
||||
|
||||
// Parse the message
|
||||
try {
|
||||
kpMsg = new KeypadMessage(msg);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MessageParseException(e.getMessage());
|
||||
}
|
||||
|
||||
if (kpMsg.panelClear()) {
|
||||
// the panel is clear, so we can assume that all contacts that we
|
||||
// have not heard from are open
|
||||
notifyChildHandlersPanelReady();
|
||||
}
|
||||
|
||||
notifyChildHandlers(kpMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle relay and expander messages. The REL and EXP messages have identical format.
|
||||
*
|
||||
* @param mt message type of incoming message
|
||||
* @param msg string containing incoming message payload
|
||||
* @throws MessageParseException
|
||||
*/
|
||||
private void parseRelayOrExpanderMessage(ADMsgType mt, String msg) throws MessageParseException {
|
||||
// mt is unused at the moment
|
||||
EXPMessage expMsg;
|
||||
|
||||
try {
|
||||
expMsg = new EXPMessage(msg);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MessageParseException(e.getMessage());
|
||||
}
|
||||
|
||||
notifyChildHandlers(expMsg);
|
||||
|
||||
AlarmDecoderDiscoveryService ds = discoveryService;
|
||||
if (discovery && ds != null) {
|
||||
ds.processZone(expMsg.address, expMsg.channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle RFX messages.
|
||||
*
|
||||
* @param msg string containing incoming message payload
|
||||
* @throws MessageParseException
|
||||
*/
|
||||
private void parseRFMessage(String msg) throws MessageParseException {
|
||||
RFXMessage rfxMsg;
|
||||
|
||||
try {
|
||||
rfxMsg = new RFXMessage(msg);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MessageParseException(e.getMessage());
|
||||
}
|
||||
|
||||
notifyChildHandlers(rfxMsg);
|
||||
|
||||
AlarmDecoderDiscoveryService ds = discoveryService;
|
||||
if (discovery && ds != null) {
|
||||
ds.processRFZone(rfxMsg.serial);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle LRR messages.
|
||||
*
|
||||
* @param msg string containing incoming message payload
|
||||
* @throws MessageParseException
|
||||
*/
|
||||
private void parseLRRMessage(String msg) throws MessageParseException {
|
||||
LRRMessage lrrMsg;
|
||||
|
||||
// Parse the message
|
||||
try {
|
||||
lrrMsg = new LRRMessage(msg);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MessageParseException(e.getMessage());
|
||||
}
|
||||
|
||||
notifyChildHandlers(lrrMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle version (VER) message. This just updates bridge properties.
|
||||
*
|
||||
* @param msg string containing incoming message payload
|
||||
* @throws MessageParseException
|
||||
*/
|
||||
private void parseVersionMessage(String msg) throws MessageParseException {
|
||||
VersionMessage verMsg;
|
||||
|
||||
try {
|
||||
verMsg = new VersionMessage(msg);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MessageParseException(e.getMessage());
|
||||
}
|
||||
|
||||
logger.trace("Processing version message sn:{} ver:{} cap:{}", verMsg.serial, verMsg.version,
|
||||
verMsg.capabilities);
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_SERIALNUM, verMsg.serial);
|
||||
properties.put(PROPERTY_VERSION, verMsg.version);
|
||||
properties.put(PROPERTY_CAPABILITIES, verMsg.capabilities);
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify appropriate child thing handlers of an AD message by calling their handleUpdate() methods.
|
||||
*
|
||||
* @param msg message to forward to child handler(s)
|
||||
*/
|
||||
private void notifyChildHandlers(ADMessage msg) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
ADThingHandler handler = (ADThingHandler) thing.getHandler();
|
||||
//@formatter:off
|
||||
if (handler != null && ((handler instanceof ZoneHandler && msg instanceof EXPMessage) ||
|
||||
(handler instanceof RFZoneHandler && msg instanceof RFXMessage) ||
|
||||
(handler instanceof KeypadHandler && msg instanceof KeypadMessage) ||
|
||||
(handler instanceof LRRHandler && msg instanceof LRRMessage))) {
|
||||
handler.handleUpdate(msg);
|
||||
}
|
||||
//@formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify child thing handlers that the alarm panel is in the ready state. Since there is no way to poll, all
|
||||
* contact channels are initialized into the UNDEF state. This method is called when there is reason to assume that
|
||||
* there are no faulted zones, because the alarm panel is in state READY. Zone handlers that have not yet received
|
||||
* updates can then set their contact states to CLOSED. Only executes the first time panel is ready after bridge
|
||||
* connect/reconnect.
|
||||
*/
|
||||
private void notifyChildHandlersPanelReady() {
|
||||
if (!panelReadyReceived) {
|
||||
panelReadyReceived = true;
|
||||
logger.trace("Notifying child handlers that panel is in ready state");
|
||||
|
||||
// Notify child zone handlers by calling notifyPanelReady() for each
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
ADThingHandler handler = (ADThingHandler) thing.getHandler();
|
||||
if (handler != null) {
|
||||
handler.notifyPanelReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown by message parsing code when it encounters a malformed message
|
||||
*/
|
||||
private static class MessageParseException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MessageParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADCommand;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ADThingHandler} is the abstract base class for all AD thing handlers.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ADThingHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ADThingHandler.class);
|
||||
protected final AtomicBoolean firstUpdateReceived = new AtomicBoolean(false);
|
||||
|
||||
public ADThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize device state and set status for handler. Should be called at the end of initialize(). Also called by
|
||||
* bridgeStatusChanged() when bridge status changes from OFFLINE to ONLINE. Calls initChannelState() to initialize
|
||||
* channels if setting status to ONLINE.
|
||||
*/
|
||||
protected void initDeviceState() {
|
||||
logger.trace("Initializing device state");
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
initChannelState();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize channel states if necessary
|
||||
*/
|
||||
public abstract void initChannelState();
|
||||
|
||||
/**
|
||||
* Notify handler that panel is in ready state so that any un-updated contact channels can be set to default
|
||||
* (closed).
|
||||
*/
|
||||
public abstract void notifyPanelReady();
|
||||
|
||||
/**
|
||||
* Notify handler of a message from the AD via the bridge
|
||||
*
|
||||
* @param msg The ADMessage to handle
|
||||
*/
|
||||
public abstract void handleUpdate(ADMessage msg);
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
|
||||
logger.debug("Bridge status changed to {} for AD handler", bridgeStatus);
|
||||
|
||||
if (bridgeStatus == ThingStatus.ONLINE
|
||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||
initDeviceState();
|
||||
|
||||
} else if (bridgeStatus == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command via the bridge
|
||||
*
|
||||
* @param command command to send
|
||||
*/
|
||||
protected void sendCommand(ADCommand command) {
|
||||
Bridge bridge = getBridge();
|
||||
ADBridgeHandler bridgeHandler = bridge == null ? null : (ADBridgeHandler) bridge.getHandler();
|
||||
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
} else {
|
||||
bridgeHandler.sendADCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
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.alarmdecoder.internal.config.IPBridgeConfig;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating via TCP with the Nu Tech Alarm Decoder device.
|
||||
* Based on and including code from the original OH1 alarmdecoder binding.
|
||||
*
|
||||
* @author Bernd Pfrommer - Initial contribution (OH1 version)
|
||||
* @author Bob Adair - Re-factored into OH2 binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IPBridgeHandler extends ADBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IPBridgeHandler.class);
|
||||
|
||||
private IPBridgeConfig config = new IPBridgeConfig();
|
||||
|
||||
private @Nullable Socket socket = null;
|
||||
|
||||
public IPBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing IP bridge handler");
|
||||
config = getConfigAs(IPBridgeConfig.class);
|
||||
discovery = config.discovery;
|
||||
|
||||
if (config.hostname == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "hostname not configured");
|
||||
return;
|
||||
}
|
||||
if (config.tcpPort <= 0 || config.tcpPort > 65535) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "invalid port number configured");
|
||||
return;
|
||||
}
|
||||
|
||||
// set the thing status to UNKNOWN temporarily and let the background connect task decide the real status.
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
scheduler.submit(this::connect); // start the async connect task
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void connect() {
|
||||
disconnect(); // make sure we are disconnected
|
||||
writeException = false;
|
||||
try {
|
||||
Socket socket = new Socket(config.hostname, config.tcpPort);
|
||||
this.socket = socket;
|
||||
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), AD_CHARSET));
|
||||
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), AD_CHARSET));
|
||||
logger.debug("connected to {}:{}", config.hostname, config.tcpPort);
|
||||
panelReadyReceived = false;
|
||||
startMsgReader();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Start connection check job
|
||||
logger.debug("Scheduling connection check job with interval {} minutes.", config.reconnect);
|
||||
lastReceivedTime = new Date();
|
||||
connectionCheckJob = scheduler.scheduleWithFixedDelay(this::connectionCheck, config.reconnect,
|
||||
config.reconnect, TimeUnit.MINUTES);
|
||||
} catch (UnknownHostException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "unknown host");
|
||||
disconnect();
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
disconnect();
|
||||
scheduleConnectRetry(config.reconnect); // Possibly a retryable error. Try again later.
|
||||
}
|
||||
}
|
||||
|
||||
protected synchronized void connectionCheck() {
|
||||
logger.trace("Connection check job running");
|
||||
|
||||
Thread mrThread = msgReaderThread;
|
||||
if (mrThread != null && !mrThread.isAlive()) {
|
||||
logger.debug("Reader thread has exited abnormally. Restarting.");
|
||||
scheduler.submit(this::connect);
|
||||
} else if (writeException) {
|
||||
logger.debug("Write exception encountered. Resetting connection.");
|
||||
scheduler.submit(this::connect);
|
||||
} else {
|
||||
Date now = new Date();
|
||||
Date last = lastReceivedTime;
|
||||
if (last != null && config.timeout > 0
|
||||
&& ((last.getTime() + (config.timeout * 60 * 1000)) < now.getTime())) {
|
||||
logger.warn("Last valid message received at {}. Resetting connection.", last);
|
||||
scheduler.submit(this::connect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void disconnect() {
|
||||
logger.trace("Disconnecting");
|
||||
// stop scheduled connection check and retry jobs
|
||||
ScheduledFuture<?> crJob = connectRetryJob;
|
||||
if (crJob != null) {
|
||||
// use cancel(false) so we don't kill ourselves when connect retry job calls disconnect()
|
||||
crJob.cancel(false);
|
||||
connectRetryJob = null;
|
||||
}
|
||||
ScheduledFuture<?> ccJob = connectionCheckJob;
|
||||
if (ccJob != null) {
|
||||
// use cancel(false) so we don't kill ourselves when reconnect job calls disconnect()
|
||||
ccJob.cancel(false);
|
||||
connectionCheckJob = null;
|
||||
}
|
||||
|
||||
// Must close the socket first so the message reader thread will exit properly.
|
||||
// The BufferedReader.readLine() call used in readerThread() is not interruptable.
|
||||
Socket s = socket;
|
||||
if (s != null) {
|
||||
try {
|
||||
s.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("error closing socket: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
socket = null;
|
||||
|
||||
stopMsgReader();
|
||||
|
||||
try {
|
||||
BufferedWriter bw = writer;
|
||||
if (bw != null) {
|
||||
bw.close();
|
||||
}
|
||||
BufferedReader br = reader;
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("error closing reader/writer: {}", e.getMessage());
|
||||
}
|
||||
writer = null;
|
||||
reader = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.KeypadConfig;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADAddress;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADCommand;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.IntCommandMap;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.KeypadMessage;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KeypadHandler} is responsible for handling keypad messages.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
* @author Bill Forsyth - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KeypadHandler extends ADThingHandler {
|
||||
|
||||
private static final Pattern VALID_COMMAND_PATTERN = Pattern.compile(ADCommand.KEYPAD_COMMAND_REGEX);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeypadHandler.class);
|
||||
|
||||
private KeypadConfig config = new KeypadConfig();
|
||||
private boolean singleAddress;
|
||||
private int sendingAddress;
|
||||
private @Nullable IntCommandMap intCommandMap;
|
||||
private @Nullable KeypadMessage previousMessage;
|
||||
private long addressMaskLong = 0;
|
||||
|
||||
public KeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(KeypadConfig.class);
|
||||
|
||||
try {
|
||||
addressMaskLong = Long.parseLong(config.addressMask, 16);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Number format exception parsing addressMask parameter: {}", e.getMessage());
|
||||
addressMaskLong = -1;
|
||||
}
|
||||
|
||||
if (addressMaskLong < 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid addressMask setting");
|
||||
return;
|
||||
}
|
||||
// If 1 and only 1 device is set in the addressMask parameter, use that device number as the sending address
|
||||
singleAddress = ADAddress.singleAddress(addressMaskLong);
|
||||
if (singleAddress) {
|
||||
ADAddress device = ADAddress.getDevice(addressMaskLong);
|
||||
if (device != null) {
|
||||
sendingAddress = device.deviceNum();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
intCommandMap = new IntCommandMap(config.commandMapping);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Invalid commmandMapping parameter supplied. Error: {}.", e.getMessage());
|
||||
intCommandMap = null;
|
||||
}
|
||||
|
||||
logger.debug("Keypad handler initializing for address mask {}", config.addressMask);
|
||||
|
||||
initDeviceState();
|
||||
|
||||
logger.trace("Keypad handler finished initializing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannelState() {
|
||||
previousMessage = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPanelReady() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
IntCommandMap intCommandMap = this.intCommandMap;
|
||||
|
||||
if (channelUID.getId().equals(CHANNEL_KP_COMMAND)) {
|
||||
if (command instanceof StringType) {
|
||||
String cmd = ((StringType) command).toString();
|
||||
handleKeypadCommand(cmd);
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_KP_INTCOMMAND)) {
|
||||
if (command instanceof Number) {
|
||||
int icmd = ((Number) command).intValue();
|
||||
if (intCommandMap != null) {
|
||||
String cmd = intCommandMap.getCommand(icmd);
|
||||
if (cmd != null) {
|
||||
handleKeypadCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeypadCommand(String command) {
|
||||
String cmd = command;
|
||||
if (cmd.length() > 0) {
|
||||
if (!config.sendCommands) {
|
||||
logger.info("Sending keypad commands is disabled. Enable using the sendCommands keypad parameter.");
|
||||
return;
|
||||
}
|
||||
|
||||
// check that received command is valid
|
||||
Matcher matcher = VALID_COMMAND_PATTERN.matcher(cmd);
|
||||
if (!matcher.matches()) {
|
||||
logger.info("Invalid characters in command. Ignoring command: {}", cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace A-H in command string with special key strings
|
||||
cmd = cmd.replace("A", ADCommand.SPECIAL_KEY_1);
|
||||
cmd = cmd.replace("B", ADCommand.SPECIAL_KEY_2);
|
||||
cmd = cmd.replace("C", ADCommand.SPECIAL_KEY_3);
|
||||
cmd = cmd.replace("D", ADCommand.SPECIAL_KEY_4);
|
||||
cmd = cmd.replace("E", ADCommand.SPECIAL_KEY_5);
|
||||
cmd = cmd.replace("F", ADCommand.SPECIAL_KEY_6);
|
||||
cmd = cmd.replace("G", ADCommand.SPECIAL_KEY_7);
|
||||
cmd = cmd.replace("H", ADCommand.SPECIAL_KEY_8);
|
||||
|
||||
if (singleAddress) {
|
||||
sendCommand(ADCommand.addressedMessage(sendingAddress, cmd)); // Send from keypad address
|
||||
} else {
|
||||
sendCommand(new ADCommand(cmd)); // Send from AD address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ADMessage msg) {
|
||||
// This will ignore a received message unless it is a KeypadMessage and either this handler's address mask is 0
|
||||
// (all), the message's address mask is 0 (all), or any bits in this handler's address mask match bits set in
|
||||
// the message's address mask.
|
||||
if (!(msg instanceof KeypadMessage)) {
|
||||
return;
|
||||
}
|
||||
KeypadMessage kpMsg = (KeypadMessage) msg;
|
||||
|
||||
long msgAddressMask = kpMsg.getLongAddressMask();
|
||||
|
||||
if (!(((addressMaskLong & msgAddressMask) != 0) || addressMaskLong == 0 || msgAddressMask == 0)) {
|
||||
return;
|
||||
}
|
||||
logger.trace("Keypad handler for address mask {} received update: {}", config.addressMask, kpMsg);
|
||||
|
||||
if (kpMsg.equals(previousMessage)) {
|
||||
return; // ignore repeated messages
|
||||
}
|
||||
|
||||
if (config.sendStar) {
|
||||
if (kpMsg.alphaMessage.contains("Hit * for faults") || kpMsg.alphaMessage.contains("Press * to show faults")
|
||||
|| kpMsg.alphaMessage.contains("Press * Key")
|
||||
|| kpMsg.alphaMessage.contains("Press * to show faults")) {
|
||||
logger.debug("Sending * command to show faults.");
|
||||
if (singleAddress) {
|
||||
sendCommand(ADCommand.addressedMessage(sendingAddress, "*")); // Send from keypad address
|
||||
} else {
|
||||
sendCommand(new ADCommand("*")); // send from AD address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateState(CHANNEL_KP_ZONE, new DecimalType(kpMsg.getZone()));
|
||||
updateState(CHANNEL_KP_TEXT, new StringType(kpMsg.alphaMessage));
|
||||
|
||||
updateState(CHANNEL_KP_READY, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_READY)));
|
||||
updateState(CHANNEL_KP_ARMEDAWAY, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_ARMEDAWAY)));
|
||||
updateState(CHANNEL_KP_ARMEDHOME, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_ARMEDHOME)));
|
||||
updateState(CHANNEL_KP_BACKLIGHT, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_BACKLIGHT)));
|
||||
updateState(CHANNEL_KP_PRORGAM, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_PRORGAM)));
|
||||
|
||||
updateState(CHANNEL_KP_BEEPS, new DecimalType(kpMsg.nbeeps));
|
||||
|
||||
updateState(CHANNEL_KP_BYPASSED, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_BYPASSED)));
|
||||
updateState(CHANNEL_KP_ACPOWER, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_ACPOWER)));
|
||||
updateState(CHANNEL_KP_CHIME, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_CHIME)));
|
||||
updateState(CHANNEL_KP_ALARMOCCURRED, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_ALARMOCCURRED)));
|
||||
updateState(CHANNEL_KP_ALARM, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_ALARM)));
|
||||
updateState(CHANNEL_KP_LOWBAT, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_LOWBAT)));
|
||||
updateState(CHANNEL_KP_DELAYOFF, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_DELAYOFF)));
|
||||
updateState(CHANNEL_KP_FIRE, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_FIRE)));
|
||||
updateState(CHANNEL_KP_SYSFAULT, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_SYSFAULT)));
|
||||
updateState(CHANNEL_KP_PERIMETER, OnOffType.from(kpMsg.getStatus(KeypadMessage.BIT_PERIMETER)));
|
||||
|
||||
previousMessage = kpMsg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.LRRConfig;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.LRRMessage;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LRRHandler} is responsible for handling long range radio (LRR) messages.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
* @author Bill Forsyth - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LRRHandler extends ADThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LRRHandler.class);
|
||||
|
||||
private LRRConfig config = new LRRConfig();
|
||||
|
||||
public LRRHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(LRRConfig.class);
|
||||
|
||||
if (config.partition < 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
return;
|
||||
}
|
||||
logger.debug("LRR handler initializing for partition {}", config.partition);
|
||||
|
||||
initDeviceState();
|
||||
|
||||
logger.trace("LRR handler finished initializing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannelState() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPanelReady() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// All channels are read-only, so ignore all commands.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ADMessage msg) {
|
||||
if (!(msg instanceof LRRMessage)) {
|
||||
return;
|
||||
}
|
||||
LRRMessage lrrMsg = (LRRMessage) msg;
|
||||
|
||||
if (config.partition == lrrMsg.partition || config.partition == 0 || lrrMsg.partition == 0) {
|
||||
logger.trace("LRR handler for partition {} received update: {}", config.partition, msg);
|
||||
updateState(CHANNEL_LRR_PARTITION, new DecimalType(lrrMsg.partition));
|
||||
updateState(CHANNEL_LRR_EVENTDATA, new DecimalType(lrrMsg.eventData));
|
||||
updateState(CHANNEL_LRR_CIDMESSAGE, new StringType(lrrMsg.cidMessage));
|
||||
updateState(CHANNEL_LRR_REPORTCODE, new StringType(lrrMsg.reportCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.RFZoneConfig;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.RFXMessage;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RFZoneHandler} is responsible for handling wired zones (i.e. RFX messages).
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
* @author Bill Forsyth - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RFZoneHandler extends ADThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RFZoneHandler.class);
|
||||
|
||||
private RFZoneConfig config = new RFZoneConfig();
|
||||
|
||||
public RFZoneHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(RFZoneConfig.class);
|
||||
|
||||
if (config.serial < 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid serial setting");
|
||||
return;
|
||||
}
|
||||
logger.debug("RF Zone handler initializing for serial {}", config.serial);
|
||||
|
||||
initDeviceState();
|
||||
|
||||
logger.trace("RF Zone handler finished initializing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact channel states to "UNDEF" at init time. The real states will be set either when the first message
|
||||
* arrives for the zone, or they will be set to "CLOSED" the first time the panel goes into the "READY" state.
|
||||
*/
|
||||
@Override
|
||||
public void initChannelState() {
|
||||
UnDefType state = UnDefType.UNDEF;
|
||||
updateState(CHANNEL_RF_LOWBAT, state);
|
||||
updateState(CHANNEL_RF_SUPERVISION, state);
|
||||
updateState(CHANNEL_RF_LOOP1, state);
|
||||
updateState(CHANNEL_RF_LOOP2, state);
|
||||
updateState(CHANNEL_RF_LOOP3, state);
|
||||
updateState(CHANNEL_RF_LOOP4, state);
|
||||
firstUpdateReceived.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPanelReady() {
|
||||
logger.trace("RF Zone handler for {} received panel ready notification.", config.serial);
|
||||
if (firstUpdateReceived.compareAndSet(false, true)) {
|
||||
updateState(CHANNEL_RF_LOOP1, OpenClosedType.CLOSED);
|
||||
updateState(CHANNEL_RF_LOOP2, OpenClosedType.CLOSED);
|
||||
updateState(CHANNEL_RF_LOOP3, OpenClosedType.CLOSED);
|
||||
updateState(CHANNEL_RF_LOOP4, OpenClosedType.CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Does not accept any commands
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ADMessage msg) {
|
||||
if (!(msg instanceof RFXMessage)) {
|
||||
return;
|
||||
}
|
||||
RFXMessage rfxMsg = (RFXMessage) msg;
|
||||
|
||||
if (config.serial == rfxMsg.serial) {
|
||||
logger.trace("RF Zone handler for serial {} received update: {}", config.serial, rfxMsg.data);
|
||||
firstUpdateReceived.set(true);
|
||||
|
||||
updateState(CHANNEL_RF_LOWBAT, (rfxMsg.data & RFXMessage.BIT_LOWBAT) == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
updateState(CHANNEL_RF_SUPERVISION,
|
||||
(rfxMsg.data & RFXMessage.BIT_SUPER) == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
|
||||
updateState(CHANNEL_RF_LOOP1,
|
||||
(rfxMsg.data & RFXMessage.BIT_LOOP1) == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(CHANNEL_RF_LOOP2,
|
||||
(rfxMsg.data & RFXMessage.BIT_LOOP2) == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(CHANNEL_RF_LOOP3,
|
||||
(rfxMsg.data & RFXMessage.BIT_LOOP3) == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(CHANNEL_RF_LOOP4,
|
||||
(rfxMsg.data & RFXMessage.BIT_LOOP4) == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.SerialBridgeConfig;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating via a serial port with the Nu Tech Alarm Decoder device.
|
||||
* Based on code from the original OH1 alarmdecoder binding. Some OHC serial transport code taken from the Zigbee
|
||||
* binding.
|
||||
*
|
||||
* @author Bernd Pfrommer - Initial contribution (OH1 version)
|
||||
* @author Bob Adair - Re-factored into OH2 binding and rewrote to use OHC serial transport.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SerialBridgeHandler extends ADBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SerialBridgeHandler.class);
|
||||
|
||||
private SerialBridgeConfig config = new SerialBridgeConfig();
|
||||
private final SerialPortManager serialPortManager;
|
||||
private @NonNullByDefault({}) SerialPortIdentifier portIdentifier;
|
||||
private @Nullable SerialPort serialPort;
|
||||
private int serialPortSpeed = 115200;
|
||||
|
||||
public SerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
super(bridge);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing serial bridge handler");
|
||||
config = getConfigAs(SerialBridgeConfig.class);
|
||||
discovery = config.discovery;
|
||||
|
||||
if (config.serialPort.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no serial port configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.bitrate > 0) {
|
||||
serialPortSpeed = config.bitrate;
|
||||
}
|
||||
|
||||
portIdentifier = serialPortManager.getIdentifier(config.serialPort);
|
||||
if (portIdentifier == null) {
|
||||
logger.debug("Serial Error: Port {} does not exist.", config.serialPort);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Configured serial port does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
logger.trace("Finished initializing serial bridge handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void connect() {
|
||||
disconnect(); // make sure we are disconnected
|
||||
try {
|
||||
SerialPort serialPort = portIdentifier.open("org.openhab.binding.alarmdecoder", 100);
|
||||
serialPort.setSerialPortParams(serialPortSpeed, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
|
||||
SerialPort.PARITY_NONE);
|
||||
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT);
|
||||
// Note: The V1 code called disableReceiveFraming() and disableReceiveThreshold() here
|
||||
|
||||
this.serialPort = serialPort;
|
||||
reader = new BufferedReader(new InputStreamReader(serialPort.getInputStream(), AD_CHARSET));
|
||||
writer = new BufferedWriter(new OutputStreamWriter(serialPort.getOutputStream(), AD_CHARSET));
|
||||
|
||||
logger.debug("connected to serial port: {}", config.serialPort);
|
||||
panelReadyReceived = false;
|
||||
startMsgReader();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (PortInUseException e) {
|
||||
logger.debug("Cannot open serial port: {}, it is already in use", config.serialPort);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Serial port already in use");
|
||||
} catch (UnsupportedCommOperationException | IOException | IllegalStateException e) {
|
||||
logger.debug("Error connecting to serial port: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void disconnect() {
|
||||
logger.trace("Disconnecting");
|
||||
SerialPort sp = serialPort;
|
||||
if (sp != null) {
|
||||
logger.trace("Closing serial port");
|
||||
sp.close();
|
||||
serialPort = null;
|
||||
}
|
||||
|
||||
stopMsgReader();
|
||||
|
||||
BufferedReader br = reader;
|
||||
if (br != null) {
|
||||
logger.trace("Closing reader");
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
logger.info("IO Exception closing reader: {}", e.getMessage());
|
||||
} finally {
|
||||
reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
BufferedWriter bw = writer;
|
||||
if (bw != null) {
|
||||
logger.trace("Closing writer");
|
||||
try {
|
||||
bw.close();
|
||||
} catch (IOException e) {
|
||||
logger.info("IO Exception closing writer: {}", e.getMessage());
|
||||
} finally {
|
||||
writer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.VZoneConfig;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADCommand;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VZoneHandler} is responsible for sending state commands to virtual zones.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VZoneHandler extends ADThingHandler {
|
||||
|
||||
public static final String CMD_OPEN = "OPEN";
|
||||
public static final String CMD_CLOSED = "CLOSED";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VZoneHandler.class);
|
||||
|
||||
private VZoneConfig config = new VZoneConfig();
|
||||
|
||||
public VZoneHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(VZoneConfig.class);
|
||||
|
||||
if (config.address < 0 || config.address > 99) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid address setting");
|
||||
return;
|
||||
}
|
||||
logger.debug("Virtual zone handler initializing for address {}", config.address);
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannelState() {
|
||||
UnDefType state = UnDefType.UNDEF;
|
||||
updateState(CHANNEL_STATE, state);
|
||||
firstUpdateReceived.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPanelReady() {
|
||||
logger.trace("Virtual zone handler for {} received panel ready notification.", config.address);
|
||||
if (firstUpdateReceived.compareAndSet(false, true)) {
|
||||
updateState(CHANNEL_STATE, OnOffType.ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
||||
if (command instanceof StringType) {
|
||||
String cmd = ((StringType) command).toString();
|
||||
if (CMD_OPEN.equalsIgnoreCase(cmd)) {
|
||||
sendCommand(ADCommand.setZone(config.address, ADCommand.ZONE_OPEN));
|
||||
setChannelState(OnOffType.OFF);
|
||||
} else if (CMD_CLOSED.equalsIgnoreCase(cmd)) {
|
||||
sendCommand(ADCommand.setZone(config.address, ADCommand.ZONE_CLOSED));
|
||||
setChannelState(OnOffType.ON);
|
||||
} else {
|
||||
logger.debug("Virtual zone handler {} received invalid command: {}", config.address, cmd);
|
||||
}
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_STATE)) {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.OFF) {
|
||||
sendCommand(ADCommand.setZone(config.address, ADCommand.ZONE_OPEN));
|
||||
setChannelState(OnOffType.OFF);
|
||||
} else if (command == OnOffType.ON) {
|
||||
sendCommand(ADCommand.setZone(config.address, ADCommand.ZONE_CLOSED));
|
||||
setChannelState(OnOffType.ON);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setChannelState(OnOffType state) {
|
||||
updateState(CHANNEL_STATE, state);
|
||||
firstUpdateReceived.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ADMessage msg) {
|
||||
// There can be no update requests
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.handler;
|
||||
|
||||
import static org.openhab.binding.alarmdecoder.internal.AlarmDecoderBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.alarmdecoder.internal.config.ZoneConfig;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.ADMessage;
|
||||
import org.openhab.binding.alarmdecoder.internal.protocol.EXPMessage;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ZoneHandler} is responsible for handling wired zones (i.e. REL & EXP messages).
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
* @author Bill Forsyth - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneHandler extends ADThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ZoneHandler.class);
|
||||
|
||||
private ZoneConfig config = new ZoneConfig();
|
||||
|
||||
public ZoneHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/** Construct zone id from address and channel */
|
||||
public static final String zoneID(int address, int channel) {
|
||||
return String.format("%d-%d", address, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(ZoneConfig.class);
|
||||
|
||||
if (config.address < 0 || config.channel < 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid address/channel setting");
|
||||
return;
|
||||
}
|
||||
logger.debug("Zone handler initializing for address {} channel {}", config.address, config.channel);
|
||||
|
||||
String id = zoneID(config.address, config.channel);
|
||||
updateProperty(PROPERTY_ID, id); // set representation property used by discovery
|
||||
|
||||
initDeviceState();
|
||||
logger.trace("Zone handler finished initializing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact channel state to "UNDEF" at init time. The real state will be set either when the first message
|
||||
* arrives for the zone, or it should be set to "CLOSED" the first time the panel goes into the "READY" state.
|
||||
*/
|
||||
@Override
|
||||
public void initChannelState() {
|
||||
UnDefType state = UnDefType.UNDEF;
|
||||
updateState(CHANNEL_CONTACT, state);
|
||||
firstUpdateReceived.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyPanelReady() {
|
||||
logger.trace("Zone handler for {},{} received panel ready notification.", config.address, config.channel);
|
||||
if (firstUpdateReceived.compareAndSet(false, true)) {
|
||||
updateState(CHANNEL_CONTACT, OpenClosedType.CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// All channels are read-only, so ignore all commands.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ADMessage msg) {
|
||||
if (!(msg instanceof EXPMessage)) {
|
||||
return;
|
||||
}
|
||||
EXPMessage expMsg = (EXPMessage) msg;
|
||||
|
||||
if (config.address == expMsg.address && config.channel == expMsg.channel) {
|
||||
logger.trace("Zone handler for {},{} received update: {}", config.address, config.channel, expMsg.data);
|
||||
|
||||
firstUpdateReceived.set(true);
|
||||
OpenClosedType state = (expMsg.data == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
|
||||
updateState(CHANNEL_CONTACT, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Defines keypad device addresses used in an AD keypad address mask.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ADAddress {
|
||||
KEYPAD0(0x01000000, 0),
|
||||
KEYPAD1(0x02000000, 1),
|
||||
KEYPAD2(0x04000000, 2),
|
||||
KEYPAD3(0x08000000, 3),
|
||||
KEYPAD4(0x10000000, 4),
|
||||
KEYPAD5(0x20000000, 5),
|
||||
KEYPAD6(0x40000000, 6),
|
||||
KEYPAD7(0x80000000, 7),
|
||||
|
||||
KEYPAD8(0x00010000, 8),
|
||||
KEYPAD9(0x00020000, 9),
|
||||
KEYPAD10(0x00040000, 10),
|
||||
KEYPAD11(0x00080000, 11),
|
||||
KEYPAD12(0x00100000, 12),
|
||||
KEYPAD13(0x00200000, 13),
|
||||
KEYPAD14(0x00400000, 14),
|
||||
KEYPAD15(0x00800000, 15),
|
||||
|
||||
KEYPAD16(0x00000100, 16),
|
||||
KEYPAD17(0x00000200, 17),
|
||||
KEYPAD18(0x00000400, 18),
|
||||
KEYPAD19(0x00000800, 19),
|
||||
KEYPAD20(0x00001000, 20),
|
||||
KEYPAD21(0x00002000, 21),
|
||||
KEYPAD22(0x00004000, 22),
|
||||
KEYPAD23(0x00008000, 23),
|
||||
|
||||
KEYPAD24(0x00000001, 24),
|
||||
KEYPAD25(0x00000002, 25),
|
||||
KEYPAD26(0x00000004, 26),
|
||||
KEYPAD27(0x00000008, 27),
|
||||
KEYPAD28(0x00000010, 28),
|
||||
KEYPAD29(0x00000020, 29),
|
||||
KEYPAD30(0x00000040, 30),
|
||||
KEYPAD31(0x00000080, 31);
|
||||
|
||||
private final long mask;
|
||||
private final int device;
|
||||
|
||||
ADAddress(long mask, int device) {
|
||||
this.mask = mask;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
/** Returns the device bit mask **/
|
||||
public long mask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/** Returns the device number (0-31) **/
|
||||
public int deviceNum() {
|
||||
return device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first device address found in addressMask or null if none are found
|
||||
*
|
||||
* @param addressMask
|
||||
*/
|
||||
public static @Nullable ADAddress getDevice(long addressMask) {
|
||||
for (ADAddress address : ADAddress.values()) {
|
||||
if ((address.mask() & addressMask) != 0) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Collection of the device addresses found in addressMask.
|
||||
* Returns an empty collection if none are found.
|
||||
*
|
||||
* @param addressMask
|
||||
*/
|
||||
public static Collection<ADAddress> getDevices(long addressMask) {
|
||||
Collection<ADAddress> addressCollection = new ArrayList<>();
|
||||
for (ADAddress address : ADAddress.values()) {
|
||||
if ((address.mask() & addressMask) != 0) {
|
||||
addressCollection.add(address);
|
||||
}
|
||||
}
|
||||
return addressCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if 1 and only 1 address bit is set in addressMask
|
||||
*/
|
||||
public static boolean singleAddress(long addressMask) {
|
||||
return (Long.bitCount(addressMask) == 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ADCCommand} class represents an alarm decoder command, and contains the static methods and definitions
|
||||
* used to construct one. Not all supported AD commands are necessarily used by the current binding.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ADCommand {
|
||||
|
||||
public static final String SPECIAL_KEY_1 = "\u0001\u0001\u0001";
|
||||
public static final String SPECIAL_KEY_2 = "\u0002\u0002\u0002";
|
||||
public static final String SPECIAL_KEY_3 = "\u0003\u0003\u0003";
|
||||
public static final String SPECIAL_KEY_4 = "\u0004\u0004\u0004";
|
||||
public static final String SPECIAL_KEY_5 = "\u0005\u0005\u0005";
|
||||
public static final String SPECIAL_KEY_6 = "\u0006\u0006\u0006";
|
||||
public static final String SPECIAL_KEY_7 = "\u0007\u0007\u0007";
|
||||
public static final String SPECIAL_KEY_8 = "\u0008\u0008\u0008";
|
||||
|
||||
public static final int ZONE_OPEN = 1;
|
||||
public static final int ZONE_CLOSED = 0;
|
||||
|
||||
// public static final String KEYPAD_COMMAND_CHARACTERS = "0123456789*#<>";
|
||||
public static final String KEYPAD_COMMAND_REGEX = "^[0-9A-H*#<>]+$";
|
||||
|
||||
private static final String TERM = "\r\n";
|
||||
|
||||
private static final String COMMAND_REBOOT = "=";
|
||||
private static final String COMMAND_CONFIG = "C";
|
||||
private static final String COMMAND_ZONE = "L";
|
||||
private static final String COMMAND_ERROR = "E";
|
||||
private static final String COMMAND_VERSION = "V";
|
||||
private static final String COMMAND_ADDRMSG = "K";
|
||||
private static final String COMMAND_ACKCRC = "R";
|
||||
|
||||
public final String command;
|
||||
|
||||
public ADCommand(String command) {
|
||||
this.command = command + TERM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public static ADCommand reboot() {
|
||||
return new ADCommand(COMMAND_REBOOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD configuration command. If configParam is null, a query configuration command will be created.
|
||||
* If configParam consists of one or more NAME=value pairs (separated by '&' characters), a set configuration
|
||||
* command will be created. The validity of configParam is not checked.
|
||||
*
|
||||
* @param configParam String containing parameters to set or null
|
||||
* @return ADCommand object containing the constructed command
|
||||
*/
|
||||
public static ADCommand config(@Nullable String configParam) {
|
||||
if (configParam == null) {
|
||||
return new ADCommand(COMMAND_CONFIG);
|
||||
} else {
|
||||
return new ADCommand(COMMAND_CONFIG + configParam);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD command to set the state of an emulated zone.
|
||||
*
|
||||
* @param zone The emulated zone number (0-99) for the command.
|
||||
* @param state The new state (0 or 1) for the emulated zone.
|
||||
* @return ADCommand object containing the constructed command
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public static ADCommand setZone(int zone, int state) throws IllegalArgumentException {
|
||||
if (zone < 0 || zone > 99 || state < 0 || state > 1) {
|
||||
throw new IllegalArgumentException("Invalid parameter(s)");
|
||||
}
|
||||
return new ADCommand(String.format("%s%02d%d", COMMAND_ZONE, zone, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD command to get and clear the error counters.
|
||||
*
|
||||
* @return ADCommand object containing the constructed command
|
||||
*/
|
||||
public static ADCommand getErrors() {
|
||||
return new ADCommand(COMMAND_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD command to request a version info message.
|
||||
*
|
||||
* @return ADCommand object containing the constructed command
|
||||
*/
|
||||
public static ADCommand getVersion() {
|
||||
return new ADCommand(COMMAND_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD command to send a message from a specific partition or keypad address, rather than from the Alarm
|
||||
* Decoder unit's configured address.
|
||||
*
|
||||
* @param address The keypad address or partition (0-99) from which to send the command
|
||||
* @param message A String containing the message to send. Length must be > 0.
|
||||
* @return ADCommand object containing the constructed command
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public static ADCommand addressedMessage(int address, String message) throws IllegalArgumentException {
|
||||
if (address < 0 || address > 99 || message.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid parameter(s)");
|
||||
}
|
||||
return new ADCommand(String.format("%s%02d%s", COMMAND_ADDRMSG, address, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AD command to acknowledge that a received CRC message was valid.
|
||||
*
|
||||
* @return ADCommand object containing the constructed command
|
||||
*/
|
||||
public static ADCommand ackCRC() {
|
||||
return new ADCommand(COMMAND_ACKCRC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Superclass for all Alarm Decoder protocol message types.
|
||||
* Includes code from the original OH1 alarmdecoder binding by Bernd Pfrommer.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ADMessage {
|
||||
|
||||
protected static final Pattern SPLIT_REGEX = Pattern.compile("[^\\,\"]+|\"[^\"]*\"");
|
||||
|
||||
/** string containing the original unparsed message */
|
||||
public final String message;
|
||||
|
||||
public ADMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/** Utility routine to split an AD message into its component parts */
|
||||
protected static List<String> splitMsg(String msg) {
|
||||
List<String> l = new ArrayList<String>();
|
||||
Matcher regexMatcher = SPLIT_REGEX.matcher(msg);
|
||||
while (regexMatcher.find()) {
|
||||
l.add(regexMatcher.group());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The various message types that come from the ad2usb/ad2pi interface
|
||||
*
|
||||
* @author Bernd Pfrommer - Initial contribution (OH1)
|
||||
* @author Bob Adair - Re-factored and removed methods unused in OH2 binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ADMsgType {
|
||||
EXP, // zone expander message
|
||||
KPM, // keypad message
|
||||
LRR, // long range radio message
|
||||
REL, // relay message
|
||||
RFX, // wireless message
|
||||
VER, // version message
|
||||
INVALID; // invalid message
|
||||
|
||||
/** hash map from protocol message heading to type */
|
||||
private static HashMap<String, @Nullable ADMsgType> startToMsgType = new HashMap<>();
|
||||
|
||||
static {
|
||||
startToMsgType.put("!REL", ADMsgType.REL);
|
||||
startToMsgType.put("!SER", ADMsgType.INVALID);
|
||||
startToMsgType.put("!RFX", ADMsgType.RFX);
|
||||
startToMsgType.put("!EXP", ADMsgType.EXP);
|
||||
startToMsgType.put("!LRR", ADMsgType.LRR);
|
||||
startToMsgType.put("!VER", ADMsgType.VER);
|
||||
startToMsgType.put("!KPM", ADMsgType.KPM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract message type from message. Relies on static map startToMsgType.
|
||||
*
|
||||
* @param s message string
|
||||
* @return message type
|
||||
*/
|
||||
public static ADMsgType getMsgType(@Nullable String s) {
|
||||
if (s == null || s.length() < 4) {
|
||||
return ADMsgType.INVALID;
|
||||
}
|
||||
if (s.startsWith("[")) {
|
||||
return ADMsgType.KPM;
|
||||
}
|
||||
ADMsgType mt = startToMsgType.get(s.substring(0, 4));
|
||||
if (mt == null) {
|
||||
mt = ADMsgType.INVALID;
|
||||
}
|
||||
return mt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link EXPMessage} class represents a parsed zone (EXP or REL) message.
|
||||
* Based partly on code from the OH1 alarmdecoder binding by Bernd Pfrommer.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EXPMessage extends ADMessage {
|
||||
|
||||
// Example: !EXP:07,01,01
|
||||
// Example: !REL:12,01,01
|
||||
|
||||
/** Address number */
|
||||
public final int address;
|
||||
|
||||
/** Channel number */
|
||||
public final int channel;
|
||||
|
||||
/** Message data */
|
||||
public final int data;
|
||||
|
||||
public EXPMessage(String message) throws IllegalArgumentException {
|
||||
super(message);
|
||||
|
||||
String topLevel[] = message.split(":");
|
||||
if (topLevel.length != 2) {
|
||||
throw new IllegalArgumentException("Multiple colons found in EXP message");
|
||||
}
|
||||
|
||||
List<String> parts = splitMsg(topLevel[1]);
|
||||
|
||||
if (parts.size() != 3) {
|
||||
throw new IllegalArgumentException("Invalid number of parts in EXP message");
|
||||
}
|
||||
|
||||
try {
|
||||
address = Integer.parseInt(parts.get(0));
|
||||
channel = Integer.parseInt(parts.get(1));
|
||||
data = Integer.parseInt(parts.get(2));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("EXP message contains invalid number: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if ((data & ~0x1) != 0) {
|
||||
throw new IllegalArgumentException("zone status should only be 0 or 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IntCommandMap} class contains an integer to command map used by the keypad intcommand channel.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IntCommandMap {
|
||||
private static final Pattern VALID_COMMAND_PATTERN = Pattern.compile(ADCommand.KEYPAD_COMMAND_REGEX);
|
||||
|
||||
private final Map<Integer, String> commandMap;
|
||||
|
||||
public IntCommandMap(String mappingString) throws IllegalArgumentException {
|
||||
commandMap = new HashMap<>();
|
||||
|
||||
String mstring = mappingString.replace("POUND", "#");
|
||||
String[] elements = mstring.split(",");
|
||||
for (String element : elements) {
|
||||
String[] kvPair = element.split("=");
|
||||
if (kvPair.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid key-value pair format");
|
||||
}
|
||||
|
||||
Matcher matcher = VALID_COMMAND_PATTERN.matcher(kvPair[1]);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("Invalid command characters in mapping");
|
||||
}
|
||||
|
||||
try {
|
||||
commandMap.put(Integer.parseInt(kvPair[0]), kvPair[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Unable to parse integer in mapping", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCommand(int key) {
|
||||
return commandMap.get(key);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return commandMap.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link KeypadMessage} class represents a parsed keypad (KPM) message.
|
||||
* Based partly on code from the OH1 alarmdecoder binding by Bernd Pfrommer.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KeypadMessage extends ADMessage {
|
||||
|
||||
// Example: [00110011000000003A--],010,[f70700000010808c18020000000000],"ARMED ***STAY** ZONE BYPASSED "
|
||||
|
||||
public static final int BIT_READY = 17;
|
||||
public static final int BIT_ARMEDAWAY = 16;
|
||||
public static final int BIT_ARMEDHOME = 15;
|
||||
public static final int BIT_BACKLIGHT = 14;
|
||||
public static final int BIT_PRORGAM = 13;
|
||||
public static final int BIT_BYPASSED = 9;
|
||||
public static final int BIT_ACPOWER = 8;
|
||||
public static final int BIT_CHIME = 7;
|
||||
public static final int BIT_ALARMOCCURRED = 6;
|
||||
public static final int BIT_ALARM = 5;
|
||||
public static final int BIT_LOWBAT = 4;
|
||||
public static final int BIT_DELAYOFF = 3;
|
||||
public static final int BIT_FIRE = 2;
|
||||
public static final int BIT_SYSFAULT = 1;
|
||||
public static final int BIT_PERIMETER = 0;
|
||||
|
||||
public final String bitField;
|
||||
public final int numericCode;
|
||||
public final String rawData;
|
||||
public final String alphaMessage;
|
||||
public final int nbeeps;
|
||||
public final int status;
|
||||
|
||||
private final int upper;
|
||||
private final int lower;
|
||||
|
||||
public KeypadMessage(String message) throws IllegalArgumentException {
|
||||
super(message);
|
||||
List<String> parts = splitMsg(message.replace("!KPM:", ""));
|
||||
|
||||
if (parts.size() != 4) {
|
||||
throw new IllegalArgumentException("Invalid number of parts in keypad message");
|
||||
}
|
||||
if (parts.get(0).length() != 22) {
|
||||
throw new IllegalArgumentException("Invalid field length in keypad message");
|
||||
}
|
||||
|
||||
bitField = parts.get(0);
|
||||
rawData = parts.get(2);
|
||||
alphaMessage = parts.get(3).replaceAll("^\"|\"$", "");
|
||||
|
||||
try {
|
||||
int numeric = 0;
|
||||
try {
|
||||
numeric = Integer.parseInt(parts.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
numeric = Integer.parseInt(parts.get(1), 16);
|
||||
}
|
||||
this.numericCode = numeric;
|
||||
|
||||
this.upper = Integer.parseInt(parts.get(0).substring(1, 6), 2);
|
||||
this.nbeeps = Integer.parseInt(parts.get(0).substring(6, 7));
|
||||
this.lower = Integer.parseInt(parts.get(0).substring(7, 17), 2);
|
||||
this.status = ((upper & 0x1F) << 13) | ((nbeeps & 0x3) << 10) | lower;
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("keypad msg contains invalid number: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getZone() {
|
||||
return numericCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the keypad text
|
||||
*/
|
||||
public String getText() {
|
||||
return alphaMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an individual bit in the status field
|
||||
*
|
||||
* @param bit status field bit to test
|
||||
* @return true if bit is 1, false if bit is 0
|
||||
*/
|
||||
public boolean getStatus(int bit) {
|
||||
int v = (status >> bit) & 0x1;
|
||||
return (v == 0) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the READY status bit is set
|
||||
*/
|
||||
public boolean panelClear() {
|
||||
return ((status & (1 << BIT_READY)) != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the address mask of the message in hex
|
||||
*/
|
||||
public String getAddressMask() {
|
||||
return rawData.substring(3, 11);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a long containing the address mask of the message
|
||||
*/
|
||||
public long getLongAddressMask() {
|
||||
return Long.parseLong(getAddressMask(), 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two KeypadMessage objects
|
||||
*
|
||||
* @param obj KeypadMessage to compare against
|
||||
* @return true if messages are equal, false if obj is null, messages are not equal, or obj is not a KeypadMessage
|
||||
* object.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
} else if (this == obj) {
|
||||
return true;
|
||||
} else if (obj instanceof KeypadMessage) {
|
||||
KeypadMessage other = (KeypadMessage) obj;
|
||||
return this.message.equals(other.message);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link LRRMessage} class represents a parsed LRR message.
|
||||
* Based partly on code from the OH1 alarmdecoder binding by Bernd Pfrommer and Lucky Mallari.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LRRMessage extends ADMessage {
|
||||
|
||||
// Example: !LRR:012,1,CID_1441,ff
|
||||
// or: !LRR:000,1,ARM_AWAY
|
||||
|
||||
/** Event data contains user number or zone number for the event */
|
||||
public final String eventData;
|
||||
|
||||
/** Partition event applies to. 0 means all partitions. */
|
||||
public final int partition;
|
||||
|
||||
/** CID message for event as defined in SIA DC-05-1999.09 standard */
|
||||
public final String cidMessage;
|
||||
|
||||
/** Report code */
|
||||
public final String reportCode;
|
||||
|
||||
public LRRMessage(String message) throws IllegalArgumentException {
|
||||
super(message);
|
||||
|
||||
String topLevel[] = message.split(":");
|
||||
if (topLevel.length != 2) {
|
||||
throw new IllegalArgumentException("multiple colons in LRR message");
|
||||
}
|
||||
|
||||
List<String> parts = splitMsg(topLevel[1]);
|
||||
|
||||
// Apparently the 4th part of the LRR message may not be included depending on version
|
||||
if (parts.size() < 3 || parts.size() > 4) {
|
||||
throw new IllegalArgumentException("Invalid number of parts in LRR message");
|
||||
}
|
||||
|
||||
eventData = parts.get(0);
|
||||
cidMessage = parts.get(2);
|
||||
reportCode = parts.size() == 4 ? parts.get(3) : "";
|
||||
|
||||
try {
|
||||
partition = Integer.parseInt(parts.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("LRR msg contains invalid number: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RFXMessage} class represents a parsed RF zone (RFX) message.
|
||||
* Based partly on code from the OH1 alarmdecoder binding by Bernd Pfrommer.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RFXMessage extends ADMessage {
|
||||
|
||||
// Example: !RFX:0180036,80
|
||||
|
||||
public static final int BIT_LOWBAT = 0x02;
|
||||
public static final int BIT_SUPER = 0x04;
|
||||
public static final int BIT_LOOP3 = 0x10;
|
||||
public static final int BIT_LOOP2 = 0x20;
|
||||
public static final int BIT_LOOP4 = 0x40;
|
||||
public static final int BIT_LOOP1 = 0x80;
|
||||
|
||||
/** Address serial number */
|
||||
public final int serial;
|
||||
|
||||
/** Message data */
|
||||
public final int data;
|
||||
|
||||
public RFXMessage(String message) throws IllegalArgumentException {
|
||||
super(message);
|
||||
|
||||
String topLevel[] = message.split(":");
|
||||
if (topLevel.length != 2) {
|
||||
throw new IllegalArgumentException("Multiple colons found in RFX message");
|
||||
}
|
||||
|
||||
List<String> parts = splitMsg(topLevel[1]);
|
||||
|
||||
if (parts.size() != 2) {
|
||||
throw new IllegalArgumentException("Invalid number of parts in RFX message");
|
||||
}
|
||||
|
||||
try {
|
||||
serial = Integer.parseInt(parts.get(0));
|
||||
data = Integer.parseInt(parts.get(1), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("RFX message contains invalid number: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.alarmdecoder.internal.protocol;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VersionMessage} class represents a parsed VER message.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VersionMessage extends ADMessage {
|
||||
|
||||
// Example: !VER:ffffffff,V2.2a.8.2,TX;RX;SM;VZ;RF;ZX;RE;AU;3X;CG;DD;MF;LR;KE;MK;CB;DS;ER
|
||||
|
||||
/** Serial number */
|
||||
public final String serial;
|
||||
|
||||
/** Firmware version */
|
||||
public final String version;
|
||||
|
||||
/** Firmware capabilities */
|
||||
public final String capabilities;
|
||||
|
||||
public VersionMessage(String message) throws IllegalArgumentException {
|
||||
super(message);
|
||||
|
||||
String topLevel[] = message.split(":");
|
||||
if (topLevel.length != 2) {
|
||||
throw new IllegalArgumentException("Multiple colons found in VER message");
|
||||
}
|
||||
|
||||
List<String> parts = splitMsg(topLevel[1]);
|
||||
|
||||
if (parts.size() != 3) {
|
||||
throw new IllegalArgumentException("Invalid number of parts in VER message");
|
||||
}
|
||||
|
||||
serial = parts.get(0);
|
||||
version = parts.get(1);
|
||||
capabilities = parts.get(2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="alarmdecoder" 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>Alarm Decoder Binding</name>
|
||||
<description>This binding is for the Nu Tech Alarm Decoder, used for interfacing with Ademco/Honeywell and DSC alarm
|
||||
systems.</description>
|
||||
<author>Bob Adair, Bill Forsyth</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,370 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="alarmdecoder"
|
||||
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">
|
||||
|
||||
<!-- Alarm Decoder Bridge -->
|
||||
<bridge-type id="ipbridge">
|
||||
<label>Alarm Decoder IP Bridge</label>
|
||||
<description>Nu Tech Alarm Decoder IP Bridge</description>
|
||||
<properties>
|
||||
<property name="vendor">Nu Tech Software Solutions</property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<label>Host Name</label>
|
||||
<context>network-address</context>
|
||||
<description>The hostname or IP address of the Alarm Decoder device</description>
|
||||
</parameter>
|
||||
<parameter name="tcpPort" type="integer">
|
||||
<label>TCP Port</label>
|
||||
<description>TCP port number for the Alarm Decoder connection</description>
|
||||
<default>10000</default>
|
||||
</parameter>
|
||||
<parameter name="discovery" type="boolean">
|
||||
<label>Enable Discovery</label>
|
||||
<description>Enable automatic discovery of zones and RF zones</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="reconnect" type="integer" min="1" max="60" unit="min">
|
||||
<label>Reconnect Interval</label>
|
||||
<description>The period in minutes that the handler will wait between connection attempts and checks</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>2</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" min="0" max="60" unit="min">
|
||||
<label>Message Receive Timeout</label>
|
||||
<description>The period in minutes after which the connection will be reset if no valid messages have been received.
|
||||
Set to 0 to disable.</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<bridge-type id="serialbridge">
|
||||
<label>Alarm Decoder Serial Bridge</label>
|
||||
<description>Nu Tech Alarm Decoder Serial Bridge</description>
|
||||
<properties>
|
||||
<property name="vendor">Nu Tech Software Solutions</property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialPort" type="text" required="true">
|
||||
<label>Serial Or USB Port</label>
|
||||
<context>serial-port</context>
|
||||
<limitToOptions>false</limitToOptions>
|
||||
<description>The name of the serial port used to connect to the Alarm Decoder device</description>
|
||||
</parameter>
|
||||
<parameter name="bitrate" type="integer">
|
||||
<label>Bitrate</label>
|
||||
<default>115200</default>
|
||||
<description>Speed of the serial connection</description>
|
||||
</parameter>
|
||||
<parameter name="discovery" type="boolean">
|
||||
<label>Enable Discovery</label>
|
||||
<description>Enable automatic discovery of zones and RF zones</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<!-- Zone Thing Type -->
|
||||
<thing-type id="zone">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="serialbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Alarm Zone</label>
|
||||
<description>Alarm Decoder REL or EXP zone</description>
|
||||
|
||||
<channels>
|
||||
<channel id="contact" typeId="contact-channel"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>id</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="integer" required="true">
|
||||
<label>Zone Address</label>
|
||||
</parameter>
|
||||
<parameter name="channel" type="integer" required="true">
|
||||
<label>Zone Channel</label>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- RF Zone Thing Type -->
|
||||
<thing-type id="rfzone">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="serialbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Alarm RF Zone</label>
|
||||
<description>Alarm Decoder RFX zone</description>
|
||||
|
||||
<channels>
|
||||
<channel id="lowbat" typeId="system.low-battery">
|
||||
<label>Low Battery</label>
|
||||
</channel>
|
||||
<channel id="supervision" typeId="indicator-channel">
|
||||
<label>Supervision Indicator</label>
|
||||
</channel>
|
||||
<channel id="loop1" typeId="contact-channel">
|
||||
<label>Loop 1</label>
|
||||
</channel>
|
||||
<channel id="loop2" typeId="contact-channel">
|
||||
<label>Loop 2</label>
|
||||
</channel>
|
||||
<channel id="loop3" typeId="contact-channel">
|
||||
<label>Loop 3</label>
|
||||
</channel>
|
||||
<channel id="loop4" typeId="contact-channel">
|
||||
<label>Loop 4</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<representation-property>serial</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serial" type="integer" required="true">
|
||||
<label>Serial</label>
|
||||
<description>Serial number of the RF zone</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Virtual Zone Thing Type -->
|
||||
<thing-type id="vzone">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="serialbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Virtual Zone</label>
|
||||
<description>Alarm Decoder virtual zone</description>
|
||||
|
||||
<channels>
|
||||
<channel id="command" typeId="contact-command-channel"/>
|
||||
<channel id="state" typeId="switch-channel"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="address" type="integer" required="true">
|
||||
<label>Virtual Zone Number</label>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Keypad Thing Type -->
|
||||
<thing-type id="keypad">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="serialbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Alarm Keypad</label>
|
||||
<description>Alarm Decoder keypad thing</description>
|
||||
|
||||
<channels>
|
||||
<channel id="zone" typeId="number-channel">
|
||||
<label>Zone</label>
|
||||
</channel>
|
||||
<channel id="text" typeId="text-channel">
|
||||
<label>Keypad Message</label>
|
||||
</channel>
|
||||
<channel id="ready" typeId="indicator-channel">
|
||||
<label>Ready</label>
|
||||
</channel>
|
||||
<channel id="armedaway" typeId="indicator-channel">
|
||||
<label>Armed Away</label>
|
||||
</channel>
|
||||
<channel id="armedhome" typeId="indicator-channel">
|
||||
<label>Armed Stay</label>
|
||||
</channel>
|
||||
<channel id="backlight" typeId="indicator-channel">
|
||||
<label>Keypad Backlight</label>
|
||||
</channel>
|
||||
<channel id="program" typeId="indicator-channel">
|
||||
<label>Programming Mode</label>
|
||||
</channel>
|
||||
<channel id="beeps" typeId="number-channel">
|
||||
<label>Beeps</label>
|
||||
<description>Number of beeps for message</description>
|
||||
</channel>
|
||||
<channel id="bypassed" typeId="indicator-channel">
|
||||
<label>Zone Bypassed</label>
|
||||
</channel>
|
||||
<channel id="acpower" typeId="indicator-channel">
|
||||
<label>AC Power</label>
|
||||
</channel>
|
||||
<channel id="chime" typeId="indicator-channel">
|
||||
<label>Chime Enabled</label>
|
||||
</channel>
|
||||
<channel id="alarmoccurred" typeId="indicator-channel">
|
||||
<label>Alarm Occurred</label>
|
||||
<description>Alarm has occurred in the past</description>
|
||||
</channel>
|
||||
<channel id="alarm" typeId="indicator-channel">
|
||||
<label>Alarm</label>
|
||||
<description>Alarm is currently sounding</description>
|
||||
</channel>
|
||||
<channel id="lowbat" typeId="system.low-battery">
|
||||
<label>Low Battery</label>
|
||||
</channel>
|
||||
<channel id="delayoff" typeId="indicator-channel">
|
||||
<label>Entry Delay Off</label>
|
||||
</channel>
|
||||
<channel id="fire" typeId="indicator-channel">
|
||||
<label>Fire Detected</label>
|
||||
</channel>
|
||||
<channel id="sysfault" typeId="indicator-channel">
|
||||
<label>System Fault</label>
|
||||
</channel>
|
||||
<channel id="perimeter" typeId="indicator-channel">
|
||||
<label>Perimeter Only</label>
|
||||
</channel>
|
||||
<channel id="command" typeId="command-channel">
|
||||
<label>Keypad Command</label>
|
||||
</channel>
|
||||
<channel id="intcommand" typeId="int-command-channel">
|
||||
<label>Integer Mapped Keypad Command</label>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="addressMask" type="text">
|
||||
<label>Address Mask</label>
|
||||
<description>String containing the address mask in hex that the keypad thing will receive messages for. (0=any)</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="sendCommands" type="boolean">
|
||||
<label>Send Commands</label>
|
||||
<description>Allow keypad commands to be sent to the alarm system from openHAB. Enabling this means the alarm system
|
||||
will be only as secure as your openHAB system.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="sendStar" type="boolean">
|
||||
<label>Send * for Fault Info</label>
|
||||
<description>When disarmed, automatically send * character to obtain zone fault information.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="commandMapping" type="text">
|
||||
<label>Command Mapping for intcommand Channel</label>
|
||||
<description>Comma separated list of key/value pairs mapping integers to command strings for intcommand channel.</description>
|
||||
<default>0=0,1=1,2=2,3=3,4=4,5=5,6=6,7=7,8=8,9=9,10=*,11=#</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<!-- LRR Thing Type -->
|
||||
<thing-type id="lrr">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="serialbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Long Range Radio</label>
|
||||
<description>Long range radio message handler</description>
|
||||
|
||||
<channels>
|
||||
<channel id="partition" typeId="number-channel">
|
||||
<label>Partition</label>
|
||||
<description>Partition number (0 = System)</description>
|
||||
</channel>
|
||||
<channel id="eventdata" typeId="number-channel">
|
||||
<label>Event Data</label>
|
||||
<description>CID event data (user or zone)</description>
|
||||
</channel>
|
||||
<channel id="cidmessage" typeId="text-channel">
|
||||
<label>CID Message</label>
|
||||
<description>SIA Contact ID Protocol message</description>
|
||||
</channel>
|
||||
<channel id="reportcode" typeId="text-channel">
|
||||
<label>Report Code</label>
|
||||
<description>CID report code</description>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="partition" type="integer">
|
||||
<label>Partition</label>
|
||||
<description>Partition for which to receive LRR events (0=All)</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- ===== Channel Type Definitions ===== -->
|
||||
|
||||
<!-- Contact Channel Type -->
|
||||
<channel-type id="contact-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact State</label>
|
||||
<category>Switch</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Indicator Channel Type -->
|
||||
<channel-type id="indicator-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Indicator State</label>
|
||||
<category>Switch</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Switch Channel Type -->
|
||||
<channel-type id="switch-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch State</label>
|
||||
<category>Switch</category>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Number Channel Type -->
|
||||
<channel-type id="number-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number</label>
|
||||
<category>Number</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Text channel type -->
|
||||
<channel-type id="text-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Text Channel</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Command channel type -->
|
||||
<channel-type id="command-channel" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Command Channel</label>
|
||||
</channel-type>
|
||||
|
||||
<!-- Integer Command channel type -->
|
||||
<channel-type id="int-command-channel" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Integer Command Channel</label>
|
||||
</channel-type>
|
||||
|
||||
<!-- Contact command channel type -->
|
||||
<channel-type id="contact-command-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Contact Command</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="OPEN">Open</option>
|
||||
<option value="CLOSED">Closed</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user