]> git.basschouten.com Git - openhab-addons.git/commitdiff
[openwebnet] Improvements for ZigBee USB and Dimmers (#9662)
authorM Valla <12682715+mvalla@users.noreply.github.com>
Fri, 8 Jan 2021 22:00:55 +0000 (23:00 +0100)
committerGitHub <noreply@github.com>
Fri, 8 Jan 2021 22:00:55 +0000 (23:00 +0100)
* [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>
bundles/org.openhab.binding.openwebnet/README.md
bundles/org.openhab.binding.openwebnet/pom.xml
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/OpenWebNetBindingConstants.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetAutomationHandler.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetBridgeHandler.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetGenericHandler.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetLightingHandler.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThingHandler.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/handler/OwnIdTest.java

index add97ab310ca1fb4d6314a1340296f12e6d34d7b..e805668238866087490571acd9e8d24590af3e78 100644 (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
 
index 1ed200c10e209e21174aacea9b5cd199770ab036..2765abe37f1fdeab08acdef6b650fd5bbe23f90d 100644 (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>
 
index d52f7e4b08472ffbcf7e99e1fb1361d191b0e714..5eaf8dd2082a4c7942a98098e42d06da4769e0b4 100644 (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";
index 2bcc47e4b582d20ec29405f4f6a7e84a8722c949..48d25715fb85d0c194a53f4e4c9b70eb2b735acd 100644 (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);
     }
 
index 5d188f1b3cc08a014e423adf61e6caf47a6ea4e7..d906d590791638cc58535b97f68bc34db197f99d 100644 (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('#'));
                 }
             }
index d6766c904f66aece8ab8160515d2ee445474b473..d0ced83efea8b381a5a38ec1296cecce538069fa 100644 (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
+}
index 14ca7b2073216e6cad0f84c933310052c5a02efc..f6eda3fc086b02901c23a853aa41103f649d1a6c 100644 (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
      *
index a4b74949fc563469fd0d45a9aef1e0de4608942e..0d646df7ff6eb4e3f31b9576fd92a070f983c710 100644 (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;
index 7a726e4f24f487054174513474940e4f77d466f0..8dff4c1a4aa8f0b5edb327884f8add5eb965f5a0 100644 (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
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java
new file mode 100644 (file)
index 0000000..6bc1b8d
--- /dev/null
@@ -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()");
+    }
+}
index 842520c645b12345b034704081bc669ad0c9fac1..3a46344b2857dd45b9c6ec33aaf0672d0c483d5c 100644 (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));
         }
     }