[openwebnet] Improvements for ZigBee USB and Dimmers (#9662)

* [openwebnet] Log messages cleanup

Signed-off-by: Massimo Valla <mvcode00@gmail.com>

* [openwebnet] Fixed normalizeWhere
* added builder for Bus Where addresses
* added messages to OwnIdTest
* depend on own4j 0.3.3-SNAPSHOT

Signed-off-by: Massimo Valla <mvcode00@gmail.com>

* [openwebnet] Discovery & reconnect for ZigBee USB gateways
* added mgmt of disconnect/reconnect of ZigBee USB Gateway from serial (#9170)
* added ZigBee USB Gateway auto discovery (#9171)

Signed-off-by: Massimo Valla <mvcode00@gmail.com>

* [openwebnet] fixed up/down automation for old fw (#9651)
* now using openwebnet4j 0.3.3 (nrjavaserial 5.2.1)

Signed-off-by: Massimo Valla <mvcode00@gmail.com>

* [openwebnet] Fixes lowering dimmers from 20 to 10% does not change device level #9317
- bump openwebnet4j to 0.3.4

Signed-off-by: Massimo Valla <mvcode00@gmail.com>

* [openwebnet] changes after PR review

Signed-off-by: Massimo Valla <mvcode00@gmail.com>
This commit is contained in:
M Valla 2021-01-08 23:00:55 +01:00 committed by GitHub
parent d87c94d603
commit d7d4fda2b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 397 additions and 90 deletions

View File

@ -5,7 +5,7 @@ This binding integrates BTicino / Legrand MyHOME&reg; BUS and ZigBee wireless (M
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
- auto discovery of BUS/SCS IP and ZigBee USB gateways; auto discovery of devices
- commands from openHAB and feedback (events) from BUS/SCS and wireless network
![F454 Gateway](doc/F454_gateway.png)
@ -29,7 +29,7 @@ These gateways have been tested with the binding:
- **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.
**NOTE** The new BTicino Living Now&reg; and Livinglight Smart&reg; wireless systems are not supported by this binding as they do not use the OpenWebNet protocol.
The following Things and OpenWebNet `WHOs` are supported:
@ -58,19 +58,18 @@ Gateway and Things discovery is supported by this binding.
- 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
- Once the gateway is online, a second Inbox Scan 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
BUS 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.
BUS 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_, 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 it 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 a discovery is started
- ***IMPORTANT NOTE:*** As for other openHAB 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:
@ -79,7 +78,7 @@ If a device cannot be discovered automatically it's always possible to add it ma
```
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!
- Once the ZigBee USB Gateway is added and online, a second Inbox Scan will discover devices connected to it. Because of the ZigBee radio network, device 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
@ -108,12 +107,14 @@ Configuration parameters are:
- `serialPort` : the serial port where the ZigBee USB Gateway is connected (`String`, *mandatory*)
- Examples: `/dev/ttyUSB0` (Linux/RaPi), `COM3` (Windows)
Alternatively the ZigBee USB Gateway thing can be configured using the `.things` file, see `openwebnet.things` example [below](#full-example).
### Configuring Devices
Devices can be discovered automatically after a gateway has been configured and connected.
For any manually added devices, you must configure:
Devices can be discovered automatically using an Inbox Scan after a gateway has been configured and connected.
For any manually added device, you must configure:
- the associated gateway (`Bridge Selection` menu)
- the associated gateway (`Parent Bridge` menu)
- the `where` config parameter (`OpenWebNet Device Address`):
- example for BUS/SCS device with WHERE address Point to Point `A=2 PL=4` --> `where="24"`
- example for BUS/SCS device with WHERE address Point to Point `A=03 PL=11` on local bus --> `where="0311#4#01"`
@ -160,7 +161,7 @@ Bridge openwebnet:bus_gateway:mybridge "MyHOMEServer1" [ host="192.168.1.35", pa
ZigBee USB Gateway and things configuration - for radio devices:
```xtend
Bridge openwebnet:zb_gateway:myZBgateway [serialPort="COM3"] {
Bridge openwebnet:zb_gateway:myZBgateway [ serialPort="COM3" ] {
zb_dimmer myZB_dimmer [ where="765432101#9"]
zb_on_off_switch myZB_switch [ where="765432201#9"]
zb_on_off_switch2u myZB_2U_switch [ where="765432300#9"]
@ -169,7 +170,7 @@ Bridge openwebnet:zb_gateway:myZBgateway [serialPort="COM3"] {
### openwebnet.items:
Items (Light, Dimmer, etc.) will be discovered by Google Assistant/Alexa/HomeKit if their tags are configured like in the example:
Example items linked to BUS devices:
```xtend
Switch iLR_switch "Light" <light> (gLivingRoom) [ "Lighting" ] { channel="openwebnet:bus_on_off_switch:mybridge:LR_switch:switch" }
@ -177,7 +178,7 @@ Dimmer iLR_dimmer "Dimmer [%.0f %%]" <Dimmabl
Rollershutter iLR_shutter "Shutter [%.0f %%]" <rollershutter> (gShutters, gLivingRoom) [ "Blinds" ] { channel="openwebnet:bus_automation:mybridge:LR_shutter:shutter" }
```
Example items linked to ZigBee devices:
Example items linked to OpenWebNet ZigBee devices:
```xtend
Dimmer iDimmer "Dimmer [%.0f %%]" <DimmableLight> (gKitchen) [ "Lighting" ] { channel="openwebnet:zb_dimmer:myZBgateway:myZB_dimmer:brightness" }
@ -202,7 +203,7 @@ sitemap openwebnet label="OpenWebNet Binding Example Sitemap"
## 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/)
- The OpenWebNet 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

View File

@ -23,7 +23,7 @@
<dependency>
<groupId>com.github.openwebnet4j</groupId>
<artifactId>openwebnet4j</artifactId>
<version>0.3.2-1</version>
<version>0.3.4</version>
<scope>compile</scope>
</dependency>

View File

@ -103,6 +103,8 @@ public class OpenWebNetBindingConstants {
// BUS gw config properties
public static final String CONFIG_PROPERTY_HOST = "host";
public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
// properties
public static final String PROPERTY_OWNID = "ownId";
public static final String PROPERTY_ZIGBEEID = "zigbeeid";

View File

@ -41,6 +41,7 @@ import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -129,6 +130,11 @@ public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
positionEstimation = POSITION_UNKNOWN;
}
@Override
protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
return new WhereLightAutom(wStr);
}
@Override
protected void requestChannelState(ChannelUID channel) {
logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
@ -280,8 +286,9 @@ public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
@Override
protected void handleMessage(BaseOpenMessage msg) {
logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
updateAutomationState((Automation) msg);
// REMINDER: update state, then update thing status in the super method, to avoid delays
// REMINDER: update automation state, and only after update thing status using the super method, to avoid delays
super.handleMessage(msg);
}

View File

@ -111,7 +111,7 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Trying to connect gateway...");
logger.debug("Trying to connect gateway {}... ", gw);
try {
gw.connect();
scheduler.schedule(() -> {
@ -233,22 +233,22 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
if (gw != null) {
if (!gw.isDiscovering()) {
if (!gw.isConnected()) {
logger.debug("------$$ Gateway is NOT connected, cannot search for devices");
logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
return;
}
logger.info("------$$ STARTED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
try {
gw.discoverDevices();
} catch (OWNException e) {
logger.warn("------$$ OWNException while discovering devices on gateway {}: {}",
this.getThing().getUID(), e.getMessage());
logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
e.getMessage());
}
} else {
logger.debug("------$$ Searching devices on gateway {} already activated", this.getThing().getUID());
logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
return;
}
} else {
logger.debug("------$$ Cannot search devices: no gateway associated to this handler");
logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
}
}
@ -268,7 +268,7 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
@Override
public void onDiscoveryCompleted() {
logger.info("------$$ FINISHED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
}
/**
@ -418,10 +418,10 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
return;
}
if (gw instanceof USBGateway) {
logger.info("------------------- CONNECTED to ZigBee USB gateway - USB port: {}",
logger.info("---- CONNECTED to ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
((USBGateway) gw).getSerialPortName());
} else {
logger.info("------------------- CONNECTED to BUS gateway '{}' ({}:{})", thing.getUID(),
logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
// update serial number property (with MAC address)
if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
@ -437,7 +437,7 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
}
if (propertiesChanged) {
updateProperties(properties);
logger.info("properties updated for '{}'", thing.getUID());
logger.info("properties updated for bridge '{}'", thing.getUID());
}
updateStatus(ThingStatus.ONLINE);
}
@ -450,7 +450,7 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
} else {
errMsg = error.getMessage();
}
logger.info("------------------- ON CONNECTION ERROR: {}", errMsg);
logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
isGatewayConnected = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errMsg);
tryReconnectGateway();
@ -472,7 +472,7 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
} else {
errMsg = e.getMessage();
}
logger.info("------------------- DISCONNECTED from gateway. OWNException={}", errMsg);
logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Disconnected from gateway (onDisconnected - " + errMsg + ")");
tryReconnectGateway();
@ -483,29 +483,29 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
if (gw != null) {
if (!reconnecting) {
reconnecting = true;
logger.info("------------------- Starting RECONNECT cycle to gateway");
logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
try {
gw.reconnect();
} catch (OWNAuthException e) {
logger.info("------------------- AUTH error from gateway. Stopping reconnect");
logger.info("---- AUTH error from gateway. Stopping re-connect");
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");
logger.debug("---- reconnecting=true, do nothing");
}
} else {
logger.debug("------------------- cannot start RECONNECT, gateway is null");
logger.warn("---- cannot start RECONNECT, gateway is null");
}
}
@Override
public void onReconnected() {
reconnecting = false;
logger.info("------------------- RE-CONNECTED to gateway!");
OpenGateway gw = gateway;
logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
if (gw != null) {
updateStatus(ThingStatus.ONLINE);
if (gw.getFirmwareVersion() != null) {
@ -568,10 +568,10 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
if (where instanceof WhereZigBee) {
str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
} else {
if (str.indexOf("#4#") == 0) { // no changes needed for local bus: APL#4#bus
if (str.indexOf('#') == 0) { // Thermo zone via central unit: #0 or #Z (Z=[1-99]) --> Z
if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
str = str.substring(1);
} else if (str.indexOf('#') > 0) { // Thermo zone and actuator N: Z#N (Z=[1-99], N=[1-9]) --> Z
} else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
str = str.substring(0, str.indexOf('#'));
}
}

View File

@ -21,6 +21,8 @@ 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.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -65,10 +67,15 @@ public class OpenWebNetGenericHandler extends OpenWebNetThingHandler {
return "G";
}
@Override
protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
return new WhereLightAutom(wStr);
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
super.handleMessage(msg);
// do nothing
logger.warn("handleMessage(): Nothing to do!");
}
} // class
}

View File

@ -35,6 +35,7 @@ import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.What;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
@ -53,15 +54,19 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
private static final int BRIGHTNESS_CHANGE_DELAY_MSEC = 1500; // delay before sending another brightness status
// request
private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900; // we must wait some time to be sure dimmer has
// reached final level before requesting its
// status
// interval to interpret ON as response to requestStatus
private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
// time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
// level
private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
private static final int UNKNOWN_STATE = 1000;
private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
@ -83,6 +88,7 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
Where w = deviceWhere;
if (w != null) {
try {
lastStatusRequestSentTS = System.currentTimeMillis();
Response res = send(Lighting.requestStatus(toWhere(channelId)));
if (res != null && res.isSuccess()) {
// set thing online if not already
@ -214,8 +220,8 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
@Override
protected void handleMessage(BaseOpenMessage msg) {
logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
super.handleMessage(msg);
logger.debug("handleMessage() for thing: {}", thing.getUID());
ThingTypeUID thingType = thing.getThingTypeUID();
if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
updateBrightness((Lighting) msg);
@ -230,47 +236,54 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
* @param msg the Lighting message received
*/
private synchronized void updateBrightness(Lighting msg) {
long now = System.currentTimeMillis();
logger.debug(" $BRI updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
brightnessBeforeOff);
long now = System.currentTimeMillis();
long delta = now - lastBrightnessChangeSentTS;
boolean belowThresh = delta < BRIGHTNESS_CHANGE_DELAY_MSEC;
boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
logger.debug(" $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
if (belowThresh) {
// we just sent a command from OH, so we can ignore this message from network
logger.debug(" $BRI a request was sent {} < {} ms --> no action needed", delta,
BRIGHTNESS_CHANGE_DELAY_MSEC);
logger.debug(" $BRI a command was sent {} < {} ms --> no action needed", delta,
BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
} else {
if (msg.isOn()) {
logger.debug(" $BRI \"ON\" notification from network, scheduling requestStatus...");
// we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached final level
scheduler.schedule(() -> {
requestStatus(CHANNEL_BRIGHTNESS);
}, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
} else {
logger.debug(" $BRI update from network");
if (msg.getWhat() != null) {
updateBrightnessState(msg);
} else { // dimension notification
if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
int newBrightness;
try {
newBrightness = msg.parseDimmerLevel100();
} catch (FrameException fe) {
logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
return;
}
logger.debug(" $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
if (newBrightness == 0) {
brightnessBeforeOff = brightness;
}
brightness = newBrightness;
} else {
logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg,
getThing().getUID());
// if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
long deltaStatusReq = now - lastStatusRequestSentTS;
if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
logger.debug(" $BRI 'ON' is new notification from network, scheduling requestStatus...");
// we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
scheduler.schedule(() -> {
requestStatus(CHANNEL_BRIGHTNESS);
}, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
return;
} else {
// otherwise we interpret this ON event as the requestStatus response event with level=1
// so we proceed to call updateBrightnessState()
logger.debug(" $BRI 'ON' is the requestStatus response level");
}
}
logger.debug(" $BRI update from network");
if (msg.getWhat() != null) {
updateBrightnessState(msg);
} else { // dimension notification
if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
int newBrightness;
try {
newBrightness = msg.parseDimmerLevel100();
} catch (FrameException fe) {
logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
return;
}
logger.debug(" $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
if (newBrightness == 0) {
brightnessBeforeOff = brightness;
}
brightness = newBrightness;
} else {
logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
return;
}
}
}
@ -284,8 +297,12 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
* @param msg the Lighting message received
*/
private void updateBrightnessState(Lighting msg) {
if (msg.getWhat() != null) {
int newBrightnessWhat = msg.getWhat().value();
What w = msg.getWhat();
if (w != null) {
if (Lighting.WHAT.ON.equals(w)) {
w = Lighting.WHAT.DIMMER_LEVEL_2; // levels start at 2
}
int newBrightnessWhat = w.value();
int brightnessWhat = UNKNOWN_STATE;
if (brightness != UNKNOWN_STATE) {
brightnessWhat = Lighting.percentToWhat(brightness).value();
@ -344,6 +361,11 @@ public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
}
}
@Override
protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
return new WhereLightAutom(wStr);
}
/**
* Returns a WHERE address string based on channelId string
*

View File

@ -34,7 +34,6 @@ 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;
@ -77,7 +76,7 @@ public abstract class OpenWebNetThingHandler extends BaseThingHandler {
Where w;
try {
if (brH.isBusGateway()) {
w = new WhereLightAutom(deviceWhereStr);
w = buildBusWhere(deviceWhereStr);
} else {
w = new WhereZigBee(deviceWhereStr);
}
@ -192,6 +191,14 @@ public abstract class OpenWebNetThingHandler extends BaseThingHandler {
*/
protected abstract void requestChannelState(ChannelUID channel);
/**
* Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass
* (the method is used only if the Thing is associated to a BUS gateway).
*
* @param wStr the WHERE string
*/
protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
@Override
public void dispose() {
OpenWebNetBridgeHandler bh = bridgeHandler;

View File

@ -60,7 +60,7 @@ public class OpenWebNetDeviceDiscoveryService extends AbstractDiscoveryService
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return OpenWebNetDeviceDiscoveryService.SUPPORTED_THING_TYPES;
return SUPPORTED_THING_TYPES;
}
@Override

View File

@ -0,0 +1,237 @@
/**
* Copyright (c) 2010-2021 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.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
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.io.transport.serial.SerialPortIdentifier;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.ThingUID;
import org.openwebnet4j.GatewayListener;
import org.openwebnet4j.OpenDeviceType;
import org.openwebnet4j.USBGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.OpenMessage;
import org.openwebnet4j.message.Where;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link UsbGatewayDiscoveryService} extends {@link AbstractDiscoveryService} to detect ZigBee USB gateways
* connected via serial port. The service will iterate over the available serial ports and open each one to test if a
* OpenWebNet ZigBee USB gateway is connected. On successful connection, a new DiscoveryResult is created.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.openwebnet")
public class UsbGatewayDiscoveryService extends AbstractDiscoveryService implements GatewayListener {
private final Logger logger = LoggerFactory.getLogger(UsbGatewayDiscoveryService.class);
private static final int DISCOVERY_TIMEOUT_SECONDS = 30;
private static final int PORT_CHECK_TIMEOUT_MSEC = 1500;
private CountDownLatch portCheckLatch = new CountDownLatch(1);
private @Nullable ScheduledFuture<?> connectTimeout;
private final SerialPortManager serialPortManager;
private @Nullable USBGateway zbGateway;
private String currentScannedPortName = "";
/**
* Keeps a boolean during time discovery process in progress.
*/
private boolean scanning;
/**
* Constructs a new UsbGatewayDiscoveryService with the specified ZigBee USB Bridge ThingTypeUID
*/
@Activate
public UsbGatewayDiscoveryService(final @Reference SerialPortManager spm) {
super(Collections.singleton(OpenWebNetBindingConstants.THING_TYPE_ZB_GATEWAY), DISCOVERY_TIMEOUT_SECONDS,
false);
// Obtain the serial port manager service using an OSGi reference
serialPortManager = spm;
}
/**
* Starts a new discovery scan. All available Serial Ports are scanned.
*/
@Override
protected void startScan() {
logger.debug("Started OpenWebNet ZigBee USB Gateway discovery scan");
removeOlderResults(getTimestampOfLastScan());
scanning = true;
Stream<SerialPortIdentifier> portEnum = serialPortManager.getIdentifiers();
// Check each available serial port
try {
for (SerialPortIdentifier portIdentifier : portEnum.toArray(SerialPortIdentifier[]::new)) {
if (scanning) {
currentScannedPortName = portIdentifier.getName();
logger.debug("[{}] == checking serial port", currentScannedPortName);
if (portIdentifier.isCurrentlyOwned()) {
logger.debug("[{}] serial port is owned by: {}", currentScannedPortName,
portIdentifier.getCurrentOwner());
} else {
logger.debug("[{}] trying to connect to a ZigBee USB Gateway...", currentScannedPortName);
USBGateway gw = new USBGateway(currentScannedPortName);
zbGateway = gw;
gw.subscribe(this);
portCheckLatch = new CountDownLatch(1);
connectTimeout = scheduler.schedule(() -> {
logger.debug("[{}] timeout expired", currentScannedPortName);
endGwConnection();
portCheckLatch.countDown();
}, PORT_CHECK_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
try {
gw.connect();
portCheckLatch.await();
} catch (OWNException e) {
logger.debug("[{}] OWNException while trying to connect to a ZigBee USB Gateway: {}",
currentScannedPortName, e.getMessage());
cancelConnectTimeout();
endGwConnection();
}
}
logger.debug("[{}] == finished checking port", currentScannedPortName);
}
}
logger.debug("Finished checking all serial ports");
} catch (InterruptedException ie) {
logger.warn("[{}] interrupted: {}", currentScannedPortName, ie.getMessage());
endGwConnection();
logger.debug("Interrupted while checking serial ports");
}
}
@Override
protected synchronized void stopScan() {
scanning = false;
cancelConnectTimeout();
endGwConnection();
portCheckLatch.countDown();
super.stopScan();
logger.debug("Stopped OpenWebNet ZigBee USB Gateway discovery scan");
}
/**
* Ends connection to the gateway
*/
private void endGwConnection() {
USBGateway gw = zbGateway;
if (gw != null) {
gw.closeConnection();
zbGateway = null;
logger.debug("[{}] connection to gateway closed", currentScannedPortName);
}
}
private void cancelConnectTimeout() {
ScheduledFuture<?> ct = connectTimeout;
if (ct != null && !ct.isDone()) {
ct.cancel(false);
ct = null;
logger.debug("[{}] timeout cancelled", currentScannedPortName);
}
}
/**
* Create and notify a new ZigBee USB Gateway thing has been discovered
*/
private void bridgeDiscovered() {
USBGateway gw = zbGateway;
if (gw != null) {
int gatewayZigBeeId = gw.getZigBeeIdAsDecimal();
ThingUID gatewayUID = new ThingUID(OpenWebNetBindingConstants.THING_TYPE_ZB_GATEWAY,
Integer.toString(gatewayZigBeeId));
Map<String, Object> gwProperties = new HashMap<>(3);
gwProperties.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_SERIAL_PORT, gw.getSerialPortName());
gwProperties.put(OpenWebNetBindingConstants.PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
gwProperties.put(OpenWebNetBindingConstants.PROPERTY_ZIGBEEID, String.valueOf(gatewayZigBeeId));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(gatewayUID).withProperties(gwProperties)
.withLabel(OpenWebNetBindingConstants.THING_LABEL_ZB_GATEWAY + " (" + gw.getSerialPortName() + ")")
.withRepresentationProperty(OpenWebNetBindingConstants.PROPERTY_ZIGBEEID).build();
logger.debug("--- ZigBee USB Gateway thing discovered: {} fw: {}", discoveryResult.getLabel(),
gw.getFirmwareVersion());
thingDiscovered(discoveryResult);
}
}
@Override
public void onConnected() {
logger.debug("[{}] found ZigBee USB Gateway", currentScannedPortName);
cancelConnectTimeout();
bridgeDiscovered();
endGwConnection();
portCheckLatch.countDown();
}
@Override
public void onConnectionError(@Nullable OWNException error) {
OWNException e = error;
String msg = (e != null ? e.getMessage() : "");
logger.debug("[{}] onConnectionError(): {}", currentScannedPortName, msg);
}
@Override
public void onConnectionClosed() {
logger.debug("UsbGatewayDiscoveryService received onConnectionClosed()");
}
@Override
public void onDisconnected(@Nullable OWNException error) {
logger.debug("UsbGatewayDiscoveryService received onDisconnected()");
}
@Override
public void onReconnected() {
logger.debug("UsbGatewayDiscoveryService received onReconnected()");
}
@Override
public void onEventMessage(@Nullable OpenMessage msg) {
logger.debug("UsbGatewayDiscoveryService received onEventMessage(): {}", msg);
}
@Override
public void onNewDevice(@Nullable Where where, @Nullable OpenDeviceType deviceType,
@Nullable BaseOpenMessage message) {
logger.debug("UsbGatewayDiscoveryService received onNewDevice()");
}
@Override
public void onDiscoveryCompleted() {
logger.debug("UsbGatewayDiscoveryService received onDiscoveryCompleted()");
}
}

View File

@ -16,13 +16,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.openhab.core.thing.Bridge;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test class for {@link OpenWebNetBridgeHandler#ownID} and ThingID calculation using {@link OpenWebNetBridgeHandler}
@ -33,6 +37,8 @@ import org.openwebnet4j.message.Who;
@NonNullByDefault
public class OwnIdTest {
private final Logger logger = LoggerFactory.getLogger(OwnIdTest.class);
// @formatter:off
/**
*
@ -57,22 +63,37 @@ public class OwnIdTest {
// @formatter:on
public enum TEST {
zb_switch(new WhereZigBee("789309801#9"), Who.fromValue(1), "789309800h9", "1.789309800h9", "789309800h9"),
zb_switch_2u_1(new WhereZigBee("789301201#9"), Who.fromValue(1), "789301200h9", "1.789301200h9", "789301200h9"),
zb_switch_2u_2(new WhereZigBee("789301202#9"), Who.fromValue(1), "789301200h9", "1.789301200h9", "789301200h9"),
bus_switch(new WhereLightAutom("51"), Who.fromValue(1), "51", "1.51", "51"),
bus_localbus(new WhereLightAutom("25#4#01"), Who.fromValue(1), "25h4h01", "1.25h4h01", "25h4h01");
// bus_thermo("#1", "4", "1", "4.1", "1"),
// @formatter:off
zb_switch(new WhereZigBee("789309801#9"), Who.fromValue(1), "*1*1*789309801#9##", "789309800h9", "1.789309800h9", "789309800h9"),
zb_switch_2u_1(new WhereZigBee("789301201#9"), Who.fromValue(1), "*1*1*789301201#9##", "789301200h9", "1.789301200h9", "789301200h9"),
zb_switch_2u_2(new WhereZigBee("789301202#9"), Who.fromValue(1), "*1*1*789301202#9##", "789301200h9", "1.789301200h9", "789301200h9"),
bus_switch(new WhereLightAutom("51"), Who.fromValue(1), "*1*1*51##", "51", "1.51", "51"),
bus_localbus(new WhereLightAutom("25#4#01"), Who.fromValue(1), "*1*1*25#4#01##", "25h4h01", "1.25h4h01", "25h4h01");
//bus_thermo_zone(new WhereThermo("1"), Who.fromValue(4),"*#4*1*0*0020##" , "1", "4.1", "1"),
//bus_thermo_zone_act(new WhereThermo("2#1"), Who.fromValue(4),"*#4*2#1*20*0##" ,"2", "4.2", "2"),
//bus_thermo_via_cu(new WhereThermo("#1"), Who.fromValue(4),"*#4*#1*0*0020##" ,"1", "4.1", "1"),
// bus_tempSensor("500", "4", "500", "4.500", "500"),
// bus_energy("51", "18", "51", "18.51", "51");
// @formatter:on
private final Logger logger = LoggerFactory.getLogger(TEST.class);
public final Where where;
public final Who who;
public final @Nullable BaseOpenMessage msg;
public final String norm, ownId, thingId;
private TEST(Where where, Who who, String norm, String ownId, String thingId) {
private TEST(Where where, Who who, String msg, String norm, String ownId, String thingId) {
this.where = where;
this.who = who;
BaseOpenMessage bmsg = null;
try {
bmsg = (BaseOpenMessage) BaseOpenMessage.parse(msg);
} catch (FrameException e) {
logger.warn("something is wrong in the test table. ownIdFromMessage test will be skipped");
}
this.msg = bmsg;
this.norm = norm;
this.ownId = ownId;
this.thingId = thingId;
@ -83,13 +104,16 @@ public class OwnIdTest {
public void testOwnId() {
Bridge mockBridge = mock(Bridge.class);
OpenWebNetBridgeHandler brH = new OpenWebNetBridgeHandler(mockBridge);
BaseOpenMessage bmsg;
for (int i = 0; i < TEST.values().length; i++) {
TEST test = TEST.values()[i];
// System.out.println("testing where=" + test.where);
logger.info("testing where={}", test.where);
assertEquals(test.norm, brH.normalizeWhere(test.where));
assertEquals(test.ownId, brH.ownIdFromWhoWhere(test.who, test.where));
assertEquals(test.ownId, brH.ownIdFromMessage(Lighting.requestTurnOn(test.where.value())));
bmsg = test.msg;
if (bmsg != null) {
assertEquals(test.ownId, brH.ownIdFromMessage(bmsg));
}
assertEquals(test.thingId, brH.thingIdFromWhere(test.where));
}
}