added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

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

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.digiplex</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>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,110 @@
# Digiplex/EVO Binding
This binding provides integration with Digiplex/EVO alarm systems from [Paradox](http://paradox.com).
It utilizes [PRT3 module](http://www.paradox.com/Products/default.asp?CATID=7&SUBCATID=75&PRD=234) for serial communication.
## Supported Things
### PRT3 Module
Before the binding can be used, a serial adapter must be added manually. Select `PRT3 Module` and enter serial port parameters.
Please refer to PRT3 module manual for instructions how to modify baudrate (default setting is 2400)
## Discovery
Once `PRT3 Module` is added and communication with the alarm system confirmed by its `online` status, please start discovery process to automatically discover (and add as new Things) all zones and areas defined in the alarm system.
## Binding Configuration
There is no binding level configuration required.
## Thing Configuration
### PRT3 Module Configuration
The following section lists the PRT3 Module configuration. If using manual configuration in text files, the parameter names are given in the square brackets.
#### Serial Port [port]
Sets the serial port name for the communication with the alarm system
#### Baud Rate [baudrate]
Baud rate to use for serial port communication
### Area configuration
#### Refresh time of area status (in seconds) [refreshPeriod]
Controls how often area status is refreshed from the alarm system.
## Channels
### PTR3 Module Channels
The table below summarizes all the channels available from the `PTR3 Module` thing.
| Channel | Description |
|--------------------|-------------------------------------------------------------------------|
| messages_sent | Counts the number of messages sent to the module |
| responses_received | Counts the number of responses received from the module |
| events_received | Counts the number of events received from the module |
### Zone Channels
The table below summarizes all the channels available from the `zone` thing.
| Channel | Description |
|------------------|-------------------------------------------------------------------------|
| status | Simple zone status (open/closed) |
| extended_status | Extended zone status as a String (Open/Closed/Tampered/Fire Loop Alarm) |
| alarm | Information whether zone is in alarm (open/closed) |
| fire_alarm | Same as above for fire alarm |
| supervision_lost | Information whether supervision has been lost (open/closed) |
| low_battery | Low battery warning (open/closed) |
### Area Channels
The table below summarizes all the channels available from the `area` thing.
| Channel | Description |
|------------------|-----------------------------------------------------------------------------------|
| status | Area status available as a String |
| armed | Simple (open/closed) information whether zone is armed |
| zone_in_memory | Information whether there are zones in the memory (after alarm has been triggered |
| trouble | Information whether some of the zones are in 'trouble' (malfunctioning) |
| ready | Information whether area is ready (no open zones) |
| in_programming | Checks for programming mode enabled |
| alarm | Information whether area is in alarm |
| strobe | Information whether area is in strobe alarm |
| control | Channel for controlling area |
User is able to send commands through `control` channel to arm/quick arm/disarm the zone.
Every sent message is followed by the channel state change to either `Ok` or `Failed` depending whether command has been accepted by the alarm system.
Note that PRT3 module is capable of handling more kinds of messages, but those are not yet supported by this binding.
Message format is as follows:
| Command | String sent to the `control` channel |
|-------------------|--------------------------------------|
| Regular Arm | AA`<pin>` |
| Force Arm | AF`<pin>` |
| Stay Arm | AS`<pin>` |
| Instant Arm | AI`<pin>` |
| Regular Quick Arm | QA |
| Force Quick Arm | QF |
| Stay Quick Arm | QS |
| Instant Quick Arm | QI |
| Disarm | D`<pin>` |
`<pin>` is your PIN as entered on a keypad.
**Note**: For security reasons please consider not storing your PIN in openHAB configuration files.
**Note2**: Please consult your alarm system manual how to enable `Quick Arm` feature. It is not enabled by default.
For example, the following sitemap item can be used to send commands to the area and receive response status as modified color of a label:
```
Switch item=areaControl label="Actions[]" mappings=[QA="Regular Quick Arm",QS="Stay Quick Arm",D1111="Disarm"] labelcolor=[Ok="green",Fail="red"]
```

View 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.digiplex</artifactId>
<name>openHAB Add-ons :: Bundles :: Digiplex/EVO Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.digiplex-${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-digiplex" description="Digiplex/EVO Alarm System 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.digiplex/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,23 @@
/**
* 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.digiplex.internal;
/**
* The {@link DigiplexAreaConfiguration} class contains fields mapping area configuration parameters.
*
* @author Robert Michalak - Initial contribution
*/
public class DigiplexAreaConfiguration {
public int refreshPeriod;
}

View File

@@ -0,0 +1,89 @@
/**
* 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.digiplex.internal;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link DigiplexBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class DigiplexBindingConstants {
private static final String BINDING_ID = "digiplex";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
public static final ThingTypeUID THING_TYPE_AREA = new ThingTypeUID(BINDING_ID, "area");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(DigiplexBindingConstants.THING_TYPE_BRIDGE, DigiplexBindingConstants.THING_TYPE_ZONE,
DigiplexBindingConstants.THING_TYPE_AREA)
.collect(Collectors.toSet());
public static final String PROPERTY_ZONE_NO = "ZONE_ID";
public static final String PROPERTY_AREA_NO = "AREA_ID";
// List of all Channel ids
// Bridge
public static final String BRIDGE_MESSAGES_SENT = "statistics#messages_sent";
public static final String BRIDGE_RESPONSES_RECEIVED = "statistics#responses_received";
public static final String BRIDGE_EVENTS_RECEIVED = "statistics#events_received";
public static final String BRIDGE_TLM_TROUBLE = "troubles#tlm_trouble";
public static final String BRIDGE_AC_FAILURE = "troubles#ac_failure";
public static final String BRIDGE_BATTERY_FAILURE = "troubles#battery_failure";
public static final String BRIDGE_AUX_CURRENT_LIMIT = "troubles#aux_current_limit";
public static final String BRIDGE_BELL_CURRENT_LIMIT = "troubles#bell_current_limit";
public static final String BRIDGE_BELL_ABSENT = "troubles#bell_absent";
public static final String BRIDGE_CLOCK_TROUBLE = "troubles#clock_trouble";
public static final String BRIDGE_GLOBAL_FIRE_LOOP = "troubles#global_fire_loop";
// Zone
public static final String ZONE_STATUS = "status";
public static final String ZONE_EXTENDED_STATUS = "extended_status";
public static final String ZONE_ALARM = "alarm";
public static final String ZONE_FIRE_ALARM = "fire_alarm";
public static final String ZONE_SUPERVISION_LOST = "supervision_lost";
public static final String ZONE_LOW_BATTERY = "low_battery";
public static final String ZONE_LAST_TRIGGERED = "last_triggered";
// Area
public static final String AREA_STATUS = "status";
public static final String AREA_ARMED = "armed";
public static final String AREA_ZONE_IN_MEMORY = "zone_in_memory";
public static final String AREA_TROUBLE = "trouble";
public static final String AREA_READY = "ready";
public static final String AREA_IN_PROGRAMMING = "in_programming";
public static final String AREA_ALARM = "alarm";
public static final String AREA_STROBE = "strobe";
public static final String AREA_CONTROL = "control";
public static final List<String> ZONE_DEFAULT_NAMES = Arrays.asList("Zone %03d", "Zone %d");
public static final String AREA_DEFAULT_NAME = "Area %d";
public static final StringType COMMAND_OK = new StringType("Ok");
public static final StringType COMMAND_FAIL = new StringType("Fail");
public static final int GLOBAL_AREA_NO = 0;
}

View File

@@ -0,0 +1,24 @@
/**
* 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.digiplex.internal;
/**
* The {@link DigiplexBridgeConfiguration} class contains fields mapping bridge configuration parameters.
*
* @author Robert Michalak - Initial contribution
*/
public class DigiplexBridgeConfiguration {
public String port;
public int baudrate;
}

View File

@@ -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.digiplex.internal;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.digiplex.internal.handler.DigiplexAreaHandler;
import org.openhab.binding.digiplex.internal.handler.DigiplexBridgeHandler;
import org.openhab.binding.digiplex.internal.handler.DigiplexZoneHandler;
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.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link DigiplexHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Robert Michalak - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.digiplex")
@NonNullByDefault
public class DigiplexHandlerFactory extends BaseThingHandlerFactory {
private final SerialPortManager serialPortManager;
@Activate
public DigiplexHandlerFactory(final @Reference SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_ZONE)) {
return new DigiplexZoneHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_AREA)) {
return new DigiplexAreaHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
return new DigiplexBridgeHandler((Bridge) thing, serialPortManager);
}
return null;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Base class for all responses retrieved from PRT3 module.
*
* Handles success/failure.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public abstract class AbstractResponse implements DigiplexResponse {
public final boolean success;
public AbstractResponse() {
this.success = true;
}
public AbstractResponse(boolean success) {
this.success = success;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Response for arm, quick arm and disarm requests
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaArmDisarmResponse extends AbstractResponse {
public final int areaNo;
public final ArmDisarmType type;
private AreaArmDisarmResponse(int areaNo, ArmDisarmType type, boolean success) {
super(success);
this.areaNo = areaNo;
this.type = type;
}
/**
* Builds a response for a given areaNo and type. Indicates that request failed.
*/
public static AreaArmDisarmResponse failure(int areaNo, ArmDisarmType type) {
return new AreaArmDisarmResponse(areaNo, type, false);
}
/**
* Builds a response for a given areaNo and type. Indicates that request was successful.
*/
public static AreaArmDisarmResponse success(int areaNo, ArmDisarmType type) {
return new AreaArmDisarmResponse(areaNo, type, true);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleArmDisarmAreaResponse(this);
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for arming area
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaArmRequest implements DigiplexRequest {
private int areaNo;
private ArmType armType;
private String pin;
public AreaArmRequest(int areaNo, ArmType armType, String pin) {
this.areaNo = areaNo;
this.armType = armType;
this.pin = pin;
}
@Override
public String getSerialMessage() {
return String.format("AA%03d%c%s\r", areaNo, armType.getIndicator(), pin);
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for disarming area
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaDisarmRequest implements DigiplexRequest {
private int areaNo;
private String pin;
public AreaDisarmRequest(int areaNo, String pin) {
this.areaNo = areaNo;
this.pin = pin;
}
@Override
public String getSerialMessage() {
return String.format("AD%03d%s\r", areaNo, pin);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for requesting area label information from PRT3 device
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaLabelRequest implements DigiplexRequest {
private int areaNo;
public AreaLabelRequest(int areaNo) {
this.areaNo = areaNo;
}
@Override
public String getSerialMessage() {
return String.format("AL%03d\r", areaNo);
}
}

View File

@@ -0,0 +1,61 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Response for {@link AreaLabelRequest}
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class AreaLabelResponse extends AbstractResponse {
public final int areaNo;
public final @Nullable String areaName;
private AreaLabelResponse(int areaNo, String areaName) {
super(true);
this.areaNo = areaNo;
this.areaName = areaName;
}
private AreaLabelResponse(int areaNo) {
super(false);
this.areaNo = areaNo;
this.areaName = null;
}
/**
* Builds a response for a given areaNo. Indicates that request failed.
*/
public static AreaLabelResponse failure(int areaNo) {
return new AreaLabelResponse(areaNo);
}
/**
* Builds a response for a given areaNo and areaName. Indicates that request was successful.
*/
public static AreaLabelResponse success(int areaNo, String areaName) {
return new AreaLabelResponse(areaNo, areaName);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleAreaLabelResponse(this);
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for quick arming area
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaQuickArmRequest implements DigiplexRequest {
private int areaNo;
private ArmType armType;
public AreaQuickArmRequest(int areaNo, ArmType armType) {
this.areaNo = areaNo;
this.armType = armType;
}
@Override
public String getSerialMessage() {
return String.format("AQ%03d%c\r", areaNo, armType.getIndicator());
}
}

View File

@@ -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.digiplex.internal.communication;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
/**
* Area status, as received for the Area Status requests
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum AreaStatus {
DISARMED('D'),
ARMED('A'),
ARMED_FORCE('F'),
ARMED_STAY('S'),
ARMED_INSTANT('I'),
UNKNOWN('u');
private char indicator;
AreaStatus(char indicator) {
this.indicator = indicator;
}
public OpenClosedType toOpenClosedType() {
return this == DISARMED ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
}
public static AreaStatus fromMessage(char indicator) {
return Arrays.stream(AreaStatus.values()).filter(type -> type.indicator == indicator).findFirst()
.orElse(UNKNOWN);
}
public StringType toStringType() {
return new StringType(this.toString());
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for requesting zone status information from PRT3 device
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaStatusRequest implements DigiplexRequest {
private int areaNo;
public AreaStatusRequest(int areaNo) {
this.areaNo = areaNo;
}
@Override
public String getSerialMessage() {
return String.format("RA%03d\r", areaNo);
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Response for {@link AreaStatusRequest}
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class AreaStatusResponse extends AbstractResponse {
public final int areaNo;
public final @Nullable AreaStatus status;
public final boolean zoneInMemory;
public final boolean trouble;
public final boolean ready;
public final boolean inProgramming;
public final boolean alarm;
public final boolean strobe;
private AreaStatusResponse(int areaNo, AreaStatus status, boolean zoneInMemory, boolean trouble, boolean ready,
boolean inProgramming, boolean alarm, boolean strobe) {
this.areaNo = areaNo;
this.status = status;
this.zoneInMemory = zoneInMemory;
this.trouble = trouble;
this.ready = ready;
this.inProgramming = inProgramming;
this.alarm = alarm;
this.strobe = strobe;
}
private AreaStatusResponse(int areaNo) {
super(false);
this.areaNo = areaNo;
this.status = null;
this.zoneInMemory = false;
this.trouble = false;
this.ready = false;
this.inProgramming = false;
this.alarm = false;
this.strobe = false;
}
/**
* Builds a response for a given areaNo. Indicates that request failed.
*/
public static AreaStatusResponse failure(int areaNo) {
return new AreaStatusResponse(areaNo);
}
/**
* Builds a response for a given parameters. Indicates that request was successful.
*/
public static AreaStatusResponse success(int areaNo, AreaStatus status, boolean zoneInMemory, boolean trouble,
boolean ready, boolean inProgramming, boolean alarm, boolean strobe) {
return new AreaStatusResponse(areaNo, status, zoneInMemory, trouble, ready, inProgramming, alarm, strobe);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleAreaStatusResponse(this);
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.digiplex.internal.communication;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* Indicates type of arm/disarm message returned for PRT3 module
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum ArmDisarmType {
ARM("AA"),
QUICK_ARM("AQ"),
DISARM("AD"),
UNKNOWN("");
private String indicator;
ArmDisarmType(String indicator) {
this.indicator = indicator;
}
public static ArmDisarmType fromMessage(String indicator) {
return Arrays.stream(ArmDisarmType.values()).filter(type -> type.indicator.equals(indicator)).findFirst()
.orElse(UNKNOWN);
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.digiplex.internal.communication;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Indicates arm type
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum ArmType {
REGULAR_ARM('A'),
FORCE_ARM('F'),
STAY_ARM('S'),
INSTANT_ARM('I'),
UNKNOWN('u');
private char indicator;
ArmType(char indicator) {
this.indicator = indicator;
}
public char getIndicator() {
return indicator;
}
public static ArmType fromMessage(char indicator) {
return Arrays.stream(values()).filter(type -> type.indicator == indicator).findFirst().orElse(UNKNOWN);
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Message indicating communication status between PRT3 device and Digiplex controller.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class CommunicationStatus extends AbstractResponse {
public static final CommunicationStatus OK = new CommunicationStatus(true);
public static final CommunicationStatus FAILURE = new CommunicationStatus(false);
private CommunicationStatus(boolean success) {
super(success);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleCommunicationStatus(this);
}
}

View File

@@ -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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link DigiplexMessage} is a common ancestor for all commands send to/received from the PRT3 device
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public interface DigiplexMessage {
}

View File

@@ -0,0 +1,73 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
import org.openhab.binding.digiplex.internal.communication.events.SpecialAlarmEvent;
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
import org.openhab.binding.digiplex.internal.communication.events.ZoneEvent;
import org.openhab.binding.digiplex.internal.communication.events.ZoneStatusEvent;
/**
* Interface for message handlers.
*
* Visitor pattern is used to dispatch message processing to proper methods.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public interface DigiplexMessageHandler {
default void handleCommunicationStatus(CommunicationStatus response) {
}
default void handleZoneLabelResponse(ZoneLabelResponse response) {
}
default void handleZoneStatusResponse(ZoneStatusResponse response) {
}
default void handleAreaLabelResponse(AreaLabelResponse response) {
}
default void handleAreaStatusResponse(AreaStatusResponse response) {
}
default void handleArmDisarmAreaResponse(AreaArmDisarmResponse response) {
}
default void handleUnknownResponse(UnknownResponse response) {
}
// Events
default void handleZoneEvent(ZoneEvent event) {
}
default void handleZoneStatusEvent(ZoneStatusEvent event) {
}
default void handleSpecialAlarmEvent(SpecialAlarmEvent event) {
}
default void handleAreaEvent(AreaEvent event) {
}
default void handleGenericEvent(GenericEvent event) {
}
default void handleTroubleEvent(TroubleEvent troubleEvent) {
}
}

View File

@@ -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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Common ancestor for all requests
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public interface DigiplexRequest extends DigiplexMessage {
String getSerialMessage();
}

View File

@@ -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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Common ancestor for all responses
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public interface DigiplexResponse extends DigiplexMessage {
void accept(DigiplexMessageHandler visitor);
}

View File

@@ -0,0 +1,217 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
import org.openhab.binding.digiplex.internal.communication.events.AreaEventType;
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
import org.openhab.binding.digiplex.internal.communication.events.SpecialAlarmEvent;
import org.openhab.binding.digiplex.internal.communication.events.SpecialAlarmType;
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
import org.openhab.binding.digiplex.internal.communication.events.TroubleType;
import org.openhab.binding.digiplex.internal.communication.events.ZoneEvent;
import org.openhab.binding.digiplex.internal.communication.events.ZoneEventType;
import org.openhab.binding.digiplex.internal.communication.events.ZoneStatusEvent;
/**
* Resolves serial messages to appropriate classes
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class DigiplexResponseResolver {
private static final String OK = "&ok";
// TODO: handle failures
private static final String FAIL = "&fail";
public static DigiplexResponse resolveResponse(String message) {
if (message.length() < 4) { // sanity check: try to filter out malformed responses
return new UnknownResponse(message);
}
int zoneNo, areaNo;
String commandType = message.substring(0, 2);
switch (commandType) {
case "CO": // communication status
if (message.contains(FAIL)) {
return CommunicationStatus.FAILURE;
} else {
return CommunicationStatus.OK;
}
case "ZL": // zone label
zoneNo = Integer.valueOf(message.substring(2, 5));
if (message.contains(FAIL)) {
return ZoneLabelResponse.failure(zoneNo);
} else {
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
}
case "AL": // area label
areaNo = Integer.valueOf(message.substring(2, 5));
if (message.contains(FAIL)) {
return AreaLabelResponse.failure(areaNo);
} else {
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
}
case "RZ": // zone status
zoneNo = Integer.valueOf(message.substring(2, 5));
if (message.contains(FAIL)) {
return ZoneStatusResponse.failure(zoneNo);
} else {
return ZoneStatusResponse.success(zoneNo, // zone number
ZoneStatus.fromMessage(message.charAt(5)), // status
toBoolean(message.charAt(6)), // alarm
toBoolean(message.charAt(7)), // fire alarm
toBoolean(message.charAt(8)), // supervision lost
toBoolean(message.charAt(9))); // battery low
}
case "RA": // area status
areaNo = Integer.valueOf(message.substring(2, 5));
if (message.contains(FAIL)) {
return AreaStatusResponse.failure(areaNo);
} else {
return AreaStatusResponse.success(areaNo, // zone number
AreaStatus.fromMessage(message.charAt(5)), // status
toBoolean(message.charAt(6)), // zone in memory
toBoolean(message.charAt(7)), // trouble
!toBoolean(message.charAt(8)), // ready (note ! in front)
toBoolean(message.charAt(9)), // in programming
toBoolean(message.charAt(10)), // in alarm
toBoolean(message.charAt(11))); // strobe
}
case "AA": // area arm
case "AQ": // area quick arm
case "AD": // area disarm
areaNo = Integer.valueOf(message.substring(2, 5));
if (message.contains(FAIL)) {
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
} else {
return AreaArmDisarmResponse.success(areaNo, ArmDisarmType.fromMessage(commandType));
}
case "UL": // user label
case "PG": // PGM events
default:
if (message.startsWith("G")) {
return resolveSystemEvent(message);
} else {
return new UnknownResponse(message);
}
}
}
private static boolean toBoolean(char value) {
if (value == 'O') {
return false;
} else {
return true;
}
}
private static DigiplexResponse resolveSystemEvent(String message) {
int eventGroup = Integer.parseInt(message.substring(1, 4));
int eventNumber = Integer.parseInt(message.substring(5, 8));
int areaNumber = Integer.parseInt(message.substring(9, 12));
switch (eventGroup) {
case 0:
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);
case 1:
return new ZoneStatusEvent(eventNumber, ZoneStatus.OPEN, areaNumber);
case 2:
return new ZoneStatusEvent(eventNumber, ZoneStatus.TAMPERED, areaNumber);
case 3:
return new ZoneStatusEvent(eventNumber, ZoneStatus.FIRE_LOOP_TROUBLE, areaNumber);
case 8:
return new ZoneEvent(eventNumber, ZoneEventType.TX_DELAY_ZONE_ALARM, areaNumber);
case 13:
case 14:
case 15:
case 16:
case 17:
case 18:
return new AreaEvent(AreaEventType.DISARMED, areaNumber);
case 23:
return new ZoneEvent(eventNumber, ZoneEventType.BYPASSED, areaNumber);
case 24:
return new ZoneEvent(eventNumber, ZoneEventType.ALARM, areaNumber);
case 25:
return new ZoneEvent(eventNumber, ZoneEventType.FIRE_ALARM, areaNumber);
case 26:
return new ZoneEvent(eventNumber, ZoneEventType.ALARM_RESTORE, areaNumber);
case 27:
return new ZoneEvent(eventNumber, ZoneEventType.FIRE_ALARM_RESTORE, areaNumber);
case 30:
return new SpecialAlarmEvent(areaNumber, SpecialAlarmType.fromMessage(eventNumber));
case 32:
return new ZoneEvent(eventNumber, ZoneEventType.SHUTDOWN, areaNumber);
case 33:
return new ZoneEvent(eventNumber, ZoneEventType.TAMPER, areaNumber);
case 34:
return new ZoneEvent(eventNumber, ZoneEventType.TAMPER_RESTORE, areaNumber);
case 36:
return new TroubleEvent(TroubleType.fromEventNumber(eventNumber), TroubleStatus.TROUBLE_STARTED,
areaNumber);
case 37:
return new TroubleEvent(TroubleType.fromEventNumber(eventNumber), TroubleStatus.TROUBLE_RESTORED,
areaNumber);
case 41:
return new ZoneEvent(eventNumber, ZoneEventType.LOW_BATTERY, areaNumber);
case 42:
return new ZoneEvent(eventNumber, ZoneEventType.SUPERVISION_TROUBLE, areaNumber);
case 43:
return new ZoneEvent(eventNumber, ZoneEventType.LOW_BATTERY_RESTORE, areaNumber);
case 44:
return new ZoneEvent(eventNumber, ZoneEventType.SUPERVISION_TROUBLE_RESTORE, areaNumber);
case 55:
return new ZoneEvent(eventNumber, ZoneEventType.INTELLIZONE_TRIGGERED, areaNumber);
case 64:
switch (eventNumber) {
case 0:
return new AreaEvent(AreaEventType.ARMED, areaNumber);
case 1:
return new AreaEvent(AreaEventType.ARMED_FORCE, areaNumber);
case 2:
return new AreaEvent(AreaEventType.ARMED_STAY, areaNumber);
case 3:
return new AreaEvent(AreaEventType.ARMED_INSTANT, areaNumber);
case 4:
return new AreaEvent(AreaEventType.ALARM_STROBE, areaNumber);
case 5:
return new AreaEvent(AreaEventType.ALARM_SILENT, areaNumber);
case 6:
return new AreaEvent(AreaEventType.ALARM_AUDIBLE, areaNumber);
case 7:
return new AreaEvent(AreaEventType.ALARM_FIRE, areaNumber);
}
break;
case 65:
switch (eventNumber) {
case 0:
return new AreaEvent(AreaEventType.READY, areaNumber);
case 1:
return new AreaEvent(AreaEventType.EXIT_DELAY, areaNumber);
case 2:
return new AreaEvent(AreaEventType.ENTRY_DELAY, areaNumber);
case 3:
return new AreaEvent(AreaEventType.SYSTEM_IN_TROUBLE, areaNumber);
case 4:
return new AreaEvent(AreaEventType.ALARM_IN_MEMORY, areaNumber);
case 5:
return new AreaEvent(AreaEventType.ZONES_BYPASSED, areaNumber);
}
}
return new GenericEvent(eventGroup, eventNumber, areaNumber);
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Unknown message from PRT3
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class UnknownResponse implements DigiplexResponse {
public final String message;
public UnknownResponse(String message) {
this.message = message;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleUnknownResponse(this);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for requesting zone label information from PRT3 device
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class ZoneLabelRequest implements DigiplexRequest {
private int zoneNo;
public ZoneLabelRequest(int zoneNo) {
this.zoneNo = zoneNo;
}
@Override
public String getSerialMessage() {
return String.format("ZL%03d\r", zoneNo);
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Response for {@link ZoneLabelRequest}
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class ZoneLabelResponse extends AbstractResponse {
public final int zoneNo;
public final @Nullable String zoneName;
private ZoneLabelResponse(int zoneNo, String zoneName) {
super(true);
this.zoneNo = zoneNo;
this.zoneName = zoneName;
}
private ZoneLabelResponse(int zoneNo) {
super(false);
this.zoneNo = zoneNo;
this.zoneName = null;
}
/**
* Builds a response for a given zoneNo. Indicates that request failed.
*/
public static ZoneLabelResponse failure(int zoneNo) {
return new ZoneLabelResponse(zoneNo);
}
/**
* Builds a response for a given zoneNo. Indicates that request was successful.
*/
public static ZoneLabelResponse success(int zoneNo, String zoneName) {
return new ZoneLabelResponse(zoneNo, zoneName);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleZoneLabelResponse(this);
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.digiplex.internal.communication;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OpenClosedType;
/**
* Zone status, as received from the alarm system
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum ZoneStatus {
CLOSED('C'),
OPEN('O'),
TAMPERED('T'),
FIRE_LOOP_TROUBLE('F'),
UNKNOWN('u');
private char indicator;
ZoneStatus(char indicator) {
this.indicator = indicator;
}
public OpenClosedType toOpenClosedType() {
return this == CLOSED ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
}
public static ZoneStatus fromMessage(char indicator) {
return Arrays.stream(ZoneStatus.values()).filter(type -> type.indicator == indicator).findFirst()
.orElse(UNKNOWN);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Command for requesting zone status information from PRT3 device
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class ZoneStatusRequest implements DigiplexRequest {
private int zoneNo;
public ZoneStatusRequest(int zoneNo) {
this.zoneNo = zoneNo;
}
@Override
public String getSerialMessage() {
return String.format("RZ%03d\r", zoneNo);
}
}

View File

@@ -0,0 +1,75 @@
/**
* 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.digiplex.internal.communication;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Response for {@link ZoneStatusRequest}
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class ZoneStatusResponse extends AbstractResponse {
public final int zoneNo;
@Nullable
public final ZoneStatus status;
public final boolean alarm;
public final boolean fireAlarm;
public final boolean supervisionLost;
public final boolean lowBattery;
private ZoneStatusResponse(int zoneNo, ZoneStatus status, boolean alarm, boolean fireAlarm, boolean supervisionLost,
boolean lowBattery) {
super(true);
this.zoneNo = zoneNo;
this.status = status;
this.alarm = alarm;
this.fireAlarm = fireAlarm;
this.supervisionLost = supervisionLost;
this.lowBattery = lowBattery;
}
private ZoneStatusResponse(int zoneNo) {
super(false);
this.zoneNo = zoneNo;
this.status = null;
this.alarm = false;
this.fireAlarm = false;
this.supervisionLost = false;
this.lowBattery = false;
}
/**
* Builds a response for a given zoneNo. Indicates that request failed.
*/
public static ZoneStatusResponse failure(int zoneNo) {
return new ZoneStatusResponse(zoneNo);
}
/**
* Builds a response for a given zoneNo. Indicates that request was successful.
*/
public static ZoneStatusResponse success(int zoneNo, ZoneStatus status, boolean alarm, boolean fireAlarm,
boolean supervisionLost, boolean lowBattery) {
return new ZoneStatusResponse(zoneNo, status, alarm, fireAlarm, supervisionLost, lowBattery);
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleZoneStatusResponse(this);
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
/**
* Common ancestor for all events received from Digiplex system
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public abstract class AbstractEvent implements DigiplexResponse {
private int areaNo;
public AbstractEvent(int areaNo) {
this.areaNo = areaNo;
}
public int getAreaNo() {
return areaNo;
}
public boolean isForArea(int areaNo) {
if (this.areaNo == 0 || this.areaNo == areaNo) {
return true;
}
// TODO: According to documentation: areaNo = 255 - Occurs in at least one area enabled in the system.
// I did never encounter 255 on my system though (EVO192).
// 15 is returned instead, which (I believe) has the same meaning.
if (this.areaNo == 15 || this.areaNo == 255) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
/**
* Message providing miscellaneous area informations
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class AreaEvent extends AbstractEvent {
private AreaEventType type;
public AreaEvent(AreaEventType type, int areaNo) {
super(areaNo);
this.type = type;
}
public AreaEventType getType() {
return type;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleAreaEvent(this);
}
}

View File

@@ -0,0 +1,43 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Area event type.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum AreaEventType {
ARMED,
ARMED_FORCE,
ARMED_STAY,
ARMED_INSTANT,
DISARMED,
ALARM_STROBE,
ALARM_SILENT,
ALARM_AUDIBLE,
ALARM_FIRE,
READY,
EXIT_DELAY,
ENTRY_DELAY,
SYSTEM_IN_TROUBLE,
ALARM_IN_MEMORY,
ZONES_BYPASSED,
}

View File

@@ -0,0 +1,50 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
/**
* Represents generic event received from PRT3 module.
*
* It is created when no specific handler is found for the received event.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class GenericEvent extends AbstractEvent {
private int eventGroup;
private int eventNumber;
public GenericEvent(int eventGroup, int eventNumber, int areaNumber) {
super(areaNumber);
this.eventGroup = eventGroup;
this.eventNumber = eventNumber;
}
public int getEventGroup() {
return eventGroup;
}
public int getEventNumber() {
return eventNumber;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleGenericEvent(this);
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
/**
* Message providing information about special alarm events
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class SpecialAlarmEvent extends AbstractEvent {
private SpecialAlarmType type;
public SpecialAlarmEvent(int areaNo, SpecialAlarmType type) {
super(areaNo);
this.type = type;
}
public SpecialAlarmType getType() {
return type;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleSpecialAlarmEvent(this);
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.digiplex.internal.communication.events;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Type of special alarm.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum SpecialAlarmType {
EMERGENCY_PANIC(0),
MEDICAL_PANIC(1),
FIRE_PANIC(2),
RECENT_CLOSING(3),
POLICE_CODE(4),
GLOBAL_SHUTDOWN(5),
UNKNOWN(-1);
private int indicator;
SpecialAlarmType(int indicator) {
this.indicator = indicator;
}
public static SpecialAlarmType fromMessage(int indicator) {
return Arrays.stream(values()).filter(type -> type.indicator == indicator).findFirst().orElse(UNKNOWN);
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
/**
* Message providing global trouble status
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class TroubleEvent extends AbstractEvent {
private TroubleStatus status;
private TroubleType type;
public TroubleEvent(TroubleType type, TroubleStatus status, int areaNo) {
super(areaNo);
this.type = type;
this.status = status;
}
public TroubleStatus getStatus() {
return status;
}
public TroubleType getType() {
return type;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleTroubleEvent(this);
}
}

View File

@@ -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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Trouble status.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum TroubleStatus {
TROUBLE_STARTED,
TROUBLE_RESTORED;
}

View File

@@ -0,0 +1,49 @@
/**
* 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.digiplex.internal.communication.events;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Trouble event type.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum TroubleType {
TLM_TROUBLE(BRIDGE_TLM_TROUBLE),
AC_FAILURE(BRIDGE_AC_FAILURE),
BATTERY_FAILURE(BRIDGE_BATTERY_FAILURE),
AUXILIARY_CURRENT_LIMIT(BRIDGE_AUX_CURRENT_LIMIT),
BELL_CURRENT_LIMIT(BRIDGE_BELL_CURRENT_LIMIT),
BELL_ABSENT(BRIDGE_BELL_ABSENT),
CLOCK_TROUBLE(BRIDGE_CLOCK_TROUBLE),
GLOBAL_FIRE_LOOP(BRIDGE_GLOBAL_FIRE_LOOP);
private String bridgeChannel;
private TroubleType(String bridgeChannel) {
this.bridgeChannel = bridgeChannel;
}
public String getBridgeChannel() {
return bridgeChannel;
}
public static TroubleType fromEventNumber(int eventNumber) {
return TroubleType.values()[eventNumber];
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
/**
* Message providing miscellaneous zone informations
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class ZoneEvent extends AbstractEvent {
private int zoneNo;
private ZoneEventType type;
public ZoneEvent(int zoneNo, ZoneEventType type, int areaNo) {
super(areaNo);
this.zoneNo = zoneNo;
this.type = type;
}
public int getZoneNo() {
return zoneNo;
}
public ZoneEventType getType() {
return type;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleZoneEvent(this);
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Type of zone-related event
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public enum ZoneEventType {
TX_DELAY_ZONE_ALARM,
BYPASSED,
ALARM,
FIRE_ALARM,
ALARM_RESTORE,
FIRE_ALARM_RESTORE,
SHUTDOWN,
TAMPER,
TAMPER_RESTORE,
LOW_BATTERY,
LOW_BATTERY_RESTORE,
SUPERVISION_TROUBLE,
SUPERVISION_TROUBLE_RESTORE,
INTELLIZONE_TRIGGERED
}

View File

@@ -0,0 +1,50 @@
/**
* 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.digiplex.internal.communication.events;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
import org.openhab.binding.digiplex.internal.communication.ZoneStatus;
/**
* Message indicating zone status.
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class ZoneStatusEvent extends AbstractEvent implements DigiplexResponse {
private int zoneNo;
private ZoneStatus state;
public ZoneStatusEvent(int zoneNo, ZoneStatus state, int areaNo) {
super(areaNo);
this.zoneNo = zoneNo;
this.state = state;
}
public int getZoneNo() {
return zoneNo;
}
public ZoneStatus getStatus() {
return state;
}
@Override
public void accept(DigiplexMessageHandler visitor) {
visitor.handleZoneStatusEvent(this);
}
}

View File

@@ -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.digiplex.internal.discovery;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.digiplex.internal.communication.AreaLabelRequest;
import org.openhab.binding.digiplex.internal.communication.AreaLabelResponse;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
import org.openhab.binding.digiplex.internal.communication.ZoneLabelRequest;
import org.openhab.binding.digiplex.internal.communication.ZoneLabelResponse;
import org.openhab.binding.digiplex.internal.handler.DigiplexBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
/**
* Service for discovering things on Digiplex alarm systems
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class DigiplexDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService, DigiplexMessageHandler {
private static final int MAX_ZONE = 96;
private static final int MAX_AREA = 8;
private static final int DISCOVERY_TIMEOUT = 30;
private @Nullable DigiplexBridgeHandler bridgeHandler;
public DigiplexDiscoveryService() {
super(Collections.singleton(THING_TYPE_ZONE), DISCOVERY_TIMEOUT, false);
}
@Override
@SuppressWarnings("null")
protected void startScan() {
bridgeHandler.registerMessageHandler(this);
// find zones
for (int i = 1; i <= MAX_ZONE; i++) {
DigiplexRequest command = new ZoneLabelRequest(i);
bridgeHandler.sendRequest(command);
}
// find areas
for (int i = 1; i <= MAX_AREA; i++) {
DigiplexRequest command = new AreaLabelRequest(i);
bridgeHandler.sendRequest(command);
}
}
@Override
@SuppressWarnings("null")
protected synchronized void stopScan() {
bridgeHandler.unregisterMessageHandler(this);
super.stopScan();
}
@Override
@SuppressWarnings("null")
public void handleZoneLabelResponse(ZoneLabelResponse response) {
// we have no other option to check whether zone is actually enabled than to compare its name with the default
if (isDefaultName(response)) {
return;
}
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(THING_TYPE_ZONE, bridgeUID, String.format("zone%d", response.zoneNo));
Map<String, Object> properties = new HashMap<>(1);
properties.put(PROPERTY_ZONE_NO, Integer.toString(response.zoneNo));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withProperties(properties).withLabel(response.zoneName).build();
thingDiscovered(discoveryResult);
}
private boolean isDefaultName(ZoneLabelResponse response) {
return ZONE_DEFAULT_NAMES.stream().anyMatch(format -> {
if (String.format(format, response.zoneNo).equals(response.zoneName)) {
return true;
} else {
return false;
}
});
}
@Override
@SuppressWarnings("null")
public void handleAreaLabelResponse(AreaLabelResponse response) {
// we have no other option to check whether area is actually enabled than to compare its name with the default
if (response.success && response.areaName.equals(String.format(AREA_DEFAULT_NAME, response.areaNo))) {
return;
}
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID thingUID = new ThingUID(THING_TYPE_AREA, bridgeUID, String.format("area%d", response.areaNo));
Map<String, Object> properties = new HashMap<>(1);
properties.put(PROPERTY_AREA_NO, Integer.toString(response.areaNo));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withProperties(properties).withLabel(response.areaName).build();
thingDiscovered(discoveryResult);
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof DigiplexBridgeHandler) {
bridgeHandler = (DigiplexBridgeHandler) handler;
bridgeHandler.registerMessageHandler(this);
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
}

View File

@@ -0,0 +1,310 @@
/**
* 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.digiplex.internal.handler;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import static org.openhab.binding.digiplex.internal.handler.TypeUtils.openClosedFromBoolean;
import java.util.Optional;
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.digiplex.internal.DigiplexAreaConfiguration;
import org.openhab.binding.digiplex.internal.DigiplexBindingConstants;
import org.openhab.binding.digiplex.internal.communication.AreaArmDisarmResponse;
import org.openhab.binding.digiplex.internal.communication.AreaArmRequest;
import org.openhab.binding.digiplex.internal.communication.AreaDisarmRequest;
import org.openhab.binding.digiplex.internal.communication.AreaQuickArmRequest;
import org.openhab.binding.digiplex.internal.communication.AreaStatus;
import org.openhab.binding.digiplex.internal.communication.AreaStatusRequest;
import org.openhab.binding.digiplex.internal.communication.AreaStatusResponse;
import org.openhab.binding.digiplex.internal.communication.ArmType;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
/**
* The {@link DigiplexAreaHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class DigiplexAreaHandler extends BaseThingHandler {
private @Nullable DigiplexAreaConfiguration config;
private @Nullable DigiplexBridgeHandler bridgeHandler;
private DigiplexAreaMessageHandler visitor = new DigiplexAreaMessageHandler();
private int areaNo;
private OpenClosedType armed = OpenClosedType.CLOSED;
private StringType status = AreaStatus.DISARMED.toStringType();
private OpenClosedType zoneInMemory = OpenClosedType.CLOSED;
private OpenClosedType trouble = OpenClosedType.CLOSED;
private OpenClosedType ready = OpenClosedType.CLOSED;
private OpenClosedType inProgramming = OpenClosedType.CLOSED;
private OpenClosedType alarm = OpenClosedType.CLOSED;
private OpenClosedType strobe = OpenClosedType.CLOSED;
private StringType lastCommandResult = new StringType();
private @Nullable ScheduledFuture<?> refreshTask;
public DigiplexAreaHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case AREA_STATUS:
if (command == RefreshType.REFRESH) {
updateState(AREA_STATUS, status);
}
break;
case AREA_ARMED:
if (command == RefreshType.REFRESH) {
updateState(AREA_ARMED, armed);
}
break;
case AREA_ZONE_IN_MEMORY:
if (command == RefreshType.REFRESH) {
updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
}
break;
case AREA_TROUBLE:
if (command == RefreshType.REFRESH) {
updateState(AREA_TROUBLE, trouble);
}
break;
case AREA_READY:
if (command == RefreshType.REFRESH) {
updateState(AREA_READY, ready);
}
break;
case AREA_IN_PROGRAMMING:
if (command == RefreshType.REFRESH) {
updateState(AREA_IN_PROGRAMMING, inProgramming);
}
break;
case AREA_ALARM:
if (command == RefreshType.REFRESH) {
updateState(AREA_ALARM, alarm);
}
break;
case AREA_STROBE:
if (command == RefreshType.REFRESH) {
updateState(AREA_STROBE, strobe);
}
break;
case AREA_CONTROL:
if (command == RefreshType.REFRESH) {
updateState(AREA_CONTROL, lastCommandResult);
} else if (command instanceof StringType) {
processControlCommand(((StringType) command).toString());
}
break;
}
}
@SuppressWarnings("null")
private void processControlCommand(String command) {
if (command.length() < 2) {
updateControlChannel(COMMAND_FAIL);
return;
}
char commandType = command.charAt(0);
char commandSubType = command.charAt(1);
switch (commandType) {
case 'A':
bridgeHandler.sendRequest(
new AreaArmRequest(areaNo, ArmType.fromMessage(commandSubType), command.substring(2)));
break;
case 'Q':
bridgeHandler.sendRequest(new AreaQuickArmRequest(areaNo, ArmType.fromMessage(commandSubType)));
break;
case 'D':
bridgeHandler.sendRequest(new AreaDisarmRequest(areaNo, command.substring(1)));
break;
}
}
@SuppressWarnings("null")
@Override
public void initialize() {
config = getConfigAs(DigiplexAreaConfiguration.class);
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
bridgeHandler = (DigiplexBridgeHandler) bridge.getHandler();
String areaParm = getThing().getProperties().get(DigiplexBindingConstants.PROPERTY_AREA_NO);
areaNo = Integer.parseInt(areaParm);
bridgeHandler.registerMessageHandler(visitor);
updateStatus(ThingStatus.ONLINE);
refreshTask = scheduler.scheduleWithFixedDelay(() -> {
sendStatusUpdateRequest();
}, 0, config.refreshPeriod, TimeUnit.SECONDS);
}
private void updateChannelsAfterStatusResponse() {
updateState(AREA_STATUS, status);
updateState(AREA_ARMED, armed);
updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
updateState(AREA_TROUBLE, trouble);
updateState(AREA_READY, ready);
updateState(AREA_IN_PROGRAMMING, inProgramming);
updateState(AREA_ALARM, alarm);
updateState(AREA_STROBE, strobe);
}
@SuppressWarnings("null")
@Override
public void handleRemoval() {
if (visitor != null) {
bridgeHandler.unregisterMessageHandler(visitor);
}
if (refreshTask != null) {
refreshTask.cancel(true);
}
super.handleRemoval();
}
@Override
public void bridgeStatusChanged(ThingStatusInfo thingStatusInfo) {
if (thingStatusInfo.getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, thingStatusInfo.getStatusDetail());
} else if (thingStatusInfo.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
sendStatusUpdateRequest();
}
}
private synchronized void updateControlChannel(StringType response) {
lastCommandResult = response;
updateState(AREA_CONTROL, lastCommandResult);
}
@SuppressWarnings("null")
private void sendStatusUpdateRequest() {
DigiplexRequest request = new AreaStatusRequest(areaNo);
bridgeHandler.sendRequest(request);
}
private class DigiplexAreaMessageHandler implements DigiplexMessageHandler {
@Override
public void handleAreaStatusResponse(AreaStatusResponse response) {
if (response.success && response.areaNo == DigiplexAreaHandler.this.areaNo) {
status = new StringType(response.status.toString());
armed = response.status.toOpenClosedType();
zoneInMemory = openClosedFromBoolean(response.zoneInMemory);
trouble = openClosedFromBoolean(response.trouble);
ready = openClosedFromBoolean(response.ready);
inProgramming = openClosedFromBoolean(response.inProgramming);
alarm = openClosedFromBoolean(response.alarm);
strobe = openClosedFromBoolean(response.strobe);
updateChannelsAfterStatusResponse();
}
}
@Override
public void handleArmDisarmAreaResponse(AreaArmDisarmResponse response) {
if (response.areaNo == DigiplexAreaHandler.this.areaNo) {
if (response.success) {
updateControlChannel(COMMAND_OK);
} else {
updateControlChannel(COMMAND_FAIL);
}
}
}
@Override
public void handleAreaEvent(AreaEvent event) {
if (event.isForArea(DigiplexAreaHandler.this.areaNo)) {
switch (event.getType()) {
case READY: // TODO: not sure what it means. Let's send status update request
case DISARMED: // in case of disarm we want to ensure that all other channels are updated as well
sendStatusUpdateRequest();
break;
case ALARM_STROBE:
strobe = OpenClosedType.OPEN;
updateState(AREA_STROBE, strobe);
// no break intentionally
case ALARM_FIRE:
case ALARM_AUDIBLE:
case ALARM_IN_MEMORY:
case ALARM_SILENT:
alarm = OpenClosedType.OPEN;
updateState(AREA_ALARM, alarm);
break;
case ARMED:
case ARMED_FORCE:
case ARMED_INSTANT:
case ARMED_STAY:
armed = OpenClosedType.OPEN;
updateState(AREA_ARMED, armed);
break;
case SYSTEM_IN_TROUBLE:
trouble = OpenClosedType.OPEN;
updateState(AREA_TROUBLE, trouble);
break;
case ZONES_BYPASSED:
case ENTRY_DELAY:
case EXIT_DELAY:
default:
break;
}
// update status separately, for more concise logic
Optional<AreaStatus> tempStatus = Optional.empty();
switch (event.getType()) {
case ARMED:
tempStatus = Optional.of(AreaStatus.ARMED);
break;
case ARMED_FORCE:
tempStatus = Optional.of(AreaStatus.ARMED_FORCE);
break;
case ARMED_INSTANT:
tempStatus = Optional.of(AreaStatus.ARMED_INSTANT);
break;
case ARMED_STAY:
tempStatus = Optional.of(AreaStatus.ARMED_STAY);
break;
case DISARMED:
tempStatus = Optional.of(AreaStatus.DISARMED);
break;
default:
break;
}
tempStatus.ifPresent(s -> updateState(AREA_STATUS, s.toStringType()));
}
}
}
}

View File

@@ -0,0 +1,382 @@
/**
* 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.digiplex.internal.handler;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.TooManyListenersException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.digiplex.internal.DigiplexBridgeConfiguration;
import org.openhab.binding.digiplex.internal.communication.CommunicationStatus;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
import org.openhab.binding.digiplex.internal.discovery.DigiplexDiscoveryService;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortEvent;
import org.openhab.core.io.transport.serial.SerialPortEventListener;
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.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
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.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link DigiplexBridgeHandler} is responsible for handling communication with PRT3 module
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class DigiplexBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
private static final int REINITIALIZE_DELAY = 1; // in minutes
private static final int STALLED_MESSAGES_THRESHOLD = 5;
private static final int END_OF_MESSAGE = '\r';
private static final int END_OF_STREAM = -1;
private final Logger logger = LoggerFactory.getLogger(DigiplexBridgeHandler.class);
private @Nullable DigiplexBridgeConfiguration config;
private @Nullable SerialPort serialPort;
private @Nullable DigiplexReceiverThread receiverThread;
private @Nullable DigiplexSenderThread senderThread;
private final BlockingQueue<DigiplexRequest> sendQueue = new LinkedBlockingQueue<>();
private final SerialPortManager serialPortManager;
private final Set<DigiplexMessageHandler> handlers = ConcurrentHashMap.newKeySet();
@Nullable
private ScheduledFuture<?> reinitializeTask;
private AtomicLong messagesSent = new AtomicLong(0);
private AtomicLong responsesReceived = new AtomicLong(0);
private AtomicLong eventsReceived = new AtomicLong(0);
public DigiplexBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
}
@SuppressWarnings("null")
@Override
public void initialize() {
config = getConfigAs(DigiplexBridgeConfiguration.class);
if (config.port == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
return;
}
SerialPortIdentifier portId = serialPortManager.getIdentifier(config.port);
if (portId == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"No such port: " + config.port);
return;
}
try {
serialPort = initializeSerialPort(portId);
InputStream inputStream = serialPort.getInputStream();
OutputStream outputStream = serialPort.getOutputStream();
if (inputStream == null || outputStream == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Input/Output stream null");
return;
}
receiverThread = new DigiplexReceiverThread(inputStream);
senderThread = new DigiplexSenderThread(outputStream);
registerMessageHandler(new BridgeMessageHandler());
messagesSent.set(0);
responsesReceived.set(0);
eventsReceived.set(0);
receiverThread.start();
senderThread.start();
updateStatus(ThingStatus.ONLINE);
} catch (PortInUseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Port in use: " + config.port);
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Communication error: " + e.getMessage());
}
}
@SuppressWarnings("null")
private @Nullable SerialPort initializeSerialPort(SerialPortIdentifier portId)
throws PortInUseException, TooManyListenersException, UnsupportedCommOperationException {
SerialPort serialPort = portId.open(getThing().getUID().toString(), 2000);
serialPort.setSerialPortParams(config.baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
serialPort.enableReceiveThreshold(0);
serialPort.enableReceiveTimeout(1000);
// RXTX serial port library causes high CPU load
// Start event listener, which will just sleep and slow down event loop
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
return serialPort;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == RefreshType.REFRESH && isLinked(channelUID.getId())) {
switch (channelUID.getId()) {
case BRIDGE_MESSAGES_SENT:
updateState(BRIDGE_MESSAGES_SENT, new DecimalType(messagesSent.get()));
break;
case BRIDGE_RESPONSES_RECEIVED:
updateState(BRIDGE_RESPONSES_RECEIVED, new DecimalType(responsesReceived.get()));
break;
case BRIDGE_EVENTS_RECEIVED:
updateState(BRIDGE_EVENTS_RECEIVED, new DecimalType(eventsReceived.get()));
break;
}
}
}
public void sendRequest(DigiplexRequest request) {
sendQueue.add(request);
}
public void handleResponse(String message) {
DigiplexResponse response = DigiplexResponseResolver.resolveResponse(message);
handlers.forEach(visitor -> response.accept(visitor));
if (response instanceof AbstractEvent) {
updateState(BRIDGE_EVENTS_RECEIVED, new DecimalType(eventsReceived.incrementAndGet()));
} else {
updateState(BRIDGE_RESPONSES_RECEIVED, new DecimalType(responsesReceived.incrementAndGet()));
}
}
public void registerMessageHandler(DigiplexMessageHandler handler) {
handlers.add(handler);
}
public void unregisterMessageHandler(DigiplexMessageHandler handler) {
handlers.remove(handler);
}
/**
* Closes the connection to the PRT3 module.
*/
@SuppressWarnings("null")
@Override
public void dispose() {
stopThread(senderThread);
stopThread(receiverThread);
senderThread = null;
receiverThread = null;
if (serialPort != null) {
try {
InputStream inputStream = serialPort.getInputStream();
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
logger.debug("Error closing input stream", e);
}
try {
OutputStream outputStream = serialPort.getOutputStream();
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
logger.debug("Error closing output stream", e);
}
serialPort.close();
serialPort = null;
}
logger.info("Stopped Digiplex serial handler");
super.dispose();
}
private void stopThread(@Nullable Thread thread) {
if (thread != null) {
thread.interrupt();
try {
thread.join(1000);
} catch (InterruptedException e) {
}
}
}
public void handleCommunicationError() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
if (reinitializeTask == null) {
reinitializeTask = scheduler.schedule(() -> {
logger.info("Reconnecting to PRT3 device...");
thingUpdated(getThing());
reinitializeTask = null;
}, REINITIALIZE_DELAY, TimeUnit.MINUTES);
}
}
@Override
public void serialEvent(@Nullable SerialPortEvent arg0) {
try {
logger.trace("RXTX library CPU load workaround, sleep forever");
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ignored) {
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(DigiplexDiscoveryService.class);
}
private class BridgeMessageHandler implements DigiplexMessageHandler {
@Override
public void handleCommunicationStatus(CommunicationStatus response) {
if (response.success) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
@Override
public void handleTroubleEvent(TroubleEvent troubleEvent) {
if (troubleEvent.getAreaNo() == GLOBAL_AREA_NO) {
String channel = troubleEvent.getType().getBridgeChannel();
State state = OnOffType.from(troubleEvent.getStatus() == TroubleStatus.TROUBLE_STARTED);
updateState(channel, state);
}
}
}
private class DigiplexReceiverThread extends Thread {
private final Logger logger = LoggerFactory.getLogger(DigiplexReceiverThread.class);
private final InputStream stream;
DigiplexReceiverThread(InputStream stream) {
super("DigiplexReceiveThread");
this.stream = stream;
}
@Override
public void run() {
logger.debug("Receiver thread started");
while (!interrupted()) {
try {
Optional<String> message = readLineBlocking();
message.ifPresent(m -> {
logger.debug("message received: '{}'", m);
handleResponse(m);
});
if (messagesSent.get() - responsesReceived.get() > STALLED_MESSAGES_THRESHOLD) {
throw new IOException("PRT3 module is not responding!");
}
} catch (IOException e) {
handleCommunicationError();
break;
}
}
logger.debug("Receiver thread finished");
}
private Optional<String> readLineBlocking() throws IOException {
StringBuilder s = new StringBuilder();
while (true) {
int c = stream.read();
if (c == END_OF_STREAM) {
return Optional.empty();
}
if (c == END_OF_MESSAGE) {
break;
}
s.append((char) c);
}
return Optional.of(s.toString());
}
}
private class DigiplexSenderThread extends Thread {
private static final int SLEEP_TIME = 150;
private final Logger logger = LoggerFactory.getLogger(DigiplexSenderThread.class);
private OutputStream stream;
public DigiplexSenderThread(OutputStream stream) {
super("DigiplexSenderThread");
this.stream = stream;
}
@Override
public void run() {
logger.debug("Sender thread started");
while (!interrupted()) {
try {
DigiplexRequest request = sendQueue.take();
stream.write(request.getSerialMessage().getBytes());
stream.flush();
updateState(BRIDGE_MESSAGES_SENT, new DecimalType(messagesSent.incrementAndGet()));
logger.debug("message sent: '{}'", request.getSerialMessage().replaceAll("\r", ""));
Thread.sleep(SLEEP_TIME); // do not flood PRT3 with messages as it creates unpredictable responses
} catch (IOException e) {
handleCommunicationError();
break;
} catch (InterruptedException e) {
break;
}
}
logger.debug("Sender thread finished");
}
}
}

View File

@@ -0,0 +1,238 @@
/**
* 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.digiplex.internal.handler;
import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
import static org.openhab.binding.digiplex.internal.handler.TypeUtils.openClosedFromBoolean;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.digiplex.internal.DigiplexBindingConstants;
import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
import org.openhab.binding.digiplex.internal.communication.ZoneStatusRequest;
import org.openhab.binding.digiplex.internal.communication.ZoneStatusResponse;
import org.openhab.binding.digiplex.internal.communication.events.ZoneEvent;
import org.openhab.binding.digiplex.internal.communication.events.ZoneEventType;
import org.openhab.binding.digiplex.internal.communication.events.ZoneStatusEvent;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link DigiplexZoneHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Robert Michalak - Initial contribution
*/
@NonNullByDefault
public class DigiplexZoneHandler extends BaseThingHandler {
private @Nullable DigiplexBridgeHandler bridgeHandler;
private DigiplexZoneMessageHandler messageHandler = new DigiplexZoneMessageHandler();
private int zoneNo;
private int areaNo = 0; // not known at the beginning (protocol limitation)
private OpenClosedType status = OpenClosedType.CLOSED;
private StringType extendedStatus = new StringType("CLOSED");
private OpenClosedType alarm = OpenClosedType.CLOSED;
private OpenClosedType fireAlarm = OpenClosedType.CLOSED;
private OpenClosedType supervisionLost = OpenClosedType.CLOSED;
private OpenClosedType lowBattery = OpenClosedType.CLOSED;
private State lastTriggered = UnDefType.NULL;
public DigiplexZoneHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case ZONE_STATUS:
if (command == RefreshType.REFRESH) {
updateState(ZONE_STATUS, status);
}
break;
case ZONE_EXTENDED_STATUS:
if (command == RefreshType.REFRESH) {
updateState(ZONE_EXTENDED_STATUS, extendedStatus);
}
break;
case ZONE_ALARM:
if (command == RefreshType.REFRESH) {
updateState(ZONE_ALARM, alarm);
}
break;
case ZONE_FIRE_ALARM:
if (command == RefreshType.REFRESH) {
updateState(ZONE_FIRE_ALARM, fireAlarm);
}
break;
case ZONE_SUPERVISION_LOST:
if (command == RefreshType.REFRESH) {
updateState(ZONE_SUPERVISION_LOST, supervisionLost);
}
break;
case ZONE_LOW_BATTERY:
if (command == RefreshType.REFRESH) {
updateState(ZONE_LOW_BATTERY, lowBattery);
}
break;
case ZONE_LAST_TRIGGERED:
if (command == RefreshType.REFRESH) {
if (lastTriggered != UnDefType.NULL) {
updateState(ZONE_LAST_TRIGGERED, lastTriggered);
}
}
break;
}
}
@SuppressWarnings("null")
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
this.bridgeHandler = (DigiplexBridgeHandler) bridge.getHandler();
String nodeParm = getThing().getProperties().get(DigiplexBindingConstants.PROPERTY_ZONE_NO);
zoneNo = Integer.parseInt(nodeParm);
String areaParm = getThing().getProperties().get(DigiplexBindingConstants.PROPERTY_AREA_NO);
if (areaParm != null) {
areaNo = Integer.parseInt(areaParm);
}
bridgeHandler.registerMessageHandler(messageHandler);
DigiplexRequest request = new ZoneStatusRequest(zoneNo);
bridgeHandler.sendRequest(request);
updateStatus(ThingStatus.ONLINE);
}
private void updateChannels(boolean allChannels) {
updateState(ZONE_STATUS, status);
updateState(ZONE_EXTENDED_STATUS, extendedStatus);
if (lastTriggered != UnDefType.NULL) {
updateState(ZONE_LAST_TRIGGERED, lastTriggered);
}
if (allChannels) {
updateState(ZONE_ALARM, alarm);
updateState(ZONE_FIRE_ALARM, fireAlarm);
updateState(ZONE_SUPERVISION_LOST, supervisionLost);
updateState(ZONE_LOW_BATTERY, lowBattery);
}
}
@SuppressWarnings("null")
@Override
public void handleRemoval() {
if (messageHandler != null) {
bridgeHandler.unregisterMessageHandler(messageHandler);
}
super.handleRemoval();
}
@SuppressWarnings("null")
@Override
public void bridgeStatusChanged(ThingStatusInfo thingStatusInfo) {
if (thingStatusInfo.getStatus() == ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, thingStatusInfo.getStatusDetail());
} else if (thingStatusInfo.getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
DigiplexRequest request = new ZoneStatusRequest(zoneNo);
bridgeHandler.sendRequest(request);
}
}
private void updateAreaNo(int areaNo) {
if (this.areaNo == 0) {
this.areaNo = areaNo;
getThing().setProperty(DigiplexBindingConstants.PROPERTY_AREA_NO, Integer.toString(areaNo));
}
}
private class DigiplexZoneMessageHandler implements DigiplexMessageHandler {
@Override
public void handleZoneStatusResponse(ZoneStatusResponse response) {
if (response.zoneNo == DigiplexZoneHandler.this.zoneNo) {
status = response.status.toOpenClosedType();
extendedStatus = new StringType(response.status.toString());
alarm = openClosedFromBoolean(response.alarm);
fireAlarm = openClosedFromBoolean(response.fireAlarm);
supervisionLost = openClosedFromBoolean(response.supervisionLost);
lowBattery = openClosedFromBoolean(response.lowBattery);
updateChannels(true);
}
}
@Override
public void handleZoneStatusEvent(ZoneStatusEvent event) {
if (event.getZoneNo() == DigiplexZoneHandler.this.zoneNo) {
status = event.getStatus().toOpenClosedType();
extendedStatus = new StringType(event.getStatus().toString());
lastTriggered = new DateTimeType(ZonedDateTime.now());
updateChannels(false);
updateAreaNo(event.getAreaNo());
}
}
@Override
public void handleZoneEvent(ZoneEvent event) {
if (event.getZoneNo() == DigiplexZoneHandler.this.zoneNo) {
switch (event.getType()) {
case ALARM:
case ALARM_RESTORE:
alarm = openClosedFromBoolean(event.getType() == ZoneEventType.ALARM);
updateState(ZONE_ALARM, alarm);
break;
case FIRE_ALARM:
case FIRE_ALARM_RESTORE:
fireAlarm = openClosedFromBoolean(event.getType() == ZoneEventType.FIRE_ALARM);
updateState(ZONE_FIRE_ALARM, fireAlarm);
break;
case LOW_BATTERY:
case LOW_BATTERY_RESTORE:
lowBattery = openClosedFromBoolean(event.getType() == ZoneEventType.LOW_BATTERY);
updateState(ZONE_LOW_BATTERY, lowBattery);
break;
case SUPERVISION_TROUBLE:
case SUPERVISION_TROUBLE_RESTORE:
supervisionLost = openClosedFromBoolean(event.getType() == ZoneEventType.SUPERVISION_TROUBLE);
updateState(ZONE_SUPERVISION_LOST, supervisionLost);
break;
default:
break;
}
updateAreaNo(event.getAreaNo());
}
}
}
}

View File

@@ -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.digiplex.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.OpenClosedType;
/**
* Utility classes for type conversions
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class TypeUtils {
public static OpenClosedType openClosedFromBoolean(boolean value) {
return value ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="digiplex" 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>Digiplex/EVO Binding</name>
<description>Binding for Digiplex/EVO alarm systems (utilizing PRT3 module)</description>
<author>Robert Michalak</author>
</binding:binding>

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="digiplex"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="area" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Area</label>
<description>Area</description>
<channels>
<channel typeId="area_status" id="status"/>
<channel typeId="area_armed" id="armed"/>
<channel typeId="zone_in_memory" id="zone_in_memory"/>
<channel typeId="trouble" id="trouble"/>
<channel typeId="ready" id="ready"/>
<channel typeId="in_programming" id="in_programming"/>
<channel typeId="alarm" id="alarm"/>
<channel typeId="strobe" id="strobe"/>
<channel typeId="control" id="control"/>
</channels>
<config-description>
<parameter name="refreshPeriod" type="integer" unit="s">
<label>Refresh Time of Area Status</label>
<description>Controls how often area status will be refreshed from the PRT3 module</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="area_status">
<item-type>String</item-type>
<label>Area Status</label>
<description>Area Status as received from 'Area Status Request'</description>
<state readOnly="true">
<options>
<option value="DISARMED">Disarmed</option>
<option value="ARMED">Armed</option>
<option value="ARMED_FORCE">Force armed</option>
<option value="ARMED_STAY">Stay armed</option>
<option value="ARMED_INSTANT">Instant armed</option>
</options>
</state>
</channel-type>
<channel-type id="area_armed">
<item-type>Contact</item-type>
<label>Area Armed</label>
<description>Indicates if area is armed</description>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">Armed</option>
</options>
</state>
</channel-type>
<channel-type id="zone_in_memory">
<item-type>Contact</item-type>
<label>Zone in Memory</label>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">Zone in memory</option>
</options>
</state>
</channel-type>
<channel-type id="trouble">
<item-type>Contact</item-type>
<label>Trouble</label>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">Trouble</option>
</options>
</state>
</channel-type>
<channel-type id="ready">
<item-type>Contact</item-type>
<label>Area Ready</label>
<description>Indicates if area is ready (no open zones)</description>
<state readOnly="true">
<options>
<option value="CLOSED">Not ready</option>
<option value="OPEN">Ready</option>
</options>
</state>
</channel-type>
<channel-type id="in_programming">
<item-type>Contact</item-type>
<label>Area in Programming Mode</label>
<description>Indicates if area is in the programming mode</description>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">In programming mode</option>
</options>
</state>
</channel-type>
<channel-type id="alarm">
<item-type>Contact</item-type>
<label>Area in Alarm</label>
<description>Indicates if area is in alarm</description>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">Alarm</option>
</options>
</state>
</channel-type>
<channel-type id="strobe">
<item-type>Contact</item-type>
<label>Strobe</label>
<state readOnly="true">
<options>
<option value="CLOSED">Ok</option>
<option value="OPEN">Strobe</option>
</options>
</state>
</channel-type>
<channel-type id="control">
<item-type>String</item-type>
<label>Control Alarm System</label>
<description>Used to control area status. By reading its state one can check result of the last command sent to the
alarm system.</description>
<state>
<options>
<option value="OK">Ok</option>
<option value="FAIL">Fail</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="digiplex"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridge">
<label>Digiplex PRT3 Module</label>
<description>Digiplex PRT3 module with Serial Interface</description>
<channel-groups>
<channel-group typeId="statistics" id="statistics"/>
<channel-group typeId="troubles" id="troubles"/>
</channel-groups>
<config-description>
<parameter-group name="port">
<context>communication</context>
<label>Port Configuration</label>
<description></description>
</parameter-group>
<parameter name="port" type="text" required="true" groupName="port">
<label>Serial Port</label>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<description>Set the serial port used to access PRT3 device</description>
<default></default>
</parameter>
<parameter name="baudrate" type="integer" required="true" groupName="port">
<label>Baud Rate</label>
<context>serial-port</context>
<description>Set the serial port baud rate</description>
<default>2400</default>
<limitToOptions>true</limitToOptions>
<options>
<option value="2400">2400</option>
<option value="9600">9600</option>
<option value="19200">19200</option>
<option value="57600">57600</option>
</options>
</parameter>
</config-description>
</bridge-type>
<channel-group-type id="statistics">
<label>Statistics</label>
<description>Statistics of PRT3 communication</description>
<channels>
<channel typeId="messages_sent" id="messages_sent"/>
<channel typeId="responses_received" id="responses_received"/>
<channel typeId="events_received" id="events_received"/>
</channels>
</channel-group-type>
<channel-group-type id="troubles">
<label>Troubles</label>
<description>Problems reported by the alarm system</description>
<channels>
<channel typeId="tlm_trouble" id="tlm_trouble"/>
<channel typeId="ac_failure" id="ac_failure"/>
<channel typeId="battery_failure" id="battery_failure"/>
<channel typeId="aux_current_limit" id="aux_current_limit"/>
<channel typeId="bell_current_limit" id="bell_current_limit"/>
<channel typeId="bell_absent" id="bell_absent"/>
<channel typeId="clock_trouble" id="clock_trouble"/>
<channel typeId="global_fire_loop" id="global_fire_loop"/>
</channels>
</channel-group-type>
<channel-type id="messages_sent">
<item-type>Number</item-type>
<label>Messages Sent</label>
<description>Counts messages sent to the alarm system</description>
</channel-type>
<channel-type id="responses_received">
<item-type>Number</item-type>
<label>Responses Received</label>
<description>Counts responses received from the alarm system</description>
</channel-type>
<channel-type id="events_received">
<item-type>Number</item-type>
<label>Events Received</label>
<description>Counts events received from the alarm system</description>
</channel-type>
<channel-type id="tlm_trouble">
<item-type>Switch</item-type>
<label>Telephone Line</label>
<description>Reports telephone line failure</description>
<state readOnly="true">
<options>
<option value="ON">Failure</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="ac_failure">
<item-type>Switch</item-type>
<label>AC Line</label>
<description>Reports power line failure</description>
<category>Energy</category>
<state readOnly="true">
<options>
<option value="ON">Failure</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="battery_failure">
<item-type>Switch</item-type>
<label>Battery</label>
<description>Reports battery failure</description>
<category>LowBattery</category>
<state readOnly="true">
<options>
<option value="ON">Failure</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="aux_current_limit">
<item-type>Switch</item-type>
<label>AUX Current Limit</label>
<description>Auxiliary Outputs have exceeded their current limits</description>
<category>Energy</category>
<state readOnly="true">
<options>
<option value="ON">Exceeded</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="bell_current_limit">
<item-type>Switch</item-type>
<label>Bell Current Limit</label>
<description>Bell Output has exceeded its current limit</description>
<category>Energy</category>
<state readOnly="true">
<options>
<option value="ON">Exceeded</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="bell_absent">
<item-type>Switch</item-type>
<label>Bell Status</label>
<description>Reports if bell is absent</description>
<state readOnly="true">
<options>
<option value="ON">Absent</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="clock_trouble">
<item-type>Switch</item-type>
<label>Clock</label>
<description>Reports if clock is not malfunctioning</description>
<category>Time</category>
<state readOnly="true">
<options>
<option value="ON">Failure</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
<channel-type id="global_fire_loop">
<item-type>Switch</item-type>
<label>Global Fire Loop</label>
<description>Reports if fire loop has been triggered</description>
<category>Smoke</category>
<state readOnly="true">
<options>
<option value="ON">Fire!</option>
<option value="OFF">OK</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="digiplex"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="zone" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Zone</label>
<description>Zone</description>
<channels>
<channel typeId="status" id="status"/>
<channel typeId="extended_status" id="extended_status"/>
<channel typeId="alarm" id="alarm"/>
<channel typeId="fire_alarm" id="fire_alarm"/>
<channel typeId="supervision_lost" id="supervision_lost"/>
<channel typeId="low_battery" id="low_battery"/>
<channel typeId="last_triggered" id="last_triggered"/>
</channels>
</thing-type>
<channel-type id="status">
<item-type>Contact</item-type>
<label>Zone Status</label>
<description>Zone Status (Open/Closed)</description>
<state readOnly="true">
<options>
<option value="CLOSED">Closed</option>
<option value="OPEN">Open</option>
</options>
</state>
</channel-type>
<channel-type id="extended_status">
<item-type>String</item-type>
<label>Extended Zone Status</label>
<description>Indicates actual zone state as a string</description>
<state readOnly="true">
<options>
<option value="CLOSED">Closed</option>
<option value="OPEN">Open</option>
<option value="TAMPERED">Tampered</option>
<option value="FIRE_LOOP_TROUBLE">Fire Loop Trouble</option>
</options>
</state>
</channel-type>
<channel-type id="alarm">
<item-type>Contact</item-type>
<label>Alarm Triggered</label>
<description>Indicates if zone is in alarm</description>
<state readOnly="true">
<options>
<option value="CLOSED">No</option>
<option value="OPEN">Yes</option>
</options>
</state>
</channel-type>
<channel-type id="fire_alarm">
<item-type>Contact</item-type>
<label>Fire Alarm Triggered</label>
<description>Indicates if zone is in fire alarm</description>
<state readOnly="true">
<options>
<option value="CLOSED">No</option>
<option value="OPEN">Yes</option>
</options>
</state>
</channel-type>
<channel-type id="supervision_lost">
<item-type>Contact</item-type>
<label>Supervision Lost</label>
<description>Indicates if zone has lost a supervision</description>
<state readOnly="true">
<options>
<option value="CLOSED">No</option>
<option value="OPEN">Yes</option>
</options>
</state>
</channel-type>
<channel-type id="low_battery">
<item-type>Contact</item-type>
<label>Low Battery Warning</label>
<description>Indicates if zone is low on battery</description>
<state readOnly="true">
<options>
<option value="CLOSED">No</option>
<option value="OPEN">Yes</option>
</options>
</state>
</channel-type>
<channel-type id="last_triggered">
<item-type>DateTime</item-type>
<label>Last Triggered Time</label>
<description>Indicates when the zone has been triggered for the last time</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>