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,33 @@
<?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="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" 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="module" value="true"/>
<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,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.openwebnet</name>
<comment></comment>
<projects>
<project>openwebnet-java</project>
</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,19 @@
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
## Third-party Content
openwebnet4j
* License: Eclipse Public License 2.0
* Source: https://github.com/mvalla/openwebnet4j

View File

@@ -0,0 +1,214 @@
# OpenWebNet (BTicino/Legrand) Binding
This binding integrates BTicino / Legrand MyHOME&reg; BUS and ZigBee wireless (MyHOME_Play&reg;) devices using the [OpenWebNet](https://en.wikipedia.org/wiki/OpenWebNet) protocol.
The binding supports:
- both wired BUS/SCS (MyHOME) and wireless setups (MyHOME ZigBee). The two networks can be configured simultaneously
- auto discovery of BUS/SCS IP gateways and devices and auto discovery of ZigBee USB devices
- commands from openHAB and feedback (events) from BUS/SCS and wireless network
![F454 Gateway](doc/F454_gateway.png)
![USB ZigBee Gateway](doc/USB_gateway.jpg)
## Supported Things
In order for this binding to work, a **BTicino/Legrand OpenWebNet gateway** is needed in your home system to talk to devices.
These gateways have been tested with the binding:
- **IP gateways** or scenario programmers, such as BTicino
[F454](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=006),
[MyHOMEServer1](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=067),
[MyHOME_Screen10](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=001),
[MH201](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=053),
[MH202](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=059),
[F455](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=051),
[MH200N](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=016),
[F453](http://www.homesystems-legrandgroup.com/BtHomeSystems/productDetail.action?lang=EN&productId=027), etc.
- **ZigBee USB Gateways**, such as [BTicino 3578](https://catalogo.bticino.it/BTI-3578-IT), also known as Legrand 088328
**NOTE** The new BTicino Living Now&reg; wireless system is not supported by this binding as it does not use the OpenWebNet protocol.
The following Things and OpenWebNet `WHOs` are supported:
### For BUS/SCS
| Category | WHO | Thing Type IDs | Description | Status |
| -------------------- | :----------: | :---------------------------------: | ----------------------------------------------------------- | ---------------- |
| Gateway Management | `13` | `bus_gateway` | Any IP gateway supporting OpenWebNet protocol should work (e.g. F454 / MyHOMEServer1 / MH202 / F455 / MH200N, ...) | Successfully tested: F454, MyHOMEServer1, MyHOME_Screen10, F455, F452, F453AV, MH201, MH202, MH200N. Some connection stability issues/gateway resets reported with MH202 |
| Lighting | `1` | `bus_on_off_switch`, `bus_dimmer` | BUS switches and dimmers. | Successfully tested: F411/2, F411/4, F411U2, F422, F429. Some discovery issues reported with F429 (DALI Dimmers) |
| Automation | `2` | `bus_automation` | BUS roller shutters, with position feedback and auto-calibration | Successfully tested: LN4672M2 |
### For ZigBee (Radio)
| Category | WHO | Thing Type IDs | Description | Status |
| ---------- | :---: | :-------------------------------: | :-------------------------------------------------------------------: | ------------------------------------ |
| Gateway | `13` | `zb_gateway` | Wireless ZigBee USB Gateway (models: BTI-3578 / LG 088328) | Tested: BTI-3578 and LG 088328 |
| Lighting | `1` | `zb_dimmer`, `zb_on_off_switch`, `zb_on_off_switch2u` | ZigBee dimmers, switches and 2-unit switches | Tested: BTI-4591, BTI-3584, BTI-4585 |
| Automation | `2` | `zb_automation` | ZigBee roller shutters | |
## Discovery
Gateway and Things discovery is supported using PaperUI by pressing the discovery ("+") button form Inbox.
### BUS/SCS Discovery
- BUS Gateway automatic discovery will work only for newer gateways supporting UPnP: F454, MyHOMEServer1, MH201, MH202, MH200N, MyHOME_Screen 10.
For other gateways you can add them manually, see [Thing Configuration](#thing-configuration) below.
- After gateway is discovered and added a connection with default password (`12345`) is tested first: if it does not work the gateway will go offline and an error status will be set. A correct password must then be set in the gateway Thing configuration otherwise the gateway will not become online.
- Once the gateway is online, a second Scan request from Inbox will discover BUS devices
- BUS/SCS Dimmers must be ON and dimmed (30%-100%) during a Scan, otherwise they will be discovered as simple On/Off switches
- *KNOWN ISSUE*: In some cases dimmers connected to a F429 Dali-interface are not automatically discovered
#### Discovery by Activation
Devices can also be discovered if activated while an Inbox Scan is active: start a new Scan, wait 15-20 seconds and then _while the Scan is still active_ (spinning arrow in Inbox), activate the physical device (for example dim the dimmer) to have it discovered by the binding.
If a device cannot be discovered automatically it's always possible to add them manually, see [Configuring Devices](#configuring-devices).
### ZigBee Discovery
- Zigbee USB gateway discovery is *not supported* at the moment: the gateway thing must be added manually see [Thing Configuration](#thing-configuration) below
- The ZigBee USB Gateway must be inserted in one of the USB ports of the openHAB computer before discovery is started
- ***IMPORTANT NOTE:*** As for other OH2 bindings using the USB/serial ports, on Linux the `openhab` user must be member of the `dialout` group, to be able to use USB/serial port: set the group with the following command:
```
$ sudo usermod -a -G dialout openhab
```
The user will need to logout and login to see the new group added. If you added your user to this group and still cannot get permission, reboot Linux to ensure the new group permission is attached to the `openhab` user.
- Once the USB gateway is configured/added, a discovery request from Inbox will discover devices connected to it. Because of the ZigBee radio network, discovery will take ~40-60 sec. Be patient!
- Wireless devices must be part of the same ZigBee network of the ZigBee USB Gateway to discover them. Please refer to [this video by BTicino](https://www.youtube.com/watch?v=CoIgg_Xqhbo) to setup a ZigBee wireless network which includes the ZigBee USB Gateway
- Only powered wireless devices part of the same ZigBee network and within radio coverage of the ZigBee USB Gateway will be discovered. Unreachable or not powered devices will be discovered as *GENERIC* devices and cannot be controlled
- Wireless control units cannot be discovered by the ZigBee USB Gateway and therefore are not supported
## Thing Configuration
### Configuring BUS/SCS Gateway
To add a BUS gateway manually using PaperUI: go to *Inbox > "+" > OpenWebNet > click `ADD MANUALLY`* and then select `BUS Gateway`.
Configuration parameters are:
- `host` : IP address / hostname of the BUS/SCS gateway (`String`, *mandatory*)
- Example: `192.168.1.35`
- `port` : port (`int`, *optional*, default: `20000`)
- `passwd` : gateway password (`String`, *required* for gateways that have a password. Default: `12345`)
- Example: `abcde` or `12345`
- if the BUS/SCS gateway is configured to accept connections from the openHAB computer IP address, no password should be required
- in all other cases, a password must be configured. This includes gateways that have been discovered and added from Inbox: without a password configured they will remain OFFLINE
- `discoveryByActivation`: discover BUS devices when they are activated also when a device scan is not currently active (`boolean`, *optional*, default: `false`). See [Discovery by Activation](#discovery-by-activation).
Alternatively the BUS/SCS Gateway thing can be configured using the `.things` file, see `openwebnet.things` example [below](#full-example).
### Configuring Wireless ZigBee USB Gateway
To add a ZigBee USB gateway manually using PaperUI: go to *Inbox > "+" > OpenWebNet > click `ADD MANUALLY`* and then select `ZigBee USB Gateway`.
Configuration parameters are:
- `serialPort` : the serial port where the ZigBee USB Gateway is connected (`String`, *mandatory*)
- Example: `COM3`
### Configuring Devices
Devices can be discovered automatically from Inbox after a gateway has been configured and connected.
Devices can be also added manually from PaperUI. For each device it must be configured:
- the associated gateway (`Bridge Selection` menu)
- the `WHERE` config parameter (`OpenWebNet Device Address`):
- example for BUS/SCS: Point to Point `A=2 PL=4` --> `WHERE="24"`
- example for BUS/SCS: Point to Point `A=6 PL=4` on local bus --> `WHERE="64#4#01"`
- example for ZigBee devices: use decimal format address without the UNIT part and network: ZigBee `WHERE=414122201#9` --> `WHERE="4141222"`
## Channels
Devices support some of the following channels:
| Channel Type ID (channel ID) | Item Type | Description | Read/Write |
|--------------------------|---------------|-------------------------------------------------------------------------|:----------:|
| `switch` | Switch | To switch the device `ON` and `OFF` | R/W |
| `brightness` | Dimmer | To adjust the brightness value (Percent, `ON`, `OFF`) | R/W |
| `shutter` | Rollershutter | To activate roller shutters (`UP`, `DOWN`, `STOP`, Percent - [see Shutter position](#shutter-position)) | R/W |
### Notes on channels
#### `shutter` position
For Percent commands and position feedback to work correctly, the `shutterRun` Thing config parameter must be configured equal to the time (in ms) to go from full UP to full DOWN.
It's possible to enter a value manually or set `shutterRun=AUTO` (default) to calibrate `shutterRun` automatically: in this case a *UP >> DOWN >> Position%* cycle will be performed automatically the first time a Percent command is sent to the shutter.
- if `shutterRun` is not set, or is set to AUTO but calibration has not been performed yet, then position estimation will remain `UNDEFINED`
- if `shutterRun` is wrongly set higher than the actual runtime, then position estimation will remain `UNDEFINED`: try to reduce shutterRun until you find the right value
- before adding/configuring roller shutter Things it is suggested to have all roller shutters `UP`, otherwise the Percent command wont work until the roller shutter is fully rolled up
- if the gateways gets disconnected the binding cannot estimate anymore the shutter positions: just roll the shutter all `UP` or `DOWN` and its position will be estimated again
- the shutter position is estimated based on UP/DOWN timing: an error of ±2% is normal
## Full Example
### openwebnet.things:
```xtend
Bridge openwebnet:bus_gateway:mybridge "MyHOMEServer1" [ host="192.168.1.35", passwd="abcde", port=20000, discoveryByActivation=false ] {
bus_on_off_switch LR_switch "Living Room Light" [ where="51" ]
bus_dimmer LR_dimmer "Living Room Dimmer" [ where="25#4#01" ]
bus_dimmer LR_dalidimmer "Living Room Dali-Dimmer" [ where="0311#4#01" ]
bus_automation LR_shutter "Living Room Shutter" [ where="93", shutterRun="10050"]
}
```
```xtend
// ZigBee USB Gateway configuration for radio devices
Bridge openwebnet:zb_gateway:myZBgateway [serialPort="COM3"] {
zb_dimmer myzigbeedimmer [ where="123456700#9"]
zb_on_off_switch myzigbeeswitch [ where="765432200#9"]
}
```
### openwebnet.items:
Items (Light, Dimmer, etc.) will be discovered by Google Assistant/Alexa/HomeKit if their tags are configured like in the example.
```xtend
Switch iLR_switch "Light" <light> (gLivingRoom) [ "Lighting" ] { channel="openwebnet:bus_on_off_switch:mybridge:LR_switch:switch" }
Dimmer iLR_dimmer "Dimmer [%.0f %%]" <DimmableLight> (gLivingRoom) [ "Lighting" ] { channel="openwebnet:bus_dimmer:mybridge:LR_dimmer:brightness" }
Dimmer iLR_dalidimmer "Dali-Dimmer [%.0f %%]" <DimmableLight> (gLivingRoom) [ "Lighting" ] { channel="openwebnet:bus_dimmer:mybridge:LR_dalidimmer:brightness" }
/* For Dimmers, use category DimmableLight to have Off/On switch in addition to the Percent slider in PaperUI */
Rollershutter iLR_shutter "Shutter [%.0f %%]" <rollershutter> (gShutters, gLivingRoom) [ "Blinds" ] { channel="openwebnet:bus_automation:mybridge:LR_shutter:shutter" }
```
### openwebnet.sitemap
```xtend
sitemap openwebnet label="OpenWebNet Binding Example Sitemap"
{
Frame label="Living Room"
{
Default item=iLR_switch icon="light"
Default item=iLR_dimmer icon="light"
Default item=iLR_dalidimmer icon="light"
Default item=iLR_shutter
}
}
```
## Notes
- The Open Web Net protocol is maintained and Copyright by BTicino/Legrand. The documentation of the protocol if freely accessible for developers on the [Legrand developer web site](https://developer.legrand.com/documentation/open-web-net-for-myhome/)
## Special thanks
Special thanks for helping on testing this binding go to:
[@m4rk](https://community.openhab.org/u/m4rk/),
[@bastler](https://community.openhab.org/u/bastler),
[@gozilla01](https://community.openhab.org/u/gozilla01),
[@enrico.mcc](https://community.openhab.org/u/enrico.mcc),
[@k0nti](https://community.openhab.org/u/k0nti/),
[@gilberto.cocchi](https://community.openhab.org/u/gilberto.cocchi/),
[@llegovich](https://community.openhab.org/u/llegovich),
[@gabriele.daltoe](https://community.openhab.org/u/gabriele.daltoe)
and many others at the fantastic openHAB community!

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,32 @@
<?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.openwebnet</artifactId>
<name>openHAB Add-ons :: Bundles :: OpenWebNet (BTicino/Legrand) Binding</name>
<properties>
<bnd.importpackage>gnu.io;version="[3.12,6)"</bnd.importpackage>
</properties>
<dependencies>
<dependency>
<groupId>com.github.openwebnet4j</groupId>
<artifactId>openwebnet4j</artifactId>
<version>0.3.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -0,0 +1,112 @@
/**
* 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.openwebnet;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link OpenWebNetBindingConstants} class defines common constants, which are used across the whole binding.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetBindingConstants {
public static final String BINDING_ID = "openwebnet";
public static final int THING_STATE_REQ_TIMEOUT_SEC = 5;
// #LIST OF Thing Type UIDs
// generic device (used for not identified devices)
public static final ThingTypeUID THING_TYPE_GENERIC_DEVICE = new ThingTypeUID(BINDING_ID, "generic_device");
public static final String THING_LABEL_GENERIC_DEVICE = "GENERIC Device";
// bridges
public static final ThingTypeUID THING_TYPE_ZB_GATEWAY = new ThingTypeUID(BINDING_ID, "zb_gateway");
public static final String THING_LABEL_ZB_GATEWAY = "ZigBee USB Gateway";
public static final ThingTypeUID THING_TYPE_BUS_GATEWAY = new ThingTypeUID(BINDING_ID, "bus_gateway");
public static final String THING_LABEL_BUS_GATEWAY = "BUS Gateway";
// other thing types
// BUS
public static final ThingTypeUID THING_TYPE_BUS_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "bus_on_off_switch");
public static final String THING_LABEL_BUS_ON_OFF_SWITCH = "Switch";
public static final ThingTypeUID THING_TYPE_BUS_DIMMER = new ThingTypeUID(BINDING_ID, "bus_dimmer");
public static final String THING_LABEL_BUS_DIMMER = "Dimmer";
public static final ThingTypeUID THING_TYPE_BUS_AUTOMATION = new ThingTypeUID(BINDING_ID, "bus_automation");
public static final String THING_LABEL_BUS_AUTOMATION = "Automation";
// ZIGBEE
public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "zb_on_off_switch");
public static final String THING_LABEL_ZB_ON_OFF_SWITCH = "ZigBee Switch";
public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS = new ThingTypeUID(BINDING_ID,
"zb_on_off_switch2u");
public static final String THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS = "ZigBee 2-units Switch";
public static final ThingTypeUID THING_TYPE_ZB_DIMMER = new ThingTypeUID(BINDING_ID, "zb_dimmer");
public static final String THING_LABEL_ZB_DIMMER = "ZigBee Dimmer";
public static final ThingTypeUID THING_TYPE_ZB_AUTOMATION = new ThingTypeUID(BINDING_ID, "zb_automation");
public static final String THING_LABEL_ZB_AUTOMATION = "ZigBee Automation";
// #SUPPORTED THINGS SETS
// ## Generic
public static final Set<ThingTypeUID> GENERIC_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_GENERIC_DEVICE));
// ## Lighting
public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_ON_OFF_SWITCH, THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS, THING_TYPE_ZB_DIMMER,
THING_TYPE_BUS_ON_OFF_SWITCH, THING_TYPE_BUS_DIMMER));
// ## Automation
public static final Set<ThingTypeUID> AUTOMATION_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_AUTOMATION, THING_TYPE_BUS_AUTOMATION));
// ## Groups
public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
.of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES, GENERIC_SUPPORTED_THING_TYPES)
.flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_GATEWAY, THING_TYPE_BUS_GATEWAY));
public static final Set<ThingTypeUID> ALL_SUPPORTED_THING_TYPES = Stream
.of(DEVICE_SUPPORTED_THING_TYPES, BRIDGE_SUPPORTED_THING_TYPES).flatMap(Collection::stream)
.collect(Collectors.toCollection(HashSet::new));
// LIST OF ALL CHANNEL IDs
// lighting
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_SWITCH_01 = "switch_01";
public static final String CHANNEL_SWITCH_02 = "switch_02";
public static final String CHANNEL_BRIGHTNESS = "brightness";
// automation
public static final String CHANNEL_SHUTTER = "shutter";
// devices config properties
public static final String CONFIG_PROPERTY_WHERE = "where";
public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun";
// BUS gw config properties
public static final String CONFIG_PROPERTY_HOST = "host";
// properties
public static final String PROPERTY_OWNID = "ownId";
public static final String PROPERTY_ZIGBEEID = "zigbeeid";
public static final String PROPERTY_FIRMWARE_VERSION = "firmwareVersion";
public static final String PROPERTY_MODEL = "model";
public static final String PROPERTY_SERIAL_NO = "serialNumber";
}

View File

@@ -0,0 +1,426 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SHUTTER;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.Automation;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetAutomationHandler} is responsible for handling commands/messages for an Automation OpenWebNet
* device. It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetAutomationHandler.class);
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("ss.SSS");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.AUTOMATION_SUPPORTED_THING_TYPES;
// moving states
public static final int MOVING_STATE_STOPPED = 0;
public static final int MOVING_STATE_MOVING_UP = 1;
public static final int MOVING_STATE_MOVING_DOWN = 2;
public static final int MOVING_STATE_UNKNOWN = -1;
// calibration states
public static final int CALIBRATION_INACTIVE = -1;
public static final int CALIBRATION_ACTIVATED = 0;
public static final int CALIBRATION_GOING_UP = 1;
public static final int CALIBRATION_GOING_DOWN = 2;
// positions
public static final int POSITION_MAX_STEPS = 100;
public static final int POSITION_DOWN = 100;
public static final int POSITION_UP = 0;
public static final int POSITION_UNKNOWN = -1;
public static final int SHUTTER_RUN_UNDEFINED = -1;
private int shutterRun = SHUTTER_RUN_UNDEFINED;
private static final String AUTO_CALIBRATION = "AUTO";
private long startedMovingAt = -1;
private int movingState = MOVING_STATE_UNKNOWN;
private int positionEstimation = POSITION_UNKNOWN;
private @Nullable ScheduledFuture<?> moveSchedule;
private int positionRequested = POSITION_UNKNOWN;
private int calibrating = CALIBRATION_INACTIVE;
private static final int MIN_STEP_TIME_MSEC = 50;
private @Nullable Command commandRequestedWhileMoving = null;
public OpenWebNetAutomationHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
Object shutterRunConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN);
try {
if (shutterRunConfig == null) {
shutterRunConfig = AUTO_CALIBRATION;
logger.debug("shutterRun null --> default to AUTO");
} else if (shutterRunConfig instanceof String) {
if (AUTO_CALIBRATION.equalsIgnoreCase(((String) shutterRunConfig))) {
logger.debug("shutterRun set to AUTO via configuration");
shutterRun = SHUTTER_RUN_UNDEFINED; // reset shutterRun
} else { // try to parse int>=1000
int shutterRunInt = Integer.parseInt((String) shutterRunConfig);
if (shutterRunInt < 1000) {
throw new NumberFormatException();
}
shutterRun = shutterRunInt;
logger.debug("shutterRun set to {} via configuration", shutterRun);
}
} else {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
logger.debug("Wrong configuration: {} setting must be {} or an integer >= 1000",
OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, AUTO_CALIBRATION);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.wrong-configuration");
shutterRun = SHUTTER_RUN_UNDEFINED;
}
updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
positionEstimation = POSITION_UNKNOWN;
}
@Override
protected void requestChannelState(ChannelUID channel) {
logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
Where w = deviceWhere;
if (w != null) {
try {
send(Automation.requestStatus(w.value()));
} catch (OWNException e) {
logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
}
}
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
switch (channel.getId()) {
case CHANNEL_SHUTTER:
handleShutterCommand(command);
break;
default: {
logger.info("Unsupported channel UID {}", channel);
}
}
}
/**
* Handles Automation Roller shutter command (UP/DOWN, STOP/MOVE, PERCENT xx%)
*/
private void handleShutterCommand(Command command) {
Where w = deviceWhere;
if (w != null) {
calibrating = CALIBRATION_INACTIVE; // cancel calibration if we receive a command
commandRequestedWhileMoving = null;
try {
if (StopMoveType.STOP.equals(command)) {
send(Automation.requestStop(w.value()));
} else if (command instanceof UpDownType || command instanceof PercentType) {
if (movingState == MOVING_STATE_MOVING_UP || movingState == MOVING_STATE_MOVING_DOWN) { // already
// moving
logger.debug("# {} # already moving, STOP then defer command", deviceWhere);
commandRequestedWhileMoving = command;
sendHighPriority(Automation.requestStop(w.value()));
return;
} else {
if (command instanceof UpDownType) {
if (UpDownType.UP.equals(command)) {
send(Automation.requestMoveUp(w.value()));
} else {
send(Automation.requestMoveDown(w.value()));
}
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command, w.value());
}
}
} else {
logger.debug("Unsupported command {} for thing {}", command, thing.getUID());
}
} catch (OWNException e) {
logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
}
}
}
/**
* Handles Automation PERCENT xx% command
*/
private void handlePercentCommand(PercentType command, String w) {
int percent = command.intValue();
if (percent == positionEstimation) {
logger.debug("# {} # handlePercentCommand() Command {}% == positionEstimation -> nothing to do", w,
percent);
return;
}
try {
if (percent == POSITION_DOWN) { // GO TO 100%
send(Automation.requestMoveDown(w));
} else if (percent == POSITION_UP) { // GO TO 0%
send(Automation.requestMoveUp(w));
} else { // GO TO XX%
logger.debug("# {} # {}% requested", deviceWhere, percent);
if (shutterRun == SHUTTER_RUN_UNDEFINED) {
logger.debug("& {} & CALIBRATION - shutterRun not configured, starting CALIBRATION...",
deviceWhere);
calibrating = CALIBRATION_ACTIVATED;
send(Automation.requestMoveUp(w));
positionRequested = percent;
} else if (shutterRun >= 1000 && positionEstimation != POSITION_UNKNOWN) {
// these two must be known to calculate moveTime.
// Calculate how much time we have to move and set a deadline to stop after that time
int moveTime = Math
.round(((float) Math.abs(percent - positionEstimation) / POSITION_MAX_STEPS * shutterRun));
logger.debug("# {} # target moveTime={}", deviceWhere, moveTime);
if (moveTime > MIN_STEP_TIME_MSEC) {
ScheduledFuture<?> mSch = moveSchedule;
if (mSch != null && !mSch.isDone()) {
// a moveSchedule was already scheduled and is not done... let's cancel the schedule
mSch.cancel(false);
logger.debug("# {} # new XX% requested, old moveSchedule cancelled", deviceWhere);
}
// send a requestFirmwareVersion message to BUS gateways to wake up the CMD connection before
// sending further cmds
OpenWebNetBridgeHandler h = bridgeHandler;
if (h != null && h.isBusGateway()) {
OpenGateway gw = h.gateway;
if (gw != null) {
if (!gw.isCmdConnectionReady()) {
logger.debug("# {} # waking-up CMD connection...", deviceWhere);
send(GatewayMgmt.requestFirmwareVersion());
}
}
}
// REMINDER: start the schedule BEFORE sending the command, because the synch command waits for
// ACK and can take some 300ms
logger.debug("# {} # Starting schedule...", deviceWhere);
moveSchedule = scheduler.schedule(() -> {
logger.debug("# {} # moveSchedule expired, sending STOP...", deviceWhere);
try {
sendHighPriority(Automation.requestStop(w));
} catch (OWNException ex) {
logger.debug("Exception while sending request for command {}: {}", command,
ex.getMessage(), ex);
}
}, moveTime, TimeUnit.MILLISECONDS);
logger.debug("# {} # ...schedule started, now sending highPriority command...", deviceWhere);
if (percent < positionEstimation) {
sendHighPriority(Automation.requestMoveUp(w));
} else {
sendHighPriority(Automation.requestMoveDown(w));
}
logger.debug("# {} # ...gateway.sendHighPriority() returned", deviceWhere);
} else {
logger.debug("# {} # moveTime <= MIN_STEP_TIME_MSEC ---> do nothing", deviceWhere);
}
} else {
logger.info(
"Command {} cannot be executed: unknown position or shutterRun configuration params not/wrongly set (thing={})",
command, thing.getUID());
}
}
} catch (OWNException e) {
logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
}
}
@Override
protected String ownIdPrefix() {
return Who.AUTOMATION.value().toString();
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
updateAutomationState((Automation) msg);
// REMINDER: update state, then update thing status in the super method, to avoid delays
super.handleMessage(msg);
}
/**
* Updates automation device state based on the Automation message received from OWN network
*
* @param msg the Automation message
*/
private void updateAutomationState(Automation msg) {
logger.debug("updateAutomationState() - msg={} what={}", msg, msg.getWhat());
try {
if (msg.isCommandTranslation()) {
logger.debug("msg is command translation, ignoring it");
return;
}
} catch (FrameException fe) {
logger.warn("Exception while checking WHERE command translation for frame {}: {}, ignoring it", msg,
fe.getMessage());
}
if (msg.isUp()) {
updateMovingState(MOVING_STATE_MOVING_UP);
if (calibrating == CALIBRATION_ACTIVATED) {
calibrating = CALIBRATION_GOING_UP;
logger.debug("& {} & CALIBRATION - started going ALL UP...", deviceWhere);
}
} else if (msg.isDown()) {
updateMovingState(MOVING_STATE_MOVING_DOWN);
if (calibrating == CALIBRATION_ACTIVATED) {
calibrating = CALIBRATION_GOING_DOWN;
logger.debug("& {} & CALIBRATION - started going ALL DOWN...", deviceWhere);
}
} else if (msg.isStop()) {
long stoppedAt = System.currentTimeMillis();
if (calibrating == CALIBRATION_GOING_DOWN && shutterRun == SHUTTER_RUN_UNDEFINED) {
shutterRun = (int) (stoppedAt - startedMovingAt);
logger.debug("& {} & CALIBRATION - reached DOWN ---> shutterRun={}", deviceWhere, shutterRun);
updateMovingState(MOVING_STATE_STOPPED);
logger.debug("& {} & CALIBRATION - COMPLETED, now going to {}%", deviceWhere, positionRequested);
handleShutterCommand(new PercentType(positionRequested));
Configuration configuration = editConfiguration();
configuration.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, Integer.toString(shutterRun));
updateConfiguration(configuration);
logger.debug("& {} & CALIBRATION - configuration updated: shutterRun = {}ms", deviceWhere, shutterRun);
} else if (calibrating == CALIBRATION_GOING_UP) {
updateMovingState(MOVING_STATE_STOPPED);
logger.debug("& {} & CALIBRATION - reached UP, now sending DOWN command...", deviceWhere);
calibrating = CALIBRATION_ACTIVATED;
if (deviceWhere != null) {
String w = deviceWhere.value();
try {
send(Automation.requestMoveDown(w));
} catch (OWNException e) {
logger.debug("Exception while sending DOWN command during calibration: {}", e.getMessage(), e);
calibrating = CALIBRATION_INACTIVE;
}
}
} else {
updateMovingState(MOVING_STATE_STOPPED);
// do deferred command, if present
Command cmd = commandRequestedWhileMoving;
if (cmd != null) {
handleShutterCommand(cmd);
}
}
} else {
logger.debug("Frame {} not supported for thing {}, ignoring it.", msg, thing.getUID());
}
}
/**
* Updates movingState to newState
*/
private void updateMovingState(int newState) {
if (movingState == MOVING_STATE_STOPPED) {
if (newState != MOVING_STATE_STOPPED) { // moving after stop
startedMovingAt = System.currentTimeMillis();
logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAt,
FORMATTER.format(new Date(startedMovingAt)));
}
} else { // we were moving
updatePosition();
if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
startedMovingAt = System.currentTimeMillis();
logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAt,
FORMATTER.format(new Date(startedMovingAt)));
}
// cancel the schedule
ScheduledFuture<?> mSc = moveSchedule;
if (mSc != null && !mSc.isDone()) {
mSc.cancel(false);
}
}
movingState = newState;
logger.debug("# {} # movingState={} positionEstimation={} - calibrating={} shutterRun={}", deviceWhere,
movingState, positionEstimation, calibrating, shutterRun);
}
/**
* Updates positionEstimation and then channel state based on movedTime and current movingState
*/
private void updatePosition() {
int newPos = POSITION_UNKNOWN;
if (shutterRun > 0) {// we have shutterRun defined, let's calculate new positionEstimation
long movedTime = System.currentTimeMillis() - startedMovingAt;
logger.debug("# {} # current positionEstimation={} movedTime={}", deviceWhere, positionEstimation,
movedTime);
int movedSteps = Math.round((float) movedTime / shutterRun * POSITION_MAX_STEPS);
logger.debug("# {} # movedSteps: {} {}", deviceWhere, movedSteps,
(movingState == MOVING_STATE_MOVING_DOWN) ? "DOWN(+)" : "UP(-)");
if (positionEstimation == POSITION_UNKNOWN && movedSteps >= POSITION_MAX_STEPS) { // we did a full run
newPos = (movingState == MOVING_STATE_MOVING_DOWN) ? POSITION_DOWN : POSITION_UP;
} else if (positionEstimation != POSITION_UNKNOWN) {
newPos = positionEstimation
+ ((movingState == MOVING_STATE_MOVING_DOWN) ? movedSteps : -1 * movedSteps);
logger.debug("# {} # {} {} {} = {}", deviceWhere, positionEstimation,
(movingState == MOVING_STATE_MOVING_DOWN) ? "+" : "-", movedSteps, newPos);
if (newPos > POSITION_DOWN) {
newPos = POSITION_DOWN;
} else if (newPos < POSITION_UP) {
newPos = POSITION_UP;
}
}
}
if (newPos != POSITION_UNKNOWN) {
if (newPos != positionEstimation) {
updateState(CHANNEL_SHUTTER, new PercentType(newPos));
}
} else {
updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
}
positionEstimation = newPos;
}
@Override
public void dispose() {
ScheduledFuture<?> mSc = moveSchedule;
if (mSc != null) {
mSc.cancel(true);
}
super.dispose();
}
}

View File

@@ -0,0 +1,544 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.binding.openwebnet.handler.config.OpenWebNetBusBridgeConfig;
import org.openhab.binding.openwebnet.handler.config.OpenWebNetZigBeeBridgeConfig;
import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
import org.openhab.core.config.core.status.ConfigStatusMessage;
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.ThingTypeUID;
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openwebnet4j.BUSGateway;
import org.openwebnet4j.GatewayListener;
import org.openwebnet4j.OpenDeviceType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.USBGateway;
import org.openwebnet4j.communication.OWNAuthException;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.Automation;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.OpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
// ConcurrentHashMap of devices registered to this BridgeHandler
// association is: ownId (String) -> OpenWebNetThingHandler, with ownId = WHO.WHERE
private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
protected @Nullable OpenGateway gateway;
private boolean isBusGateway = false;
private boolean isGatewayConnected = false;
public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
private boolean reconnecting = false; // we are trying to reconnect to gateway
private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
private boolean discoveryByActivation;
public OpenWebNetBridgeHandler(Bridge bridge) {
super(bridge);
}
public boolean isBusGateway() {
return isBusGateway;
}
@Override
public void initialize() {
ThingTypeUID thingType = getThing().getThingTypeUID();
OpenGateway gw;
if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
gw = initZigBeeGateway();
} else {
gw = initBusGateway();
isBusGateway = true;
}
if (gw != null) {
gateway = gw;
gw.subscribe(this);
if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
isGatewayConnected = true;
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Trying to connect gateway...");
try {
gw.connect();
scheduler.schedule(() -> {
// if status is still UNKNOWN after timer ends, set the device as OFFLINE
if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Could not connect to gateway before " + GATEWAY_ONLINE_TIMEOUT_SEC + "s");
}
}, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (OWNException e) {
logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
// status is updated by callback onConnectionError()
}
}
}
}
/**
* Init a ZigBee gateway based on config
*/
private @Nullable OpenGateway initZigBeeGateway() {
logger.debug("Initializing ZigBee USB gateway");
OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
String serialPort = zbBridgeConfig.getSerialPort();
if (serialPort == null || serialPort.isEmpty()) {
logger.warn("Cannot connect to gateway. No serial port has been provided in Bridge configuration.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-serial-port");
return null;
} else {
return new USBGateway(serialPort);
}
}
/**
* Init a BUS gateway based on config
*/
private @Nullable OpenGateway initBusGateway() {
logger.debug("Initializing BUS gateway");
OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
String host = busBridgeConfig.getHost();
if (host == null || host.isEmpty()) {
logger.warn("Cannot connect to gateway. No host/IP has been provided in Bridge configuration.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-ip-address");
return null;
} else {
int port = busBridgeConfig.getPort().intValue();
String passwd = busBridgeConfig.getPasswd();
String passwdMasked;
if (passwd.length() >= 4) {
passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
} else {
passwdMasked = "******";
}
discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
logger.debug("Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}",
host, port, passwdMasked, discoveryByActivation);
return new BUSGateway(host, port, passwd);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand (command={} - channel={})", command, channelUID);
OpenGateway gw = gateway;
if (gw != null && !gw.isConnected()) {
logger.warn("Gateway is NOT connected, skipping command");
return;
} else {
logger.warn("Channel not supported: channel={}", channelUID);
}
}
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
return Collections.emptyList();
}
@Override
public void handleRemoval() {
disconnectGateway();
super.handleRemoval();
}
@Override
public void dispose() {
disconnectGateway();
super.dispose();
}
private void disconnectGateway() {
OpenGateway gw = gateway;
if (gw != null) {
gw.closeConnection();
gw.unsubscribe(this);
logger.debug("gateway {} connection closed and unsubscribed", gw.toString());
gateway = null;
}
reconnecting = false;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OpenWebNetDeviceDiscoveryService.class);
}
/**
* Search for devices connected to this bridge handler's gateway
*
* @param listener to receive device found notifications
*/
public synchronized void searchDevices() {
scanIsActive = true;
logger.debug("------$$ scanIsActive={}", scanIsActive);
OpenGateway gw = gateway;
if (gw != null) {
if (!gw.isDiscovering()) {
if (!gw.isConnected()) {
logger.debug("------$$ Gateway is NOT connected, cannot search for devices");
return;
}
logger.info("------$$ STARTED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
try {
gw.discoverDevices();
} catch (OWNException e) {
logger.warn("------$$ OWNException while discovering devices on gateway {}: {}",
this.getThing().getUID(), e.getMessage());
}
} else {
logger.debug("------$$ Searching devices on gateway {} already activated", this.getThing().getUID());
return;
}
} else {
logger.debug("------$$ Cannot search devices: no gateway associated to this handler");
}
}
@Override
public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
if (discService != null) {
if (w != null && deviceType != null) {
discService.newDiscoveryResult(w, deviceType, message);
} else {
logger.warn("onNewDevice with null where/deviceType, msg={}", message);
}
} else {
logger.warn("onNewDevice but null deviceDiscoveryService");
}
}
@Override
public void onDiscoveryCompleted() {
logger.info("------$$ FINISHED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
}
/**
* Notifies that the scan has been stopped/aborted by OpenWebNetDeviceDiscoveryService
*/
public void scanStopped() {
scanIsActive = false;
logger.debug("------$$ scanIsActive={}", scanIsActive);
}
private void discoverByActivation(BaseOpenMessage baseMsg) {
logger.debug("BridgeHandler.discoverByActivation() msg={}", baseMsg);
OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
if (discService == null) {
logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
return;
}
if (baseMsg instanceof Lighting) {
OpenDeviceType type = null;
try {
type = baseMsg.detectDeviceType();
} catch (FrameException e) {
logger.warn("Exception while detecting device type: {}", e.getMessage());
}
if (type != null) {
discService.newDiscoveryResult(baseMsg.getWhere(), type, baseMsg);
} else {
logger.debug("discoverByActivation: no device type detected from msg: {}", baseMsg);
}
}
}
/**
* Register a device ThingHandler to this BridgHandler
*
* @param ownId the device OpenWebNet id
* @param thingHandler the thing handler to be registered
*/
protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
if (registeredDevices.containsKey(ownId)) {
logger.warn("registering device with an existing ownId={}", ownId);
}
registeredDevices.put(ownId, thingHandler);
logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
}
/**
* Un-register a device from this bridge handler
*
* @param oId the device OpenWebNet id
*/
protected void unregisterDevice(String oId) {
if (registeredDevices.remove(oId) != null) {
logger.debug("un-registered device ownId={}", oId);
} else {
logger.warn("could not un-register ownId={} (not found)", oId);
}
}
@Override
public void onEventMessage(@Nullable OpenMessage msg) {
logger.trace("RECEIVED <<<<< {}", msg);
if (msg == null) {
logger.warn("received event msg is null");
return;
}
if (msg.isACK() || msg.isNACK()) {
return; // we ignore ACKS/NACKS
}
// GATEWAY MANAGEMENT
if (msg instanceof GatewayMgmt) {
// noop
return;
}
BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
// let's try to get the Thing associated with this message...
if (baseMsg instanceof Lighting || baseMsg instanceof Automation) {
String ownId = ownIdFromMessage(baseMsg);
logger.debug("ownId={}", ownId);
OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
if (deviceHandler == null) {
OpenGateway gw = gateway;
if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
|| (discoveryByActivation && !scanIsActive))) {
discoverByActivation(baseMsg);
} else {
logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId);
}
} else {
deviceHandler.handleMessage(baseMsg);
}
} else {
logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
baseMsg.getWho());
}
}
@Override
public void onConnected() {
isGatewayConnected = true;
Map<String, String> properties = editProperties();
boolean propertiesChanged = false;
OpenGateway gw = gateway;
if (gw == null) {
logger.warn("received onConnected() but gateway is null");
return;
}
if (gw instanceof USBGateway) {
logger.info("------------------- CONNECTED to USB (ZigBee) gateway - USB port: {}",
((USBGateway) gw).getSerialPortName());
} else {
logger.info("------------------- CONNECTED to BUS gateway '{}' ({}:{})", thing.getUID(),
((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
// update serial number property (with MAC address)
if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
propertiesChanged = true;
logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
}
}
if (properties.get(PROPERTY_FIRMWARE_VERSION) != gw.getFirmwareVersion()) {
properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
propertiesChanged = true;
logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
}
if (propertiesChanged) {
updateProperties(properties);
logger.info("properties updated for '{}'", thing.getUID());
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void onConnectionError(@Nullable OWNException error) {
String errMsg;
if (error == null) {
errMsg = "unknown error";
} else {
errMsg = error.getMessage();
}
logger.info("------------------- ON CONNECTION ERROR: {}", errMsg);
isGatewayConnected = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errMsg);
tryReconnectGateway();
}
@Override
public void onConnectionClosed() {
isGatewayConnected = false;
logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
// NOTE: cannot change to OFFLINE here because we are already in REMOVING state
}
@Override
public void onDisconnected(@Nullable OWNException e) {
isGatewayConnected = false;
String errMsg;
if (e == null) {
errMsg = "unknown error";
} else {
errMsg = e.getMessage();
}
logger.info("------------------- DISCONNECTED from gateway. OWNException={}", errMsg);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Disconnected from gateway (onDisconnected - " + errMsg + ")");
tryReconnectGateway();
}
private void tryReconnectGateway() {
OpenGateway gw = gateway;
if (gw != null) {
if (!reconnecting) {
reconnecting = true;
logger.info("------------------- Starting RECONNECT cycle to gateway");
try {
gw.reconnect();
} catch (OWNAuthException e) {
logger.info("------------------- AUTH error from gateway. Stopping reconnect");
reconnecting = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Authentication error. Check gateway password in Thing Configuration Parameters (" + e
+ ")");
}
} else {
logger.debug("------------------- reconnecting=true, do nothing");
}
} else {
logger.debug("------------------- cannot start RECONNECT, gateway is null");
}
}
@Override
public void onReconnected() {
reconnecting = false;
logger.info("------------------- RE-CONNECTED to gateway!");
OpenGateway gw = gateway;
if (gw != null) {
updateStatus(ThingStatus.ONLINE);
if (gw.getFirmwareVersion() != null) {
this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
}
}
}
/**
* Return a ownId string (=WHO.WHERE) from a deviceWhere thing config parameter (already normalized) and its
* handler.
*
* @param deviceWhere the device WHERE config parameter
* @param handler the thing handler
* @return the ownId
*/
protected String ownIdFromDeviceWhere(@Nullable String deviceWhere, OpenWebNetThingHandler handler) {
return handler.ownIdPrefix() + "." + deviceWhere;
}
/**
* Returns a ownId string (=WHO.WHERE) from a Where address and Who
*
* @param where the Where address (to be normalized)
* @param who the Who
* @return the ownId
*/
public String ownIdFromWhoWhere(Where where, Who who) {
return who.value() + "." + normalizeWhere(where);
}
/**
* Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
*
* @param baseMsg the BaseOpenMessage
* @return the ownId String
*/
private String ownIdFromMessage(BaseOpenMessage baseMsg) {
return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
}
/**
* Transform a Where address into a Thing id string based on bridge type (BUS/USB ZigBee).
* '#' in WHERE are changed to 'h'
*
* @param where the Where address
* @return the thing Id
*/
public String thingIdFromWhere(Where where) {
return normalizeWhere(where).replace('#', 'h'); // '#' cannot be used in ThingUID;
}
/**
* Normalize a Where address for Thermo and Zigbee devices
*
* @param where the Where address
* @return the normalized address
*/
public String normalizeWhere(Where where) {
String str = "";
if (isBusGateway) {
if (where.value().indexOf('#') < 0) { // no hash present
str = where.value();
} else if (where.value().indexOf("#4#") > 0) { // local bus: APL#4#bus
str = where.value();
} else if (where.value().indexOf('#') == 0) { // thermo zone via central unit: #0 or #Z (Z=[1-99]) --> Z
str = where.value().substring(1);
} else if (where.value().indexOf('#') > 0) { // thermo zone and actuator N: Z#N (Z=[1-99], N=[1-9]) -- > Z
str = where.value().substring(0, where.value().indexOf('#'));
} else {
logger.warn("normalizeWhere() unexpected WHERE: {}", where);
str = where.value();
}
return str;
} else {
return where.value();
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* 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.openwebnet.handler;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openwebnet4j.message.BaseOpenMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetGenericHandler} is responsible for handling Generic OpenWebNet
* devices. It does not too much, but it is needed to avoid handler errors and to tell the user
* that some device has been found by the gateway but it was not recognised.
* It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetGenericHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetGenericHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.GENERIC_SUPPORTED_THING_TYPES;
public OpenWebNetGenericHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
}
@Override
protected void requestChannelState(ChannelUID channel) {
// do nothing
logger.warn("There are no channels");
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
// do nothing
logger.warn("There are no channels");
}
@Override
protected String ownIdPrefix() {
return "G";
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
super.handleMessage(msg);
// do nothing
logger.warn("handleMessage(): Nothing to do!");
}
} // class

View File

@@ -0,0 +1,362 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.What;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
* It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
private static final int BRIGHTNESS_CHANGE_DELAY_MSEC = 1500; // delay to wait before sending another brightness
// status request
private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
private boolean brightnessLevelRequested = false; // was the brightness level requested ?
private int latestBrightnessWhat = -1; // latest brightness WHAT value (-1 = unknown)
private int latestBrightnessWhatBeforeOff = -1; // latest brightness WHAT value before device was set to off
public OpenWebNetLightingHandler(Thing thing) {
super(thing);
}
@Override
protected void requestChannelState(ChannelUID channel) {
logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
try {
send(Lighting.requestStatus(toWhere(channel)));
} catch (OWNException e) {
logger.warn("Exception while requesting channel {} state: {}", channel, e.getMessage());
}
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
switch (channel.getId()) {
case CHANNEL_BRIGHTNESS:
handleBrightnessCommand(command);
break;
case CHANNEL_SWITCH:
case CHANNEL_SWITCH_01:
case CHANNEL_SWITCH_02:
handleSwitchCommand(channel, command);
break;
default: {
logger.warn("Unsupported channel UID {}", channel);
}
}
}
/**
* Handles Lighting switch command for a channel
*
* @param channel the channel
* @param command the command
*/
private void handleSwitchCommand(ChannelUID channel, Command command) {
logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
if (command instanceof OnOffType) {
try {
if (OnOffType.ON.equals(command)) {
send(Lighting.requestTurnOn(toWhere(channel)));
} else if (OnOffType.OFF.equals(command)) {
send(Lighting.requestTurnOff(toWhere(channel)));
}
} catch (OWNException e) {
logger.warn("Exception while processing command {}: {}", command, e.getMessage());
}
} else {
logger.warn("Unsupported command: {}", command);
}
}
/**
* Handles Lighting brightness command (ON, OFF, xx%, INCREASE, DECREASE)
*
* @param command the command
*/
private void handleBrightnessCommand(Command command) {
logger.debug("handleBrightnessCommand() command={}", command);
if (command instanceof PercentType) {
int percent = ((PercentType) command).intValue();
dimLightTo(Lighting.percentToWhat(percent).value(), command);
} else if (command instanceof IncreaseDecreaseType) {
if (IncreaseDecreaseType.INCREASE.equals(command)) {
dimLightTo(latestBrightnessWhat + 1, command);
} else { // DECREASE
dimLightTo(latestBrightnessWhat - 1, command);
}
} else if (command instanceof OnOffType) {
if (OnOffType.ON.equals(command)) {
dimLightTo(latestBrightnessWhat, command);
} else { // OFF
dimLightTo(0, command);
}
} else {
logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
}
}
/**
* Helper method to dim light to a valid OWN value
*/
private void dimLightTo(int whatInt, Command command) {
int newWhatInt = whatInt;
logger.debug("-DIM- dimLightTo() latestBriWhat={} latestBriBeforeOff={} briLevelRequested={}",
latestBrightnessWhat, latestBrightnessWhatBeforeOff, brightnessLevelRequested);
What newWhat;
if (OnOffType.ON.equals(command) && latestBrightnessWhat <= 0) {
// ON after OFF/Unknown -> we reset channel to last value before OFF (if exists)
if (latestBrightnessWhatBeforeOff > 0) { // we know last brightness -> set dimmer to it
newWhatInt = latestBrightnessWhatBeforeOff;
} else { // we do not know last brightness -> set dimmer to 100%
newWhatInt = 10;
}
}
logger.debug("-DIM- requested level={}", newWhatInt);
if (newWhatInt != latestBrightnessWhat) {
if (newWhatInt >= 0 && newWhatInt <= 10) {
newWhat = Lighting.WHAT.fromValue(newWhatInt);
if (newWhat.equals(Lighting.WHAT.ON)) {
// change it to WHAT.DIMMER_20 (dimming to 10% is not allowed in OWN)
newWhat = Lighting.WHAT.DIMMER_20;
}
// save current brightness level before sending bri=0 command to device
if (newWhatInt == 0) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
Where w = deviceWhere;
if (w != null) {
try {
lastBrightnessChangeSentTS = System.currentTimeMillis();
send(Lighting.requestDimTo(w.value(), newWhat));
} catch (OWNException e) {
logger.warn("Exception while sending dimLightTo for command {}: {}", command, e.getMessage());
}
}
} else {
logger.debug("-DIM- do nothing");
}
} else {
logger.debug("-DIM- do nothing");
}
logger.debug("-DIM- latestBriWhat={} latestBriBeforeOff={} briLevelRequested={}", latestBrightnessWhat,
latestBrightnessWhatBeforeOff, brightnessLevelRequested);
}
@Override
protected String ownIdPrefix() {
return Who.LIGHTING.value().toString();
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
super.handleMessage(msg);
updateLightState((Lighting) msg);
}
/**
* Updates light state based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private void updateLightState(Lighting msg) {
logger.debug("updateLightState() for thing: {}", thing.getUID());
ThingTypeUID thingType = thing.getThingTypeUID();
if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
updateLightBrightnessState(msg);
} else {
updateLightOnOffState(msg);
}
}
/**
* Updates on/off state based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private void updateLightOnOffState(Lighting msg) {
String channelID;
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null) {
if (brH.isBusGateway()) {
channelID = CHANNEL_SWITCH;
} else {
WhereZigBee w = (WhereZigBee) (msg.getWhere());
if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
channelID = CHANNEL_SWITCH_02;
} else {
channelID = CHANNEL_SWITCH_01;
}
}
if (msg.isOn()) {
updateState(channelID, OnOffType.ON);
} else if (msg.isOff()) {
updateState(channelID, OnOffType.OFF);
} else {
logger.debug("updateLightOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}",
getThing().getUID(), msg.getFrameValue());
}
}
}
/**
* Updates brightness level based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private synchronized void updateLightBrightnessState(Lighting msg) {
final String channel = CHANNEL_BRIGHTNESS;
logger.debug(" $BRI updateLightBrightnessState() msg={}", msg);
logger.debug(" $BRI updateLightBr() latestBriWhat={} latestBriBeforeOff={} brightnessLevelRequested={}",
latestBrightnessWhat, latestBrightnessWhatBeforeOff, brightnessLevelRequested);
long now = System.currentTimeMillis();
long delta = now - lastBrightnessChangeSentTS;
logger.debug(" $BRI now={} -> delta={}", now, delta);
if (msg.isOn() && !brightnessLevelRequested) {
if (delta >= BRIGHTNESS_CHANGE_DELAY_MSEC) {
// we send a light brightness status request ONLY if last brightness change
// was not just sent (>=BRIGHTNESS_CHANGE_DELAY_MSEC ago)
logger.debug(" $BRI change sent >={}ms ago, sending requestStatus...", BRIGHTNESS_CHANGE_DELAY_MSEC);
Where w = deviceWhere;
if (w != null) {
try {
send(Lighting.requestStatus(w.value()));
brightnessLevelRequested = true;
} catch (OWNException e) {
logger.warn(" $BRI exception while requesting light state: {}", e.getMessage());
}
}
} else {
logger.debug(" $BRI change sent {}<{}ms, NO requestStatus needed", delta,
BRIGHTNESS_CHANGE_DELAY_MSEC);
}
} else {
logger.debug(" $BRI update from network -> level should be present in WHAT part of the message");
if (msg.getWhat() != null) {
int newLevel = msg.getWhat().value();
logger.debug(" $BRI current level={} ----> new level={}", latestBrightnessWhat, newLevel);
if (latestBrightnessWhat != newLevel) {
updateState(channel, new PercentType(Lighting.levelToPercent(newLevel)));
if (msg.isOff()) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
latestBrightnessWhat = newLevel;
} else {
logger.debug(" $BRI no change");
}
brightnessLevelRequested = false;
} else { // dimension notification
if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
int newPercent;
try {
newPercent = msg.parseDimmerLevel100();
} catch (FrameException fe) {
logger.warn("updateLightBrightnessState() Wrong value for dimmerLevel100 in message: {}", msg);
return;
}
int newLevel = Lighting.percentToWhat(newPercent).value();
logger.debug(" $BRI latest level={} ----> new percent={} ----> new level={}", latestBrightnessWhat,
newPercent, newLevel);
updateState(channel, new PercentType(newPercent));
if (newPercent == 0) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
latestBrightnessWhat = newLevel;
brightnessLevelRequested = false;
} else {
logger.warn("updateLightBrightnessState() Cannot handle message {} for thing {}", msg,
getThing().getUID());
return;
}
}
}
logger.debug(" $BRI latestBriWhat={} latestBriBeforeOff={} brightnessLevelRequested={}", latestBrightnessWhat,
latestBrightnessWhatBeforeOff, brightnessLevelRequested);
}
/**
* Returns a WHERE address string based on bridge type and unit (optional)
*
* @param unit the device unit
**/
@Nullable
protected String toWhere(String unit) {
Where w = deviceWhere;
if (w != null) {
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null && brH.isBusGateway()) {
return w.value();
} else {
return w + unit;
}
} else {
return null;
}
}
/**
* Returns a WHERE address string based on channel
*
* @param channel the channel
**/
@Nullable
protected String toWhere(ChannelUID channel) {
Where w = deviceWhere;
if (w != null) {
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null) {
if (brH.isBusGateway()) {
return w.value();
} else if (channel.getId().equals(CHANNEL_SWITCH_02)) {
return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
} else { // CHANNEL_SWITCH_01 or other channels
return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,206 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.communication.Response;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.OpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.openwebnet4j.message.WhereZigBee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetThingHandler} is responsible for handling commands for a OpenWebNet device.
* It's the abstract class for all OpenWebNet things. It should be extended by each specific OpenWebNet category of
* device (WHO).
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public abstract class OpenWebNetThingHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
protected @Nullable OpenWebNetBridgeHandler bridgeHandler;
protected @Nullable String ownId; // OpenWebNet identifier for this device: WHO.WHERE
protected @Nullable Where deviceWhere; // this device Where address
protected @Nullable ScheduledFuture<?> refreshTimeout;
public OpenWebNetThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge != null) {
OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
if (brH != null) {
bridgeHandler = brH;
Object deviceWhereConfig = getConfig().get(CONFIG_PROPERTY_WHERE);
if (!(deviceWhereConfig instanceof String)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"WHERE parameter in configuration is null or invalid");
return;
} else {
String deviceWhereStr = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
Where w;
if (brH.isBusGateway()) {
w = new WhereLightAutom(deviceWhereStr);
} else {
w = new WhereZigBee(deviceWhereStr);
}
deviceWhere = w;
final String oid = brH.ownIdFromDeviceWhere(w.value(), this);
ownId = oid;
Map<String, String> properties = editProperties();
properties.put(PROPERTY_OWNID, oid);
updateProperties(properties);
brH.registerDevice(oid, this);
logger.debug("associated thing to bridge with ownId={}", ownId);
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "waiting state update...");
}
return;
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No bridge associated, please assign a bridge in thing configuration.");
}
@Override
public void handleCommand(ChannelUID channel, Command command) {
logger.debug("handleCommand() (command={} - channel={})", command, channel);
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null && !gw.isConnected()) {
logger.info("Cannot handle command {}:{} for {}: gateway is not connected", channel, command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
requestChannelState(channel);
// set a schedule to put device OFFLINE if no answer is received after THING_STATE_REQ_TIMEOUT_SEC
refreshTimeout = scheduler.schedule(() -> {
if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not get channel state (timer expired)");
}
}, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
} else {
handleChannelCommand(channel, command);
}
} else {
logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
}
}
/**
* Handles a command for the specific channel for this thing.
* It must be implemented by each specific OpenWebNet category of device (WHO), based on channel
*
* @param channel specific ChannleUID
* @param command the Command to be executed
*/
protected abstract void handleChannelCommand(ChannelUID channel, Command command);
/**
* Handle incoming message from OWN network via bridge Thing, directed to this device. It should be further
* implemented by each specific device handler.
*
* @param msg the message to handle
*/
protected void handleMessage(BaseOpenMessage msg) {
ThingStatus ts = getThing().getStatus();
if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Helper method to send OWN messages from ThingsHandlers
*/
protected @Nullable Response send(OpenMessage msg) throws OWNException {
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null) {
return gw.send(msg);
}
}
return null;
}
/**
* Helper method to send with high priority OWN messages from ThingsHandlers
*/
protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null) {
return gw.sendHighPriority(msg);
}
}
return null;
}
/**
* Request to gateway state for thing channel. It must be implemented by each specific device handler.
*
* @param channel the channel to request the state for
*/
protected abstract void requestChannelState(ChannelUID channel);
@Override
public void dispose() {
OpenWebNetBridgeHandler bh = bridgeHandler;
String oid = ownId;
if (bh != null && oid != null) {
bh.unregisterDevice(oid);
}
ScheduledFuture<?> sc = refreshTimeout;
if (sc != null) {
sc.cancel(true);
}
super.dispose();
}
/**
* Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
*
* @return
*/
protected abstract String ownIdPrefix();
}

View File

@@ -0,0 +1,49 @@
/**
* 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.openwebnet.handler.config;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* BUS Bridge configuration object
*
* @author Massimo Valla - Initial contribution
*
*/
@NonNullByDefault
public class OpenWebNetBusBridgeConfig {
private BigDecimal port = new BigDecimal(20000);
private @Nullable String host;
private String passwd = "12345";
private boolean discoveryByActivation = false;
public BigDecimal getPort() {
return port;
}
public @Nullable String getHost() {
return host;
}
public String getPasswd() {
return passwd;
}
public Boolean getDiscoveryByActivation() {
return discoveryByActivation;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.openwebnet.handler.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* ZigBee USB Bridge configuration object
*
* @author Massimo Valla - Initial contribution
*
*/
@NonNullByDefault
public class OpenWebNetZigBeeBridgeConfig {
private @Nullable String serialPort;
public @Nullable String getSerialPort() {
return serialPort;
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.openwebnet.internal;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.ALL_SUPPORTED_THING_TYPES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.handler.OpenWebNetAutomationHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetBridgeHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetGenericHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetLightingHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetHandlerFactory} is responsible for creating thing handlers.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.openwebnet", service = ThingHandlerFactory.class)
public class OpenWebNetHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetHandlerFactory.class);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return ALL_SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (OpenWebNetBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW BRIDGE Handler");
return new OpenWebNetBridgeHandler((Bridge) thing);
} else if (OpenWebNetGenericHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW GENERIC Handler");
return new OpenWebNetGenericHandler(thing);
} else if (OpenWebNetLightingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW LIGHTING Handler");
return new OpenWebNetLightingHandler(thing);
} else if (OpenWebNetAutomationHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW AUTOMATION Handler");
return new OpenWebNetAutomationHandler(thing);
}
logger.warn("ThingType {} is not supported by this binding", thing.getThingTypeUID());
return null;
}
}

View File

@@ -0,0 +1,232 @@
/**
* 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.openwebnet.internal.discovery;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.DeviceDetails;
import org.jupnp.model.meta.ManufacturerDetails;
import org.jupnp.model.meta.ModelDetails;
import org.jupnp.model.meta.RemoteDevice;
import org.jupnp.model.meta.RemoteDeviceIdentity;
import org.jupnp.model.types.UDN;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BusGatewayUpnpDiscovery} is responsible for discovering supported BTicino BUS
* gateways devices using UPnP. It implements {@link UpnpDiscoveryParticipant}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
@Component(service = UpnpDiscoveryParticipant.class)
public class BusGatewayUpnpDiscovery implements UpnpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(BusGatewayUpnpDiscovery.class);
public enum BusGatewayId {
MH201("IPscenarioModule", "MH201"),
MH202("scheduler", "MH202"),
F454("webserver", "F454"),
MY_HOME_SERVER1("myhomeserver1", "MYHOMESERVER1"),
TOUCH_SCREEN_10("ts10", "TOUCHSCREEN10"),
MH200N("lightingcontrolunit", "MH200N");
private final String value, thingId;
private BusGatewayId(String value, String thingId) {
this.value = value;
this.thingId = thingId;
}
public static @Nullable BusGatewayId fromValue(String s) {
Optional<BusGatewayId> m = Arrays.stream(values()).filter(val -> s.equals(val.value)).findFirst();
if (m.isPresent()) {
return m.get();
} else {
return null;
}
}
public String getThingId() {
return thingId;
}
}
/**
* DeviceInfo bean to store device useful info (and log them)
*/
public class DeviceInfo {
@Nullable
private String friendlyName;
private String modelName = "<unknown>";
private String modelDescription = "<unknown>";
private String modelNumber = "<unknown>";
private String serialNumber = "<unknown>";
@Nullable
private String host;
private String manufacturer = "<unknown>";
@Nullable
private UDN udn;
private boolean isBTicino = false;
private DeviceInfo(RemoteDevice device) {
String deviceLog = "Discovered device:\n+=== UPnP =========================================";
RemoteDeviceIdentity identity = device.getIdentity();
if (identity != null) {
this.udn = identity.getUdn();
deviceLog += "\n| ID.UDN : " + udn;
if (identity.getDescriptorURL() != null) {
deviceLog += "\n| ID.DESC URL : " + identity.getDescriptorURL();
this.host = identity.getDescriptorURL().getHost();
}
deviceLog += "\n| ID.MAX AGE : " + identity.getMaxAgeSeconds();
}
deviceLog += "\n| --------------";
DeviceDetails details = device.getDetails();
if (details != null) {
ManufacturerDetails manufacturerDetails = details.getManufacturerDetails();
if (manufacturerDetails != null) {
this.manufacturer = manufacturerDetails.getManufacturer();
deviceLog += "\n| MANUFACTURER : " + manufacturer + " (" + manufacturerDetails.getManufacturerURI()
+ ")";
if (manufacturer.toUpperCase().contains("BTICINO")) {
this.isBTicino = true;
}
}
ModelDetails modelDetails = details.getModelDetails();
if (modelDetails != null) {
// Model Name | Desc | Number (Uri)
this.modelName = modelDetails.getModelName();
this.modelDescription = modelDetails.getModelDescription();
this.modelNumber = modelDetails.getModelNumber();
deviceLog += "\n| MODEL : " + modelName + " | " + modelDescription + " | " + modelNumber
+ " (" + modelDetails.getModelURI() + ")";
}
if (isBTicino) {
this.friendlyName = details.getFriendlyName();
deviceLog += "\n| FRIENDLY NAME: " + friendlyName;
this.serialNumber = details.getSerialNumber();
deviceLog += "\n| SERIAL # : " + serialNumber;
deviceLog += "\n| BASE URL : " + details.getBaseURL();
deviceLog += "\n| UPC : " + details.getUpc();
}
}
deviceLog += "\n+==================================================";
logger.debug(deviceLog);
}
} /* DeviceInfo */
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(OpenWebNetBindingConstants.THING_TYPE_BUS_GATEWAY);
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
logger.info("Found device {}", device.getType());
DeviceInfo devInfo = new DeviceInfo(device);
if (!devInfo.manufacturer.matches("<unknown>")) {
logger.info(" |- {} ({})", devInfo.modelName, devInfo.manufacturer);
}
ThingUID thingId = generateThingUID(devInfo);
if (thingId != null) {
String host = devInfo.host;
if (host != null) {
String label = "BUS Gateway";
String fn = devInfo.friendlyName;
if (fn != null) {
if (!fn.isEmpty()) {
label = fn;
}
}
label = label + " (" + devInfo.modelName + ", " + devInfo.modelNumber + ", " + devInfo.host + ")";
Map<String, Object> properties = new HashMap<>(4);
properties.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_HOST, host);
properties.put(OpenWebNetBindingConstants.PROPERTY_FIRMWARE_VERSION, devInfo.modelNumber);
properties.put(OpenWebNetBindingConstants.PROPERTY_MODEL, devInfo.modelName);
properties.put(OpenWebNetBindingConstants.PROPERTY_SERIAL_NO, devInfo.serialNumber);
DiscoveryResult result = DiscoveryResultBuilder.create(thingId).withProperties(properties)
.withRepresentationProperty(OpenWebNetBindingConstants.PROPERTY_SERIAL_NO).withLabel(label)
.build();
UDN udn = devInfo.udn;
String udnStr;
if (udn != null) {
udnStr = udn.getIdentifierString();
} else {
udnStr = null;
}
logger.info("Created a DiscoveryResult for gateway '{}' (UDN={})", devInfo.friendlyName, udnStr);
return result;
} else {
logger.warn("Could not get host for device (UDN={})", devInfo.udn);
return null;
}
} else {
return null;
}
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
return generateThingUID(new DeviceInfo(device));
}
/**
* Returns a ThingUID for supported devices from already extracted DeviceInfo
*
* @param devInfo the device info
* @return a new ThingUID, or null if the device is not supported by the binding
*/
private @Nullable ThingUID generateThingUID(DeviceInfo devInfo) {
if (devInfo.isBTicino) {
UDN udn = devInfo.udn;
String idString = null;
if (udn != null) {
idString = udn.getIdentifierString();
if (idString != null) {
String[] spl = idString.split("-");
if (spl.length > 3) {
BusGatewayId gwId = BusGatewayId.fromValue(spl[1]);
if (gwId != null) {
logger.debug("'{}' is a supported gateway", gwId);
String mac = spl[3]; // extract MAC address
String normalizedMac = mac.toLowerCase().replaceAll("[^a-f0-9]", "");
if (!normalizedMac.isEmpty()) {
return new ThingUID(OpenWebNetBindingConstants.THING_TYPE_BUS_GATEWAY,
gwId.getThingId() + "_" + normalizedMac);
}
}
}
}
}
logger.info("Found BTicino device: not a OpenWebNet gateway or is not supported (UDN={})", idString);
}
return null;
}
}

