]> git.basschouten.com Git - openhab-addons.git/commitdiff
[nikobus] discovery for push buttons, (#9134)
authorBoris Krivonog <boris.krivonog@inova.si>
Fri, 27 Nov 2020 01:02:49 +0000 (02:02 +0100)
committerGitHub <noreply@github.com>
Fri, 27 Nov 2020 01:02:49 +0000 (17:02 -0800)
* removed state update if value didn't change so expiry binding can actually - expire. If expire time was larger than refresh interval of the binding, expiration never happened ...

Signed-off-by: Boris Krivonog <boris.krivonog@inova.si>
bundles/org.openhab.binding.nikobus/README.md
bundles/org.openhab.binding.nikobus/doc/s2.png [new file with mode: 0644]
bundles/org.openhab.binding.nikobus/doc/s4.png [new file with mode: 0644]
bundles/org.openhab.binding.nikobus/doc/s8.png [new file with mode: 0644]
bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/discovery/NikobusDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusModuleHandler.java
bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/handler/NikobusPcLinkHandler.java
bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/utils/CRCUtil.java
bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/utils/Utils.java

index 48aa0065795c644daf6a6c063ca6aa788f9d189f..c23fe401f3245cef6e02f7f82f057e1d63344863 100644 (file)
@@ -33,10 +33,6 @@ The bridge enables communication with other Nikobus components:
 * `rollershutter-module` - Nikobus roller shutter module,
 * `push-button` - Nikobus physical push button.
 
-## Discovery
-
-The binding does not support any automatic discovery of Things.
-
 ## Bridge Configuration
 
 The binding can connect to the PC-Link via serial interface.
@@ -174,6 +170,84 @@ Thing push-button pb1 [ address = "28092A", impactedModules = "switch-module:s1:
 
 In addition to the status requests triggered by button presses, there is also a scheduled status update interval defined by the `refreshInterval` parameter and explained above.
 
+## Discovery
+
+Pressing a physical Nikobus push-button will generate a new inbox entry with an exception of buttons already discovered or setup.
+
+Nikobus push buttons have the following format in inbox:
+
+```
+Nikobus Push Button 14E7F4:3
+4BF9CA
+nikobus:push-button
+```
+
+where first line contains name of the discovered button and second one contains button's bus address.
+
+Each discovered button has a Nikobus address appended to its name, same as can be seen in Nikobus's PC application, `14E7F4:3` in above example.
+
+ * `14E7F4` - address of the Nikobus switch, as can be seen in Nikobus PC software and
+ * `3` - represents a button on Nikobus switch.
+
+### Button mappings
+
+##### 2 buttons switch
+
+![Nikobus Switch with 2 buttons](doc/s2.png)
+
+```
+ 1 = A
+ 2 = B
+ ```
+
+##### 4 buttons switch
+
+![Nikobus Switch with 4 buttons](doc/s4.png)
+
+maps as 
+
+```
+ 3  1  
+ 4  2
+```
+
+so
+
+```
+1 = C
+2 = D
+3 = A
+4 = B
+```
+
+##### 8 buttons switch
+
+![Nikobus Switch with 8 buttons](doc/s8.png)
+
+maps as
+
+```
+ 7  5  3  1  
+ 8  6  4  2
+```
+
+so
+
+```
+1 = 2C
+2 = 2D
+3 = 2A
+4 = 2B
+5 = 1C
+6 = 1D
+7 = 1A
+8 = 1B
+```
+
+Above example `14E7F4:3` would give:
+* for 4 buttons switch - push button A,
+* for 8 buttons switch - push button 2A.
+
 ## Full Example
 
 ### nikobus.things
diff --git a/bundles/org.openhab.binding.nikobus/doc/s2.png b/bundles/org.openhab.binding.nikobus/doc/s2.png
new file mode 100644 (file)
index 0000000..26f003a
Binary files /dev/null and b/bundles/org.openhab.binding.nikobus/doc/s2.png differ
diff --git a/bundles/org.openhab.binding.nikobus/doc/s4.png b/bundles/org.openhab.binding.nikobus/doc/s4.png
new file mode 100644 (file)
index 0000000..de0044e
Binary files /dev/null and b/bundles/org.openhab.binding.nikobus/doc/s4.png differ
diff --git a/bundles/org.openhab.binding.nikobus/doc/s8.png b/bundles/org.openhab.binding.nikobus/doc/s8.png
new file mode 100644 (file)
index 0000000..ad4eebb
Binary files /dev/null and b/bundles/org.openhab.binding.nikobus/doc/s8.png differ
diff --git a/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/discovery/NikobusDiscoveryService.java b/bundles/org.openhab.binding.nikobus/src/main/java/org/openhab/binding/nikobus/internal/discovery/NikobusDiscoveryService.java
new file mode 100644 (file)
index 0000000..9e08f79
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * 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.nikobus.internal.discovery;
+
+import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.nikobus.internal.handler.NikobusPcLinkHandler;
+import org.openhab.binding.nikobus.internal.utils.Utils;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link NikobusDiscoveryService} discovers push button things for Nikobus switches.
+ * Buttons are not discovered via scan but only when physical button is pressed and a new
+ * nikobus push button bus address is detected.
+ *
+ * @author Boris Krivonog - Initial contribution
+ */
+@NonNullByDefault
+public class NikobusDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+
+    private final Logger logger = LoggerFactory.getLogger(NikobusDiscoveryService.class);
+    private @Nullable NikobusPcLinkHandler bridgeHandler;
+
+    public NikobusDiscoveryService() throws IllegalArgumentException {
+        super(Collections.singleton(THING_TYPE_PUSH_BUTTON), 0);
+    }
+
+    @Override
+    protected void startScan() {
+    }
+
+    @Override
+    protected void stopBackgroundDiscovery() {
+        NikobusPcLinkHandler handler = bridgeHandler;
+        if (handler != null) {
+            handler.resetUnhandledCommandProcessor();
+        }
+    }
+
+    @Override
+    protected void startBackgroundDiscovery() {
+        NikobusPcLinkHandler handler = bridgeHandler;
+        if (handler != null) {
+            handler.setUnhandledCommandProcessor(this::process);
+        }
+    }
+
+    private void process(String command) {
+        if (command.length() <= 2 || !command.startsWith("#N")) {
+            logger.debug("Ignoring command() '{}'", command);
+        }
+
+        String address = command.substring(2);
+        logger.debug("Received address = '{}'", address);
+
+        NikobusPcLinkHandler handler = bridgeHandler;
+        if (handler != null) {
+            ThingUID thingUID = new ThingUID(THING_TYPE_PUSH_BUTTON, handler.getThing().getUID(), address);
+
+            Map<String, Object> properties = new HashMap<>();
+            properties.put(CONFIG_ADDRESS, address);
+
+            String humanReadableNikobusAddress = Utils.convertToHumanReadableNikobusAddress(address).toUpperCase();
+            logger.debug("Detected Nikobus Push Button: '{}'", humanReadableNikobusAddress);
+            thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_PUSH_BUTTON)
+                    .withLabel("Nikobus Push Button " + humanReadableNikobusAddress).withProperties(properties)
+                    .withRepresentationProperty(CONFIG_ADDRESS).withBridge(handler.getThing().getUID()).build());
+        }
+    }
+
+    @Override
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof NikobusPcLinkHandler) {
+            bridgeHandler = (NikobusPcLinkHandler) handler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return bridgeHandler;
+    }
+
+    @Override
+    public void activate() {
+        super.activate(null);
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+    }
+}
index bb7e2eb04f11d0fb1b9d70962857ceb6f42e4518..85cbb2561b3e1258c72636d3cd60ce7287786bd5 100644 (file)
@@ -176,14 +176,16 @@ abstract class NikobusModuleHandler extends NikobusBaseThingHandler {
 
         logger.debug("setting channel '{}' to {}", channelId, value);
 
+        Integer previousValue;
         synchronized (cachedStates) {
-            cachedStates.put(channelId, value);
+            previousValue = cachedStates.put(channelId, value);
         }
 
-        updateState(channelId, stateFromValue(value));
+        if (previousValue == null || previousValue.intValue() != value) {
+            updateState(channelId, stateFromValue(value));
+        }
     }
 
