[elroconnects] New binding for Elro Connects (#11189)

* Adjustments after review.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Fix pom.xml formatting.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Some fixes and removed redundant null checks.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Proper thread naming.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Adjust brand name capitalization. Some README adjustments.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Fix format issue.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Fix threadname.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Update development cycle

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Review fixes

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Avoid communication restart when disposing

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Update CODEOWNERS

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Code review.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Background discovery

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Fix formatting

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Create i18n properties file

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Add IP Adress parameter

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>

* Moved hostname resolving out of initialize

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege
2022-03-20 20:18:57 +01:00
committed by GitHub
parent 5f9096b63e
commit 7c29e4d565
34 changed files with 3545 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.elroconnects-${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-elroconnects" description="ELRO Connects Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.elroconnects/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ElroConnectsBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsBindingConstants {
private static final String BINDING_ID = "elroconnects";
// List of all Thing Type UIDs
public static final String TYPE_CONNECTOR = "connector";
public static final ThingTypeUID THING_TYPE_CONNECTOR = new ThingTypeUID(BINDING_ID, TYPE_CONNECTOR);
public static final String TYPE_SMOKEALARM = "smokealarm";
public static final String TYPE_COALARM = "coalarm";
public static final String TYPE_HEATALARM = "heatalarm";
public static final String TYPE_WATERALARM = "wateralarm";
public static final String TYPE_ENTRYSENSOR = "entrysensor";
public static final String TYPE_MOTIONSENSOR = "motionsensor";
public static final String TYPE_THSENSOR = "temperaturesensor";
public static final String TYPE_POWERSOCKET = "powersocket";
public static final ThingTypeUID THING_TYPE_SMOKEALARM = new ThingTypeUID(BINDING_ID, TYPE_SMOKEALARM);
public static final ThingTypeUID THING_TYPE_COALARM = new ThingTypeUID(BINDING_ID, TYPE_COALARM);
public static final ThingTypeUID THING_TYPE_HEATALARM = new ThingTypeUID(BINDING_ID, TYPE_HEATALARM);
public static final ThingTypeUID THING_TYPE_WATERALARM = new ThingTypeUID(BINDING_ID, TYPE_WATERALARM);
public static final ThingTypeUID THING_TYPE_ENTRYSENSOR = new ThingTypeUID(BINDING_ID, TYPE_ENTRYSENSOR);
public static final ThingTypeUID THING_TYPE_MOTIONSENSOR = new ThingTypeUID(BINDING_ID, TYPE_MOTIONSENSOR);
public static final ThingTypeUID THING_TYPE_THSENSOR = new ThingTypeUID(BINDING_ID, TYPE_THSENSOR);
public static final ThingTypeUID THING_TYPE_POWERSOCKET = new ThingTypeUID(BINDING_ID, TYPE_POWERSOCKET);
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_TYPES_UIDS = Set.of(THING_TYPE_CONNECTOR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CONNECTOR,
THING_TYPE_SMOKEALARM, THING_TYPE_COALARM, THING_TYPE_HEATALARM, THING_TYPE_WATERALARM,
THING_TYPE_ENTRYSENSOR, THING_TYPE_MOTIONSENSOR, THING_TYPE_THSENSOR, THING_TYPE_POWERSOCKET);
// List of all Channel ids
public static final String SCENE = "scene";
public static final String BATTERY_LEVEL = "battery";
public static final String LOW_BATTERY = "lowBattery";
public static final String MUTE_ALARM = "muteAlarm";
public static final String TEST_ALARM = "testAlarm";
public static final String ENTRY = "entry";
public static final String MOTION = "motion";
public static final String POWER_STATE = "powerState";
public static final String TEMPERATURE = "temperature";
public static final String HUMIDITY = "humidity";
public static final String SMOKE_ALARM = "smokeAlarm";
public static final String CO_ALARM = "coAlarm";
public static final String HEAT_ALARM = "heatAlarm";
public static final String WATER_ALARM = "waterAlarm";
public static final String ENTRY_ALARM = "entryAlarm";
public static final String MOTION_ALARM = "motionAlarm";
// Config properties
public static final String CONFIG_CONNECTOR_ID = "connectorId";
public static final String CONFIG_REFRESH_INTERVAL_S = "refreshInterval";
public static final String CONFIG_DEVICE_ID = "deviceId";
public static final String CONFIG_DEVICE_TYPE = "deviceType";
// ELRO cmd constants
public static final int ELRO_DEVICE_CONTROL = 101;
public static final int ELRO_GET_DEVICE_NAME = 14;
public static final int ELRO_GET_DEVICE_STATUSES = 15;
public static final int ELRO_REC_DEVICE_NAME = 17;
public static final int ELRO_REC_DEVICE_STATUS = 19;
public static final int ELRO_SYNC_DEVICES = 29;
public static final int ELRO_SELECT_SCENE = 106;
public static final int ELRO_GET_SCENE = 18;
public static final int ELRO_REC_SCENE = 28;
public static final int ELRO_REC_SCENE_NAME = 26;
public static final int ELRO_REC_SCENE_TYPE = 27;
public static final int ELRO_SYNC_SCENES = 131;
public static final int ELRO_REC_ALARM = 25;
public static final int ELRO_IGNORE_YES_NO = 11;
// ELRO device types
public static enum ElroDeviceType {
ENTRY_SENSOR,
CO_ALARM,
CXSM_ALARM,
MOTION_SENSOR,
SM_ALARM,
POWERSOCKET,
THERMAL_ALARM,
TH_SENSOR,
WT_ALARM,
DEFAULT
}
public static final Map<ElroDeviceType, ThingTypeUID> THING_TYPE_MAP = Map.ofEntries(
Map.entry(ElroDeviceType.ENTRY_SENSOR, THING_TYPE_ENTRYSENSOR),
Map.entry(ElroDeviceType.CO_ALARM, THING_TYPE_COALARM),
Map.entry(ElroDeviceType.CXSM_ALARM, THING_TYPE_SMOKEALARM),
Map.entry(ElroDeviceType.MOTION_SENSOR, THING_TYPE_MOTIONSENSOR),
Map.entry(ElroDeviceType.SM_ALARM, THING_TYPE_SMOKEALARM),
Map.entry(ElroDeviceType.THERMAL_ALARM, THING_TYPE_HEATALARM),
Map.entry(ElroDeviceType.WT_ALARM, THING_TYPE_WATERALARM),
Map.entry(ElroDeviceType.TH_SENSOR, THING_TYPE_THSENSOR),
Map.entry(ElroDeviceType.POWERSOCKET, THING_TYPE_POWERSOCKET));
public static final Set<String> T_ENTRY_SENSOR = Set.of("0101", "1101", "2101");
public static final Set<String> T_POWERSOCKET = Set.of("0200", "1200", "2200");
public static final Set<String> T_MOTION_SENSOR = Set.of("0100", "1100", "2100");
public static final Set<String> T_CO_ALARM = Set.of("0000", "1000", "2000", "0008", "1008", "2008", "000E", "100E",
"200E");
public static final Set<String> T_SM_ALARM = Set.of("0001", "1001", "2001", "0009", "1009", "2009", "000F", "100F",
"200F");
public static final Set<String> T_WT_ALARM = Set.of("0004", "1004", "2004", "000C", "100C", "200C", "0012", "1012",
"2012");
public static final Set<String> T_TH_SENSOR = Set.of("0102", "1102", "2102");
public static final Set<String> T_CXSM_ALARM = Set.of("0005", "1109", "2109", "000D", "100D", "200D", "0013",
"1013", "2013");
public static final Set<String> T_THERMAL_ALARM = Set.of("0003", "1003", "2003", "000B", "100B", "200B", "0011",
"1011", "2011");
public static final Map<ElroDeviceType, Set<String>> DEVICE_TYPE_MAP = Map.ofEntries(
Map.entry(ElroDeviceType.ENTRY_SENSOR, T_ENTRY_SENSOR), Map.entry(ElroDeviceType.CO_ALARM, T_CO_ALARM),
Map.entry(ElroDeviceType.CXSM_ALARM, T_CXSM_ALARM),
Map.entry(ElroDeviceType.MOTION_SENSOR, T_MOTION_SENSOR), Map.entry(ElroDeviceType.SM_ALARM, T_SM_ALARM),
Map.entry(ElroDeviceType.POWERSOCKET, T_POWERSOCKET),
Map.entry(ElroDeviceType.THERMAL_ALARM, T_THERMAL_ALARM), Map.entry(ElroDeviceType.TH_SENSOR, T_TH_SENSOR),
Map.entry(ElroDeviceType.WT_ALARM, T_WT_ALARM));
public static final Map<String, ElroDeviceType> TYPE_MAP = DEVICE_TYPE_MAP.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
// ELRO device status
public static enum ElroDeviceStatus {
NORMAL,
TRIGGERED,
TEST,
SILENCE,
OPEN,
FAULT,
UNDEF
}
// Listener threadname prefix
public static final String THREAD_NAME_PREFIX = "binding-";
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.openhab.core.types.StateDescription;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Mark Herwege - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, ElroConnectsDynamicStateDescriptionProvider.class })
@NonNullByDefault
public class ElroConnectsDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDynamicStateDescriptionProvider.class);
private final Map<ChannelUID, @Nullable StateDescription> descriptions = new ConcurrentHashMap<>();
public void setDescription(ChannelUID channelUID, @Nullable StateDescription description) {
logger.debug("Adding command description for channel {}", channelUID);
descriptions.put(channelUID, description);
}
public void removeAllDescriptions() {
logger.debug("Removing all command descriptions");
descriptions.clear();
}
@Override
public @Nullable StateDescription getStateDescription(Channel channel,
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
StateDescription description = descriptions.get(channel.getUID());
return description;
}
@Deactivate
public void deactivate() {
descriptions.clear();
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsCOAlarmHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsEntrySensorHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsHeatAlarmHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsMotionSensorHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsPowerSocketHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsSmokeAlarmHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsWaterAlarmHandler;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ElroConnectsHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.elroconnects", service = ThingHandlerFactory.class)
public class ElroConnectsHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private @NonNullByDefault({}) ElroConnectsDynamicStateDescriptionProvider dynamicStateDescriptionProvider;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
switch (thing.getThingTypeUID().getId()) {
case TYPE_CONNECTOR:
return new ElroConnectsBridgeHandler((Bridge) thing, networkAddressService,
dynamicStateDescriptionProvider);
case TYPE_SMOKEALARM:
return new ElroConnectsSmokeAlarmHandler(thing);
case TYPE_WATERALARM:
return new ElroConnectsWaterAlarmHandler(thing);
case TYPE_COALARM:
return new ElroConnectsCOAlarmHandler(thing);
case TYPE_HEATALARM:
return new ElroConnectsHeatAlarmHandler(thing);
case TYPE_ENTRYSENSOR:
return new ElroConnectsEntrySensorHandler(thing);
case TYPE_MOTIONSENSOR:
return new ElroConnectsMotionSensorHandler(thing);
case TYPE_POWERSOCKET:
return new ElroConnectsPowerSocketHandler(thing);
case TYPE_THSENSOR:
return new ElroConnectsDeviceHandler(thing);
default:
return null;
}
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
@Reference
protected void setDynamicStateDescriptionProvider(
ElroConnectsDynamicStateDescriptionProvider dynamicStateDescriptionProver) {
this.dynamicStateDescriptionProvider = dynamicStateDescriptionProver;
}
protected void unsetDynamicStateDescriptionProvider(
ElroConnectsDynamicStateDescriptionProvider dynamicStateDescriptionProver) {
this.dynamicStateDescriptionProvider = null;
}
}

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.elroconnects.internal.util.ElroConnectsUtil;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ElroConnectsMessage} represents the JSON messages exchanged with the ELRO Connects K1 Connector. This
* class is used to serialize/deserialize the messages.
*
* @author Mark Herwege - Initial contribution
*/
@SuppressWarnings("unused") // Suppress warning on serialized fields
@NonNullByDefault
public class ElroConnectsMessage {
private static class Data {
private int cmdId;
@SerializedName(value = "device_ID")
private @Nullable Integer deviceId;
@SerializedName(value = "device_name")
private @Nullable String deviceName;
@SerializedName(value = "device_status")
private @Nullable String deviceStatus;
@SerializedName(value = "answer_content")
private @Nullable String answerContent;
@SerializedName(value = "sence_group")
private @Nullable Integer sceneGroup;
@SerializedName(value = "scene_type")
private @Nullable Integer sceneType;
@SerializedName(value = "scene_content")
private @Nullable String sceneContent;
}
private static class Params {
private String devTid = "";
private String ctrlKey = "";
private Data data = new Data();
}
private int msgId;
private String action = "appSend";
private Params params = new Params();
public ElroConnectsMessage(int msgId, String devTid, String ctrlKey, int cmdId) {
this.msgId = msgId;
params.devTid = devTid;
params.ctrlKey = ctrlKey;
params.data.cmdId = cmdId;
}
public ElroConnectsMessage(int msgId) {
this.msgId = msgId;
action = "heartbeat";
}
public ElroConnectsMessage withDeviceStatus(String deviceStatus) {
params.data.deviceStatus = deviceStatus;
return this;
}
public ElroConnectsMessage withDeviceId(int deviceId) {
params.data.deviceId = deviceId;
return this;
}
public ElroConnectsMessage withSceneType(int sceneType) {
params.data.sceneType = sceneType;
return this;
}
public ElroConnectsMessage withSceneGroup(int sceneGroup) {
params.data.sceneGroup = sceneGroup;
return this;
}
public ElroConnectsMessage withSceneContent(String sceneContent) {
params.data.sceneContent = sceneContent;
return this;
}
public ElroConnectsMessage withAnswerContent(String answerContent) {
params.data.answerContent = answerContent;
return this;
}
public int getMsgId() {
return msgId;
}
public String getAction() {
return action;
}
public int getCmdId() {
return params.data.cmdId;
}
public String getDeviceStatus() {
return ElroConnectsUtil.stringOrEmpty(params.data.deviceStatus);
}
public int getSceneGroup() {
return ElroConnectsUtil.intOrZero(params.data.sceneGroup);
}
public int getSceneType() {
return ElroConnectsUtil.intOrZero(params.data.sceneType);
}
public String getSceneContent() {
return ElroConnectsUtil.stringOrEmpty(params.data.sceneContent);
}
public String getAnswerContent() {
return ElroConnectsUtil.stringOrEmpty(params.data.answerContent);
}
public int getDeviceId() {
return ElroConnectsUtil.intOrZero(params.data.deviceId);
}
public String getDeviceName() {
return ElroConnectsUtil.stringOrEmpty(params.data.deviceName);
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
/**
* The {@link ElroConnectsDevice} is an abstract class representing all basic properties for ELRO Connects devices.
* Concrete subclasses will contain specific logic for each device type.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public abstract class ElroConnectsDevice {
// minimum data to create an instance of the class
protected int deviceId;
protected ElroConnectsBridgeHandler bridge;
protected volatile String deviceName = "";
protected volatile String deviceType = "";
protected volatile String deviceStatus = "";
protected volatile Map<String, ElroDeviceStatus> statusMap = Map.of();
/**
* Create a new instance of a subclass of {@link ElroConnectsDevice}. These instances get created by an instance
* {@link ElroConnectsBridgeHandler}. The deviceId will be set on creation. Other fields will be set as and when the
* information is received from the K1 hub.
*
* @param deviceId
* @param bridge
*/
public ElroConnectsDevice(int deviceId, ElroConnectsBridgeHandler bridge) {
this.deviceId = deviceId;
this.bridge = bridge;
}
/**
* Get the current status of the device.
*
* @return status
*/
protected ElroDeviceStatus getStatus() {
String deviceStatus = this.deviceStatus;
ElroDeviceStatus elroStatus = ElroDeviceStatus.UNDEF;
if (deviceStatus.length() >= 6) {
elroStatus = statusMap.getOrDefault(deviceStatus.substring(4, 6), ElroDeviceStatus.UNDEF);
}
return elroStatus;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public void setDeviceStatus(String deviceStatus) {
this.deviceStatus = deviceStatus;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceType() {
return deviceType;
}
/**
* Retrieve the {@link ElroConnectsDeviceHandler} for device.
*
* @return handler for the device.
*/
protected @Nullable ElroConnectsDeviceHandler getHandler() {
return bridge.getDeviceHandler(deviceId);
}
/**
* Update all {@link ElroConnectsDeviceHandler} channel states with information received from the device. This
* method needs to be implemented in the concrete subclass when any state updates are received from the device.
*/
public abstract void updateState();
/**
* Send alarm test message to the device. This method is called from the {@link ElroConnectsDeviceHandler}. The
* method needs to be implemented in the concrete subclass when test alarms are supported.
*/
public abstract void testAlarm();
/**
* Send alarm mute message to the device. This method is called from the {@link ElroConnectsDeviceHandler}. The
* method needs to be implemented in the concrete subclass when alarm muting is supported.
*/
public abstract void muteAlarm();
/**
* Send state switch message to the device. This method is called from the {@link ElroConnectsDeviceHandler}. The
* method needs to be implemented in the concrete subclass when switching the state on/off is supported.
*/
public abstract void switchState(boolean state);
}

View File

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDeviceCxsmAlarm} is representing an ELRO Connects Cxsm Alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceCxsmAlarm extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDeviceCxsmAlarm.class);
// device states
private static final String STAT_ALARM = "19";
private static final String STAT_TEST = "17";
private static final String STAT_FAULT = "12";
private static final String STAT_SILENCE_1 = "1B";
private static final String STAT_SILENCE_2 = "15";
private static final String STAT_NORMAL = "AA";
private static final Set<String> T_ALARM = Set.of(STAT_ALARM);
private static final Set<String> T_TEST = Set.of(STAT_TEST);
private static final Set<String> T_FAULT = Set.of(STAT_FAULT);
private static final Set<String> T_SILENCE = Set.of(STAT_SILENCE_1, STAT_SILENCE_2);
private static final Set<String> T_NORMAL = Set.of(STAT_NORMAL);
private static final Map<ElroDeviceStatus, Set<String>> DEVICE_STATUS_MAP = Map.ofEntries(
Map.entry(ElroDeviceStatus.NORMAL, T_NORMAL), Map.entry(ElroDeviceStatus.TRIGGERED, T_ALARM),
Map.entry(ElroDeviceStatus.TEST, T_TEST), Map.entry(ElroDeviceStatus.SILENCE, T_SILENCE),
Map.entry(ElroDeviceStatus.FAULT, T_FAULT));
// device commands
private static final String CMD_TEST = "17000000";
private static final String CMD_SILENCE_1 = "1B000000";
private static final String CMD_SILENCE_2 = "15000000";
public ElroConnectsDeviceCxsmAlarm(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
statusMap = DEVICE_STATUS_MAP.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void muteAlarm() {
try {
ElroDeviceStatus elroStatus = getStatus();
if (ElroDeviceStatus.FAULT.equals(elroStatus)) {
bridge.deviceControl(deviceId, CMD_SILENCE_2);
} else {
bridge.deviceControl(deviceId, CMD_SILENCE_1);
}
} catch (IOException e) {
logger.debug("Failed to control device: {}", e.getMessage());
}
}
@Override
public void testAlarm() {
try {
ElroDeviceStatus elroStatus = getStatus();
if (!(ElroDeviceStatus.TRIGGERED.equals(elroStatus) || ElroDeviceStatus.TEST.equals(elroStatus))) {
bridge.deviceControl(deviceId, CMD_TEST);
}
} catch (IOException e) {
logger.debug("Failed to control device: {}", e.getMessage());
}
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
ElroDeviceStatus elroStatus = getStatus();
int batteryLevel = 0;
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() >= 6) {
batteryLevel = Integer.parseInt(deviceStatus.substring(2, 4), 16);
} else {
elroStatus = ElroDeviceStatus.FAULT;
logger.debug("Could not decode device status: {}", deviceStatus);
}
switch (elroStatus) {
case UNDEF:
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Device " + deviceId + " is not syncing with K1 hub");
break;
case FAULT:
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Device " + deviceId + " has a fault");
break;
default:
handler.updateState(BATTERY_LEVEL, new DecimalType(batteryLevel));
handler.updateState(LOW_BATTERY, (batteryLevel < 15) ? OnOffType.ON : OnOffType.OFF);
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void switchState(boolean state) {
// nothing
}
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDeviceEntrySensor} is representing a generic ELRO Connects Entry Sensor device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceEntrySensor extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDeviceEntrySensor.class);
// device states
private static final String STAT_OPEN = "55";
private static final String STAT_STILL_OPEN = "66";
private static final String STAT_FAULT = "11";
private static final String STAT_NORMAL = "AA";
private static final Set<String> T_OPEN = Set.of(STAT_OPEN, STAT_STILL_OPEN);
private static final Set<String> T_FAULT = Set.of(STAT_FAULT);
private static final Set<String> T_NORMAL = Set.of(STAT_NORMAL);
private static final Map<ElroDeviceStatus, Set<String>> DEVICE_STATUS_MAP = Map.ofEntries(
Map.entry(ElroDeviceStatus.NORMAL, T_NORMAL), Map.entry(ElroDeviceStatus.OPEN, T_OPEN),
Map.entry(ElroDeviceStatus.FAULT, T_FAULT));
public ElroConnectsDeviceEntrySensor(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
statusMap = DEVICE_STATUS_MAP.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
ElroDeviceStatus elroStatus = getStatus();
int batteryLevel = 0;
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() >= 6) {
batteryLevel = Integer.parseInt(deviceStatus.substring(2, 4), 16);
} else {
elroStatus = ElroDeviceStatus.FAULT;
logger.debug("Could not decode device status: {}", deviceStatus);
}
switch (elroStatus) {
case UNDEF:
handler.updateState(ENTRY, UnDefType.UNDEF);
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Device " + deviceId + " is not syncing with K1 hub");
break;
case FAULT:
handler.updateState(ENTRY, UnDefType.UNDEF);
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Device " + deviceId + " has a fault");
break;
default:
handler.updateState(ENTRY,
ElroDeviceStatus.OPEN.equals(elroStatus) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
handler.updateState(BATTERY_LEVEL, new DecimalType(batteryLevel));
handler.updateState(LOW_BATTERY, (batteryLevel < 15) ? OnOffType.ON : OnOffType.OFF);
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void testAlarm() {
// nothing
}
@Override
public void muteAlarm() {
// nothing
}
@Override
public void switchState(boolean state) {
// nothing
}
}

View File

@@ -0,0 +1,134 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDeviceGenericAlarm} is representing a generic ELRO Connects Alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceGenericAlarm extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDeviceGenericAlarm.class);
// device states
private static final String STAT_ALARM = "55";
private static final String STAT_TEST = "BB";
private static final String STAT_FAULT = "11";
private static final String STAT_SILENCE = "50";
private static final String STAT_NORMAL = "AA";
private static final Set<String> T_ALARM = Set.of(STAT_ALARM);
private static final Set<String> T_TEST = Set.of(STAT_TEST);
private static final Set<String> T_FAULT = Set.of(STAT_FAULT);
private static final Set<String> T_SILENCE = Set.of(STAT_SILENCE);
private static final Set<String> T_NORMAL = Set.of(STAT_NORMAL);
private static final Map<ElroDeviceStatus, Set<String>> DEVICE_STATUS_MAP = Map.ofEntries(
Map.entry(ElroDeviceStatus.NORMAL, T_NORMAL), Map.entry(ElroDeviceStatus.TRIGGERED, T_ALARM),
Map.entry(ElroDeviceStatus.TEST, T_TEST), Map.entry(ElroDeviceStatus.SILENCE, T_SILENCE),
Map.entry(ElroDeviceStatus.FAULT, T_FAULT));
// device commands
protected static final String CMD_TEST = "BB000000";
protected static final String CMD_SILENCE = "50000000";
public ElroConnectsDeviceGenericAlarm(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
statusMap = DEVICE_STATUS_MAP.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void muteAlarm() {
try {
bridge.deviceControl(deviceId, CMD_SILENCE);
} catch (IOException e) {
logger.debug("Failed to control device: {}", e.getMessage());
}
}
@Override
public void testAlarm() {
try {
ElroDeviceStatus elroStatus = getStatus();
if (!(ElroDeviceStatus.TRIGGERED.equals(elroStatus) || ElroDeviceStatus.TEST.equals(elroStatus))) {
bridge.deviceControl(deviceId, CMD_TEST);
}
} catch (IOException e) {
logger.debug("Failed to control device: {}", e.getMessage());
}
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
ElroDeviceStatus elroStatus = getStatus();
int batteryLevel = 0;
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() >= 6) {
batteryLevel = Integer.parseInt(deviceStatus.substring(2, 4), 16);
} else {
elroStatus = ElroDeviceStatus.FAULT;
logger.debug("Could not decode device status: {}", deviceStatus);
}
switch (elroStatus) {
case UNDEF:
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Device " + deviceId + " is not syncing with K1 hub");
break;
case FAULT:
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Device " + deviceId + " has a fault");
break;
default:
handler.updateState(BATTERY_LEVEL, new DecimalType(batteryLevel));
handler.updateState(LOW_BATTERY, (batteryLevel < 15) ? OnOffType.ON : OnOffType.OFF);
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void switchState(boolean state) {
// nothing
}
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDeviceMotionSensor} is representing an ELRO Connects Motion Sensor device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceMotionSensor extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDeviceMotionSensor.class);
// device states
private static final String STAT_TRIGGERED = "55";
private static final String STAT_TEARED = "A0";
private static final String STAT_FAULT = "11";
private static final String STAT_NORMAL = "AA";
private static final Set<String> T_TRIGGERED = Set.of(STAT_TEARED, STAT_TRIGGERED);
private static final Set<String> T_FAULT = Set.of(STAT_FAULT);
private static final Set<String> T_NORMAL = Set.of(STAT_NORMAL);
private static final Map<ElroDeviceStatus, Set<String>> DEVICE_STATUS_MAP = Map.ofEntries(
Map.entry(ElroDeviceStatus.NORMAL, T_NORMAL), Map.entry(ElroDeviceStatus.TRIGGERED, T_TRIGGERED),
Map.entry(ElroDeviceStatus.FAULT, T_FAULT));
public ElroConnectsDeviceMotionSensor(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
statusMap = DEVICE_STATUS_MAP.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
ElroDeviceStatus elroStatus = getStatus();
int batteryLevel = 0;
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() >= 6) {
batteryLevel = Integer.parseInt(deviceStatus.substring(2, 4), 16);
} else {
elroStatus = ElroDeviceStatus.FAULT;
logger.debug("Could not decode device status: {}", deviceStatus);
}
switch (elroStatus) {
case UNDEF:
handler.updateState(MOTION, UnDefType.UNDEF);
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Device " + deviceId + " is not syncing with K1 hub");
break;
case FAULT:
handler.updateState(MOTION, UnDefType.UNDEF);
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Device " + deviceId + " has a fault");
break;
default:
handler.updateState(MOTION,
ElroDeviceStatus.TRIGGERED.equals(elroStatus) ? OnOffType.ON : OnOffType.OFF);
handler.updateState(BATTERY_LEVEL, new DecimalType(batteryLevel));
handler.updateState(LOW_BATTERY, (batteryLevel < 15) ? OnOffType.ON : OnOffType.OFF);
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void testAlarm() {
// nothing
}
@Override
public void muteAlarm() {
// nothing
}
@Override
public void switchState(boolean state) {
// nothing
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.POWER_STATE;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDevicePowerSocket} is representing an ELRO Connects power socket device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDevicePowerSocket extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDevicePowerSocket.class);
// device states
private static final String STAT_ON = "00";
private static final String STAT_OFF = "01";
// device commands
protected static final String CMD_OFF = "0101FFFF";
protected static final String CMD_ON = "0100FFFF";
public ElroConnectsDevicePowerSocket(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
}
@Override
public void switchState(boolean state) {
try {
bridge.deviceControl(deviceId, state ? CMD_ON : CMD_OFF);
} catch (IOException e) {
logger.debug("Failed to control device: {}", e.getMessage());
}
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() < 6) {
logger.debug("Could not decode device status: {}", deviceStatus);
return;
}
String status = deviceStatus.substring(4, 6);
State state = STAT_ON.equals(status) ? OnOffType.ON
: (STAT_OFF.equals(status) ? OnOffType.OFF : UnDefType.UNDEF);
handler.updateState(POWER_STATE, state);
if (UnDefType.UNDEF.equals(state)) {
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Device " + deviceId + " is not syncing with K1 hub");
} else {
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void testAlarm() {
// nothing
}
@Override
public void muteAlarm() {
// nothing
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.devices;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceStatus;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsDeviceHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDeviceTemperatureSensor} is representing an ELRO Connects temperature and humidity sensor
* device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceTemperatureSensor extends ElroConnectsDevice {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDeviceTemperatureSensor.class);
public ElroConnectsDeviceTemperatureSensor(int deviceId, ElroConnectsBridgeHandler bridge) {
super(deviceId, bridge);
}
@Override
public void updateState() {
ElroConnectsDeviceHandler handler = getHandler();
if (handler == null) {
return;
}
ElroDeviceStatus elroStatus = ElroDeviceStatus.NORMAL;
int batteryLevel = 0;
int temperature = 0;
int humidity = 0;
String deviceStatus = this.deviceStatus;
if (deviceStatus.length() >= 8) {
batteryLevel = Integer.parseInt(deviceStatus.substring(2, 4), 16);
temperature = Byte.parseByte(deviceStatus.substring(4, 6), 16);
humidity = Integer.parseInt(deviceStatus.substring(6, 8));
} else {
elroStatus = ElroDeviceStatus.FAULT;
logger.debug("Could not decode device status: {}", deviceStatus);
}
switch (elroStatus) {
case FAULT:
handler.updateState(BATTERY_LEVEL, UnDefType.UNDEF);
handler.updateState(LOW_BATTERY, UnDefType.UNDEF);
handler.updateState(TEMPERATURE, UnDefType.UNDEF);
handler.updateState(HUMIDITY, UnDefType.UNDEF);
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Device " + deviceId + " has a fault");
break;
default:
handler.updateState(BATTERY_LEVEL, new DecimalType(batteryLevel));
handler.updateState(LOW_BATTERY, (batteryLevel < 15) ? OnOffType.ON : OnOffType.OFF);
handler.updateState(TEMPERATURE, new QuantityType<>(temperature, CELSIUS));
handler.updateState(HUMIDITY, new QuantityType<>(humidity, Units.PERCENT));
handler.updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void testAlarm() {
// nothing
}
@Override
public void muteAlarm() {
// nothing
}
@Override
public void switchState(boolean state) {
// nothing
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.discovery;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
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.elroconnects.internal.ElroConnectsBindingConstants;
import org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceType;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.binding.elroconnects.internal.handler.ElroConnectsBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ElroConnectsDiscoveryService} discovers devices connected to the ELRO Connects K1 Controller.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsDiscoveryService.class);
private @Nullable ElroConnectsBridgeHandler bridgeHandler;
private static final int TIMEOUT_SECONDS = 5;
private static final int REFRESH_INTERVAL_SECONDS = 60;
private @Nullable ScheduledFuture<?> discoveryJob;
public ElroConnectsDiscoveryService() {
super(ElroConnectsBindingConstants.SUPPORTED_THING_TYPES_UIDS, TIMEOUT_SECONDS);
logger.debug("Bridge discovery service started");
}
@Override
protected void startScan() {
discoverDevices();
}
private void discoverDevices() {
logger.debug("Starting device discovery scan");
ElroConnectsBridgeHandler bridge = bridgeHandler;
if (bridge != null) {
Map<Integer, ElroConnectsDevice> devices = bridge.getDevices();
ThingUID bridgeUID = bridge.getThing().getUID();
devices.entrySet().forEach(e -> {
String deviceId = e.getKey().toString();
String deviceName = e.getValue().getDeviceName();
String deviceType = e.getValue().getDeviceType();
if (!deviceType.isEmpty()) {
ElroDeviceType type = TYPE_MAP.get(deviceType);
if (type != null) {
ThingTypeUID thingTypeUID = THING_TYPE_MAP.get(type);
if (thingTypeUID != null) {
thingDiscovered(DiscoveryResultBuilder
.create(new ThingUID(thingTypeUID, bridgeUID, deviceId)).withLabel(deviceName)
.withBridge(bridgeUID).withProperty(CONFIG_DEVICE_ID, deviceId)
.withRepresentationProperty(CONFIG_DEVICE_ID).build());
}
}
}
});
}
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Start device background discovery");
ScheduledFuture<?> job = discoveryJob;
if (job == null || job.isCancelled()) {
discoveryJob = scheduler.scheduleWithFixedDelay(this::discoverDevices, 0, REFRESH_INTERVAL_SECONDS,
TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop device background discovery");
ScheduledFuture<?> job = discoveryJob;
if (job != null) {
job.cancel(true);
discoveryJob = null;
}
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof ElroConnectsBridgeHandler) {
bridgeHandler = (ElroConnectsBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ElroConnectsBridgeConfiguration} class contains fields mapping bridge configuration parameters.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsBridgeConfiguration {
public String connectorId = "";
public String ipAddress = "";
public int refreshInterval = 60;
}

View File

@@ -0,0 +1,864 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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.elroconnects.internal.ElroConnectsBindingConstants.ElroDeviceType;
import org.openhab.binding.elroconnects.internal.ElroConnectsDynamicStateDescriptionProvider;
import org.openhab.binding.elroconnects.internal.ElroConnectsMessage;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDeviceCxsmAlarm;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDeviceEntrySensor;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDeviceGenericAlarm;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDeviceMotionSensor;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevicePowerSocket;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDeviceTemperatureSensor;
import org.openhab.binding.elroconnects.internal.discovery.ElroConnectsDiscoveryService;
import org.openhab.binding.elroconnects.internal.util.ElroConnectsUtil;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.StateOption;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link ElroConnectsBridgeHandler} is the bridge handler responsible to for handling all communication with the
* ELRO Connects K1 Hub. All individual device communication passes through the hub.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(ElroConnectsBridgeHandler.class);
private static final int PORT = 1025; // UDP port for UDP socket communication with K1 hub
private static final int RESPONSE_TIMEOUT_MS = 5000; // max time to wait for receiving all responses on a request
// Default scene names are not received from K1 hub, so kept here
private static final Map<Integer, String> DEFAULT_SCENES = Map.ofEntries(Map.entry(0, "Home"), Map.entry(1, "Away"),
Map.entry(2, "Sleep"));
private static final int MAX_DEFAULT_SCENE = 2;
// Command filter when syncing devices and scenes, other values would filter what gets received
private static final String SYNC_COMMAND = "0002";
// Regex for valid connectorId
private static final Pattern CONNECTOR_ID_PATTERN = Pattern.compile("^ST_([0-9a-f]){12}$");
// Message string for acknowledging receipt of data
private static final String ACK_STRING = "{\"answer\": \"APP_answer_OK\"}";
private static final byte[] ACK = ACK_STRING.getBytes(StandardCharsets.UTF_8);
// Connector expects to receive messages with an increasing id for each message
// Max msgId is 65536, therefore use short and convert to unsigned Integer when using it
private short msgId;
private NetworkAddressService networkAddressService;
// Used when restarting connection, delay restart for 1s to avoid high network traffic
private volatile boolean restart;
static final int RESTART_DELAY_MS = 1000;
private volatile String connectorId = "";
// Used for getting IP address and keep connection alive messages
private static final String QUERY_BASE_STRING = "IOT_KEY?";
private volatile String queryString = QUERY_BASE_STRING + connectorId;
// Regex to retrieve ctrlKey from response on IP address message
private static final Pattern CTRL_KEY_PATTERN = Pattern.compile("KEY:([0-9a-f]*)");
private int refreshInterval = 60;
private volatile @Nullable InetAddress addr;
private volatile String ctrlKey = "";
private volatile @Nullable DatagramSocket socket;
private volatile @Nullable DatagramPacket ackPacket;
private volatile @Nullable ScheduledFuture<?> syncFuture;
private volatile @Nullable CompletableFuture<Boolean> awaitResponse;
private ElroConnectsDynamicStateDescriptionProvider stateDescriptionProvider;
private final Map<Integer, String> scenes = new ConcurrentHashMap<>();
private final Map<Integer, ElroConnectsDevice> devices = new ConcurrentHashMap<>();
private final Map<Integer, ElroConnectsDeviceHandler> deviceHandlers = new ConcurrentHashMap<>();
private int currentScene;
// We only keep 2 gson adapters used to serialize and deserialize all messages sent and received
private final Gson gsonOut = new Gson();
private Gson gsonIn = new Gson();
public ElroConnectsBridgeHandler(Bridge bridge, NetworkAddressService networkAddressService,
ElroConnectsDynamicStateDescriptionProvider stateDescriptionProvider) {
super(bridge);
this.networkAddressService = networkAddressService;
this.stateDescriptionProvider = stateDescriptionProvider;
resetScenes();
}
@Override
public void initialize() {
ElroConnectsBridgeConfiguration config = getConfigAs(ElroConnectsBridgeConfiguration.class);
connectorId = config.connectorId;
refreshInterval = config.refreshInterval;
if (connectorId.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Device ID not set");
return;
} else if (!CONNECTOR_ID_PATTERN.matcher(connectorId).matches()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Device ID not of format ST_xxxxxxxxxxxx with xxxxxxxxxxxx the lowercase MAC address of the connector");
return;
}
queryString = QUERY_BASE_STRING + connectorId;
scheduler.submit(this::startCommunication);
}
@Override
public void dispose() {
stopCommunication();
}
private synchronized void startCommunication() {
ElroConnectsBridgeConfiguration config = getConfigAs(ElroConnectsBridgeConfiguration.class);
InetAddress addr = this.addr;
String ipAddress = config.ipAddress;
if (!ipAddress.isEmpty()) {
try {
addr = InetAddress.getByName(ipAddress);
this.addr = addr;
} catch (UnknownHostException e) {
addr = null;
logger.warn("Unknown host for {}, trying to discover address", ipAddress);
}
}
try {
addr = getAddr(addr == null);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error trying to find IP address for connector ID " + connectorId + ".");
stopCommunication();
return;
}
if (addr == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error trying to find IP address for connector ID " + connectorId + ".");
stopCommunication();
return;
}
String ctrlKey = this.ctrlKey;
if (ctrlKey.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Communication data error while starting communication.");
stopCommunication();
return;
}
DatagramSocket socket;
try {
socket = createSocket(false);
this.socket = socket;
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Socket error while starting communication: " + e.getMessage());
stopCommunication();
return;
}
ackPacket = new DatagramPacket(ACK, ACK.length, addr, PORT);
logger.debug("Connected to connector {} at {}:{}", connectorId, addr, PORT);
try {
// Start ELRO Connects listener. This listener will act on all messages coming from ELRO K1 Connector.
(new NamedThreadFactory(THREAD_NAME_PREFIX + thing.getUID().getAsString()).newThread(this::runElroEvents))
.start();
keepAlive();
// First get status, then name. The status response contains the device types needed to instantiate correct
// classes.
getDeviceStatuses();
getDeviceNames();
syncScenes();
getCurrentScene();
updateStatus(ThingStatus.ONLINE);
updateState(SCENE, new StringType(String.valueOf(currentScene)));
} catch (IOException e) {
restartCommunication("Error in communication getting initial data: " + e.getMessage());
return;
}
scheduleSyncStatus();
}
/**
* Get the IP address and ctrl key of the connector by broadcasting message with connectorId. This should be used
* when initializing the connection. the ctrlKey field is set.
*
* @param broadcast, if true find address by broadcast, otherwise simply send to configured address to retrieve key
* only
* @return IP address of connector
* @throws IOException
*/
private @Nullable InetAddress getAddr(boolean broadcast) throws IOException {
try (DatagramSocket socket = createSocket(true)) {
String response = sendAndReceive(socket, queryString, broadcast);
Matcher keyMatcher = CTRL_KEY_PATTERN.matcher(response);
ctrlKey = keyMatcher.find() ? keyMatcher.group(1) : "";
logger.debug("Key: {}", ctrlKey);
return addr;
}
}
/**
* Send keep alive message.
*
* @throws IOException
*/
private void keepAlive() throws IOException {
DatagramSocket socket = this.socket;
if (socket != null) {
logger.trace("Keep alive");
// Sending query string, so the connection with the K1 hub stays alive
awaitResponse(true);
send(socket, queryString, false);
} else {
restartCommunication("Error in communication, no socket to send keep alive");
}
}
/**
* Cleanup socket when the communication with ELRO Connects connector is closed.
*
*/
private synchronized void stopCommunication() {
ScheduledFuture<?> sync = syncFuture;
if (sync != null) {
sync.cancel(true);
}
syncFuture = null;
stopAwaitResponse();
DatagramSocket socket = this.socket;
if (socket != null && !socket.isClosed()) {
socket.close();
}
this.socket = null;
logger.debug("Communication stopped");
}
/**
* Close and restart communication with ELRO Connects system, to be called after error in communication.
*
* @param offlineMessage message for thing status
*/
private synchronized void restartCommunication(String offlineMessage) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, offlineMessage);
stopCommunication();
if (!restart) {
logger.debug("Restart communication");
restart = true;
scheduler.schedule(this::startFromRestart, RESTART_DELAY_MS, TimeUnit.MILLISECONDS);
}
}
private synchronized void startFromRestart() {
restart = false;
if (ThingStatus.OFFLINE.equals(thing.getStatus())) {
startCommunication();
}
}
private DatagramSocket createSocket(boolean timeout) throws SocketException {
DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true);
if (timeout) {
socket.setSoTimeout(1000);
}
return socket;
}
/**
* Read messages received through UDP socket.
*
* @param socket
*/
private void runElroEvents() {
DatagramSocket socket = this.socket;
if (socket != null) {
logger.debug("Listening for messages");
try {
byte[] buffer = new byte[4096];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (!Thread.interrupted()) {
String response = receive(socket, buffer, packet);
processMessage(socket, response);
}
} catch (IOException e) {
restartCommunication("Communication error in listener: " + e.getMessage());
}
} else {
restartCommunication("Error in communication, no socket to start listener");
}
}
/**
* Schedule regular queries to sync devices and scenes.
*/
private void scheduleSyncStatus() {
syncFuture = scheduler.scheduleWithFixedDelay(() -> {
try {
keepAlive();
syncDevices();
syncScenes();
getCurrentScene();
} catch (IOException e) {
restartCommunication("Error in communication refreshing device status: " + e.getMessage());
}
}, refreshInterval, refreshInterval, TimeUnit.SECONDS);
}
/**
* Process response received from K1 Hub and send acknowledgement through open socket.
*
* @param socket
* @param response
* @throws IOException
*/
private void processMessage(DatagramSocket socket, String response) throws IOException {
if (!response.startsWith("{")) {
// Not a Json to interpret, just ignore
stopAwaitResponse();
return;
}
ElroConnectsMessage message;
String json = "";
try {
json = response.split("\\R")[0];
message = gsonIn.fromJson(json, ElroConnectsMessage.class);
sendAck(socket);
} catch (JsonSyntaxException ignore) {
logger.debug("Cannot decode, not a valid json: {}", json);
return;
}
if (message == null) {
return;
}
switch (message.getCmdId()) {
case ELRO_IGNORE_YES_NO:
break;
case ELRO_REC_DEVICE_NAME:
processDeviceNameMessage(message);
break;
case ELRO_REC_DEVICE_STATUS:
processDeviceStatusMessage(message);
break;
case ELRO_REC_ALARM:
processAlarmTriggerMessage(message);
break;
case ELRO_REC_SCENE_NAME:
processSceneNameMessage(message);
break;
case ELRO_REC_SCENE_TYPE:
processSceneTypeMessage(message);
break;
case ELRO_REC_SCENE:
processSceneMessage(message);
break;
default:
logger.debug("CmdId not implemented: {}", message.getCmdId());
}
}
private void processDeviceStatusMessage(ElroConnectsMessage message) {
int deviceId = message.getDeviceId();
String deviceStatus = message.getDeviceStatus();
if ("OVER".equals(deviceStatus)) {
// last message in series received
stopAwaitResponse();
return;
}
ElroConnectsDevice device = devices.get(deviceId);
device = (device == null) ? addDevice(message) : device;
if (device == null) {
// device type not recognized, could not be added
return;
}
device.setDeviceStatus(deviceStatus);
device.updateState();
}
private void processDeviceNameMessage(ElroConnectsMessage message) {
String answerContent = message.getAnswerContent();
if ("NAME_OVER".equals(answerContent)) {
// last message in series received
stopAwaitResponse();
return;
}
if (answerContent.length() <= 4) {
logger.debug("Could not decode answer {}", answerContent);
return;
}
int deviceId = Integer.parseInt(answerContent.substring(0, 4), 16);
String deviceName = (new String(HexUtils.hexToBytes(answerContent.substring(4)))).replaceAll("[@$]*", "");
ElroConnectsDevice device = devices.get(deviceId);
if (device != null) {
device.setDeviceName(deviceName);
logger.debug("Device ID {} name: {}", deviceId, deviceName);
}
}
private void processSceneNameMessage(ElroConnectsMessage message) {
int sceneId = message.getSceneGroup();
String answerContent = message.getAnswerContent();
String sceneName;
if (sceneId > MAX_DEFAULT_SCENE) {
if (answerContent.length() < 44) {
logger.debug("Could not decode answer {}", answerContent);
return;
}
sceneName = (new String(HexUtils.hexToBytes(answerContent.substring(6, 38)))).replaceAll("[@$]*", "");
scenes.put(sceneId, sceneName);
logger.debug("Scene ID {} name: {}", sceneId, sceneName);
}
}
private void processSceneTypeMessage(ElroConnectsMessage message) {
String sceneContent = message.getSceneContent();
if ("OVER".equals(sceneContent)) {
// last message in series received
stopAwaitResponse();
updateSceneOptions();
}
}
private void processSceneMessage(ElroConnectsMessage message) {
int sceneId = message.getSceneGroup();
currentScene = sceneId;
updateState(SCENE, new StringType(String.valueOf(currentScene)));
}
private void processAlarmTriggerMessage(ElroConnectsMessage message) {
String answerContent = message.getAnswerContent();
if (answerContent.length() < 10) {
logger.debug("Could not decode answer {}", answerContent);
return;
}
int deviceId = Integer.parseInt(answerContent.substring(6, 10), 16);
ElroConnectsDeviceHandler handler = deviceHandlers.get(deviceId);
if (handler != null) {
handler.triggerAlarm();
}
logger.debug("Device ID {} alarm", deviceId);
}
private @Nullable ElroConnectsDevice addDevice(ElroConnectsMessage message) {
int deviceId = message.getDeviceId();
String deviceType = message.getDeviceName();
ElroDeviceType type = TYPE_MAP.getOrDefault(deviceType, ElroDeviceType.DEFAULT);
ElroConnectsDevice device;
switch (type) {
case CO_ALARM:
case SM_ALARM:
case WT_ALARM:
case THERMAL_ALARM:
device = new ElroConnectsDeviceGenericAlarm(deviceId, this);
break;
case CXSM_ALARM:
device = new ElroConnectsDeviceCxsmAlarm(deviceId, this);
break;
case POWERSOCKET:
device = new ElroConnectsDevicePowerSocket(deviceId, this);
break;
case ENTRY_SENSOR:
device = new ElroConnectsDeviceEntrySensor(deviceId, this);
break;
case MOTION_SENSOR:
device = new ElroConnectsDeviceMotionSensor(deviceId, this);
break;
case TH_SENSOR:
device = new ElroConnectsDeviceTemperatureSensor(deviceId, this);
break;
default:
logger.debug("Device type {} not supported", deviceType);
return null;
}
device.setDeviceType(deviceType);
devices.put(deviceId, device);
return device;
}
/**
* Just before sending message, this method should be called to make sure we wait for all responses that are still
* expected to be received. The last response will be indicated by a token in the last response message.
*
* @param waitResponse true if we want to wait for response for next message to be sent before allowing subsequent
* message
*/
private void awaitResponse(boolean waitResponse) {
CompletableFuture<Boolean> waiting = awaitResponse;
if (waiting != null) {
try {
logger.trace("Waiting for previous response before sending");
waiting.get(RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ignore) {
logger.trace("Wait for previous response timed out");
}
}
awaitResponse = waitResponse ? new CompletableFuture<>() : null;
}
/**
* This method is called when all responses on a request have been received.
*/
private void stopAwaitResponse() {
CompletableFuture<Boolean> future = awaitResponse;
if (future != null) {
future.complete(true);
}
awaitResponse = null;
}
private void sendAck(DatagramSocket socket) throws IOException {
logger.debug("Send Ack: {}", ACK_STRING);
socket.send(ackPacket);
}
private String sendAndReceive(DatagramSocket socket, String query, boolean broadcast)
throws UnknownHostException, IOException {
send(socket, query, broadcast);
byte[] buffer = new byte[4096];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
return receive(socket, buffer, packet);
}
private void send(DatagramSocket socket, String query, boolean broadcast) throws IOException {
final InetAddress address = broadcast
? InetAddress.getByName(networkAddressService.getConfiguredBroadcastAddress())
: addr;
if (address == null) {
if (broadcast) {
restartCommunication("No broadcast address, check network configuration");
} else {
restartCommunication("Failed sending, hub address was not set");
}
return;
}
logger.debug("Send: {}", query);
final byte[] queryBuffer = query.getBytes(StandardCharsets.UTF_8);
DatagramPacket queryPacket = new DatagramPacket(queryBuffer, queryBuffer.length, address, PORT);
socket.send(queryPacket);
}
private String receive(DatagramSocket socket, byte[] buffer, DatagramPacket packet) throws IOException {
socket.receive(packet);
String response = new String(packet.getData(), packet.getOffset(), packet.getLength());
logger.debug("Received: {}", response);
addr = packet.getAddress();
return response;
}
/**
* Basic method to send an {@link ElroConnectsMessage} to the K1 hub.
*
* @param elroMessage
* @param waitResponse true if no new messages should be allowed to be sent before receiving the full response
* @throws IOException
*/
private synchronized void sendElroMessage(ElroConnectsMessage elroMessage, boolean waitResponse)
throws IOException {
DatagramSocket socket = this.socket;
if (socket != null) {
String message = gsonOut.toJson(elroMessage);
awaitResponse(waitResponse);
send(socket, message, false);
} else {
throw new IOException("No socket");
}
}
/**
* Send device control command. The device command string various by device type. The calling method is responsible
* for creating the appropriate command string.
*
* @param deviceId
* @param deviceCommand ELRO Connects device command string
* @throws IOException
*/
public void deviceControl(int deviceId, String deviceCommand) throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Device control {}, status {}", deviceId, deviceCommand);
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_DEVICE_CONTROL).withDeviceId(ElroConnectsUtil.encode(deviceId)).withDeviceStatus(deviceCommand);
sendElroMessage(elroMessage, false);
}
/**
* Send request to receive all device names.
*
* @throws IOException
*/
private void getDeviceNames() throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Get device names");
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_GET_DEVICE_NAME).withDeviceId(0);
sendElroMessage(elroMessage, true);
}
/**
* Send request to receive all device statuses.
*
* @throws IOException
*/
private void getDeviceStatuses() throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Get all equipment status");
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_GET_DEVICE_STATUSES);
sendElroMessage(elroMessage, true);
}
/**
* Send request to sync all devices statuses.
*
* @throws IOException
*/
private void syncDevices() throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Sync device status");
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_SYNC_DEVICES).withDeviceStatus(SYNC_COMMAND);
sendElroMessage(elroMessage, true);
}
/**
* Send request to get the currently selected scene.
*
* @throws IOException
*/
private void getCurrentScene() throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Get current scene");
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_GET_SCENE);
sendElroMessage(elroMessage, true);
}
/**
* Send message to set the current scene.
*
* @throws IOException
*/
private void selectScene(int scene) throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Select scene {}", scene);
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_SELECT_SCENE).withSceneType(ElroConnectsUtil.encode(scene));
sendElroMessage(elroMessage, false);
}
/**
* Send request to sync all scenes.
*
* @throws IOException
*/
private void syncScenes() throws IOException {
String connectorId = this.connectorId;
String ctrlKey = this.ctrlKey;
logger.debug("Sync scenes");
ElroConnectsMessage elroMessage = new ElroConnectsMessage(msgIdIncrement(), connectorId, ctrlKey,
ELRO_SYNC_SCENES).withSceneGroup(ElroConnectsUtil.encode(0)).withSceneContent(SYNC_COMMAND)
.withAnswerContent(SYNC_COMMAND);
sendElroMessage(elroMessage, true);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Channel {}, command {}, type {}", channelUID, command, command.getClass());
if (SCENE.equals(channelUID.getId())) {
if (command instanceof RefreshType) {
updateState(SCENE, new StringType(String.valueOf(currentScene)));
} else if (command instanceof DecimalType) {
try {
selectScene(((DecimalType) command).intValue());
} catch (IOException e) {
restartCommunication("Error in communication while setting scene: " + e.getMessage());
return;
}
}
}
}
/**
* We do not get scene delete messages, therefore call this method before requesting list of scenes to clear list of
* scenes.
*/
private void resetScenes() {
scenes.clear();
scenes.putAll(DEFAULT_SCENES);
updateSceneOptions();
}
/**
* Update state option list for scene selection channel.
*/
private void updateSceneOptions() {
// update the command scene command options
List<StateOption> stateOptionList = new ArrayList<>();
scenes.forEach((id, scene) -> {
StateOption option = new StateOption(Integer.toString(id), scene);
stateOptionList.add(option);
});
logger.trace("Scenes: {}", stateOptionList);
Channel channel = thing.getChannel(SCENE);
if (channel != null) {
ChannelUID channelUID = channel.getUID();
StateDescription stateDescription = StateDescriptionFragmentBuilder.create().withReadOnly(false)
.withOptions(stateOptionList).build().toStateDescription();
stateDescriptionProvider.setDescription(channelUID, stateDescription);
}
}
/**
* Messages need to be sent with consecutive id's. Increment the msgId field and rotate at max unsigned short.
*
* @return new message id
*/
private int msgIdIncrement() {
return Short.toUnsignedInt(msgId++);
}
/**
* Set the {@link ElroConnectsDeviceHandler} for the device with deviceId, should be called from the thing handler
* when initializing the thing.
*
* @param deviceId
* @param handler
*/
public void setDeviceHandler(int deviceId, ElroConnectsDeviceHandler handler) {
deviceHandlers.put(deviceId, handler);
}
/**
* Unset the {@link ElroConnectsDeviceHandler} for the device with deviceId, should be called from the thing handler
* when disposing the thing.
*
* @param deviceId
* @param handler
*/
public void unsetDeviceHandler(int deviceId, ElroConnectsDeviceHandler handler) {
deviceHandlers.remove(deviceId, handler);
}
public @Nullable ElroConnectsDeviceHandler getDeviceHandler(int deviceId) {
return deviceHandlers.get(deviceId);
}
public @Nullable ElroConnectsDevice getDevice(int deviceId) {
return devices.get(deviceId);
}
/**
* Get full list of devices connected to the K1 hub. This can be used by the {@link ElroConnectsDiscoveryService} to
* scan for devices connected to the K1 hub.
*
* @return devices
*/
public Map<Integer, ElroConnectsDevice> getDevices() {
return devices;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ElroConnectsDiscoveryService.class);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link ElroConnectsCOAlarmHandler} represents the thing handler for an ELRO Connects CO alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsCOAlarmHandler extends ElroConnectsDeviceHandler {
public ElroConnectsCOAlarmHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Integer id = deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
ElroConnectsDevice device = bridgeHandler.getDevice(id);
if (device != null) {
switch (channelUID.getId()) {
case MUTE_ALARM:
if (OnOffType.ON.equals(command)) {
device.muteAlarm();
}
break;
case TEST_ALARM:
if (OnOffType.ON.equals(command)) {
device.testAlarm();
}
break;
}
}
}
super.handleCommand(channelUID, command);
}
@Override
public void triggerAlarm() {
triggerChannel(CO_ALARM);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ElroConnectsDeviceConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceConfiguration {
public int deviceId = 0;
}

View File

@@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.binding.elroconnects.internal.util.ElroConnectsUtil;
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.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
/**
* The {@link ElroConnectsDeviceHandler} represents the thing handler for an ELRO Connects device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsDeviceHandler extends BaseThingHandler {
protected int deviceId;
public ElroConnectsDeviceHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
ElroConnectsDeviceConfiguration config = getConfigAs(ElroConnectsDeviceConfiguration.class);
deviceId = config.deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
bridgeHandler.setDeviceHandler(deviceId, this);
updateProperties(bridgeHandler);
refreshChannels(bridgeHandler);
}
}
@Override
public void dispose() {
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
bridgeHandler.unsetDeviceHandler(deviceId, this);
}
}
/**
* Get the bridge handler for this thing handler.
*
* @return {@link ElroConnectsBridgeHandler}, null if no bridge handler set
*/
protected @Nullable ElroConnectsBridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No bridge defined for device " + String.valueOf(deviceId));
return null;
}
ElroConnectsBridgeHandler bridgeHandler = (ElroConnectsBridgeHandler) bridge.getHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No bridge handler defined for device " + String.valueOf(deviceId));
return null;
}
return bridgeHandler;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
if (command instanceof RefreshType) {
refreshChannels(bridgeHandler);
}
}
}
/**
* Update thing properties.
*
* @param bridgeHandler
*/
protected void updateProperties(ElroConnectsBridgeHandler bridgeHandler) {
ElroConnectsDevice device = bridgeHandler.getDevice(deviceId);
if (device != null) {
Map<String, String> properties = new HashMap<>();
properties.put("deviceType", ElroConnectsUtil.stringOrEmpty(device.getDeviceType()));
thing.setProperties(properties);
}
}
/**
* Refresh all thing channels.
*
* @param bridgeHandler
*/
protected void refreshChannels(ElroConnectsBridgeHandler bridgeHandler) {
ElroConnectsDevice device = bridgeHandler.getDevice(deviceId);
if (device != null) {
device.updateState();
}
}
@Override
public void updateState(String channelID, State state) {
super.updateState(channelID, state);
}
@Override
public void updateStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail,
@Nullable String description) {
super.updateStatus(thingStatus, thingStatusDetail, description);
}
@Override
public void updateStatus(ThingStatus thingStatus) {
super.updateStatus(thingStatus);
}
/**
* Method to be called when an alarm event is received from the K1 hub. This should trigger a trigger channel. The
* method should be implemented in subclasses for the appropriate trigger channel if it applies.
*/
public void triggerAlarm() {
// nothing by default
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.ENTRY_ALARM;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
/**
* The {@link ElroConnectsEntrySensorHandler} represents the thing handler for an ELRO Connects entry sensor device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsEntrySensorHandler extends ElroConnectsDeviceHandler {
public ElroConnectsEntrySensorHandler(Thing thing) {
super(thing);
}
@Override
public void triggerAlarm() {
triggerChannel(ENTRY_ALARM);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link ElroConnectsHeatAlarmHandler} represents the thing handler for an ELRO Connects heat alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsHeatAlarmHandler extends ElroConnectsDeviceHandler {
public ElroConnectsHeatAlarmHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Integer id = deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
ElroConnectsDevice device = bridgeHandler.getDevice(id);
if (device != null) {
switch (channelUID.getId()) {
case MUTE_ALARM:
if (OnOffType.ON.equals(command)) {
device.muteAlarm();
}
break;
case TEST_ALARM:
if (OnOffType.ON.equals(command)) {
device.testAlarm();
}
break;
}
}
}
super.handleCommand(channelUID, command);
}
@Override
public void triggerAlarm() {
triggerChannel(HEAT_ALARM);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.MOTION_ALARM;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
/**
* The {@link ElroConnectsMotionSensorHandler} represents the thing handler for an ELRO Connects motion sensor device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsMotionSensorHandler extends ElroConnectsDeviceHandler {
public ElroConnectsMotionSensorHandler(Thing thing) {
super(thing);
}
@Override
public void triggerAlarm() {
triggerChannel(MOTION_ALARM);
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.POWER_STATE;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link ElroConnectsPowerSocketHandler} represents the thing handler for an ELRO Connects power socket device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsPowerSocketHandler extends ElroConnectsDeviceHandler {
public ElroConnectsPowerSocketHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Integer id = deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
ElroConnectsDevice device = bridgeHandler.getDevice(id);
if (device != null) {
switch (channelUID.getId()) {
case POWER_STATE:
if (OnOffType.ON.equals(command)) {
device.switchState(OnOffType.ON.equals(command) ? true : false);
}
break;
}
}
}
super.handleCommand(channelUID, command);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link ElroConnectsSmokeAlarmHandler} represents the thing handler for an ELRO Connects smoke alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsSmokeAlarmHandler extends ElroConnectsDeviceHandler {
public ElroConnectsSmokeAlarmHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Integer id = deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
ElroConnectsDevice device = bridgeHandler.getDevice(id);
if (device != null) {
switch (channelUID.getId()) {
case MUTE_ALARM:
if (OnOffType.ON.equals(command)) {
device.muteAlarm();
}
break;
case TEST_ALARM:
if (OnOffType.ON.equals(command)) {
device.testAlarm();
}
break;
}
}
}
super.handleCommand(channelUID, command);
}
@Override
public void triggerAlarm() {
triggerChannel(SMOKE_ALARM);
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.handler;
import static org.openhab.binding.elroconnects.internal.ElroConnectsBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.elroconnects.internal.devices.ElroConnectsDevice;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
/**
* The {@link ElroConnectsWaterAlarmHandler} represents the thing handler for an ELRO Connects water alarm device.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ElroConnectsWaterAlarmHandler extends ElroConnectsDeviceHandler {
public ElroConnectsWaterAlarmHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Integer id = deviceId;
ElroConnectsBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
ElroConnectsDevice device = bridgeHandler.getDevice(id);
if (device != null) {
switch (channelUID.getId()) {
case MUTE_ALARM:
if (OnOffType.ON.equals(command)) {
device.muteAlarm();
}
break;
case TEST_ALARM:
if (OnOffType.ON.equals(command)) {
device.testAlarm();
}
break;
}
}
}
super.handleCommand(channelUID, command);
}
@Override
public void triggerAlarm() {
triggerChannel(WATER_ALARM);
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2022 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.elroconnects.internal.util;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ElroConnectsUtil} contains a few utility methods for the ELRO Connects binding.
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public final class ElroConnectsUtil {
public static int encode(int value) {
return (((value ^ 0xFFFFFFFF) + 0x10000) ^ 0x123) ^ 0x1234;
}
public static String stringOrEmpty(@Nullable String data) {
return (data == null ? "" : data);
}
public static int intOrZero(@Nullable Integer data) {
return (data == null ? 0 : data);
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="elroconnects" 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>ELRO Connects Binding</name>
<description>This is the binding for the ELRO Connects smart home system.</description>
</binding:binding>

View File

@@ -0,0 +1,75 @@
# binding
binding.elroconnects.name = ELRO Connects Binding
binding.elroconnects.description = This is the binding for the ELRO Connects smart home system.
# thing types
thing-type.elroconnects.coalarm.label = CO Alarm
thing-type.elroconnects.coalarm.description = ELRO Connects CO alarm
thing-type.elroconnects.connector.label = ELRO Connects Connector
thing-type.elroconnects.connector.description = This bridge represents an ELRO Connects K1 Connector
thing-type.elroconnects.entrysensor.label = Door/Window Sensor
thing-type.elroconnects.entrysensor.description = ELRO Connects door/window contact sensor
thing-type.elroconnects.heatalarm.label = Heat Alarm
thing-type.elroconnects.heatalarm.description = ELRO Connects heat alarm
thing-type.elroconnects.motionsensor.label = Motion Sensor
thing-type.elroconnects.motionsensor.description = ELRO Connects motion sensor
thing-type.elroconnects.powersocket.label = Power Socket
thing-type.elroconnects.powersocket.description = ELRO Connects power socket
thing-type.elroconnects.smokealarm.label = Smoke Alarm
thing-type.elroconnects.smokealarm.description = ELRO Connects smoke alarm
thing-type.elroconnects.temperaturesensor.label = Temperature Sensor
thing-type.elroconnects.temperaturesensor.description = ELRO Connects motion sensor
thing-type.elroconnects.wateralarm.label = Water Alarm
thing-type.elroconnects.wateralarm.description = ELRO Connects water alarm
# thing types config
thing-type.config.elroconnects.coalarm.deviceId.label = Device ID
thing-type.config.elroconnects.coalarm.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.connector.connectorId.label = Connector ID
thing-type.config.elroconnects.connector.connectorId.description = ID of the ELRO Connects K1 Connector, should be ST_xxxxxxxxxxxx with xxxxxxxxxxxx the lowercase MAC address of the connector.
thing-type.config.elroconnects.connector.ipAddress.label = IP Address
thing-type.config.elroconnects.connector.ipAddress.description = IP address of the ELRO Connects K1 Connector, not required if connector and openHAB server in same subnet.
thing-type.config.elroconnects.connector.refreshInterval.label = Refresh Interval
thing-type.config.elroconnects.connector.refreshInterval.description = Heartbeat device refresh interval for communication with ELRO Connects K1 Connector in seconds, default 60s.
thing-type.config.elroconnects.entrysensor.deviceId.label = Device ID
thing-type.config.elroconnects.entrysensor.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.heatalarm.deviceId.label = Device ID
thing-type.config.elroconnects.heatalarm.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.motionsensor.deviceId.label = Device ID
thing-type.config.elroconnects.motionsensor.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.powersocket.deviceId.label = Device ID
thing-type.config.elroconnects.powersocket.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.smokealarm.deviceId.label = Device ID
thing-type.config.elroconnects.smokealarm.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.temperaturesensor.deviceId.label = Device ID
thing-type.config.elroconnects.temperaturesensor.deviceId.description = ID of the ELRO Connects Device.
thing-type.config.elroconnects.wateralarm.deviceId.label = Device ID
thing-type.config.elroconnects.wateralarm.deviceId.description = ID of the ELRO Connects Device.
# channel types
channel-type.elroconnects.coalarm.label = CO Alarm
channel-type.elroconnects.coalarm.description = CO alarm triggered
channel-type.elroconnects.entry.label = Entry Contact
channel-type.elroconnects.entry.description = Door/window contact open/closed
channel-type.elroconnects.entryalarm.label = Entry Alarm
channel-type.elroconnects.entryalarm.description = Entry alarm triggered
channel-type.elroconnects.heatalarm.label = Heat Alarm
channel-type.elroconnects.heatalarm.description = Heat alarm triggered
channel-type.elroconnects.motionalarm.label = Motion Alarm
channel-type.elroconnects.motionalarm.description = Motion alarm triggered
channel-type.elroconnects.mutealarm.label = Mute Alarm
channel-type.elroconnects.mutealarm.description = Mute
channel-type.elroconnects.scene.label = Scene
channel-type.elroconnects.scene.description = Scene selection from scenes configured in the ELRO Connects app, enables configuring alarm modes
channel-type.elroconnects.smokealarm.label = Smoke Alarm
channel-type.elroconnects.smokealarm.description = Smoke alarm triggered
channel-type.elroconnects.temperature.label = Temperature
channel-type.elroconnects.temperature.description = Current temperature
channel-type.elroconnects.testalarm.label = Test Alarm
channel-type.elroconnects.testalarm.description = Trigger alarm test sound
channel-type.elroconnects.wateralarm.label = Water Alarm
channel-type.elroconnects.wateralarm.description = Water alarm triggered

View File

@@ -0,0 +1,303 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="elroconnects"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="connector">
<label>ELRO Connects Connector</label>
<description>This bridge represents an ELRO Connects K1 Connector</description>
<channels>
<channel id="scene" typeId="scene"/>
</channels>
<representation-property>connectorId</representation-property>
<config-description>
<parameter name="connectorId" type="text" required="true">
<label>Connector ID</label>
<description>ID of the ELRO Connects K1 Connector, should be ST_xxxxxxxxxxxx with xxxxxxxxxxxx the lowercase MAC
address of the connector.</description>
</parameter>
<parameter name="ipAddress" type="text">
<label>IP Address</label>
<description>IP address of the ELRO Connects K1 Connector, not required if connector and openHAB server in same
subnet.</description>
<context>network-address</context>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer">
<label>Refresh Interval</label>
<description>Heartbeat device refresh interval for communication with ELRO Connects K1 Connector in seconds, default
60s.</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<thing-type id="smokealarm">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Smoke Alarm</label>
<description>ELRO Connects smoke alarm</description>
<category>SmokeDetector</category>
<channels>
<channel id="smokeAlarm" typeId="smokealarm"/>
<channel id="muteAlarm" typeId="mutealarm"/>
<channel id="testAlarm" typeId="testalarm"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="coalarm">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>CO Alarm</label>
<description>ELRO Connects CO alarm</description>
<channels>
<channel id="coAlarm" typeId="coalarm"/>
<channel id="muteAlarm" typeId="mutealarm"/>
<channel id="testAlarm" typeId="testalarm"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="heatalarm">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Heat Alarm</label>
<description>ELRO Connects heat alarm</description>
<channels>
<channel id="heatAlarm" typeId="heatalarm"/>
<channel id="muteAlarm" typeId="mutealarm"/>
<channel id="testAlarm" typeId="testalarm"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="wateralarm">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Water Alarm</label>
<description>ELRO Connects water alarm</description>
<channels>
<channel id="waterAlarm" typeId="wateralarm"/>
<channel id="muteAlarm" typeId="mutealarm"/>
<channel id="testAlarm" typeId="testalarm"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="entrysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Door/Window Sensor</label>
<description>ELRO Connects door/window contact sensor</description>
<category>Door</category>
<channels>
<channel id="entryAlarm" typeId="entryalarm"/>
<channel id="entry" typeId="entry"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="motionsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Motion Sensor</label>
<description>ELRO Connects motion sensor</description>
<category>MotionDetector</category>
<channels>
<channel id="motionAlarm" typeId="motionalarm"/>
<channel id="motion" typeId="system.motion"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="temperaturesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Temperature Sensor</label>
<description>ELRO Connects motion sensor</description>
<category>Sensor</category>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="system.atmospheric-humidity"/>
<channel id="battery" typeId="system.battery-level"/>
<channel id="lowBattery" typeId="system.low-battery"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="powersocket">
<supported-bridge-type-refs>
<bridge-type-ref id="connector"/>
</supported-bridge-type-refs>
<label>Power Socket</label>
<description>ELRO Connects power socket</description>
<category>PowerOutlet</category>
<channels>
<channel id="powerState" typeId="system.power"/>
</channels>
<representation-property>deviceId</representation-property>
<config-description>
<parameter name="deviceId" type="integer" required="true">
<label>Device ID</label>
<description>ID of the ELRO Connects Device.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="scene">
<item-type>String</item-type>
<label>Scene</label>
<description>Scene selection from scenes configured in the ELRO Connects app, enables configuring alarm modes</description>
</channel-type>
<channel-type id="smokealarm">
<kind>trigger</kind>
<label>Smoke Alarm</label>
<description>Smoke alarm triggered</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>Smoke</tag>
</tags>
</channel-type>
<channel-type id="coalarm">
<kind>trigger</kind>
<label>CO Alarm</label>
<description>CO alarm triggered</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>CO</tag>
</tags>
</channel-type>
<channel-type id="heatalarm">
<kind>trigger</kind>
<label>Heat Alarm</label>
<description>Heat alarm triggered</description>
<category>Fire</category>
<tags>
<tag>Alarm</tag>
<tag>Temperature</tag>
</tags>
</channel-type>
<channel-type id="wateralarm">
<kind>trigger</kind>
<label>Water Alarm</label>
<description>Water alarm triggered</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>Water</tag>
</tags>
</channel-type>
<channel-type id="entryalarm">
<kind>trigger</kind>
<label>Entry Alarm</label>
<description>Entry alarm triggered</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>OpenState</tag>
</tags>
</channel-type>
<channel-type id="motionalarm">
<kind>trigger</kind>
<label>Motion Alarm</label>
<description>Motion alarm triggered</description>
<category>Alarm</category>
<tags>
<tag>Alarm</tag>
<tag>Presence</tag>
</tags>
</channel-type>
<channel-type id="entry">
<item-type>Contact</item-type>
<label>Entry Contact</label>
<description>Door/window contact open/closed</description>
<tags>
<tag>OpenState</tag>
</tags>
<state readOnly="true"></state>
</channel-type>
<channel-type id="mutealarm">
<item-type>Switch</item-type>
<label>Mute Alarm</label>
<description>Mute</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="testalarm">
<item-type>Switch</item-type>
<label>Test Alarm</label>
<description>Trigger alarm test sound</description>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>