added migrated 2.x add-ons

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

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.jeelink</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

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,229 @@
# Jeelink Binding
This binding integrates JeeLink USB RF receivers and LaCrosseGateways.
## Introduction
Binding should be compatible with JeeLink USB receivers and LaCrosseGateways. It supports connected LaCrosse temperature sensors, EC3000 sensors, PCA301 power monitoring wireless sockets and TX22 temperature and humidity sensors (including connected TX23 wind and TX26 rain sensors).
## Supported Things
This binding supports:
* JeeLink (connected to USB)
* JeeLink (connected over TCP)
* LaCrosseGateway (connected to USB)
* LaCrosseGateway (connected over TCP)
* LaCrosse temperature sensors
* EC3000 power monitors
* Revolt power monitors
* PCA301 power monitoring wireless sockets
* TX22 temperature & humidity Sensors (including connected TX23 wind and TX26 rain sensors)
## Binding configuration
Configuration of the binding is not needed.
## Thing discovery
Only sensor discovery is supported, the thing for the USB receiver / LaCrosseGateway has to be created manually. Pay attention to use the correct serial port, as otherwise the binding may interfere with other bindings accessing serial ports.
Afterwards, discovery reads from the USB receiver / LaCrosseGateways to find out which sensors are currently connected.
It then creates a thing for every unknown sensor and puts it in the Inbox.
Discovery only creates things for sensors that actually send a value during discovery. LaCrosse temperature sensors send values every few seconds, so that they are normally caught by the discovery. In rare cases, a second discovery run is needed.
PCA301 sockets are polled every 120 seconds by default. This results in sockets not being found by the discovery. In order to make sure the socket is discovered, press the button on the socket during discovery (and make sure you have paired the socket to the USB stick / LaCrosseGateway before by pressing the button for 3 seconds while the receiver is powered).
## Thing configuration
#### JeeLink / LaCrosseGateway (connected to USB)
| Parameter | Item Type | Description |
|---------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------|
| Serial Port | String | The serial port name for the USB receiver / LaCrosseGateway. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or /dev/ttyUSB0 for Linux |
| Baud Rate | Number | The baud rate of the USB Receiver. Valid values are 9600, 19200, 38400, 57600 (default), and 115200 |
| Init Commands | String | A semicolon separated list of init commands that will be send to the Jeelink / LaCrosseGateway, e.g. "0a" for disabling the LED |
The available init commands depend on the sketch that is running on the USB stick / LaCrosseGateway.
#### JeeLink / LaCrosseGateway (connected over TCP)
| Parameter | Item Type | Description |
|---------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------|
| IP Address | String | The IP address of the Server to which the USB Receiver is connected, or the IP address of the LaCrosseGateway |
| TCP Port | Number | The TCP port over which the serial port is made available, or the LaCrosseGateway port (which usually is 81) |
| Init Commands | String | A semicolon separated list of init commands that will be send to the Jeelink / LaCrosseGateway, e.g. "0a" for disabling the LED |
#### LaCrosse temperature sensors
| Parameter | Item Type | Description |
|----------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| Sensor ID | Number | The ID of the connected sensor |
| Sensor Timeout | Number | The amount of time in seconds that should result in OFFLINE status when no readings have been received from the sensor |
| Update Interval | Number | The update interval in seconds how often value updates are propagated. A value of 0 leads to propagation of every value |
| Buffer Size | Number | The number of readings used for computing the rolling average |
| Lower Temperature Limit | Decimal | The lowest allowed valid temperature. Lower temperature readings will be ignored |
| Upper Temperature Limit | Decimal | The highest allowed valid temperature. Higher temperature readings will be ignored |
| Maximum allowed difference | Decimal | The maximum allowed difference from a value to the previous value (0 disables this check). If the difference is higher, the reading will be ignored. |
#### EC3000 power monitors
| Parameter | Item Type | Description |
|-----------------|-----------|-------------------------------------------------------------------------------------------------------------------------|
| Sensor ID | Number | The ID of the connected sensor |
| Sensor Timeout | Number | The amount of time in seconds that should result in OFFLINE status when no readings have been received from the sensor |
| Update Interval | Number | The update interval in seconds how often value updates are propagated. A value of 0 leads to propagation of every value |
| Buffer Size | Number | The number of readings used for computing the rolling average |
#### PCA301 power monitoring wireless sockets
| Parameter | Item Type | Description |
|-------------------|--------------|------------------------------------------------------------------------------------------------------------------------|
| Sensor ID | Number | The ID of the connected sensor |
| Sensor Timeout | Number | The amount of time in seconds that should result in OFFLINE status when no readings have been received from the sensor |
| Retry Count | Number | The number of times a switch command will be resent to the socket until giving up |
#### Revolt power monitors
| Parameter | Item Type | Description |
|-------------------|--------------|------------------------------------------------------------------------------------------------------------------------|
| Sensor ID | Number | The ID of the connected sensor |
| Sensor Timeout | Number | The amount of time in seconds that should result in OFFLINE status when no readings have been received from the sensor |
## Channels
#### LaCrosse temperature sensors
| Channel Type ID | Item Type | Description |
|-----------------|-----------------------|---------------------------------------------------|
| temperature | Number:Temperature | Temperature reading |
| humidity | Number:Dimensionless | Humidity reading |
| batteryNew | Contact | Whether the battery is new (CLOSED) or not (OPEN) |
| batteryLow | Contact | Whether the battery is low (CLOSED) or not (OPEN) |
#### TX22 temperature and humidity sensors
| Channel Type ID | Item Type | Description |
|-----------------|-----------------------|----------------------------|
| temperature | Number:Temperature | Temperature reading |
| humidity | Number:Dimensionless | Humidity reading |
| pressure | Number:Pressure | Current pressure reading |
| rain | Number:Length | Rainfall today |
| windStrength | Number:Speed | Current wind speed |
| windAngle | Number:Angle | Current wind direction |
| gustStrength | Number:Speed | Gust speed |
#### EC3000 power monitors
| Channel Type ID | Item Type | Description |
|------------------|---------------|-------------------------------------------|
| currentPower | Number:Power | Current power draw |
| maxPower | Number:Power | Maximum power draw |
| consumptionTotal | Number:Energy | Total energy consumption |
| applianceTime | Number:Time | Total electrical appliance operating time |
| sensorTime | Number:Time | Total turn on time of power monitor |
| resets | Number | Number of resets |
#### PCA301 power monitoring wireless sockets
| Channel Type ID | Item Type | Description |
|-------------------------|---------------|------------------------------------------------------|
| switchingState | Switch | Whether the sockets are currently switched on or off |
| currentPower | Number:Power | Current power draw |
| consumptionTotal | Number:Energy | Total energy consumption |
#### Revolt power monitors
| Channel Type ID | Item Type | Description |
|-------------------|--------------------------|-------------------------------------------|
| currentPower | Number:Power | Current power draw |
| consumptionTotal | Number:Energy | Total energy consumption |
| powerFactor | Number | Ratio of real power to apparent power |
| electricCurrent | Number:ElectricCurrent | The measured Electric Current |
| electricPotential | Number:ElectricPotential | The measured Electric Potential |
| powerFrequency | Number:Frequency | The measured AC power frequency |
## Commands
#### PCA301 power monitoring wireless sockets
| Channel Type ID | Item Type | Description |
|-------------------------|--------------|---------------------------------------------------|
| switchingState | Switch | Supports ON and OFF commands to switch the socket |
## Full Example
A typical thing configuration for PCA301 looks like this:
```
Bridge jeelink:jeelinkUsb:pca301 "Jeelink pca301" @ "home" [ serialPort="/dev/ttyUSB0" ]
Thing jeelink:pca301:1-160-236 "ec3k 1" (jeelink:jeelinkUsb:pca301) @ "home" [ sensorId="1-160-236"]
```
A typical thing configuration for EC3000 looks like this:
```
Bridge jeelink:jeelinkUsb:ec3k "Jeelink ec3k" @ "home" [ serialPort="COM4" ]
Thing jeelink:ec3k:0E3D "ec3k 1" (jeelink:jeelinkUsb:ec3k) @ "home" [ sensorId="0E3D"]
Thing jeelink:ec3k:14E7 "ec3k 2" (jeelink:jeelinkUsb:ec3k) @ "home" [ sensorId="14E7"]
```
A typical Thing configuration for lacrosse looks like this:
```
Bridge jeelink:jeelinkUsb:lacrosse "Jeelink lacrosse" @ "home" [ serialPort="COM6" ]
Thing jeelink:lacrosse:sensor1 "Jeelink lacrosse 1" (jeelink:jeelinkUsb:lacrosse) @ "home" [ sensorId="16", minTemp=10, maxTemp=32]
Thing jeelink:lacrosse:sensor2 "Jeelink lacrosse 2" (jeelink:jeelinkUsb:lacrosse) @ "home" [ sensorId="18", minTemp=10, maxTemp=32]
```
A typical thing configuration for Revolt looks like this:
```
Bridge jeelink:jeelinkUsb:revolt "Jeelink revolt" @ "home" [ serialPort="COM4" ]
Thing jeelink:revolt:4F1B "revolt 1" (jeelink:jeelinkUsb:revolt) @ "home" [ sensorId="4F1B"]
```
A typical item configuration for a LaCrosse temperature sensor looks like this:
```
Number:Dimensionless Humidty_LR "Living Room [%.1f %unit%]" <humidity> {channel="jeelink:lacrosse:42:humidity"}
Number:Temperature Temperature_LR "Living Room [%.1f %unit%]" <temperature> {channel="jeelink:lacrosse:42:temperature"}
Contact Battery_Low_LR "Battery Low Living Room" {channel="jeelink:lacrosse:42:batteryLow"}
Contact Battery_New_LR "Battery New Living Room" {channel="jeelink:lacrosse:42:batteryNew"}
```
A typical item configuration for a PCA301 power monitoring wireless sockets looks like this:
```
Switch SocketSwitch {channel="jeelink:pca301:1-160-236:switchingState"}
Number:Power SocketWattage {channel="jeelink:pca301:1-160-236:currentPower"}
Number:Energy SocketConsumption {channel="jeelink:pca301:1-160-236:consumptionTotal"}
```
A typical item configuration for a TX22 temperature and humidity sensor looks like this:
```
Number:Dimensionless Humidity "Outside [%.1f %unit%]" <humidity> {channel="jeelink:tx22:42:humidity"}
Number:Temperature Temperature "Outside [%.1f %unit%]" <temperature> {channel="jeelink:tx22:42:temperature"}
Contact Battery_Low_LR "Battery Low Outside" {channel="jeelink:tx22:42:batteryLow"}
Contact Battery_New_LR "Battery New Outside" {channel="jeelink:tx22:42:batteryNew"}
Number:Length Rain "Outside [%.1f %unit%]" {channel="jeelink:tx22:42:rain"}
Number:Speed WindStrength "Wind [%.1f %unit%]" {channel="jeelink:tx22:42:windStrength"}
Number:Angle WindDir "Wind dir [%.1f %unit%]" {channel="jeelink:tx22:42:windAngle"}
Number:Speed GustStrength "Gust [%.1f %unit%]" {channel="jeelink:tx22:42:gustStrength"}
```
A typical item configuration for a Revolt power monitor looks like this:
```
Number:Power SocketWattage {channel="jeelink:revolt:4F1B:currentPower"}
Number:Energy SocketConsumption {channel="jeelink:revolt:4F1B:consumptionTotal"}
Number:Dimensionless POwerFactor {channel="jeelink:revolt:4F1B:powerFactor"}
Number:ElectricCurrent Current {channel="jeelink:revolt:4F1B:electricCurrent"}
Number:ElectricPotential Voltage {channel="jeelink:revolt:4F1B:electricPotential"}
Number:Frequency PowerFrequency {channel="jeelink:revolt:4F1B:powerFrequency"}
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.jeelink</artifactId>
<name>openHAB Add-ons :: Bundles :: JeeLink Binding</name>
</project>

View File

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

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Converter that simply ignores input.
*
* @author Volker Bier - Initial contribution
*/
public class IgnoringConverter implements JeeLinkReadingConverter<Reading> {
@Override
public Reading createReading(String inputLine) {
return null;
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* Defines common constants, which are used across the whole binding.
*
* @author Volker Bier - Initial contribution
*/
@NonNullByDefault
public class JeeLinkBindingConstants {
private JeeLinkBindingConstants() {
}
public static final String BINDING_ID = "jeelink";
// List of all Thing Type UIDs
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
public static final Set<ThingTypeUID> SUPPORTED_SENSOR_THING_TYPES_UIDS = new HashSet<>();
public static final ThingTypeUID JEELINK_USB_STICK_THING_TYPE = new ThingTypeUID(BINDING_ID, "jeelinkUsb");
public static final ThingTypeUID JEELINK_TCP_STICK_THING_TYPE = new ThingTypeUID(BINDING_ID, "jeelinkTcp");
public static final ThingTypeUID LGW_USB_STICK_THING_TYPE = new ThingTypeUID(BINDING_ID, "lgwUsb");
public static final ThingTypeUID LGW_TCP_STICK_THING_TYPE = new ThingTypeUID(BINDING_ID, "lgwTcp");
public static final ThingTypeUID LACROSSE_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "lacrosse");
public static final ThingTypeUID EC3000_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "ec3k");
public static final ThingTypeUID PCA301_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "pca301");
public static final ThingTypeUID TX22_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "tx22");
public static final ThingTypeUID REVOLT_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "revolt");
public static final ThingTypeUID LGW_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "lgw");
// List of all channel ids for lacrosse sensor things
public static final String TEMPERATURE_CHANNEL = "temperature";
public static final String HUMIDITY_CHANNEL = "humidity";
public static final String BATTERY_NEW_CHANNEL = "batteryNew";
public static final String BATTERY_LOW_CHANNEL = "batteryLow";
public static final String PROPERTY_SENSOR_ID = "sensorId";
// List of all additional channel ids for ec3k sensor things
public static final String CURRENT_POWER_CHANNEL = "currentPower";
public static final String MAX_POWER_CHANNEL = "maxPower";
public static final String CONSUMPTION_CHANNEL = "consumptionTotal";
public static final String APPLIANCE_TIME_CHANNEL = "applianceTime";
public static final String SENSOR_TIME_CHANNEL = "sensorTime";
public static final String RESETS_CHANNEL = "resets";
// List of all additional channel ids for pca301 sensor things
public static final String SWITCHING_STATE_CHANNEL = "switchingState";
// List of all additional channel ids for revolt sensor things
public static final String POWER_FACTOR_CHANNEL = "powerFactor";
public static final String ELECTRIC_CURRENT_CHANNEL = "electricCurrent";
public static final String ELECTRIC_POTENTIAL_CHANNEL = "electricPotential";
public static final String FREQUENCY_CHANNEL = "powerFrequency";
// List of all additional channel ids for tx22 sensor things
public static final String PRESSURE_CHANNEL = "pressure";
public static final String RAIN_CHANNEL = "rain";
public static final String WIND_STENGTH_CHANNEL = "windStrength";
public static final String WIND_ANGLE_CHANNEL = "windAngle";
public static final String GUST_STRENGTH_CHANNEL = "gustStrength";
static {
for (SensorDefinition<?> def : SensorDefinition.getDefinitions()) {
SUPPORTED_SENSOR_THING_TYPES_UIDS.add(def.getThingTypeUID());
}
SUPPORTED_THING_TYPES_UIDS.add(JeeLinkBindingConstants.JEELINK_USB_STICK_THING_TYPE);
SUPPORTED_THING_TYPES_UIDS.add(JeeLinkBindingConstants.JEELINK_TCP_STICK_THING_TYPE);
SUPPORTED_THING_TYPES_UIDS.add(JeeLinkBindingConstants.LGW_USB_STICK_THING_TYPE);
SUPPORTED_THING_TYPES_UIDS.add(JeeLinkBindingConstants.LGW_TCP_STICK_THING_TYPE);
SUPPORTED_THING_TYPES_UIDS.addAll(SUPPORTED_SENSOR_THING_TYPES_UIDS);
}
}

View File

@@ -0,0 +1,290 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.openhab.binding.jeelink.internal.config.JeeLinkConfig;
import org.openhab.binding.jeelink.internal.connection.ConnectionListener;
import org.openhab.binding.jeelink.internal.connection.JeeLinkConnection;
import org.openhab.binding.jeelink.internal.connection.JeeLinkSerialConnection;
import org.openhab.binding.jeelink.internal.connection.JeeLinkTcpConnection;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a JeeLink USB Receiver thing.
*
* @author Volker Bier - Initial contribution
*/
public class JeeLinkHandler extends BaseBridgeHandler implements BridgeHandler, ConnectionListener {
private final Logger logger = LoggerFactory.getLogger(JeeLinkHandler.class);
private final List<JeeLinkReadingConverter<?>> converters = new ArrayList<>();
private final Map<String, JeeLinkReadingConverter<?>> sensorTypeConvertersMap = new HashMap<>();
private final Map<Class<?>, Set<ReadingHandler<? extends Reading>>> readingClassHandlerMap = new HashMap<>();
private final SerialPortManager serialPortManager;
private JeeLinkConnection connection;
private AtomicBoolean connectionInitialized = new AtomicBoolean(false);
private ScheduledFuture<?> connectJob;
private ScheduledFuture<?> initJob;
private long lastReadingTime;
private ScheduledFuture<?> monitorJob;
public JeeLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
super(bridge);
this.serialPortManager = serialPortManager;
}
@Override
public void initialize() {
JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
if (cfg.serialPort != null && cfg.baudRate != null) {
SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(cfg.serialPort);
if (serialPortIdentifier == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Port not found: " + cfg.serialPort);
return;
}
connection = new JeeLinkSerialConnection(serialPortIdentifier, cfg.baudRate, this);
connection.openConnection();
} else if (cfg.ipAddress != null && cfg.port != null) {
connection = new JeeLinkTcpConnection(cfg.ipAddress + ":" + cfg.port, scheduler, this);
connection.openConnection();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Connection configuration incomplete");
}
}
@Override
public void connectionOpened() {
logger.debug("Connection to port {} opened.", connection.getPort());
updateStatus(ThingStatus.ONLINE);
if (connectJob != null) {
logger.debug("Connection to port {} established. Reconnect cancelled.", connection.getPort());
connectJob.cancel(true);
connectJob = null;
}
JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
initJob = scheduler.schedule(() -> {
intializeConnection();
}, cfg.initDelay, TimeUnit.SECONDS);
logger.debug("Init commands scheduled in {} seconds.", cfg.initDelay);
if (cfg.reconnectInterval > 0) {
monitorJob = scheduler.scheduleWithFixedDelay(new Runnable() {
private long lastMonitorTime;
@Override
public void run() {
if (getThing().getStatus() == ThingStatus.ONLINE && lastReadingTime < lastMonitorTime) {
logger.debug("Monitoring job for port {} detected missing readings. Triggering reconnect...",
connection.getPort());
connection.closeConnection();
updateStatus(ThingStatus.OFFLINE);
connection.openConnection();
}
lastMonitorTime = System.currentTimeMillis();
}
}, cfg.reconnectInterval, cfg.reconnectInterval, TimeUnit.SECONDS);
logger.debug("Monitoring job started.");
}
}
@Override
public void connectionClosed() {
logger.debug("Connection to port {} closed.", connection.getPort());
updateStatus(ThingStatus.OFFLINE);
connectionInitialized.set(false);
if (initJob != null) {
initJob.cancel(true);
}
if (monitorJob != null) {
monitorJob.cancel(true);
}
}
@Override
public void connectionAborted(String cause) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, cause);
if (monitorJob != null) {
monitorJob.cancel(true);
}
if (initJob != null) {
initJob.cancel(true);
}
connectionInitialized.set(false);
connectJob = scheduler.schedule(() -> {
connection.openConnection();
}, 10, TimeUnit.SECONDS);
logger.debug("Connection to port {} aborted ({}). Reconnect scheduled.", connection.getPort(), cause);
}
public void addReadingHandler(ReadingHandler<? extends Reading> h) {
synchronized (readingClassHandlerMap) {
Set<ReadingHandler<? extends Reading>> handlers = readingClassHandlerMap.get(h.getReadingClass());
if (handlers == null) {
handlers = new HashSet<>();
// this is the first handler for this reading class => also setup converter
readingClassHandlerMap.put(h.getReadingClass(), handlers);
if (SensorDefinition.ALL_TYPE == h.getSensorType()) {
converters.addAll(SensorDefinition.getDiscoveryConverters());
} else {
JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
if (c != null) {
converters.add(c);
sensorTypeConvertersMap.put(h.getSensorType(), c);
}
}
}
if (!handlers.contains(h)) {
logger.debug("Adding reading handler for class {}: {}", h.getReadingClass(), h);
handlers.add(h);
}
}
}
public void removeReadingHandler(ReadingHandler<? extends Reading> h) {
synchronized (readingClassHandlerMap) {
Set<ReadingHandler<? extends Reading>> handlers = readingClassHandlerMap.get(h.getReadingClass());
if (handlers != null) {
logger.debug("Removing reading handler for class {}: {}", h.getReadingClass(), h);
handlers.remove(h);
if (handlers.isEmpty()) {
// this was the last handler for this reading class => also remove converter
readingClassHandlerMap.remove(h.getReadingClass());
if (SensorDefinition.ALL_TYPE == h.getSensorType()) {
converters.removeAll(SensorDefinition.getDiscoveryConverters());
} else {
JeeLinkReadingConverter<?> c = SensorDefinition.getConverter(h.getSensorType());
if (c != null) {
converters.remove(c);
}
}
}
}
}
}
@Override
public void handleCommand(ChannelUID channelUid, Command command) {
}
@Override
public void handleInput(String input) {
lastReadingTime = System.currentTimeMillis();
// try all associated converters to find the correct one
for (JeeLinkReadingConverter<?> c : converters) {
Reading r = c.createReading(input);
if (r != null) {
// this converter is responsible
intializeConnection();
// propagate to the appropriate sensor handler
synchronized (readingClassHandlerMap) {
Set<ReadingHandler<? extends Reading>> handlers = getAllHandlers(r.getClass());
for (ReadingHandler h : handlers) {
h.handleReading(r);
}
}
break;
}
}
}
private Set<ReadingHandler<? extends Reading>> getAllHandlers(Class<? extends Reading> readingClass) {
Set<ReadingHandler<? extends Reading>> handlers = new HashSet<>();
Set<ReadingHandler<? extends Reading>> typeHandlers = readingClassHandlerMap.get(readingClass);
if (typeHandlers != null) {
handlers.addAll(typeHandlers);
}
Set<ReadingHandler<? extends Reading>> discoveryHandlers = readingClassHandlerMap.get(Reading.class);
if (discoveryHandlers != null) {
handlers.addAll(discoveryHandlers);
}
return handlers;
}
private void intializeConnection() {
if (!connectionInitialized.getAndSet(true)) {
JeeLinkConfig cfg = getConfig().as(JeeLinkConfig.class);
String initCommands = cfg.initCommands;
if (initCommands != null && !initCommands.trim().isEmpty()) {
logger.debug("Sending init commands for port {}: {}", connection.getPort(), initCommands);
connection.sendCommands(initCommands);
}
}
}
@Override
public void dispose() {
if (connectJob != null) {
connectJob.cancel(true);
connectJob = null;
}
if (connection != null) {
connection.closeConnection();
}
super.dispose();
}
public JeeLinkConnection getConnection() {
return connection;
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.openhab.binding.jeelink.internal.discovery.SensorDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link JeeLinkHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Volker Bier - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.jeelink")
public class JeeLinkHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(JeeLinkHandlerFactory.class);
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final SerialPortManager serialPortManager;
@Activate
public JeeLinkHandlerFactory(final @Reference SerialPortManager serialPortManager) {
this.serialPortManager = serialPortManager;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUid) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUid);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUid = thing.getThingTypeUID();
ThingHandler handler = null;
if (thingTypeUid.equals(JEELINK_USB_STICK_THING_TYPE) || thingTypeUid.equals(JEELINK_TCP_STICK_THING_TYPE)
|| thingTypeUid.equals(LGW_TCP_STICK_THING_TYPE) || thingTypeUid.equals(LGW_USB_STICK_THING_TYPE)) {
logger.debug("creating JeeLinkHandler for thing {}...", thing.getUID().getId());
handler = new JeeLinkHandler((Bridge) thing, serialPortManager);
registerSensorDiscoveryService((JeeLinkHandler) handler);
} else {
handler = SensorDefinition.createHandler(thingTypeUid, thing);
if (handler == null) {
logger.debug("skipping creation of unknown handler for thing {} with type {}...",
thing.getUID().getId(), thing.getThingTypeUID().getId());
}
}
return handler;
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof JeeLinkHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
private synchronized void registerSensorDiscoveryService(JeeLinkHandler bridgeHandler) {
logger.debug("registering sensor discovery service...");
SensorDiscoveryService discoveryService = new SensorDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Interface for converting input read from a JeeLinkConnection to a Reading.
*
* @author Volker Bier - Initial contribution
*/
public interface JeeLinkReadingConverter<R extends Reading> {
public R createReading(String inputLine);
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.jeelink.internal.config.JeeLinkSensorConfig;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
/**
* Abstract thing handler for sensors connected to a JeeLink.
*
* @author Volker Bier - Initial contribution
*/
public abstract class JeeLinkSensorHandler<R extends Reading> extends BaseThingHandler implements ReadingHandler<R> {
protected String id;
protected final String sensorType;
private ReadingPublisher<R> publisher;
private long secsSinceLastReading;
private ScheduledFuture<?> statusUpdateJob;
public JeeLinkSensorHandler(Thing thing, String sensorType) {
super(thing);
this.sensorType = sensorType;
}
public abstract ReadingPublisher<R> createPublisher();
@Override
public String getSensorType() {
return sensorType;
}
@Override
public void handleReading(R r) {
if (r != null && id.equals(r.getSensorId())) {
secsSinceLastReading = 0;
updateStatus(ThingStatus.ONLINE);
if (publisher != null) {
publisher.publish(r);
}
}
}
@Override
public synchronized void handleCommand(ChannelUID channelUid, Command command) {
}
@Override
public synchronized void initialize() {
JeeLinkHandler jlh = (JeeLinkHandler) getBridge().getHandler();
jlh.addReadingHandler(this);
JeeLinkSensorConfig cfg = getConfigAs(JeeLinkSensorConfig.class);
id = cfg.sensorId;
statusUpdateJob = createStatusUpdateJob(scheduler, cfg.sensorTimeout);
publisher = createPublisher();
updateStatus(ThingStatus.UNKNOWN);
}
@Override
public synchronized void dispose() {
id = null;
JeeLinkHandler jlh = (JeeLinkHandler) getBridge().getHandler();
jlh.removeReadingHandler(this);
if (statusUpdateJob != null) {
statusUpdateJob.cancel(true);
statusUpdateJob = null;
}
if (publisher != null) {
publisher.dispose();
publisher = null;
}
super.dispose();
}
private ScheduledFuture<?> createStatusUpdateJob(ScheduledExecutorService execService, final int sensorTimeout) {
return execService.scheduleWithFixedDelay(() -> {
if (secsSinceLastReading++ > sensorTimeout) {
updateStatus(ThingStatus.OFFLINE);
}
}, sensorTimeout, 1, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Interface for a Reading from a Sensor. Addiionally provided basic arithmetic operations needed
* for computing average values for readings.
*
* @author Volker Bier - Initial contribution
*/
public interface Reading {
/**
* @return the sensor ID of the sensor that provided this reading.
*/
String getSensorId();
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Interface for classes that handle Readings.
*
* @author Volker Bier - Initial contribution
*/
public interface ReadingHandler<R extends Reading> {
public void handleReading(R r);
public Class<R> getReadingClass();
public String getSensorType();
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Interface for classes that publish readings.
*
* @author Volker Bier - Initial contribution
*/
public interface ReadingPublisher<R extends Reading> {
public void publish(R reading);
public void dispose();
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Computes a rolling average of readings that is passed on to the next publisher
* after a given time frame.
*
* @author Volker Bier - Initial contribution
*/
public abstract class RollingAveragePublisher<R extends Reading> implements ReadingPublisher<R> {
private final ReadingPublisher<R> publisher;
private ScheduledFuture<?> valueUpdateJob;
private RollingReadingAverage<R> rollingAvg;
public RollingAveragePublisher(int bufferSize, int interval, ReadingPublisher<R> p,
ScheduledExecutorService execService) {
publisher = p;
valueUpdateJob = createUpdateJob(execService, interval);
rollingAvg = createRollingReadingAverage(bufferSize);
}
public abstract RollingReadingAverage<R> createRollingReadingAverage(int bufferSize);
@Override
public void publish(R reading) {
rollingAvg.add(reading);
}
@Override
public void dispose() {
if (valueUpdateJob != null) {
valueUpdateJob.cancel(true);
valueUpdateJob = null;
}
publisher.dispose();
}
private ScheduledFuture<?> createUpdateJob(ScheduledExecutorService execService, final int updateInterval) {
return execService.scheduleWithFixedDelay(() -> {
publisher.publish(rollingAvg.getAverage());
}, updateInterval, updateInterval, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
/**
* Computes a rolling average of readings.
*
* @author Volker Bier - Initial contribution
*/
public abstract class RollingReadingAverage<R extends Reading> {
private int size = 0;
private int maxSize;
private R total = null;
private int index = 0;
private R[] samples;
public RollingReadingAverage(R[] array) {
maxSize = array.length;
samples = array;
}
public void add(R reading) {
if (size < maxSize) {
size++;
}
if (total == null) {
total = reading;
} else {
total = add(total, reading);
total = substract(total, samples[index]);
}
samples[index] = reading;
if (++index == maxSize) {
index = 0;
}
}
public R getAverage() {
if (total == null) {
return null;
}
return divide(total, size);
}
protected abstract R add(R value1, R value2);
protected abstract R substract(R from, R value);
protected abstract R divide(R value, int count);
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.jeelink.internal.ec3k.Ec3kSensorDefinition;
import org.openhab.binding.jeelink.internal.lacrosse.LaCrosseSensorDefinition;
import org.openhab.binding.jeelink.internal.lacrosse.LgwSensorDefinition;
import org.openhab.binding.jeelink.internal.lacrosse.Tx22SensorDefinition;
import org.openhab.binding.jeelink.internal.pca301.Pca301SensorDefinition;
import org.openhab.binding.jeelink.internal.revolt.RevoltSensorDefinition;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
/**
* Base class for sensor definitions.
*
* @author Volker Bier - Initial contribution
*
* @param <R> the Reading type this sensor provides.
*/
public abstract class SensorDefinition<R extends Reading> {
public static final String ALL_TYPE = "All";
private static final Set<SensorDefinition<?>> SENSOR_DEFS = Stream
.of(new LaCrosseSensorDefinition(), new Ec3kSensorDefinition(), new Pca301SensorDefinition(),
new Tx22SensorDefinition(), new RevoltSensorDefinition(), new LgwSensorDefinition())
.collect(Collectors.toSet());
private static final Set<JeeLinkReadingConverter<?>> CONVERTERS = SENSOR_DEFS.stream()
.map(SensorDefinition::createConverter).collect(Collectors.toSet());
protected final String type;
final ThingTypeUID thingTypeUid;
final String name;
public SensorDefinition(ThingTypeUID thingTypeUid, String name, String type) {
this.thingTypeUid = thingTypeUid;
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public ThingTypeUID getThingTypeUID() {
return thingTypeUid;
}
public abstract Class<R> getReadingClass();
public abstract JeeLinkSensorHandler<R> createHandler(Thing thing);
public abstract JeeLinkReadingConverter<R> createConverter();
public static SensorDefinition<?> getSensorDefinition(Reading reading) {
for (SensorDefinition<?> sensor : SENSOR_DEFS) {
if (sensor.getReadingClass().equals(reading.getClass())) {
return sensor;
}
}
return null;
}
public static Set<SensorDefinition<?>> getDefinitions() {
return SENSOR_DEFS;
}
public static ThingHandler createHandler(ThingTypeUID thingTypeUid, Thing thing) {
for (SensorDefinition<?> sensor : SENSOR_DEFS) {
if (sensor.getThingTypeUID().equals(thingTypeUid)) {
return sensor.createHandler(thing);
}
}
return null;
}
public static JeeLinkReadingConverter<?> getConverter(String sensorType) {
for (SensorDefinition<?> sensor : SENSOR_DEFS) {
if (sensor.getSensorType().equals(sensorType)) {
return sensor.createConverter();
}
}
return null;
}
public static Set<JeeLinkReadingConverter<?>> getDiscoveryConverters() {
return CONVERTERS;
}
private String getSensorType() {
return type;
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.config;
/**
* Configuration for a Handler that is able to buffer values.
*
* @author Volker Bier - Initial contribution
*/
public class BufferedSensorConfig extends JeeLinkSensorConfig {
public int updateInterval;
public int bufferSize;
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.config;
/**
* Configuration for a JeeLinkHandler.
*
* @author Volker Bier - Initial contribution
*/
public class JeeLinkConfig {
public String ipAddress;
public Integer port;
public String serialPort;
public Integer baudRate;
public String initCommands;
public Integer initDelay;
public Integer reconnectInterval;
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.config;
/**
* Configuration for a JeeLinkSensorHandler.
*
* @author Volker Bier - Initial contribution
*/
public class JeeLinkSensorConfig {
public String sensorId;
public int sensorTimeout;
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.config;
/**
* Configuration for a LaCrossTemperatureSensorHandler.
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseTemperatureSensorConfig extends BufferedSensorConfig {
public float minTemp;
public float maxTemp;
public float maxDiff;
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.config;
/**
* Configuration for a Pca301SensorHandler.
*
* @author Volker Bier - Initial contribution
*/
public class Pca301SensorConfig extends JeeLinkSensorConfig {
public int sendCount;
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.connection;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract base class for a connection to a JeeLink.
* Manages ReadingListeners, finds out the sketch name and allows to propagate read lines.
*
* @author Volker Bier - Initial contribution
*/
public abstract class AbstractJeeLinkConnection implements JeeLinkConnection {
private final Logger logger = LoggerFactory.getLogger(AbstractJeeLinkConnection.class);
protected final ConnectionListener connectionListener;
protected String port;
private AtomicBoolean initialized = new AtomicBoolean(false);
public AbstractJeeLinkConnection(String port, ConnectionListener listener) {
this.port = port;
connectionListener = listener;
}
@Override
public String getPort() {
return port;
}
/**
* returns the stream that can be used to write the init commands to the receiver.
*/
protected abstract OutputStream getInitStream() throws IOException;
protected void notifyOpen() {
connectionListener.connectionOpened();
}
protected void notifyClosed() {
connectionListener.connectionClosed();
}
protected void notifyAbort(String cause) {
connectionListener.connectionAborted(cause);
initialized.set(false);
}
public void propagateLine(String line) throws IOException {
logger.trace("Read line from port {}: {}", port, line);
connectionListener.handleInput(line);
}
@Override
public void sendCommands(String commands) {
try {
if (commands != null && !commands.trim().isEmpty()) {
// do not create in try-with-resources as this will
// close the undelying socket for TCP connections
OutputStream initStream = getInitStream();
if (initStream == null) {
throw new IOException(
"Connection on port " + port + " did not provide an init stream for writing init commands");
}
// do not close the writer as this closes the underlying stream, and
// in case of tcp connections, the underlying socket
OutputStreamWriter w = new OutputStreamWriter(initStream);
for (String cmd : commands.split(";")) {
logger.debug("Writing to device on port {}: {} ", port, cmd);
w.write(cmd + "\n");
}
w.flush();
}
} catch (IOException ex) {
logger.debug("Error writing to output stream!", ex);
closeConnection();
notifyAbort("propagate: " + ex.getMessage());
}
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.connection;
/**
* Listener that is notified on connection status changes of JeeLinkConnections
* as well as when input has been read from the connection.
*
* @author Volker Bier - Initial contribution
*/
public interface ConnectionListener {
/**
* Called when the connection has been opened.
*/
void connectionOpened();
/**
* Called when the connection has been closed.
*/
void connectionClosed();
/**
* Called when the connection has been aborted.
*
* @param cause a text describing the cause of the abort.
*/
void connectionAborted(String cause);
/**
* Called whenever input has been read from the connection.
*/
public void handleInput(String input);
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.connection;
/**
* Interface for connections to JeeLink USB Receivers.
*
* @author Volker Bier - Initial contribution
*/
public interface JeeLinkConnection {
/**
* closes the connection to the receiver.
*/
void closeConnection();
/**
* opens the connection to the receiver.
*/
void openConnection();
/**
* returns port to which the receiver is connected.
*/
String getPort();
/**
* sends the specified commands to the receiver (commands are semicolon separated)
*/
void sendCommands(String initCommands);
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.connection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.TooManyListenersException;
import org.openhab.core.io.transport.serial.PortInUseException;
import org.openhab.core.io.transport.serial.SerialPort;
import org.openhab.core.io.transport.serial.SerialPortEvent;
import org.openhab.core.io.transport.serial.SerialPortEventListener;
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reads lines from serial port and propagates them to registered InputListeners.
*
* @author Volker Bier - Initial contribution
*/
public class JeeLinkSerialConnection extends AbstractJeeLinkConnection {
private final Logger logger = LoggerFactory.getLogger(JeeLinkSerialConnection.class);
private final int baudRate;
private SerialPort serialPort;
private final SerialPortIdentifier serialPortIdentifier;
private boolean open;
public JeeLinkSerialConnection(SerialPortIdentifier serialPortIdentifier, int baudRate,
ConnectionListener listener) {
super(serialPortIdentifier.getName(), listener);
logger.debug("Creating serial connection for port {} with baud rate {}...", port, baudRate);
this.baudRate = baudRate;
this.serialPortIdentifier = serialPortIdentifier;
}
@Override
public synchronized void closeConnection() {
if (open) {
logger.debug("Closing serial connection to port {}...", port);
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
serialPort.close();
notifyClosed();
open = false;
}
}
@Override
public synchronized void openConnection() {
try {
if (!open) {
logger.debug("Opening serial connection to port {} with baud rate {}...", port, baudRate);
serialPort = serialPortIdentifier.open("openhab", 3000);
open = true;
serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
final BufferedReader input = new BufferedReader(new InputStreamReader(serialPort.getInputStream()));
serialPort.addEventListener(new SerialPortEventListener() {
@Override
public void serialEvent(SerialPortEvent event) {
try {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
propagateLine(input.readLine());
}
} catch (IOException ex) {
logger.debug("Error reading from serial port!", ex);
closeConnection();
notifyAbort("propagate: " + ex.getMessage());
}
}
});
serialPort.notifyOnDataAvailable(true);
notifyOpen();
}
} catch (UnsupportedCommOperationException | IOException | TooManyListenersException ex) {
closeConnection();
notifyAbort(ex.getMessage());
} catch (PortInUseException ex) {
notifyAbort("Port in use: " + port);
}
}
@Override
public OutputStream getInitStream() throws IOException {
return open ? serialPort.getOutputStream() : null;
}
}

View File

@@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.connection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reads lines from TCP port and propagates them to registered InputListeners.
*
* @author Volker Bier - Initial contribution
*/
public class JeeLinkTcpConnection extends AbstractJeeLinkConnection {
private static final Pattern IP_PORT_PATTERN = Pattern.compile("([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):([0-9]+)");
private final Logger logger = LoggerFactory.getLogger(JeeLinkTcpConnection.class);
private ScheduledExecutorService scheduler;
private Reader reader;
private Socket socket;
public JeeLinkTcpConnection(String port, ScheduledExecutorService scheduler, ConnectionListener l) {
super(port, l);
this.scheduler = scheduler;
}
@Override
public synchronized void closeConnection() {
if (reader != null) {
logger.debug("Closing TCP connection to port {}...", port);
reader.close();
reader = null;
closeSocketSilently();
socket = null;
notifyClosed();
}
}
@Override
public synchronized void openConnection() {
if (reader != null) {
logger.debug("TCP connection to port {} is already open!", port);
return;
}
Matcher ipm = IP_PORT_PATTERN.matcher(port);
if (!ipm.matches()) {
notifyAbort("Invalid TCP port specification: " + port);
}
String hostName = ipm.group(1);
int portNumber = Integer.parseInt(ipm.group(2));
logger.debug("Opening TCP connection to host {} port {}...", hostName, portNumber);
try {
logger.debug("Creating TCP socket to {}...", port);
socket = new Socket(hostName, portNumber);
socket.setKeepAlive(true);
logger.debug("TCP socket created.");
reader = new Reader(socket);
scheduler.execute(reader);
notifyOpen();
} catch (IOException ex) {
if (socket != null) {
closeSocketSilently();
}
notifyAbort(ex.getMessage());
}
}
private void closeSocketSilently() {
try {
socket.close();
} catch (IOException e) {
logger.debug("Failed to close socket.", e);
}
}
@Override
public OutputStream getInitStream() throws IOException {
return socket == null ? null : socket.getOutputStream();
}
private class Reader implements Runnable {
private Socket socket;
private BufferedReader inputReader;
private volatile boolean isRunning = true;
private Reader(Socket socket) throws IOException {
this.socket = socket;
inputReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
@Override
public void run() {
String line;
logger.debug("Reader for TCP port {} starting...", port);
try {
while (isRunning) {
line = inputReader.readLine();
if (line == null) {
throw new IOException("Got EOF on port " + port);
}
propagateLine(line);
}
} catch (IOException ex) {
if (isRunning) {
closeConnection();
notifyAbort(ex.getMessage());
}
} finally {
logger.debug("Reader for TCP port {} finished...", port);
}
}
public void close() {
logger.debug("Shutting down reader for TCP port {}...", port);
try {
isRunning = false;
socket.close();
inputReader.close();
} catch (IOException ex) {
logger.debug("Failed to close TCP port {}!", port, ex);
}
}
}
}

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.discovery;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.openhab.binding.jeelink.internal.JeeLinkHandler;
import org.openhab.binding.jeelink.internal.Reading;
import org.openhab.binding.jeelink.internal.ReadingHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.binding.jeelink.internal.config.JeeLinkSensorConfig;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery service for sensors connected to a JeeLink USB Receiver.
*
* @author Volker Bier - Initial contribution
*/
public class SensorDiscoveryService extends AbstractDiscoveryService implements ReadingHandler<Reading> {
private static final int DISCOVER_TIMEOUT_SECONDS = 30;
private final Logger logger = LoggerFactory.getLogger(SensorDiscoveryService.class);
JeeLinkHandler bridge;
AtomicBoolean capture = new AtomicBoolean();
/**
* Creates the discovery service for the given handler and converter.
*/
public SensorDiscoveryService(JeeLinkHandler jeeLinkHandler) {
super(SUPPORTED_SENSOR_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, true);
bridge = jeeLinkHandler;
}
@Override
protected synchronized void startScan() {
if (!capture.getAndSet(true)) {
logger.debug("discovery started for bridge {}", bridge.getThing().getUID());
// start listening for new sensor values
bridge.addReadingHandler(this);
capture.set(true);
}
}
@Override
protected void startBackgroundDiscovery() {
startScan();
}
@Override
protected synchronized void stopScan() {
if (capture.getAndSet(false)) {
bridge.removeReadingHandler(this);
logger.debug("discovery stopped for bridge {}", bridge.getThing().getUID());
}
}
@Override
protected void stopBackgroundDiscovery() {
stopScan();
}
private boolean idExistsAtBridge(String id) {
List<Thing> existingThings = bridge.getThing().getThings();
boolean idExists = false;
for (Thing t : existingThings) {
idExists = idExists || t.getUID().getId().equals(id);
}
return idExists;
}
@Override
public void handleReading(Reading reading) {
final String id = reading.getSensorId();
List<Thing> existingThings = bridge.getThing().getThings();
boolean sensorThingExists = false;
for (Thing t : existingThings) {
sensorThingExists = sensorThingExists
|| id.equals(t.getConfiguration().as(JeeLinkSensorConfig.class).sensorId);
}
ThingUID bridgeUID = bridge.getThing().getUID();
if (!sensorThingExists) {
SensorDefinition<?> def = SensorDefinition.getSensorDefinition(reading);
logger.debug("discovery for bridge {} found unknown sensor of type {} with id {}", bridgeUID,
def.getThingTypeUID(), id);
boolean idExists = idExistsAtBridge(id);
String newId = id;
if (idExists) {
logger.debug("bridge {} already has a connected sensor with thing id {}", bridgeUID, id);
int idx = 1;
while (idExists) {
newId = id + "-" + idx++;
idExists = idExistsAtBridge(newId);
}
logger.debug("Bridge {} uses thing id {} instead of {}", bridgeUID, newId, id);
}
ThingUID sensorThing = new ThingUID(def.getThingTypeUID(), bridgeUID, newId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(sensorThing).withLabel(def.getName())
.withBridge(bridgeUID).withRepresentationProperty("id").withProperty(PROPERTY_SENSOR_ID, id)
.build();
thingDiscovered(discoveryResult);
} else {
logger.debug("discovery for bridge {} found already known sensor id {}", bridgeUID, id);
}
}
@Override
public Class<Reading> getReadingClass() {
return Reading.class;
}
@Override
public String getSensorType() {
return SensorDefinition.ALL_TYPE;
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.ec3k;
import org.openhab.binding.jeelink.internal.Reading;
/**
* Reading of a EC3000 sensor.
*
* @author Volker Bier - Initial contribution
*/
public class Ec3kReading implements Reading {
private float currentWatt;
private float maxWatt;
private long consumptionTotal;
private long applianceTime;
private long sensorTime;
private String sensorId;
private int resets;
public Ec3kReading(String sensorId, float currentWatt, float maxWatt, long consumptionTotal, long applianceTime,
long sensorTime, int resets) {
this.currentWatt = currentWatt;
this.maxWatt = maxWatt;
this.consumptionTotal = consumptionTotal;
this.applianceTime = applianceTime;
this.sensorTime = sensorTime;
this.sensorId = sensorId;
this.resets = resets;
}
@Override
public String toString() {
return "sensorId=" + sensorId + ": currWatt=" + currentWatt + ", maxWatt=" + maxWatt + ", consumption="
+ consumptionTotal + ", applianceTime=" + applianceTime + ", sensorTime=" + sensorTime + ", resets="
+ resets;
}
public float getCurrentWatt() {
return currentWatt;
}
@Override
public String getSensorId() {
return sensorId;
}
public float getMaxWatt() {
return maxWatt;
}
public long getConsumptionTotal() {
return consumptionTotal;
}
public long getApplianceTime() {
return applianceTime;
}
public long getSensorTime() {
return sensorTime;
}
public int getResets() {
return resets;
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.ec3k;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
/**
* Converter for converting a line read from a ec3kSerial sketch to a Ec3kReading.
*
* @author Volker Bier - Initial contribution
*/
public class Ec3kReadingConverter implements JeeLinkReadingConverter<Ec3kReading> {
private static final Pattern LINE_P = Pattern
.compile("OK\\s+22\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)"
+ "\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)"
+ "\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)");
@Override
public Ec3kReading createReading(String inputLine) {
if (inputLine != null) {
Matcher matcher = LINE_P.matcher(inputLine);
if (matcher.matches()) {
/*
* OK 22 188 129 0 209 209 102 0 174 89 187 0 1 123 102 0 0 10 117 2 0 (ID = BC81)
*/
long id1 = Long.parseLong(matcher.group(1));
long id2 = Long.parseLong(matcher.group(2));
String id = String.format("%02X%02X", id1, id2);
long secTot1 = Long.parseLong(matcher.group(3));
long secTot2 = Long.parseLong(matcher.group(4));
long secTot3 = Long.parseLong(matcher.group(5));
long secTot4 = Long.parseLong(matcher.group(6));
long secondsTotal = (secTot1 << 24) + (secTot2 << 16) + (secTot3 << 8) + secTot4;
long secOn1 = Long.parseLong(matcher.group(7));
long secOn2 = Long.parseLong(matcher.group(8));
long secOn3 = Long.parseLong(matcher.group(9));
long secOn4 = Long.parseLong(matcher.group(10));
long secondsOn = (secOn1 << 24) + (secOn2 << 16) + (secOn3 << 8) + secOn4;
long con1 = Long.parseLong(matcher.group(11));
long con2 = Long.parseLong(matcher.group(12));
long con3 = Long.parseLong(matcher.group(13));
long con4 = Long.parseLong(matcher.group(14));
long consumptionTotal = ((con1 << 24) + (con2 << 16) + (con3 << 8) + con4) / 1000;
long cur1 = Long.parseLong(matcher.group(15));
long cur2 = Long.parseLong(matcher.group(16));
float currentWatt = ((cur1 << 8) + cur2) / 10f;
long max1 = Long.parseLong(matcher.group(17));
long max2 = Long.parseLong(matcher.group(18));
float maxWatt = ((max1 << 8) + max2) / 10f;
int resets = Integer.parseInt(matcher.group(19));
return new Ec3kReading(id, currentWatt, maxWatt, consumptionTotal, secondsOn, secondsTotal, resets);
}
}
return null;
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.ec3k;
import org.openhab.binding.jeelink.internal.RollingReadingAverage;
/**
* Computes a rolling average of readings.
*
* @author Volker Bier - Initial contribution
*/
public class Ec3kRollingReadingAverage extends RollingReadingAverage<Ec3kReading> {
public Ec3kRollingReadingAverage(int bufferSize) {
super(new Ec3kReading[bufferSize]);
}
@Override
protected Ec3kReading add(Ec3kReading value1, Ec3kReading value2) {
if (value2 != null) {
return new Ec3kReading(value2.getSensorId(), value1.getCurrentWatt() + value2.getCurrentWatt(),
value2.getMaxWatt(), value2.getConsumptionTotal(), value2.getApplianceTime(),
value2.getSensorTime(), value2.getResets());
}
return new Ec3kReading(value1.getSensorId(), value1.getCurrentWatt(), value1.getMaxWatt(),
value1.getConsumptionTotal(), value1.getApplianceTime(), value1.getSensorTime(), value1.getResets());
}
@Override
protected Ec3kReading substract(Ec3kReading from, Ec3kReading value) {
float newCurrWatt = from.getCurrentWatt();
if (value != null) {
newCurrWatt -= value.getCurrentWatt();
}
return new Ec3kReading(from.getSensorId(), newCurrWatt, from.getMaxWatt(), from.getConsumptionTotal(),
from.getApplianceTime(), from.getSensorTime(), from.getResets());
}
@Override
protected Ec3kReading divide(Ec3kReading value, int number) {
return new Ec3kReading(value.getSensorId(), value.getCurrentWatt() / number, value.getMaxWatt(),
value.getConsumptionTotal(), value.getApplianceTime(), value.getSensorTime(), value.getResets());
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.ec3k;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor Defintion of a EC3000 Power Monitor.
*
* @author Volker Bier - Initial contribution
*/
public class Ec3kSensorDefinition extends SensorDefinition<Ec3kReading> {
public Ec3kSensorDefinition() {
super(JeeLinkBindingConstants.EC3000_SENSOR_THING_TYPE, "EnergyCount 3000 Power Monitor", "22");
}
@Override
public JeeLinkReadingConverter<Ec3kReading> createConverter() {
return new Ec3kReadingConverter();
}
@Override
public Class<Ec3kReading> getReadingClass() {
return Ec3kReading.class;
}
@Override
public JeeLinkSensorHandler<Ec3kReading> createHandler(Thing thing) {
return new Ec3kSensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.ec3k;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.binding.jeelink.internal.RollingAveragePublisher;
import org.openhab.binding.jeelink.internal.RollingReadingAverage;
import org.openhab.binding.jeelink.internal.config.BufferedSensorConfig;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a EC3000 sensor thing.
*
* @author Volker Bier - Initial contribution
*/
public class Ec3kSensorHandler extends JeeLinkSensorHandler<Ec3kReading> {
private final Logger logger = LoggerFactory.getLogger(Ec3kSensorHandler.class);
public Ec3kSensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
}
@Override
public Class<Ec3kReading> getReadingClass() {
return Ec3kReading.class;
}
@Override
public ReadingPublisher<Ec3kReading> createPublisher() {
ReadingPublisher<Ec3kReading> publisher = new ReadingPublisher<Ec3kReading>() {
@Override
public void publish(Ec3kReading reading) {
if (reading != null && getThing().getStatus() == ThingStatus.ONLINE) {
BigDecimal currentWatt = new BigDecimal(reading.getCurrentWatt()).setScale(1, RoundingMode.HALF_UP);
BigDecimal maxWatt = new BigDecimal(reading.getMaxWatt()).setScale(1, RoundingMode.HALF_UP);
logger.debug(
"updating states for thing {}: currWatt={} ({}), maxWatt={}, consumption={}, secondsOn={}, secondsTotal={}",
getThing().getUID().getId(), currentWatt, reading.getCurrentWatt(), maxWatt,
reading.getConsumptionTotal(), reading.getApplianceTime(), reading.getSensorTime());
updateState(CURRENT_POWER_CHANNEL, new QuantityType<>(currentWatt, SmartHomeUnits.WATT));
updateState(MAX_POWER_CHANNEL, new QuantityType<>(maxWatt, SmartHomeUnits.WATT));
updateState(CONSUMPTION_CHANNEL,
new QuantityType<>(reading.getConsumptionTotal(), SmartHomeUnits.WATT_HOUR));
updateState(APPLIANCE_TIME_CHANNEL,
new QuantityType<>(reading.getApplianceTime(), SmartHomeUnits.HOUR));
updateState(SENSOR_TIME_CHANNEL, new QuantityType<>(reading.getSensorTime(), SmartHomeUnits.HOUR));
updateState(RESETS_CHANNEL, new DecimalType(reading.getResets()));
}
}
@Override
public void dispose() {
}
};
BufferedSensorConfig cfg = getConfigAs(BufferedSensorConfig.class);
if (cfg.bufferSize > 1 && cfg.updateInterval > 0) {
publisher = new RollingAveragePublisher<Ec3kReading>(cfg.bufferSize, cfg.updateInterval, publisher,
scheduler) {
@Override
public RollingReadingAverage<Ec3kReading> createRollingReadingAverage(int bufferSize) {
return new Ec3kRollingReadingAverage(bufferSize);
}
};
}
return publisher;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Checks that the given temperature is in range before passing it on to the next publisher.
*
* @author Volker Bier - Initial contribution
*/
public class BoundsCheckingPublisher implements ReadingPublisher<LaCrosseTemperatureReading> {
private final Logger logger = LoggerFactory.getLogger(BoundsCheckingPublisher.class);
private final ReadingPublisher<LaCrosseTemperatureReading> publisher;
private final float minTemp;
private final float maxTemp;
public BoundsCheckingPublisher(float min, float max, ReadingPublisher<LaCrosseTemperatureReading> p) {
minTemp = min;
maxTemp = max;
publisher = p;
}
@Override
public void publish(LaCrosseTemperatureReading reading) {
if (reading.getTemperature() >= minTemp && reading.getTemperature() <= maxTemp) {
publisher.publish(reading);
} else {
logger.debug("Ignoring out of bounds reading {}", reading.getTemperature());
}
}
@Override
public void dispose() {
publisher.dispose();
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Checks that the given temperature does not differ too much from the last temperature
* before passing it on to the next publisher.
*
* @author Volker Bier - Initial contribution
*/
public class DifferenceCheckingPublisher implements ReadingPublisher<LaCrosseTemperatureReading> {
private final Logger logger = LoggerFactory.getLogger(DifferenceCheckingPublisher.class);
private final ReadingPublisher<LaCrosseTemperatureReading> publisher;
private final float allowedDifference;
private LaCrosseTemperatureReading lastReading;
public DifferenceCheckingPublisher(float difference, ReadingPublisher<LaCrosseTemperatureReading> p) {
allowedDifference = difference;
publisher = p;
}
@Override
public void publish(LaCrosseTemperatureReading reading) {
if (lastReading == null
|| Math.abs(reading.getTemperature() - lastReading.getTemperature()) < allowedDifference) {
publisher.publish(reading);
} else {
logger.debug("Ignoring reading {} differing too much from previous value", reading.getTemperature());
}
lastReading = reading;
}
@Override
public void dispose() {
publisher.dispose();
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.RollingReadingAverage;
/**
* Computes a rolling average of readings.
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseRollingReadingAverage extends RollingReadingAverage<LaCrosseTemperatureReading> {
public LaCrosseRollingReadingAverage(int bufferSize) {
super(new LaCrosseTemperatureReading[bufferSize]);
}
@Override
protected LaCrosseTemperatureReading add(LaCrosseTemperatureReading value1, LaCrosseTemperatureReading value2) {
if (value2 != null) {
return new LaCrosseTemperatureReading(value2.getSensorId(), value2.getSensorType(), value2.getChannel(),
value1.getTemperature() + value2.getTemperature(), value1.getHumidity() + value2.getHumidity(),
value2.isBatteryNew(), value2.isBatteryLow());
}
return new LaCrosseTemperatureReading(value1.getSensorId(), value1.getSensorType(), value1.getChannel(),
value1.getTemperature(), value1.getHumidity(), value1.isBatteryNew(), value1.isBatteryLow());
}
@Override
protected LaCrosseTemperatureReading substract(LaCrosseTemperatureReading from, LaCrosseTemperatureReading value) {
float newTemp = from.getTemperature();
int newHum = from.getHumidity();
if (value != null) {
newTemp -= value.getTemperature();
newHum -= value.getHumidity();
}
return new LaCrosseTemperatureReading(from.getSensorId(), from.getSensorType(), from.getChannel(), newTemp,
newHum, from.isBatteryNew(), from.isBatteryLow());
}
@Override
protected LaCrosseTemperatureReading divide(LaCrosseTemperatureReading value, int number) {
return new LaCrosseTemperatureReading(value.getSensorId(), value.getSensorType(), value.getChannel(),
value.getTemperature() / number, value.getHumidity() / number, value.isBatteryNew(),
value.isBatteryLow());
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor Defintion of a LaCrosse Temperature Sensor
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseSensorDefinition extends SensorDefinition<LaCrosseTemperatureReading> {
public LaCrosseSensorDefinition() {
super(JeeLinkBindingConstants.LACROSSE_SENSOR_THING_TYPE, "LaCrosse Temperature Sensor", "9");
}
@Override
public JeeLinkReadingConverter<LaCrosseTemperatureReading> createConverter() {
return new LaCrosseTemperatureReadingConverter();
}
@Override
public Class<LaCrosseTemperatureReading> getReadingClass() {
return LaCrosseTemperatureReading.class;
}
@Override
public JeeLinkSensorHandler<LaCrosseTemperatureReading> createHandler(Thing thing) {
return new LaCrosseTemperatureSensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.Reading;
/**
* Reading of a LaCrosse Temperature Sensor.
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseTemperatureReading implements Reading {
private String sensorId;
private int sensorType;
private int channel;
private Float temp;
private Integer humidity;
private boolean batteryNew;
private boolean batteryLow;
public LaCrosseTemperatureReading(int sensorId, int sensorType, int channel, Float temp, Integer humidity,
boolean batteryNew, boolean batteryLow) {
this(String.valueOf(sensorId), sensorType, channel, temp, humidity, batteryNew, batteryLow);
}
public LaCrosseTemperatureReading(String sensorId, int sensorType, int channel, Float temp, Integer humidity,
boolean batteryNew, boolean batteryLow) {
this.sensorId = sensorId;
this.sensorType = sensorType;
this.channel = channel;
this.temp = temp;
this.humidity = humidity;
this.batteryNew = batteryNew;
this.batteryLow = batteryLow;
}
@Override
public String getSensorId() {
return sensorId;
}
public int getSensorType() {
return sensorType;
}
public Float getTemperature() {
return temp;
}
public Integer getHumidity() {
return humidity;
}
public boolean isBatteryLow() {
return batteryLow;
}
@Override
public String toString() {
return "sensorId=" + sensorId + ": channel=" + channel + ", temp=" + temp + ", hum=" + humidity + ", batLow="
+ batteryLow + ", batNew=" + batteryNew;
}
public boolean isBatteryNew() {
return batteryNew;
}
public int getChannel() {
return channel;
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Converter for converting a line read from a LaCrosseITPlusReader sketch to a LaCrosseTemperatureReading.
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseTemperatureReadingConverter implements JeeLinkReadingConverter<LaCrosseTemperatureReading> {
private static final Pattern LINE_P = Pattern
.compile("OK\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)");
private final Logger logger = LoggerFactory.getLogger(LaCrosseTemperatureReadingConverter.class);
@Override
public LaCrosseTemperatureReading createReading(String inputLine) {
// parse lines only if we have registered listeners
if (inputLine != null) {
Matcher matcher = LINE_P.matcher(inputLine);
if (matcher.matches()) {
// Format
//
// OK 9 56 1 4 156 37 (ID = 56 T: 18.0 H: 37 no NewBatt)
// OK 9 49 1 4 182 54 (ID = 49 T: 20.6 H: 54 no NewBatt)
// OK 9 55 129 4 192 56 (ID = 55 T: 21.6 H: 56 WITH NewBatt)
// OK 9 ID XXX XXX XXX XXX
// | | | | | | |
// | | | | | | --- Humidity incl. WeakBatteryFlag
// | | | | | |------ Temp * 10 + 1000 LSB
// | | | | |---------- Temp * 10 + 1000 MSB
// | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag
// | | |----------------- Sensor ID
// | |------------------- fix "9"
// |---------------------- fix "OK"
logger.trace("Creating reading from: {}", inputLine);
int sensorId = Integer.parseInt(matcher.group(2));
int int3 = Integer.parseInt(matcher.group(3));
int batteryNewInt = (int3 & 0x80) >> 7;
int type = (int3 & 0x70) >> 4;
int channel = int3 & 0x0F;
float temperature = (float) (Integer.parseInt(matcher.group(4)) * 256
+ Integer.parseInt(matcher.group(5)) - 1000) / 10;
int humidity = Integer.parseInt(matcher.group(6)) & 0x7f;
int batteryLowInt = (Integer.parseInt(matcher.group(6)) & 0x80) >> 7;
boolean batteryLow = batteryLowInt == 1;
boolean batteryNew = batteryNewInt == 1;
return new LaCrosseTemperatureReading(sensorId, type, channel, temperature, humidity, batteryNew,
batteryLow);
}
}
return null;
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.binding.jeelink.internal.RollingAveragePublisher;
import org.openhab.binding.jeelink.internal.RollingReadingAverage;
import org.openhab.binding.jeelink.internal.config.LaCrosseTemperatureSensorConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a LaCrosse Temperature Sensor thing.
*
* @author Volker Bier - Initial contribution
*/
public class LaCrosseTemperatureSensorHandler extends JeeLinkSensorHandler<LaCrosseTemperatureReading> {
private final Logger logger = LoggerFactory.getLogger(LaCrosseTemperatureSensorHandler.class);
public LaCrosseTemperatureSensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
}
@Override
public Class<LaCrosseTemperatureReading> getReadingClass() {
return LaCrosseTemperatureReading.class;
}
@Override
public ReadingPublisher<LaCrosseTemperatureReading> createPublisher() {
return new ReadingPublisher<LaCrosseTemperatureReading>() {
private final Map<Integer, ReadingPublisher<LaCrosseTemperatureReading>> channelPublishers = new HashMap<>();
@Override
public void publish(LaCrosseTemperatureReading reading) {
if (reading != null) {
int channelNo = reading.getChannel();
ReadingPublisher<LaCrosseTemperatureReading> publisher;
synchronized (channelPublishers) {
publisher = channelPublishers.get(channelNo);
if (publisher == null) {
publisher = createPublisherForChannel(channelNo);
channelPublishers.put(channelNo, publisher);
createMissingChannels(reading.getChannel());
}
}
publisher.publish(reading);
}
}
private void createMissingChannels(int channelNo) {
List<Channel> missingChannels = new ArrayList<>();
String idSuffix = channelNo > 1 ? String.valueOf(channelNo) : "";
String labelSuffix = channelNo > 1 ? " " + channelNo : "";
for (String channelName : new String[] { TEMPERATURE_CHANNEL, HUMIDITY_CHANNEL }) {
if (getThing().getChannel(channelName + idSuffix) == null) {
missingChannels.add(ChannelBuilder
.create(new ChannelUID(getThing().getUID(), channelName + idSuffix), "Number")
.withType(new ChannelTypeUID(getThing().getThingTypeUID().getBindingId(), channelName))
.withLabel(StringUtils.capitalize(channelName + labelSuffix)).build());
}
}
missingChannels.addAll(getThing().getChannels());
if (!missingChannels.isEmpty()) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withChannels(missingChannels);
updateThing(thingBuilder.build());
}
}
@Override
public void dispose() {
synchronized (channelPublishers) {
for (ReadingPublisher<LaCrosseTemperatureReading> p : channelPublishers.values()) {
p.dispose();
}
channelPublishers.clear();
}
}
};
}
public ReadingPublisher<LaCrosseTemperatureReading> createPublisherForChannel(int channelNo) {
ReadingPublisher<LaCrosseTemperatureReading> publisher = new ReadingPublisher<LaCrosseTemperatureReading>() {
@Override
public void publish(LaCrosseTemperatureReading reading) {
if (reading != null && getThing().getStatus() == ThingStatus.ONLINE) {
BigDecimal temp = new BigDecimal(reading.getTemperature()).setScale(1, RoundingMode.HALF_UP);
if (channelNo == 1) {
logger.debug(
"updating states for thing {} ({}): temp={} ({}), humidity={}, batteryNew={}, batteryLow={}",
getThing().getLabel(), getThing().getUID().getId(), temp, reading.getTemperature(),
reading.getHumidity(), reading.isBatteryNew(), reading.isBatteryLow());
updateState(TEMPERATURE_CHANNEL, new QuantityType<>(temp, SIUnits.CELSIUS));
updateState(HUMIDITY_CHANNEL,
new QuantityType<>(reading.getHumidity(), SmartHomeUnits.PERCENT));
updateState(BATTERY_NEW_CHANNEL, reading.isBatteryNew() ? OnOffType.ON : OnOffType.OFF);
updateState(BATTERY_LOW_CHANNEL, reading.isBatteryLow() ? OnOffType.ON : OnOffType.OFF);
} else {
logger.debug("updating states for channel {} of thing {} ({}): temp={} ({}), humidity={}",
reading.getChannel(), getThing().getLabel(), getThing().getUID().getId(), temp,
reading.getTemperature(), reading.getHumidity());
updateState(TEMPERATURE_CHANNEL + reading.getChannel(),
new QuantityType<>(temp, SIUnits.CELSIUS));
updateState(HUMIDITY_CHANNEL + reading.getChannel(),
new QuantityType<>(reading.getHumidity(), SmartHomeUnits.PERCENT));
}
}
}
@Override
public void dispose() {
}
};
LaCrosseTemperatureSensorConfig cfg = getConfigAs(LaCrosseTemperatureSensorConfig.class);
if (cfg.bufferSize > 1 && cfg.updateInterval > 0) {
publisher = new RollingAveragePublisher<LaCrosseTemperatureReading>(cfg.bufferSize, cfg.updateInterval,
publisher, scheduler) {
@Override
public RollingReadingAverage<LaCrosseTemperatureReading> createRollingReadingAverage(int bufferSize) {
return new LaCrosseRollingReadingAverage(bufferSize);
}
};
}
if (cfg.maxDiff > 0) {
publisher = new DifferenceCheckingPublisher(cfg.maxDiff, publisher);
}
publisher = new BoundsCheckingPublisher(cfg.minTemp, cfg.maxTemp, publisher);
return publisher;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.Reading;
/**
* Reading of sensors directly connected to a LGW.
*
* @author Volker Bier - Initial contribution
*/
public class LgwReading implements Reading {
private String sensorId;
private Float temp;
private Integer humidity;
private Integer pressure;
public LgwReading(int sensorId, Float temp, Integer humidity, Integer pressure) {
this.sensorId = String.valueOf(sensorId);
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
}
@Override
public String getSensorId() {
return sensorId;
}
public Float getTemperature() {
return temp;
}
public Integer getHumidity() {
return humidity;
}
public Integer getPressure() {
return pressure;
}
public boolean hasPressure() {
return pressure != null;
}
public boolean hasHumidity() {
return humidity != null;
}
public boolean hasTemperature() {
return temp != null;
}
@Override
public String toString() {
return "sensorId=" + sensorId + ": temp=" + temp + (hasHumidity() ? ", hum=" + humidity : "")
+ (hasPressure() ? ", pressure=" + pressure : "");
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Converter for converting a line read from a LGW to a LgwReading.
*
* @author Volker Bier - Initial contribution
*/
public class LgwReadingConverter implements JeeLinkReadingConverter<LgwReading> {
private static final Pattern LINE_P = Pattern.compile(
"OK\\s+WS\\s+([0-9]+)\\s+4\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)(?:\\s+255){8}?\\s+0\\s+([0-9]+)\\s+([0-9]+)(?:\\s+255){9}?");
private final Logger logger = LoggerFactory.getLogger(LgwReadingConverter.class);
@Override
public LgwReading createReading(String inputLine) {
// parse lines only if we have registered listeners
if (inputLine != null) {
Matcher matcher = LINE_P.matcher(inputLine);
if (matcher.matches()) {
/*
* Format
* OK WS 71 4 4 203 53 255 255 255 255 255 255 255 255 0 3 219 255 255 255 255 255 255 255 255 255
* OK WS 75 4 4 195 61 255 255 255 255 255 255 255 255 0 255 255 255 255 255 255 255 255 255 255 255
* OK WS 213 4 5 126 40 255 255 255 255 255 255 255 255 0 40 53 0 48 57
* OK WS ID XXX TTT TTT HHH RRR RRR DDD DDD SSS SSS GGG GGG FFF PPP PPP GAS GAS GAS DEB DEB DEB LUX LUX
* LUX
* | | | | | | | | | | | | | | | | | |-------------------------------------- Pressure LSB
* | | | | | | | | | | | | | | | | |------------------------------------------ Pressure MSB
* | | | | | | | | | | | | | | | |-- Fix 0
* | | | | | | |-------------------------------------- Humidity (1 ... 99 %rH) FF = none
* | | | | | |------------------------------------------ Temp * 10 + 1000 LSB (-40 ... +60 °C) FF/FF =
* none
* | | | | |---------------------------------------------- Temp * 10 + 1000 MSB
* | | | |-------------------------------------------------- fix "4"
* | | |------------------------------------------------------ Sensor ID (1 ... 63)
* | |--------------------------------------------------------- fix "WS"
* |------------------------------------------------------------ fix "OK"
*/
logger.trace("Creating reading from: {}", inputLine);
int sensorId = Integer.parseInt(matcher.group(1));
Float temperature = "255".equals(matcher.group(2)) ? null
: (float) (Integer.parseInt(matcher.group(2)) * 256 + Integer.parseInt(matcher.group(3)) - 1000)
/ 10;
Integer humidity = "255".equals(matcher.group(4)) ? null : Integer.parseInt(matcher.group(4));
Integer pressure = null;
if (!"255".equals(matcher.group(5))) {
pressure = Integer.parseInt(matcher.group(5)) * 256 + Integer.parseInt(matcher.group(6));
}
return new LgwReading(sensorId, temperature, humidity, pressure);
}
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor definition of a sensor directly connected to a LGW.
*
* @author Volker Bier - Initial contribution
*/
public class LgwSensorDefinition extends SensorDefinition<LgwReading> {
public LgwSensorDefinition() {
super(JeeLinkBindingConstants.LGW_SENSOR_THING_TYPE, "LGW Sensor", "LGW");
}
@Override
public JeeLinkReadingConverter<LgwReading> createConverter() {
return new LgwReadingConverter();
}
@Override
public Class<LgwReading> getReadingClass() {
return LgwReading.class;
}
@Override
public JeeLinkSensorHandler<LgwReading> createHandler(Thing thing) {
return new LgwSensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import static org.openhab.core.library.unit.MetricPrefix.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a LGW Sensor thing.
*
* @author Volker Bier - Initial contribution
*/
@NonNullByDefault
public class LgwSensorHandler extends JeeLinkSensorHandler<LgwReading> {
private final Logger logger = LoggerFactory.getLogger(LgwSensorHandler.class);
private boolean hasHumidityChannel;
private boolean hasPressureChannel;
public LgwSensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
hasHumidityChannel = getThing().getChannel(HUMIDITY_CHANNEL) != null;
hasPressureChannel = getThing().getChannel(PRESSURE_CHANNEL) != null;
}
@Override
public Class<LgwReading> getReadingClass() {
return LgwReading.class;
}
@Override
public ReadingPublisher<LgwReading> createPublisher() {
ReadingPublisher<LgwReading> publisher = new ReadingPublisher<LgwReading>() {
@Override
public void publish(LgwReading reading) {
if (reading != null && getThing().getStatus() == ThingStatus.ONLINE) {
logger.debug("updating states for thing {} ({}): {}", getThing().getLabel(),
getThing().getUID().getId(), reading);
if (reading.hasTemperature()) {
BigDecimal temp = new BigDecimal(reading.getTemperature()).setScale(1, RoundingMode.HALF_UP);
updateState(TEMPERATURE_CHANNEL, new QuantityType<>(temp, SIUnits.CELSIUS));
}
if (reading.hasHumidity()) {
if (!hasHumidityChannel) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withChannel(ChannelBuilder
.create(new ChannelUID(getThing().getUID(), HUMIDITY_CHANNEL), "Number:Humidity")
.withType(new ChannelTypeUID(getThing().getThingTypeUID().getBindingId(),
HUMIDITY_CHANNEL))
.withLabel(StringUtils.capitalize(HUMIDITY_CHANNEL)).build());
updateThing(thingBuilder.build());
hasHumidityChannel = true;
}
updateState(HUMIDITY_CHANNEL,
new QuantityType<>(reading.getHumidity(), SmartHomeUnits.PERCENT));
}
if (reading.hasPressure()) {
if (!hasPressureChannel) {
ThingBuilder thingBuilder = editThing();
thingBuilder.withChannel(ChannelBuilder
.create(new ChannelUID(getThing().getUID(), PRESSURE_CHANNEL), "Number:Pressure")
.withType(new ChannelTypeUID(getThing().getThingTypeUID().getBindingId(),
PRESSURE_CHANNEL))
.withLabel(StringUtils.capitalize(PRESSURE_CHANNEL)).build());
updateThing(thingBuilder.build());
hasPressureChannel = true;
}
updateState(PRESSURE_CHANNEL, new QuantityType<>(reading.getPressure(), HECTO(SIUnits.PASCAL)));
}
}
}
@Override
public void dispose() {
}
};
return publisher;
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
/**
* Reading of a TX22 Temperature/Humidity Sensor.
*
* @author Volker Bier - Initial contribution
*/
public class Tx22Reading extends LaCrosseTemperatureReading {
private Integer rain;
private Float windDirection;
private Float windSpeed;
private Float windGust;
private Integer pressure;
public Tx22Reading(int sensorId, int sensorType, int channel, Float temp, Integer humidity, boolean batteryNew,
boolean batteryLow, Integer rain, Float windDirection, Float windSpeed, Float windGust, Integer pressure) {
super(String.valueOf(sensorId), sensorType, channel, temp, humidity, batteryNew, batteryLow);
this.rain = rain;
this.windDirection = windDirection;
this.windSpeed = windSpeed;
this.windGust = windGust;
this.pressure = pressure;
}
public Integer getRain() {
return rain;
}
public Float getWindDirection() {
return windDirection;
}
public Float getWindSpeed() {
return windSpeed;
}
public Float getWindGust() {
return windGust;
}
public Integer getPressure() {
return pressure;
}
public boolean hasWindGust() {
return windGust != null;
}
public boolean hasWindSpeed() {
return windSpeed != null;
}
public boolean hasWindDirection() {
return windDirection != null;
}
public boolean hasPressure() {
return pressure != null;
}
public boolean hasRain() {
return rain != null;
}
public boolean hasHumidity() {
return getHumidity() != null;
}
public boolean hasTemperature() {
return getTemperature() != null;
}
@Override
public String toString() {
return super.toString() + " rain=" + rain + " windDirection=" + windDirection + " windSpeed=" + windSpeed
+ " windGust=" + windGust + " pressure=" + pressure;
}
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Converter for converting a line read from a LaCrosseITPlusReader sketch to a Tx22Reading.
*
* @author Volker Bier - Initial contribution
*/
public class Tx22ReadingConverter implements JeeLinkReadingConverter<Tx22Reading> {
private static final Pattern LINE_P = Pattern.compile(
"OK\\s+WS\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)(?:\\s+([0-9]+)\\s+([0-9]+))?");
private final Logger logger = LoggerFactory.getLogger(Tx22ReadingConverter.class);
@Override
public Tx22Reading createReading(String inputLine) {
// parse lines only if we have registered listeners
if (inputLine != null) {
Matcher matcher = LINE_P.matcher(inputLine);
if (matcher.matches()) {
/**
* Format
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
* -------------------------------------------------------------------
* OK WS 14 1 4 208 53 0 0 7 8 0 29 0 31 1 4 1 I D=0E 23.2°C 52%rH 0mm Dir.: 180.0° Wind:2.9m/s
* Gust:3.1m/s new Batt. 1025 hPa
* OK WS ID XXX TTT TTT HHH RRR RRR DDD DDD SSS SSS GGG GGG FFF PPP PPP
* | | | | | | | | | | | | | | | | | |-- Pressure LSB (optional, FF/FF = none)
* | | | | | | | | | | | | | | | | |------ Pressure MSB (optional)
* | | | | | | | | | | | | | | | |---------- Flags *
* | | | | | | | | | | | | | | |-------------- WindGust * 10 LSB (0.0 ... 50.0 m/s) FF/FF = none
* | | | | | | | | | | | | | |------------------ WindGust * 10 MSB
* | | | | | | | | | | | | |---------------------- WindSpeed * 10 LSB(0.0 ... 50.0 m/s) FF/FF = none
* | | | | | | | | | | | |-------------------------- WindSpeed * 10 MSB
* | | | | | | | | | | |------------------------------ WindDirection * 10 LSB (0.0 ... 365.0 Degrees)
* FF/FF = none
* | | | | | | | | | |---------------------------------- WindDirection * 10 MSB
* | | | | | | | | |-------------------------------------- Rain * 0.5mm LSB (0 ... 9999 mm) FF/FF = none
* | | | | | | | |------------------------------------------ Rain * 0.5mm MSB
* | | | | | | |---------------------------------------------- Humidity (1 ... 99 %rH) FF = none
* | | | | | |-------------------------------------------------- Temp * 10 + 1000 LSB (-40 ... +60 °C)
* FF/FF = none
* | | | | |------------------------------------------------------ Temp * 10 + 1000 MSB
* | | | |---------------------------------------------------------- Sensor type (1=TX22, 2=NodeSensor)
* | | |------------------------------------------------------------- Sensor ID (0 ... 63)
* | |---------------------------------------------------------------- fix "WS"
* |------------------------------------------------------------------- fix "OK"
*
* Flags: 128 64 32 16 8 4 2 1
* | | |
* | | |-- New battery
* | |------ ERROR
* |---------- Low battery
*/
logger.trace("Creating reading from: {}", inputLine);
int sensorId = Integer.parseInt(matcher.group(1));
int type = Integer.parseInt(matcher.group(2));
Float temperature = "255".equals(matcher.group(3)) ? null
: (float) (Integer.parseInt(matcher.group(3)) * 256 + Integer.parseInt(matcher.group(4)) - 1000)
/ 10;
Integer humidity = "255".equals(matcher.group(5)) ? null : Integer.parseInt(matcher.group(5));
Integer rain = "255".equals(matcher.group(6)) ? null
: (Integer.parseInt(matcher.group(6)) * 256 + Integer.parseInt(matcher.group(7))) * 2;
Float windDirection = "255".equals(matcher.group(8)) ? null
: (Integer.parseInt(matcher.group(8)) * 256 + Integer.parseInt(matcher.group(9))) / 10f;
Float windSpeed = "255".equals(matcher.group(10)) ? null
: (Integer.parseInt(matcher.group(10)) * 256 + Integer.parseInt(matcher.group(11))) / 10f;
Float windGust = "255".equals(matcher.group(12)) ? null
: (Integer.parseInt(matcher.group(12)) * 256 + Integer.parseInt(matcher.group(13))) / 10f;
int flags = Integer.parseInt(matcher.group(14));
boolean batteryNew = (flags & (byte) 1) > 0;
boolean batteryLow = (flags & (byte) 4) > 0;
Integer pressure = null;
if (matcher.groupCount() > 14 && matcher.group(15) != null && !"255".equals(matcher.group(15))) {
pressure = Integer.parseInt(matcher.group(15)) * 256 + Integer.parseInt(matcher.group(16));
}
return new Tx22Reading(sensorId, type, 0, temperature, humidity, batteryNew, batteryLow, rain,
windDirection, windSpeed, windGust, pressure);
}
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor Defintion of a TX22 Temperature/Humidity Sensor.
*
* @author Volker Bier - Initial contribution
*/
public class Tx22SensorDefinition extends SensorDefinition<Tx22Reading> {
public Tx22SensorDefinition() {
super(JeeLinkBindingConstants.TX22_SENSOR_THING_TYPE, "TX22 Temperature/Humidity Sensor", "WS");
}
@Override
public JeeLinkReadingConverter<Tx22Reading> createConverter() {
return new Tx22ReadingConverter();
}
@Override
public Class<Tx22Reading> getReadingClass() {
return Tx22Reading.class;
}
@Override
public JeeLinkSensorHandler<Tx22Reading> createHandler(Thing thing) {
return new Tx22SensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.lacrosse;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import static org.openhab.core.library.unit.MetricPrefix.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a TX22 Temperature/Humidity Sensor thing.
*
* @author Volker Bier - Initial contribution
*/
public class Tx22SensorHandler extends JeeLinkSensorHandler<Tx22Reading> {
private final Logger logger = LoggerFactory.getLogger(Tx22SensorHandler.class);
public Tx22SensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
}
@Override
public Class<Tx22Reading> getReadingClass() {
return Tx22Reading.class;
}
@Override
public ReadingPublisher<Tx22Reading> createPublisher() {
ReadingPublisher<Tx22Reading> publisher = new ReadingPublisher<Tx22Reading>() {
@Override
public void publish(Tx22Reading reading) {
if (reading != null && getThing().getStatus() == ThingStatus.ONLINE) {
logger.debug("updating states for thing {} ({}): {}", getThing().getLabel(),
getThing().getUID().getId(), reading);
updateState(BATTERY_NEW_CHANNEL, reading.isBatteryNew() ? OnOffType.ON : OnOffType.OFF);
updateState(BATTERY_LOW_CHANNEL, reading.isBatteryLow() ? OnOffType.ON : OnOffType.OFF);
if (reading.hasTemperature()) {
BigDecimal temp = new BigDecimal(reading.getTemperature()).setScale(1, RoundingMode.HALF_UP);
updateState(TEMPERATURE_CHANNEL, new QuantityType<>(temp, SIUnits.CELSIUS));
}
if (reading.hasHumidity()) {
updateState(HUMIDITY_CHANNEL,
new QuantityType<>(reading.getHumidity(), SmartHomeUnits.PERCENT));
}
if (reading.hasRain()) {
updateState(RAIN_CHANNEL, new QuantityType<>(reading.getRain(), MILLI(SIUnits.METRE)));
}
if (reading.hasPressure()) {
updateState(PRESSURE_CHANNEL, new QuantityType<>(reading.getPressure(), HECTO(SIUnits.PASCAL)));
}
if (reading.hasWindDirection()) {
updateState(WIND_ANGLE_CHANNEL,
new QuantityType<>(reading.getWindDirection(), SmartHomeUnits.DEGREE_ANGLE));
}
if (reading.hasWindSpeed()) {
updateState(WIND_STENGTH_CHANNEL,
new QuantityType<>(reading.getWindSpeed(), SmartHomeUnits.METRE_PER_SECOND));
}
if (reading.hasWindGust()) {
updateState(GUST_STRENGTH_CHANNEL,
new QuantityType<>(reading.getWindGust(), SmartHomeUnits.METRE_PER_SECOND));
}
}
}
@Override
public void dispose() {
}
};
return publisher;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.pca301;
import org.openhab.binding.jeelink.internal.Reading;
/**
* Reading of a PCA301 sensor.
*
* @author Volker Bier - Initial contribution
*/
public class Pca301Reading implements Reading {
private final String sensorId;
private final int channel;
private final boolean on;
private final float current;
private final long total;
public Pca301Reading(String sensorId, int channel, boolean deviceOn, float consumptionCurrent,
long consumptionTotal) {
this.sensorId = sensorId;
this.channel = channel;
on = deviceOn;
current = consumptionCurrent;
total = consumptionTotal;
}
@Override
public String getSensorId() {
return sensorId;
}
public int getChannel() {
return channel;
}
public boolean isOn() {
return on;
}
public float getCurrent() {
return current;
}
public long getTotal() {
return total;
}
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.pca301;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Converter for converting a line read from a pcaSerial sketch to a Pca301Reading.
*
* @author Volker Bier - Initial contribution
*/
public class Pca301ReadingConverter implements JeeLinkReadingConverter<Pca301Reading> {
private static final Pattern READING_P = Pattern.compile(
"OK\\s+24\\s+([0-9]+)\\s+4\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)");
private final Logger logger = LoggerFactory.getLogger(Pca301ReadingConverter.class);
@Override
public Pca301Reading createReading(String inputLine) {
// parse lines only if we have registered listeners
if (inputLine != null) {
Matcher matcher = READING_P.matcher(inputLine);
if (matcher.matches()) {
// Format
//
// OK 24 1 4 1 160 236 0 0 0 0 0
// Interpretation:
// OK 24: fixed
// 1 Byte: channel
// 1 Byte: command (04=retrieve measure data, 05=switch device, 06=identify device by toggling device
// LED
// 3 Byte: device address (UID)
// 1 Byte: data -> 1 with command=4 resets device statistics
// -> 0/1 with command=5 switches device off/on
// 2 Byte: current consumption in watt (scale 1/10)
// 2 Byte: total consumption in kWh (scale 1/100)
logger.trace("Creating reading from: {}", inputLine);
int channelId = Integer.parseInt(matcher.group(1));
String sensorId = matcher.group(2) + "-" + matcher.group(3) + "-" + matcher.group(4);
int data = Integer.parseInt(matcher.group(5));
long con1 = Long.parseLong(matcher.group(6));
long con2 = Long.parseLong(matcher.group(7));
long consumptionCurrent = ((con1 << 8) + con2);
con1 = Long.parseLong(matcher.group(8));
con2 = Long.parseLong(matcher.group(9));
long consumptionTotal = ((con1 << 8) + con2);
return new Pca301Reading(sensorId, channelId, data == 1, consumptionCurrent / 10f,
consumptionTotal * 10);
}
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.pca301;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor Defintion of a PCA301 power switchable outlet.
*
* @author Volker Bier - Initial contribution
*/
public class Pca301SensorDefinition extends SensorDefinition<Pca301Reading> {
public Pca301SensorDefinition() {
super(JeeLinkBindingConstants.PCA301_SENSOR_THING_TYPE, "PCA301 power monitoring wireless socket", "24");
}
@Override
public JeeLinkReadingConverter<Pca301Reading> createConverter() {
return new Pca301ReadingConverter();
}
@Override
public Class<Pca301Reading> getReadingClass() {
return Pca301Reading.class;
}
@Override
public JeeLinkSensorHandler<Pca301Reading> createHandler(Thing thing) {
return new Pca301SensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,177 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.pca301;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.openhab.binding.jeelink.internal.JeeLinkHandler;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.binding.jeelink.internal.config.Pca301SensorConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a EC3000 sensor thing.
*
* @author Volker Bier - Initial contribution
*/
public class Pca301SensorHandler extends JeeLinkSensorHandler<Pca301Reading> {
private final Logger logger = LoggerFactory.getLogger(Pca301SensorHandler.class);
private JeeLinkHandler bridge;
private OnOffType state;
private final AtomicInteger channel = new AtomicInteger(-1);
private ScheduledFuture<?> retry;
private int sendCount;
public Pca301SensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
}
@Override
public Class<Pca301Reading> getReadingClass() {
return Pca301Reading.class;
}
@Override
public void initialize() {
super.initialize();
bridge = (JeeLinkHandler) getBridge().getHandler();
Pca301SensorConfig cfg = getConfigAs(Pca301SensorConfig.class);
sendCount = cfg.sendCount;
logger.debug("initilized handler for thing {} ({}): sendCount = {}", getThing().getLabel(),
getThing().getUID().getId(), sendCount);
}
@Override
public void dispose() {
super.dispose();
cancelRetry();
}
@Override
public synchronized void handleCommand(ChannelUID channelUid, Command command) {
logger.debug("received command for thing {} ({}): {}", getThing().getLabel(), getThing().getUID().getId(),
command);
if (channelUid.getIdWithoutGroup().equals(SWITCHING_STATE_CHANNEL)) {
if (command instanceof OnOffType) {
sendCommandRetry((OnOffType) command);
} else {
sendCommand(command);
}
} else if (command != RefreshType.REFRESH) {
logger.warn("Unsupported command {} for channel {} of sensor with id {}.", command,
channelUid.getIdWithoutGroup(), this.id);
}
}
@Override
public ReadingPublisher<Pca301Reading> createPublisher() {
return new ReadingPublisher<Pca301Reading>() {
@Override
public void publish(Pca301Reading reading) {
if (reading != null) {
channel.set(reading.getChannel());
BigDecimal current = new BigDecimal(reading.getCurrent()).setScale(1, RoundingMode.HALF_UP);
state = reading.isOn() ? OnOffType.ON : OnOffType.OFF;
updateState(CURRENT_POWER_CHANNEL, new QuantityType<>(current, SmartHomeUnits.WATT));
updateState(CONSUMPTION_CHANNEL, new QuantityType<>(reading.getTotal(), SmartHomeUnits.WATT_HOUR));
updateState(SWITCHING_STATE_CHANNEL, state);
logger.debug("updated states for thing {} ({}): state={}, current={}, total={}",
getThing().getLabel(), getThing().getUID().getId(), state, current, reading.getTotal());
}
}
@Override
public void dispose() {
}
};
}
private void sendCommand(Command command) {
int chan = channel.get();
if (chan != -1) {
if (command == RefreshType.REFRESH) {
bridge.getConnection().sendCommands(chan + ",4," + id.replaceAll("-", ",") + ",0,255,255,255,255s");
} else if (command instanceof OnOffType) {
bridge.getConnection().sendCommands(chan + ",5," + id.replaceAll("-", ",") + ","
+ (command == OnOffType.ON ? "1" : "2") + ",255,255,255,255s");
} else {
logger.warn("Unsupported command {} for sensor with id {}.", command, this.id);
}
} else if (command != RefreshType.REFRESH && !(command instanceof OnOffType)) {
logger.warn("Could not send command {} for sensor with id {}. Ignoring command.", command, this.id);
}
}
private synchronized void sendCommandRetry(OnOffType command) {
cancelRetry();
retry = scheduler.scheduleWithFixedDelay(new Runnable() {
int remainingRetries = sendCount;
@Override
public void run() {
if (state == null) {
logger.debug("skip sending of command (current state not yet known) for thing {} ({}): {}",
getThing().getLabel(), getThing().getUID().getId(), command);
} else if ((state != command && remainingRetries > 0)) {
logger.debug("sending command for thing {} ({}) attempt {}/{}: {}", getThing().getLabel(),
getThing().getUID().getId(), (sendCount - remainingRetries + 1), sendCount, command);
sendCommand(command);
remainingRetries--;
} else {
// we get here when the state is as expected or when the state is still not as expected after
// the configured number of retries. we should cancel the retry for both cases
if (state != command) {
logger.debug("giving up command for thing {} ({}): {}", getThing().getLabel(),
getThing().getUID().getId(), command);
}
cancelRetry();
}
}
}, 0, 2, TimeUnit.SECONDS);
}
private synchronized void cancelRetry() {
if (retry != null) {
retry.cancel(true);
retry = null;
}
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.revolt;
import org.openhab.binding.jeelink.internal.Reading;
/**
* Reading of a Revolt Energy Meter sensor.
*
* @author Volker Bier - Initial contribution
*/
public class RevoltReading implements Reading {
private int voltage;
private float current;
private int frequency;
private float power;
private float powerFact;
private float consumption;
private String sensorId;
public RevoltReading(String sensorId, int voltage, float current, int frequency, float power, float powerFactor,
float consumption) {
this.sensorId = sensorId;
this.voltage = voltage;
this.current = current;
this.frequency = frequency;
this.power = power;
this.powerFact = powerFactor;
this.consumption = consumption;
}
@Override
public String toString() {
return "sensorId=" + sensorId + ": voltage=" + voltage + ", current=" + current + ", frequency=" + frequency
+ ", power=" + power + ", powerFact=" + powerFact + ", consumption=" + consumption;
}
@Override
public String getSensorId() {
return sensorId;
}
public int getVoltage() {
return voltage;
}
public float getCurrent() {
return current;
}
public int getFrequency() {
return frequency;
}
public float getPower() {
return power;
}
public float getPowerFactor() {
return powerFact;
}
public float getConsumption() {
return consumption;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.revolt;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
/**
* Converter for converting a line read from a SlowRF CUL to a RevoltReading.
*
* @author Volker Bier - Initial contribution
*/
public class RevoltReadingConverter implements JeeLinkReadingConverter<RevoltReading> {
private static final Pattern LINE_P = Pattern
.compile("r([0-9A-Za-z]{4})([0-9A-Za-z]{2})([0-9A-Za-z]{4})([0-9A-Za-z]{2})([0-9A-Za-z]{4})"
+ "([0-9A-Za-z]{2})([0-9A-Za-z]{4})[0-9A-Za-z][0-9A-Za-z]");
@Override
public RevoltReading createReading(String inputLine) {
if (inputLine != null) {
Matcher matcher = LINE_P.matcher(inputLine);
if (matcher.matches()) {
/*
* r4F1BE400513206875B312F25
*/
String id = matcher.group(1); // 4F1B
int voltage = toInt(matcher.group(2)); // 0xE4 = 228 => 228 V
float current = toInt(matcher.group(3)) / 100f; // 0x0051 = 81 => 0,81 A
int frequency = toInt(matcher.group(4)); // 0x32 = 50 => 50 Hz
float power = toInt(matcher.group(5)) / 10f; // 0x0687 = 1671 => 167,1 W
float powerFact = toInt(matcher.group(6)) / 100f; // 0x5B = 91 => 0,91 VA
float consumption = toInt(matcher.group(7)) / 100f; // 0x312F = 12591 => 125,91 Wh
return new RevoltReading(id, voltage, current, frequency, power, powerFact, consumption);
}
}
return null;
}
private int toInt(String hex) {
Integer i = Integer.parseInt(hex, 16);
return i.intValue();
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.revolt;
import org.openhab.binding.jeelink.internal.JeeLinkBindingConstants;
import org.openhab.binding.jeelink.internal.JeeLinkReadingConverter;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.SensorDefinition;
import org.openhab.core.thing.Thing;
/**
* Sensor Definition of a Revolt Energy Meter.
*
* @author Volker Bier - Initial contribution
*/
public class RevoltSensorDefinition extends SensorDefinition<RevoltReading> {
public RevoltSensorDefinition() {
super(JeeLinkBindingConstants.REVOLT_SENSOR_THING_TYPE, "Revolt Power Monitor", "r");
}
@Override
public JeeLinkReadingConverter<RevoltReading> createConverter() {
return new RevoltReadingConverter();
}
@Override
public Class<RevoltReading> getReadingClass() {
return RevoltReading.class;
}
@Override
public JeeLinkSensorHandler<RevoltReading> createHandler(Thing thing) {
return new RevoltSensorHandler(thing, type);
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.jeelink.internal.revolt;
import static org.openhab.binding.jeelink.internal.JeeLinkBindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.openhab.binding.jeelink.internal.JeeLinkSensorHandler;
import org.openhab.binding.jeelink.internal.ReadingPublisher;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for a Revolt Energy Meter sensor thing.
*
* @author Volker Bier - Initial contribution
*/
public class RevoltSensorHandler extends JeeLinkSensorHandler<RevoltReading> {
private final Logger logger = LoggerFactory.getLogger(RevoltSensorHandler.class);
public RevoltSensorHandler(Thing thing, String sensorType) {
super(thing, sensorType);
}
@Override
public Class<RevoltReading> getReadingClass() {
return RevoltReading.class;
}
@Override
public ReadingPublisher<RevoltReading> createPublisher() {
ReadingPublisher<RevoltReading> publisher = new ReadingPublisher<RevoltReading>() {
@Override
public void publish(RevoltReading reading) {
if (reading != null && getThing().getStatus() == ThingStatus.ONLINE) {
BigDecimal power = new BigDecimal(reading.getPower()).setScale(1, RoundingMode.HALF_UP);
BigDecimal powerFactor = new BigDecimal(reading.getPowerFactor()).setScale(2, RoundingMode.HALF_UP);
BigDecimal consumption = new BigDecimal(reading.getConsumption()).setScale(2, RoundingMode.HALF_UP);
BigDecimal current = new BigDecimal(reading.getCurrent()).setScale(2, RoundingMode.HALF_UP);
logger.debug(
"updating states for thing {}: power={}, powerFactor={}, consumption={}, current={}, voltage={}, frequency={} ",
getThing().getUID().getId(), power, powerFactor, consumption, current, reading.getVoltage(),
reading.getFrequency());
updateState(CURRENT_POWER_CHANNEL, new QuantityType<>(power, SmartHomeUnits.WATT));
updateState(POWER_FACTOR_CHANNEL, new DecimalType(powerFactor));
updateState(CONSUMPTION_CHANNEL, new QuantityType<>(consumption, SmartHomeUnits.WATT_HOUR));
updateState(ELECTRIC_CURRENT_CHANNEL, new QuantityType<>(current, SmartHomeUnits.AMPERE));
updateState(ELECTRIC_POTENTIAL_CHANNEL,
new QuantityType<>(reading.getVoltage(), SmartHomeUnits.VOLT));
updateState(FREQUENCY_CHANNEL, new QuantityType<>(reading.getFrequency(), SmartHomeUnits.HERTZ));
}
}
@Override
public void dispose() {
}
};
return publisher;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="jeelink" 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>@text/binding.jeelink.name</name>
<description>@text/binding.jeelink.description</description>
<author>Volker Bier</author>
</binding:binding>

View File

@@ -0,0 +1,102 @@
# binding
binding.jeelink.name = JeeLink Binding
binding.jeelink.description = This is the binding for JeeLink USB Receivers, LaCrosseGateways and connected sensors.
# bridge types
bridge-type.jeelinkUsb.label = JeeLink (connected to USB)
bridge-type.jeelinkUsb.description = Thing for a JeeLink USB Receiver. Currently supports LaCrosseITPlusReader and ec3kSerial sketches.
bridge-type.jeelinkTcp.label = JeeLink (connected over TCP)
bridge-type.jeelinkTcp.description = Thing for a JeeLink USB Receiver that is connected to a different machine on the network and made available to the openHAB server via TCP. Currently supports LaCrosseITPlusReader and ec3kSerial sketches.
bridge-type.lgwUsb.label = LaCrosseGateway (connected to USB)
bridge-type.lgwUsb.description = Thing for a LaCrosseGateway connected directly to the USB port.
bridge-type.lgwTcp.label = LaCrosseGateway (connected over TCP)
bridge-type.lgwTcp.description = Thing for a LaCrosseGateway that is connected via network.
# thing types
thing-type.lacrosse.label = Lacrosse Temperature Sensor
thing-type.lacrosse.description = Thing for a Lacrosse Temperature Sensor connected to a JeeLink USB Receiver.
thing-type.ec3k.label = ec3k
thing-type.ec3k.description = Thing for a EnergyCount 3000 Power Monitor connected to a JeeLink USB Receiver.
thing-type.pca301.label = PCA301
thing-type.pca301.description = Thing for a PCA301 power monitoring wireless socket connected to a JeeLink USB Receiver.
thing-type.tx22.label = TX22 Sensor
thing-type.tx22.description = Thing for a TX22 Sensor connected to a JeeLink USB Receiver.
thing-type.revolt.label = Revolt Power Monitor
thing-type.revolt.description = Thing for a Revolt Power Monitor connected to a JeeLink USB Receiver.
thing-type.lgw.label = LGW Sensor
thing-type.lgw.description = Thing for a Sensor directly connected to a LGW.
# parameters
parameter.serialport.label = Serial Port
parameter.serialport.description = The serial port name for the USB receiver. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or /dev/ttyUSB0 for Linux.
parameter.serialportlgw.description = The serial port name for the LaCrosseGateway. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or /dev/ttyUSB0 for Linux.
parameter.baudrate.label = Baud Rate
parameter.baudrate.description = The baud rate of the USB Receiver / LGW. Valid values are 9600, 19200, 38400, 57600 (default), and 115200.
parameter.ipaddress.label = IP Address
parameter.ipaddress.description = The IP address of the Server to which the USB Receiver is connected.
parameter.ipaddresslgw.description = The IP address of the LGW.
parameter.port.label = TCP Port
parameter.port.description = The TCP port over which the serial port is made available.
parameter.portlgw.description = The TCP port of the LGW (usually this is port 81).
parameter.initcommands.label = Init Commands
parameter.initcommands.description = Optional initialization commands (semicolon separated) that will be send after the first reading has been received, e.g. "0a" to turn of the LED.
parameter.initdelay.label = Init Delay
parameter.initdelay.description = Time after which the init command is send after the connection has been established if no readings have been received.
parameter.reconnectinterval.label = Reconnect Interval
parameter.reconnectinterval.description = The number of seconds after which a reconnect is triggered when no values could be read fron any of the sensors.
parameter.sensorid.label = Sensor ID
parameter.sensorid.description = The sensor ID used by this sensor.
parameter.updateinterval.label = Update Interval
parameter.updateinterval.description = The update interval in seconds (0 puts the sensor in live mode). Setting this to a value greater than zero only makes sense if you specify a buffer size greater one.
parameter.sensortimeout.label = Sensor Timeout
parameter.sensortimeout.description = The amount of time that should result in OFFLINE status when no readings have been received from the sensor (in seconds).
parameter.buffersize.label = Buffer Size
parameter.buffersize.description = The amount of readings that should be used to compute a rolling average (1 disables the rolling average).
parameter.mintemp.label = Lower Temperature Limit
parameter.mintemp.description = The lowest temperature allowed as valid reading from the sensor. All lower readings will be ignored.
parameter.maxtemp.label = Upper Temperature Limit
parameter.maxtemp.description = The highest temperature allowed as valid reading from the sensor. All higher readings will be ignored.
parameter.maxdiff.label = Maximum Allowed Temperature Difference
parameter.maxdiff.description = The maximum allowed absolute difference from a value to the previous value (0 disables this check). If the difference is higher, the reading will be ignored.
parameter.sendCount.label = Switching Command Count
parameter.sendCount.description = The number of times a switching command will be sent (every 2 seconds) to the socket until giving up.
# channel types
channel-type.current-power.label = Current Power
channel-type.current-power.description = The current power draw of the appliance.
channel-type.max-power.label = Max Power
channel-type.max-power.description = The maximum power draw of the appliance.
channel-type.consumption-total.label = Total Consumption
channel-type.consumption-total.description = The total consumption of the connected appliance.
channel-type.appliance-time.label = Appliance On Time
channel-type.appliance-time.description = The time the appliance was turned on.
channel-type.sensor-time.label = Sensor On Time
channel-type.sensor-time.description = The time the EC3000 was connected to an outlet.
channel-type.resets.label = Resets
channel-type.resets.description = Number of resets performed by the sensor.
channel-type.temperature.label = Temperature
channel-type.temperature.description = The temperature read from the sensor.
channel-type.humidity.label = Humidity
channel-type.humidity.description = The humidity read from the sensor.
channel-type.battery-new.label = Battery New
channel-type.battery-new.description = Indicator for new battery.
channel-type.switching-state.label = Switching State
channel-type.switching-state.description = Whether the socket is currently switched on or not.
channel-type.wind-angle.label = Wind Angle
channel-type.wind-angle.description = Current wind direction
channel-type.wind-strength.label = Wind Strength
channel-type.wind-strength.description = Current wind speed
channel-type.rain.label = Rain
channel-type.rain.description = Quantity of water
channel-type.pressure.label = Pressure
channel-type.pressure.description = Current pressure
gust-strength.label = Gust Strength
gust-strength.description = Current gust speed
channel-type.electric-current.label = Electric Current
channel-type.electric-current.description = The measured electric current.
channel-type.power-factor.label = Power Factor
channel-type.power-factor.description = The ratio of the real power absorbed by the load to the apparent power flowing in the circuit.
channel-type.electric-potential.label = Voltage
channel-type.electric-potential.description = The measured electric potential.
channel-type.power-frequency.label = Power Frequency
channel-type.power-frequency.description = The measured AC power frequency.

View File

@@ -0,0 +1,102 @@
# binding
binding.jeelink.name = JeeLink Binding
binding.jeelink.description = Binding für JeeLink USB Empfänger, LaCrosseGateways und damit verbundene Sensoren.
# bridge types
bridge-type.jeelinkUsb.label = JeeLink USB Empfänger
bridge-type.jeelinkUsb.description = JeeLink USB Empfänger. Momentan werden LaCrosseITPlusReader und ec3kSerial Sketche unterstützt.
bridge-type.jeelinkTcp.label = JeeLink über TCP
bridge-type.jeelinkTcp.description = Jeelink USB Empfänger, der an einem anderen Rechner angeschlossen ist. Die Schnittstelle muss über TCP zur Verfügung gestellt werden (z.B. auf Linux mit ser2net).
bridge-type.lgwUsb.label = LaCrosseGateway am USB Port
bridge-type.lgwUsb.description = LaCrosseGateway, das direkt am USB Port des openHAB Rechners angeschlossen ist.
bridge-type.lgwTcp.label = LaCrosseGateway über TCP
bridge-type.lgwTcp.description = LaCrosseGateway der über das Netzwerk verbunden ist.
# thing types
thing-type.lacrosse.label = Lacrosse Temperatur-Sensor
thing-type.lacrosse.description = Ein mit dem JeeLink USB Empfänger verbundener Lacrosse Temperatur-Sensor.
thing-type.ec3k.label = ec3k
thing-type.ec3k.description = Ein mit dem JeeLink USB Empfänger verbundenes EC3000 Energiekosten-Messgerät.
thing-type.pca301.label = PCA301
thing-type.pca301.description = Eine mit dem JeeLink USB Empfänger verbundene PCA301 Steckdose.
thing-type.tx22.label = TX22 Sensor
thing-type.tx22.description = Ein mit dem JeeLink USB Empfänger verbundener TX22 Sensor.
thing-type.revolt.label = Revolt Energie-Messgerät
thing-type.revolt.description = Ein mit dem JeeLink USB Empfänger verbundenes Revolt Energie-Messgerät.
thing-type.lgw.label = LGW Sensor
thing-type.lgw.description = Ein direkt mit dem LGW verbundener Sensor.
# parameters
parameter.serialport.label = Serielle Schnittstelle
parameter.serialport.description = Der Name der seriellen Schnittstelle, an die der Jeelink angeschlossen ist. Gültige Werte sind z.B. COM1 für Windows und /dev/ttyS0 oder /dev/ttyUSB0 für Linux.
parameter.serialportlgw.description = Der Name der seriellen Schnittstelle, an die das LGW angeschlossen ist. Gültige Werte sind z.B. COM1 für Windows und /dev/ttyS0 oder /dev/ttyUSB0 für Linux.
parameter.baudrate.label = Baud Rate
parameter.baudrate.description = Die Baud Rate der seriellen Schnittstelle. Gültige Werte sind 9600, 19200, 38400, 57600 (default), and 115200.
parameter.ipaddress.label = IP Adresse
parameter.ipaddress.description = Die IP Adresse des Rechners, an den der Jeelink angeschlossen ist.
parameter.ipaddresslgw.description = Die IP Adresse LGWs.
parameter.port.label = TCP Port
parameter.port.description = Der TCP Port über den die serielle Schnittstelle bereit gestellt wird.
parameter.portlgw.description = Der TCP Port des LGWs (dies ist normalerweise Port 81).
parameter.initcommands.label = Init Kommandos
parameter.initcommands.description = Optionale Initialisierungs-Kommandos (durch Semikolon getrennt) die nach Empfang des ersten Wertes an den Jeelink geschickt werden, z.B. "0a" um die LED auszuschalten.
parameter.initdelay.label = Init Verzögerung
parameter.initdelay.description = Zeit nach der die Init Kommandos gesendet werden nachdem die Verbindung hergestellt wurde wenn keine Werte empfangen wurden.
parameter.reconnectinterval.label = Reconnect Intervall
parameter.reconnectinterval.description = Die Anzahl an Sekunden, nach denen die Verbindung neu aufgebaut wird, falls in der Zwischenzeit kein Wert von einem Sensor gelesen werden konnte.
parameter.sensorid.label = Sensor ID
parameter.sensorid.description = Die Sensor ID dieses Sensors.
parameter.updateinterval.label = Update Intervall
parameter.updateinterval.description = Das Update Intervall in Sekunden (0 setzt den Sensor in den 'live' Modus, d.h. jeder Wert wird an openHAB weitergereicht). Werte größer 0 machen nur Sinn, wenn eine Buffer Größe größer als 1 konfiguriert ist.
parameter.sensortimeout.label = Sensor Timeout
parameter.sensortimeout.description = Die Anzahl an Sekunden, nach deren Ausbleiben von Messwerten der Sensor Status auf OFFLINE gesetzt wird.
parameter.buffersize.label = Buffer Größe
parameter.buffersize.description = Die Anzahl von Messwertden, die zur Berechnung des Durchschnitts benutzt werden (1 schaltet die Durchschnittsberechnung ab).
parameter.mintemp.label = Minimal gültige Temperatur
parameter.mintemp.description = Die niedrigste vom Sensor gelesene, als gültig erachtete Temperatur. Alle niedrigeren Werte werden ignoriert.
parameter.maxtemp.label = Maximal gültige Temperatur
parameter.maxtemp.description = Die höchste vom Sensor gelesene, als gültig erachtete Temperatur. Alle höheren Werte werden ignoriert.
parameter.maxdiff.label = Maximal erlaubte Temperaturdifferenz
parameter.maxdiff.description = Die maximal erlaubte absolute Differenz eines Wertes zum Vorherigen (0 schaltet diese Prüfung ab). Alle Werte mit größerer Differenz werden ignoriert.
parameter.sendCount.label = Anzahl der Schaltversuche
parameter.sendCount.description = Die Anzahl der Versuche eine Steckdose zu schalten (alle 2 Sekunden) bevor aufgegeben wird.
# channel types
channel-type.current-power.label = Momentaner Verbrauch
channel-type.current-power.description = Der momentane Verbrauch des angeschlossenen Gerätes.
channel-type.max-power.label = Höchster Verbrauch
channel-type.max-power.description = Der höchste Verbrauch des angeschlossenen Gerätes.
channel-type.consumption-total.label = Absoluter Verbrauch
channel-type.consumption-total.description = Der absolute Verbrauch des angeschlossenen Gerätes.
channel-type.appliance-time.label = Einschaltzeit des Gerätes
channel-type.appliance-time.description = Die Zeit während derer das angeschlossene Gerät eingeschaltet war.
channel-type.sensor-time.label = Einschaltzeit des Sensoren
channel-type.sensor-time.description = Die Zeit während derer der Sensor eingeschaltet war.
channel-type.resets.label = Resets
channel-type.resets.description = Anzahl der Resets dieses Sensors.
channel-type.temperature.label = Temperatur
channel-type.temperature.description = Die vom Sensor gelesene Temperatur.
channel-type.humidity.label = Luftfeuchtigkeit
channel-type.humidity.description = Die vom Sensor gelesene Luftfeuchtigkeit.
channel-type.battery-new.label = Battery neu
channel-type.battery-new.description = Indikator für eine neu eingesetzte Batterie.
channel-type.switching-state.label = Steckdose Ein
channel-type.switching-state.description = Schaltzustand der Steckdose.
channel-type.wind-angle.label = Wind Richtung
channel-type.wind-angle.description = Momentane Wind Richtung
channel-type.wind-strength.label = Windstärke
channel-type.wind-strength.description = Momentane Windstärke
channel-type.rain.label = Regen
channel-type.rain.description = Regenmenge
channel-type.pressure.label = Luftdruck
channel-type.pressure.description = Momentaner Luftdruck
gust-strength.label = Böenstärke
gust-strength.description = Momentane Böenstärke
channel-type.electric-current.label = Stromstärke
channel-type.electric-current.description = Die gemessene elektrische Stromstärke .
channel-type.power-factor.label = Leistungsfaktor
channel-type.power-factor.description = Verhältnis des Betrages der Wirkleistung zur Scheinleistung.
channel-type.electric-potential.label = Spannung
channel-type.electric-potential.description = Die gemessene elektrische Spannung.
channel-type.power-frequency.label = Stromnetzfrequenz
channel-type.power-frequency.description = Die gemessene Netzfrequenz des Stromnetzes.

View File

@@ -0,0 +1,537 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="jeelink"
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">
<!-- JeeLink USB Receiver Bridge Type -->
<bridge-type id="jeelinkUsb">
<label>@text/bridge-type.jeelinkUsb.label</label>
<description>@text/bridge-type.jeelinkUsb.description</description>
<config-description>
<parameter name="serialPort" type="text" required="true">
<label>@text/parameter.serialport.label</label>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<description>@text/parameter.serialport.description</description>
</parameter>
<parameter name="initCommands" type="text" required="false">
<label>@text/parameter.initcommands.label</label>
<description>@text/parameter.initcommands.description</description>
</parameter>
<parameter name="baudRate" type="integer" required="false">
<label>@text/parameter.baudrate.label</label>
<description>@text/parameter.baudrate.description</description>
<default>57600</default>
<advanced>true</advanced>
</parameter>
<parameter name="initDelay" type="integer" required="false">
<label>@text/parameter.initdelay.label</label>
<description>@text/parameter.initdelay.description</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="reconnectInterval" type="integer" required="false">
<label>@text/parameter.reconnectinterval.label</label>
<description>@text/parameter.reconnectinterval.description</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<!-- LGW connected to USB port -->
<bridge-type id="lgwUsb">
<label>@text/bridge-type.lgwUsb.label</label>
<description>@text/bridge-type.lgwUsb.description</description>
<config-description>
<parameter name="serialPort" type="text" required="true">
<label>@text/parameter.serialport.label</label>
<context>serial-port</context>
<limitToOptions>false</limitToOptions>
<description>@text/parameter.serialportlgw.description</description>
</parameter>
<parameter name="initCommands" type="text" required="false">
<label>@text/parameter.initcommands.label</label>
<description>@text/parameter.initcommands.description</description>
</parameter>
<parameter name="baudRate" type="integer" required="false">
<label>@text/parameter.baudrate.label</label>
<description>@text/parameter.baudrate.description</description>
<default>57600</default>
<advanced>true</advanced>
</parameter>
<parameter name="initDelay" type="integer" required="false">
<label>@text/parameter.initdelay.label</label>
<description>@text/parameter.initdelay.description</description>
<default>20</default>
<advanced>true</advanced>
</parameter>
<parameter name="reconnectInterval" type="integer" required="false">
<label>@text/parameter.reconnectinterval.label</label>
<description>@text/parameter.reconnectinterval.description</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<!-- JeeLink USB Receiver Bridge connected over TCP Type -->
<bridge-type id="jeelinkTcp">
<label>@text/bridge-type.jeelinkTcp.label</label>
<description>@text/bridge-type.jeelinkTcp.description</description>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>@text/parameter.ipaddress.label</label>
<description>@text/parameter.ipaddress.description</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" required="true">
<label>@text/parameter.port.label</label>
<description>@text/parameter.port.description</description>
</parameter>
<parameter name="initCommands" type="text" required="false">
<label>@text/parameter.initcommands.label</label>
<description>@text/parameter.initcommands.description</description>
</parameter>
<parameter name="initDelay" type="integer" required="false">
<label>@text/parameter.initdelay.label</label>
<description>@text/parameter.initdelay.description</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="reconnectInterval" type="integer" required="false">
<label>@text/parameter.reconnectinterval.label</label>
<description>@text/parameter.reconnectinterval.description</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<!-- LaCrosseGateway connected over TCP Type -->
<bridge-type id="lgwTcp">
<label>@text/bridge-type.lgwTcp.label</label>
<description>@text/bridge-type.lgwTcp.description</description>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>@text/parameter.ipaddress.label</label>
<description>@text/parameter.ipaddresslgw.description</description>
<context>network-address</context>
</parameter>
<parameter name="initCommands" type="text" required="false">
<label>@text/parameter.initcommands.label</label>
<description>@text/parameter.initcommands.description</description>
<advanced>true</advanced>
</parameter>
<parameter name="port" type="integer" required="false">
<label>@text/parameter.port.label</label>
<description>@text/parameter.portlgw.description</description>
<default>81</default>
<advanced>true</advanced>
</parameter>
<parameter name="initDelay" type="integer" required="false">
<label>@text/parameter.initdelay.label</label>
<description>@text/parameter.initdelay.description</description>
<default>20</default>
<advanced>true</advanced>
</parameter>
<parameter name="reconnectInterval" type="integer" required="false">
<label>@text/parameter.reconnectinterval.label</label>
<description>@text/parameter.reconnectinterval.description</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<!-- Lacrosse Temperature Sensor Thing Type -->
<thing-type id="lacrosse">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.lacrosse.label</label>
<description>@text/thing-type.lacrosse.description</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="batteryNew" typeId="battery-new"/>
<channel id="batteryLow" typeId="system.low-battery"/>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="updateInterval" type="integer" required="false" min="0" max="3600" unit="s" step="5">
<label>@text/parameter.updateinterval.label</label>
<description>@text/parameter.updateinterval.description</description>
<default>60</default>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
<parameter name="bufferSize" type="integer" required="false" min="1" max="100">
<label>@text/parameter.buffersize.label</label>
<description>@text/parameter.buffersize.description</description>
<default>20</default>
<advanced>true</advanced>
</parameter>
<parameter name="minTemp" type="decimal" required="false">
<label>@text/parameter.mintemp.label</label>
<description>@text/parameter.mintemp.description</description>
<default>-100</default>
</parameter>
<parameter name="maxTemp" type="decimal" required="false">
<label>@text/parameter.maxtemp.label</label>
<description>@text/parameter.maxtemp.description</description>
<default>100</default>
</parameter>
<parameter name="maxDiff" type="decimal" required="true">
<label>@text/parameter.maxdiff.label</label>
<description>@text/parameter.maxdiff.description</description>
<default>2</default>
</parameter>
</config-description>
</thing-type>
<!-- EC3000 Power Monitor Thing Type -->
<thing-type id="ec3k">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.ec3k.label</label>
<description>@text/thing-type.ec3k.description</description>
<channels>
<channel id="currentPower" typeId="current-power"/>
<channel id="maxPower" typeId="max-power"/>
<channel id="consumptionTotal" typeId="consumption-total"/>
<channel id="applianceTime" typeId="appliance-time"/>
<channel id="sensorTime" typeId="sensor-time"/>
<channel id="resets" typeId="resets"/>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="updateInterval" type="integer" required="false" min="0" max="3600" unit="s" step="5">
<label>@text/parameter.updateinterval.label</label>
<description>@text/parameter.updateinterval.description</description>
<default>60</default>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>60</default>
</parameter>
<parameter name="bufferSize" type="integer" required="false" min="1" max="100">
<label>@text/parameter.buffersize.label</label>
<description>@text/parameter.buffersize.description</description>
<default>20</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<!-- PCA301 power monitoring wireless socket Thing Type -->
<thing-type id="pca301">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.pca301.label</label>
<description>@text/thing-type.pca301.description</description>
<channels>
<channel id="switchingState" typeId="switching-state"/>
<channel id="currentPower" typeId="current-power"/>
<channel id="consumptionTotal" typeId="consumption-total"/>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>600</default>
</parameter>
<parameter name="sendCount" type="integer" required="false" min="1" max="300">
<label>@text/parameter.sendCount.label</label>
<description>@text/parameter.sendCount.description</description>
<default>10</default>
</parameter>
</config-description>
</thing-type>
<!-- Revolt Energy Meter Thing Type -->
<thing-type id="revolt">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.revolt.label</label>
<description>@text/thing-type.revolt.description</description>
<channels>
<channel id="currentPower" typeId="current-power"/>
<channel id="consumptionTotal" typeId="consumption-total"/>
<channel id="powerFactor" typeId="power-factor"/>
<channel id="electricCurrent" typeId="electric-current"/>
<channel id="electricPotential" typeId="electric-potential"/>
<channel id="powerFrequency" typeId="power-frequency"/>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<!-- TX22 Temperature/Humidity SensorThing Type -->
<thing-type id="tx22">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.tx22.label</label>
<description>@text/thing-type.tx22.description</description>
<channels>
<channel id="temperature" typeId="temperature"/>
<channel id="humidity" typeId="humidity"/>
<channel id="batteryNew" typeId="battery-new"/>
<channel id="batteryLow" typeId="system.low-battery"/>
<channel id="pressure" typeId="pressure"/>
<channel id="rain" typeId="rain"/>
<channel id="windStrength" typeId="wind-strength"/>
<channel id="windAngle" typeId="wind-angle"/>
<channel id="gustStrength" typeId="wind-strength">
<label>@text/gust-strength.label</label>
<description>@text/gust-strength.description</description>
</channel>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>600</default>
</parameter>
</config-description>
</thing-type>
<!-- LGW Sensor SensorThing Type -->
<thing-type id="lgw">
<supported-bridge-type-refs>
<bridge-type-ref id="jeelinkTcp"/>
<bridge-type-ref id="jeelinkUsb"/>
<bridge-type-ref id="lgwTcp"/>
<bridge-type-ref id="lgwUsb"/>
</supported-bridge-type-refs>
<label>@text/thing-type.lgw.label</label>
<description>@text/thing-type.lgw.description</description>
<channels>
<channel id="temperature" typeId="temperature"/>
</channels>
<config-description>
<parameter name="sensorId" type="text" required="true">
<label>@text/parameter.sensorid.label</label>
<description>@text/parameter.sensorid.description</description>
</parameter>
<parameter name="sensorTimeout" type="integer" required="false" min="5" max="3600" unit="s" step="5">
<label>@text/parameter.sensortimeout.label</label>
<description>@text/parameter.sensortimeout.description</description>
<default>600</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="wind-angle">
<item-type>Number:Angle</item-type>
<label>@text/channel-type.wind-angle.label</label>
<description>@text/channel-type.wind-angle.description</description>
<category>Wind</category>
<state min="0" max="360" step="1" readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="wind-strength">
<item-type>Number:Speed</item-type>
<label>@text/channel-type.wind-strength.label</label>
<description>@text/channel-type.wind-strength.description</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="rain">
<item-type>Number:Length</item-type>
<label>@text/channel-type.rain.label</label>
<description>@text/channel-type.rain.description</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="pressure">
<item-type>Number:Pressure</item-type>
<label>@text/channel-type.pressure.label</label>
<description>@text/channel-type.pressure.description</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.3f %unit%"/>
</channel-type>
<!-- Current Power Channel Type -->
<channel-type id="current-power">
<item-type>Number:Power</item-type>
<label>@text/channel-type.current-power.label</label>
<description>@text/channel-type.current-power.description</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<!-- Max Power Channel Type -->
<channel-type id="max-power">
<item-type>Number:Power</item-type>
<label>@text/channel-type.max-power.label</label>
<description>@text/channel-type.max-power.description</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<!-- Total Energy Consumption Channel Type -->
<channel-type id="consumption-total">
<item-type>Number:Energy</item-type>
<label>@text/channel-type.consumption-total.label</label>
<description>@text/channel-type.consumption-total.description</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<!-- Appliance On Time Channel Type -->
<channel-type id="appliance-time" advanced="true">
<item-type>Number:Time</item-type>
<label>@text/channel-type.appliance-time.label</label>
<description>@text/channel-type.appliance-time.description</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<!-- Sensor On Time Channel Type -->
<channel-type id="sensor-time" advanced="true">
<item-type>Number:Time</item-type>
<label>@text/channel-type.sensor-time.label</label>
<description>@text/channel-type.sensor-time.description</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<!-- Resets Channel Type -->
<channel-type id="resets" advanced="true">
<item-type>Number</item-type>
<label>@text/channel-type.resets.label</label>
<description>@text/channel-type.resets.description</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<!-- Temperature Channel Type -->
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>@text/channel-type.temperature.label</label>
<description>@text/channel-type.temperature.description</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<!-- Humidity Channel Type -->
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>@text/channel-type.humidity.label</label>
<description>@text/channel-type.humidity.description</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<!-- Battery New Channel Type -->
<channel-type id="battery-new">
<item-type>Switch</item-type>
<label>@text/channel-type.battery-new.label</label>
<description>@text/channel-type.battery-new.description</description>
<state readOnly="true"/>
</channel-type>
<!-- PCA301 Socket On Type -->
<channel-type id="switching-state">
<item-type>Switch</item-type>
<label>@text/channel-type.switching-state.label</label>
<description>@text/channel-type.switching-state.description</description>
</channel-type>
<!-- Revolt Current Type -->
<channel-type id="electric-current">
<item-type>Number:ElectricCurrent</item-type>
<label>@text/channel-type.electric-current.label</label>
<description>@text/channel-type.electric-current.description</description>
</channel-type>
<!-- Revolt Apparent Power Type -->
<channel-type id="power-factor">
<item-type>Number:Dimensionless</item-type>
<label>@text/channel-type.power-factor.label</label>
<description>@text/channel-type.power-factor.description</description>
</channel-type>
<!-- Revolt Electric Potential Type -->
<channel-type id="electric-potential">
<item-type>Number:ElectricPotential</item-type>
<label>@text/channel-type.electric-potential.label</label>
<description>@text/channel-type.electric-potential.description</description>
</channel-type>
<!-- Revolt Frequency Type -->
<channel-type id="power-frequency">
<item-type>Number:Frequency</item-type>
<label>@text/channel-type.power-frequency.label</label>
<description>@text/channel-type.power-frequency.description</description>
</channel-type>
</thing:thing-descriptions>