-    @SuppressWarnings({ "unused", "null" })
     private void processWrite(ChannelUID channelUID, Command command) {
         StringBuilder commandPayload = new StringBuilder();
         SwitchModuleGroup group = SwitchModuleGroup.mapFromChannel(channelUID);
index e05928c2434a63988decb17aac149709211cad44..bcb2c74a3e60c92cbd3d3626e97d9f511956bab1 100644 (file)
@@ -16,6 +16,7 @@ import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CONFI
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -24,12 +25,14 @@ import java.util.Map;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.nikobus.internal.NikobusBindingConstants;
+import org.openhab.binding.nikobus.internal.discovery.NikobusDiscoveryService;
 import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
 import org.openhab.binding.nikobus.internal.protocol.NikobusConnection;
 import org.openhab.binding.nikobus.internal.utils.Utils;
@@ -41,6 +44,7 @@ import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,6 +67,7 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
     private @Nullable ScheduledFuture<?> scheduledRefreshFuture;
     private @Nullable ScheduledFuture<?> scheduledSendCommandWatchdogFuture;
     private @Nullable String ack;
+    private @Nullable Consumer<String> unhandledCommandsProcessor;
     private int refreshThingIndex = 0;
 
     public NikobusPcLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
@@ -113,7 +118,11 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
         // Noop.
     }
 