View File

@@ -0,0 +1,193 @@
/**
* 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.openwebnet.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.binding.openwebnet.handler.OpenWebNetBridgeHandler;
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.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openwebnet4j.OpenDeviceType;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetDeviceDiscoveryService} is responsible for discovering OpenWebNet devices connected to a
* bridge/gateway
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetDeviceDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetDeviceDiscoveryService.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.DEVICE_SUPPORTED_THING_TYPES;
private static final int SEARCH_TIME_SEC = 60;
private @NonNullByDefault({}) OpenWebNetBridgeHandler bridgeHandler;
private @NonNullByDefault({}) ThingUID bridgeUID;
public OpenWebNetDeviceDiscoveryService() {
super(SUPPORTED_THING_TYPES, SEARCH_TIME_SEC);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return OpenWebNetDeviceDiscoveryService.SUPPORTED_THING_TYPES;
}
@Override
protected void startScan() {
logger.info("------ SEARCHING for DEVICES on bridge '{}' ({}) ...", bridgeHandler.getThing().getLabel(),
bridgeUID);
bridgeHandler.searchDevices();
}
@Override
protected void stopScan() {
logger.debug("------ stopScan() on bridge '{}'", bridgeUID);
bridgeHandler.scanStopped();
}
@Override
public void abortScan() {
logger.debug("------ abortScan() on bridge '{}'", bridgeUID);
bridgeHandler.scanStopped();
}
/**
* Create and notify to Inbox a new DiscoveryResult based on WHERE, OpenDeviceType and BaseOpenMessage
*
* @param where the discovered device's address (WHERE)
* @param deviceType {@link OpenDeviceType} of the discovered device
* @param message the OWN message received that identified the device (optional)
*/
public void newDiscoveryResult(Where where, OpenDeviceType deviceType, @Nullable BaseOpenMessage baseMsg) {
logger.info("newDiscoveryResult() WHERE={}, deviceType={}", where, deviceType);
ThingTypeUID thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_GENERIC_DEVICE; // generic device
String thingLabel = OpenWebNetBindingConstants.THING_LABEL_GENERIC_DEVICE;
Who deviceWho = Who.UNKNOWN;
switch (deviceType) {
case ZIGBEE_ON_OFF_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_ON_OFF_SWITCH;
deviceWho = Who.LIGHTING;
break;
case ZIGBEE_DIMMER_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_DIMMER;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_DIMMER;
deviceWho = Who.LIGHTING;
break;
case SCS_ON_OFF_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_ON_OFF_SWITCH;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_ON_OFF_SWITCH;
deviceWho = Who.LIGHTING;
break;
case SCS_DIMMER_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_DIMMER;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_DIMMER;
deviceWho = Who.LIGHTING;
break;
case SCS_SHUTTER_SWITCH:
case SCS_SHUTTER_CONTROL: {
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_AUTOMATION;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_AUTOMATION;
deviceWho = Who.AUTOMATION;
break;
}
case ZIGBEE_SHUTTER_SWITCH:
case ZIGBEE_SHUTTER_CONTROL: {
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_AUTOMATION;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_AUTOMATION;
deviceWho = Who.AUTOMATION;
break;
}
default:
logger.warn("Device type {} is not supported, default to GENERIC device (WHERE={})", deviceType, where);
if (where instanceof WhereZigBee) {
thingLabel = "ZigBee " + thingLabel;
}
if (baseMsg != null) {
deviceWho = baseMsg.getWho();
}
}
String tId = bridgeHandler.thingIdFromWhere(where);
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, tId);
DiscoveryResult discoveryResult = null;
String whereLabel = where.value();
if (where instanceof WhereZigBee && WhereZigBee.UNIT_02.equals(((WhereZigBee) where).getUnit())) {
logger.debug("UNIT=02 found (WHERE={})", where);
logger.debug("will remove previous result if exists");
thingRemoved(thingUID); // remove previously discovered thing
// re-create thingUID with new type
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS;
thingUID = new ThingUID(thingTypeUID, bridgeUID, tId);
whereLabel = whereLabel.replace("02#", "00#"); // replace unit '02' with all unit '00'
logger.debug("UNIT=02, switching type from {} to {}",
OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH,
OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS);
}
Map<String, Object> properties = new HashMap<>(2);
properties.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_WHERE, bridgeHandler.normalizeWhere(where));
properties.put(OpenWebNetBindingConstants.PROPERTY_OWNID, bridgeHandler.ownIdFromWhoWhere(where, deviceWho));
if (thingTypeUID == OpenWebNetBindingConstants.THING_TYPE_GENERIC_DEVICE) {
thingLabel = thingLabel + " (WHO=" + deviceWho + ", WHERE=" + whereLabel + ")";
} else {
thingLabel = thingLabel + " (WHERE=" + whereLabel + ")";
}
discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID).withProperties(properties)
.withRepresentationProperty(OpenWebNetBindingConstants.PROPERTY_OWNID).withBridge(bridgeUID)
.withLabel(thingLabel).build();
thingDiscovered(discoveryResult);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof OpenWebNetBridgeHandler) {
logger.debug("attaching {} to handler {} ", this, handler);
bridgeHandler = (OpenWebNetBridgeHandler) handler;
bridgeHandler.deviceDiscoveryService = this;
bridgeUID = bridgeHandler.getThing().getUID();
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="openwebnet" 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>OpenWebNet (BTicino/Legrand) Binding</name>
<description>The OpenWebNet Binding integrates the BTicino/Legrand 'MyHOME' connected home system using the OpenWebNet
protocol. It supports BUS (SCS) and ZigBee USB gateways and devices.</description>
<author>Massimo Valla</author>
</binding:binding>

View File

@@ -0,0 +1,4 @@
# Thing status descriptions
offline.conf-error-no-ip-address = Cannot connect to gateway. No host/IP has been provided in Bridge configuration.
offline.conf-error-no-serial-port = Cannot connect to gateway. No serial port has been provided in Bridge configuration.
offline.wrong-configuration = Invalid configuration. Check Thing configuration parameters.

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for BUS Automation (BTicino xxx/xxx/...) -->
<thing-type id="bus_automation">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Automation</label>
<description>A OpenWebNet BUS/SCS automation device to control roller shutters, blinds, etc. BTicino models:
xxx/yyyy/etc.</description>
<channels>
<channel id="shutter" typeId="shutter"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">514</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="shutterRun" type="text" required="true">
<label>Shutter Run</label>
<description>Time (in ms) to go from max position (e.g. CLOSED) to the other position (e.g. OPEN). Example: 12000
(=12sec).
Use AUTO (default) to calibrate the shutter automatically (UP->DOWN->Position%) the first time a Position
command (%)
is sent.</description>
<default>AUTO</default>
</parameter>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for Dimmer (BTicino yyyy/zzzz/...) -->
<thing-type id="bus_dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Dimmer</label>
<description>A OpenWebNet BUS/SCS dimmer for the dimmer control of 1 light. BTicino models: yyyy/zzzz/etc.</description>
<channels>
<channel id="brightness" typeId="brightness"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-yyyy/zzzz/etc.</property>
<property name="ownDeviceType">258</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet BUS gateway -->
<bridge-type id="bus_gateway">
<label>BUS Gateway</label>
<description>This thing allows to connect to a IP BUS/SCS gateway that supports the OpenWebNet protocol (models: F454,
MyHOMEServer1, F455, MH200N, F453, etc.)</description>
<properties>
<property name="vendor">BTicino</property>
<property name="model">Unknown</property>
<property name="firmwareVersion">Unknown</property>
</properties>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="host" type="text" required="true">
<context>network-address</context>
<label>Host</label>
<description>OpenWebNet gateway IP address / hostname (example: 192.168.1.35)</description>
</parameter>
<parameter name="port" type="integer" min="1" max="65535">
<label>Port</label>
<description>OpenWebNet gateway port (default: 20000)</description>
<default>20000</default>
</parameter>
<parameter name="passwd" type="text">
<context>password</context>
<label>Password</label>
<description>OpenWebNet gateway password (default: 12345)</description>
<default>12345</default>
</parameter>
<parameter name="discoveryByActivation" type="boolean">
<label>Discovery By Activation</label>
<description>Discover BUS devices when they are activated (also when a device scan is not active) (default: false)</description>
<default>false</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for BUS On Off Switch (BTicino xxx/xxx/...) -->
<thing-type id="bus_on_off_switch">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Switch</label>
<description>A OpenWebNet BUS/SCS switch for the control of 1 light/load. BTicino models: xxx/yyyy/etc.</description>
<channels>
<channel id="switch" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">261</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet Generic Device -->
<thing-type id="device">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>Generic Device</label>
<description>An OpenWebNet Generic Device.</description>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">Unknown</property>
<property name="ownDeviceType">0</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>It identifies one OpenWebNet device</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for Automation (BTicino xxx/xxx/...) -->
<thing-type id="zb_automation">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Automation</label>
<description>A OpenWebNet ZigBee automation device to control roller shutters, blinds, etc. BTicino models:
xxx/yyyy/etc.</description>
<channels>
<channel id="shutter" typeId="shutter"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">512</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="shutterRun" type="text">
<label>Shutter Run</label>
<description>Time (in ms) to go from max position (e.g. CLOSED) to the other position (e.g. OPEN). Example: 12000
(=12sec).
Use AUTO (default) to calibrate the shutter automatically (UP->DOWN->Position%) the first time a Position
command (%)
is sent.</description>
<required>true</required>
<default>AUTO</default>
</parameter>
<parameter name="where" type="text">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for ZigBee Dimmer (BTicino 4585/4594/...) -->
<thing-type id="zb_dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Dimmer</label>
<description>A OpenWebNet ZigBee dimmer for the dimmer control of 1 light. BTicino models: 4585/4594/etc.</description>
<channels>
<channel id="brightness" typeId="brightness"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4585/4594/etc.</property>
<property name="ownDeviceType">258</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet ZigBee USB Gateway -->
<bridge-type id="zb_gateway">
<label>ZigBee USB Gateway</label>
<description>This USB gateway (BTicino/Legrand models: BTI-3578/088328) connects to a BTicino/Legrand ZigBee network
and uses the OpenWebNet protocol. For more information see: https://catalogo.bticino.it/BTI-3578-IT and
https://www.legrand.com/ecatalogue/088328-openweb-net-zigbee-gateway-radio-interface.html</description>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-3578/088328</property>
<property name="firmwareVersion">Unknown</property>
</properties>
<representation-property>zigbeeid</representation-property>
<config-description>
<parameter name="serialPort" type="text" required="true">
<context>serial-port</context>
<label>Serial Port</label>
<description>Serial port where the OpenWebNet ZigBee USB Gateway is connected. Example: COM3 (Win), /dev/ttyUSB0
(Linux), etc.</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for OnOff Switch (BTicino 4591/3584/...) -->
<thing-type id="zb_on_off_switch">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Switch</label>
<description>A OpenWebNet ZigBee switch (actuator) for the control of 1 load/light. BTicino models: 4591/3684/etc.</description>
<channels>
<channel id="switch_01" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4591/3684/etc.</property>
<property name="ownDeviceType">256</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for ZigBee OnOff Switch with 2 units (BTicino 4592) -->
<thing-type id="zb_on_off_switch2u">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee 2-units Switch</label>
<description>A OpenWebNet ZigBee 2-units switch (actuator) for the control of 2 loads/lights. BTicino model: 4592</description>
<channels>
<channel id="switch_01" typeId="switch"/>
<channel id="switch_02" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4592</property>
<property name="ownDeviceType">256</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Switch Channel -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Switch the power ON and OFF</description>
<category>Light</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Brightness Channel -->
<channel-type id="brightness">
<item-type>Dimmer</item-type>
<label>Brightness</label>
<description>Control the brightness and switch the light ON and OFF</description>
<category>DimmableLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Shutter Channel -->
<channel-type id="shutter">
<item-type>Rollershutter</item-type>
<label>Roller shutter</label>
<description>Control the roller shutter position</description>
<category>Blinds</category>
<tags>
<tag>Blinds</tag>
</tags>
</channel-type>
</thing:thing-descriptions>