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