[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 3545 additions and 0 deletions

View File

@ -82,6 +82,7 @@
/bundles/org.openhab.binding.ekey/ @hmerk
/bundles/org.openhab.binding.electroluxair/ @jannegpriv
/bundles/org.openhab.binding.elerotransmitterstick/ @vbier
/bundles/org.openhab.binding.elroconnects/ @mherwege
/bundles/org.openhab.binding.energenie/ @hmerk
/bundles/org.openhab.binding.enigma2/ @gdolfen
/bundles/org.openhab.binding.enocean/ @fruggy83

View File

@ -401,6 +401,11 @@
<artifactId>org.openhab.binding.elerotransmitterstick</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.elroconnects</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.energenie</artifactId>

View File

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

View File

@ -0,0 +1,176 @@
# ELRO Connects Binding
The ELRO Connects binding provides integration with the [ELRO Connects](https://www.elro.eu/en/smart-home) smart home system.
The system uses a Wi-Fi Hub (K1 Connector) to enable communication with various smart home devices.
The devices communicate with the hub using 868MHz RF.
The binding only communicates with the ELRO Connects system and K1 Connector using UDP in the local network.
The binding exposes the devices' status and controls to openHAB.
The K1 connector itself allows setting up scenes through a mobile application.
The binding supports selecting a specific scene.
Many of the sensor devices are battery powered.
## Supported Things
The ELRO Connects supported device types are:
* K1 connector hub: `connector`
* Smoke detector: `smokealarm`
* Carbon monoxide detector: `coalarm`
* Heat detector: `heatalarm`
* Water detector: `wateralarm`
* Windows/door contact: `entrysensor`
* Motion detector: `motionsensor`
* Temperature and humidity monitor: `temperaturesensor`
* Plug-in switch: `powersocket`
The `connector` is the bridge thing.
All other things are connected to the bridge.
Testing was only done with smoke and water detectors connected to a K1 connector.
The firmware version of the K1 connector was 2.0.3.30 at the time of testing.
Older versions of the firmware are known to have differences in the communication protocol.
## Discovery
The K1 connector `connector` cannot be auto-discovered.
Once the bridge thing representing the K1 connector is correctly set up and online, discovery will allow discovering all devices connected to the K1 connector (as set up in the Elro Connects app).
If devices are outside reliable RF range, devices known to the K1 hub will be discovered but may stay offline when added as a thing.
Alarm devices can still trigger alarms and pass them between each other, even if the connection with the hub is lost.
It will not be possible to receive alarms and control them from openHAB in this case.
## Thing Configuration
### K1 connector hub
| Parameter | Advanced | Description |
|-------------------|:--------:|------------------------|
| `connectorId` | | Required parameter, should be set to ST_xxxxxxxxxxxx with xxxxxxxxxxxx the lowercase MAC address of the connector. This parameter can also be found in the ELRO Connects mobile application. |
| `ipAdress` | Y | IP address of the ELRO Connects K1 Connector, not required if connector and openHAB server in same subnet. |
| `refreshInterval` | Y | This parameter controls the connection refresh heartbeat interval. The default is 60s. |
### Devices connected to K1 connected hub
| Parameter | Description |
|--------------------|----------------------|
| `deviceId` | Required parameter, set by discovery and cannot easily be found manually. It should be a number. |
## Channels
### K1 connector hub
The `connector` bridge thing has only one channel:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `scene` | String | RW | current scene |
The `scene` channel has a dynamic state options list with all possible scene choices available in the hub.
## Smoke, carbon monoxide, heat and water alarms
All these things have the same channels:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `muteAlarm` | Switch | RW | mute alarm |
| `testAlarm` | Switch | RW | test alarm |
| `battery` | Number | R | battery level in % |
| `lowBattery` | Switch | R | on for low battery (below 15%) |
Each also has a trigger channel, resp. `smokeAlarm`, `coAlarm`, `heatAlarm` and `waterAlarm`.
The payload for these trigger channels is empty.
## Door/window contact
The `entrysensor` thing has the following channels:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `entry` | Contact | R | open/closed door/window |
| `battery` | Number | R | battery level in % |
| `lowBattery` | Switch | R | on for low battery (below 15%) |
The `entrysensor` thing also has a trigger channel, `entryAlarm`.
## Motion sensor
The `motionsensor` thing has the following channels:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `motion` | Switch | R | on when motion detected |
| `battery` | Number | R | battery level in % |
| `lowBattery` | Switch | R | on for low battery (below 15%) |
The `motionsensor` thing also has a trigger channel, `motionAlarm`.
## Temperature and humidity monitor
The `temperaturesensor` thing has the following channels:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `temperature` | Number:Temperature | R | temperature |
| `humidity` | Number:Dimensionless | R | device status |
| `battery` | Number | R | battery level in % |
| `lowBattery` | Switch | R | on for low battery (below 15%) |
## Plug-in switch
The `powersocket` thing has only one channel:
| Channel ID | Item Type | Access Mode | Description |
|--------------------|----------------------|:-----------:|----------------------------------------------------|
| `powerState` | Switch | RW | power on/off |
## Full Example
.things:
```
Bridge elroconnects:connector:myhub [ connectorId="ST_aabbccddaabbccdd", refreshInterval=120 ] {
smokealarm 1 "LivingRoom" [ deviceId="1" ]
coalarm 2 "Garage" [ deviceId="2" ]
heatalarm 3 "Kitchen" [ deviceId="3" ]
wateralarm 4 "Basement" [ deviceId="4" ]
entrysensor 5 "Back Door" [ deviceId="5" ]
motionsensor 6 "Hallway" [ deviceId="6" ]
temperaturesensor 7 "Family Room" [ deviceId = "7" ]
powersocket 8 "Television" [ deviceId = "8" ]
}
```
.items:
```
String Scene {channel="elroconnects:connector:myhub:scene"}
Number BatteryLevel {channel="elroconnects:smokealarm:myhub:1:battery"}
Switch AlarmTest {channel="elroconnects:smokealarm:myhub:1:test"}
```
.sitemap:
```
Text item=Scene
Number item=BatteryLevel
Switch item=AlarmTest
```
Example trigger rule:
```
rule "example trigger rule"
when
Channel 'elroconnects:smokealarm:myhub:1:smokeAlarm' triggered
then
logInfo("Smoke alarm living room")
...
end
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.elroconnects</artifactId>
<name>openHAB Add-ons :: Bundles :: ElroConnects Binding</name>
</project>

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>

View File

@ -114,6 +114,7 @@
<module>org.openhab.binding.ekey</module>
<module>org.openhab.binding.electroluxair</module>
<module>org.openhab.binding.elerotransmitterstick</module>
<module>org.openhab.binding.elroconnects</module>
<module>org.openhab.binding.energenie</module>
<module>org.openhab.binding.enigma2</module>
<module>org.openhab.binding.enocean</module>