added migrated 2.x add-ons

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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);
}
}
}

View File

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

View File

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

View File

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

View File

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