-    @SuppressWarnings("null")
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(NikobusDiscoveryService.class);
+    }
+
     private void processReceivedValue(byte value) {
         logger.trace("Received {}", value);
 
@@ -133,6 +142,11 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
                     Runnable listener = commandListeners.get(command);
                     if (listener != null) {
                         listener.run();
+                    } else {
+                        Consumer<String> processor = unhandledCommandsProcessor;
+                        if (processor != null) {
+                            processor.accept(command);
+                        }
                     }
                 }
             } catch (RuntimeException e) {
@@ -157,7 +171,6 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
         }
     }
 
-    @SuppressWarnings("null")
     public void addListener(String command, Runnable listener) {
         if (commandListeners.put(command, listener) != null) {
             logger.warn("Multiple registrations for '{}'", command);
@@ -168,6 +181,17 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
         commandListeners.remove(command);
     }
 
+    public void setUnhandledCommandProcessor(Consumer<String> processor) {
+        if (unhandledCommandsProcessor != null) {
+            logger.debug("Unexpected override of unhandledCommandsProcessor");
+        }
+        unhandledCommandsProcessor = processor;
+    }
+
+    public void resetUnhandledCommandProcessor() {
+        unhandledCommandsProcessor = null;
+    }
+
     private void processResponse(String commandPayload, @Nullable String ack) {
         NikobusCommand command;
         synchronized (pendingCommands) {
@@ -229,7 +253,6 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
         scheduler.submit(this::processCommand);
     }
 
-    @SuppressWarnings({ "unused", "null" })
     private void processCommand() {
         NikobusCommand command;
         synchronized (pendingCommands) {
index a02dc70da896f1d95f5ed55fcd7c9fd98d38d6b7..322d60e4b005681ceab9c5ee81abf54d1de950d8 100644 (file)
@@ -56,7 +56,7 @@ public class CRCUtil {
         }
 
         check = check & CRC_INIT;
-        String checksum = leftPadWithZeros(Integer.toHexString(check), 4);
+        String checksum = Utils.leftPadWithZeros(Integer.toHexString(check), 4);
         return (input + checksum).toUpperCase();
     }
 
@@ -87,14 +87,6 @@ public class CRCUtil {
             }
         }
 
-        return input + leftPadWithZeros(Integer.toHexString(check), 2).toUpperCase();
-    }
-
-    private static String leftPadWithZeros(String text, int size) {
-        StringBuilder builder = new StringBuilder(text);
-        while (builder.length() < size) {
-            builder.insert(0, '0');
-        }
-        return builder.toString();
+        return input + Utils.leftPadWithZeros(Integer.toHexString(check), 2).toUpperCase();
     }
 }
index db0c62e3897e969819b132c41166344d633282c7..45830821b70fe2e2b6c082871bc986303495a281 100644 (file)
@@ -29,4 +29,62 @@ public class Utils {
             future.cancel(true);
         }
     }
+
+    /**
+     * Convert bus address to push button's address as seen in Nikobus
+     * PC software.
+     *
+     * @param addressString
+     *            String representing a bus Push Button's address.
+     * @return Push button's address as seen in Nikobus PC software.
+     */
+    public static String convertToHumanReadableNikobusAddress(String addressString) {
+        try {
+            int address = Integer.parseInt(addressString, 16);
+            int nikobusAddress = 0;
+
+            for (int i = 0; i < 21; ++i) {
+                nikobusAddress = (nikobusAddress << 1) | ((address >> i) & 1);
+            }
+
+            nikobusAddress = (nikobusAddress << 1);
+            int button = (address >> 21) & 0x07;
+
+            return leftPadWithZeros(Integer.toHexString(nikobusAddress), 6) + ":" + mapButton(button);
+
+        } catch (NumberFormatException e) {
+            return "[" + addressString + "]";
+        }
+    }
+
+    private static String mapButton(int buttonIndex) {
+        switch (buttonIndex) {
+            case 0:
+                return "1";
+            case 1:
+                return "5";
+            case 2:
+                return "2";
+            case 3:
+                return "6";
+            case 4:
+                return "3";
+            case 5:
+                return "7";
+            case 6:
+                return "4";
+            case 7:
+                return "8";
+            default:
+                return "?";
+        }
+    }
+
+    public static String leftPadWithZeros(String text, int size) {
+        StringBuilder builder = new StringBuilder(text);
+        while (builder.length() < size) {
+            builder.insert(0, '0');
+        }
+        return builder.toString();
+    }
 }