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