]> git.basschouten.com Git - openhab-addons.git/commitdiff
[knx] Implement console commands (#15064)
authorHolger Friedrich <holgerfriedrich@users.noreply.github.com>
Tue, 13 Jun 2023 15:30:17 +0000 (17:30 +0200)
committerGitHub <noreply@github.com>
Tue, 13 Jun 2023 15:30:17 +0000 (17:30 +0200)
* [knx] Implement console commands

Add commands knx:* to openHAB console.
Initial implementation of knx:show_unknown_ga, which shows group
addresses which are seen in the installation but not used by
openHAB.

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
bundles/org.openhab.binding.knx/README.md
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/IPClient.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java [new file with mode: 0644]
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java
bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/SerialBridgeThingHandler.java

index 3b5348d5fb68d07e873e534fb220a309135a7e26..87c23942b22e5fab6f7efbc744c64af2bddbfd29 100644 (file)
@@ -428,3 +428,8 @@ knx.items:
 Switch        demoSwitch         "Light [%s]"               <light>          { channel="hue:0210:bridge:1:color", channel="knx:device:bridge:generic:controlSwitch" }
 Dimmer        demoDimmer         "Dimmer [%d %%]"           <light>          { channel="hue:0210:bridge:1:color", channel="knx:device:bridge:generic:controlDimmer" }
 ```
+
+## Console Commands
+
+The KNX binding provides additional functionality which can be triggered from the openHAB console.
+Type `openhab:knx` on the openHAB console for further information.
index 0c887983619809c2680e778e4ade8c34001d7464..732c88520d112f338e36946b10b3f7e4dff10854 100644 (file)
@@ -29,6 +29,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.knx.internal.dpt.ValueEncoder;
 import org.openhab.binding.knx.internal.handler.GroupAddressListener;
+import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
 import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
@@ -91,6 +92,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
     private final int readRetriesLimit;
     private final StatusUpdateCallback statusUpdateCallback;
     private final ScheduledExecutorService knxScheduler;
+    private final CommandExtensionData commandExtensionData;
 
     private @Nullable ProcessCommunicator processCommunicator;
     private @Nullable ProcessCommunicationResponder responseCommunicator;
@@ -137,7 +139,8 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
     };
 
     public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
-            int readRetriesLimit, ScheduledExecutorService knxScheduler, StatusUpdateCallback statusUpdateCallback) {
+            int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
+            StatusUpdateCallback statusUpdateCallback) {
         this.autoReconnectPeriod = autoReconnectPeriod;
         this.thingUID = thingUID;
         this.responseTimeout = responseTimeout;
@@ -145,6 +148,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
         this.readRetriesLimit = readRetriesLimit;
         this.knxScheduler = knxScheduler;
         this.statusUpdateCallback = statusUpdateCallback;
+        this.commandExtensionData = commandExtensionData;
     }
 
     public void initialize() {
@@ -323,11 +327,26 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
         IndividualAddress source = event.getSourceAddr();
         byte[] asdu = event.getASDU();
         logger.trace("Received a {} telegram from '{}' to '{}' with value '{}'", task, source, destination, asdu);
+        boolean isHandled = false;
         for (GroupAddressListener listener : groupAddressListeners) {
             if (listener.listensTo(destination)) {
+                isHandled = true;
                 knxScheduler.schedule(() -> action.apply(listener, source, destination, asdu), 0, TimeUnit.SECONDS);
             }
         }
+        // Store information about unhandled GAs, can be shown on console using knx:list-unknown-ga.
+        // The idea is to store GA, message type, and size as key. The value counts the number of packets.
+        if (!isHandled) {
+            logger.trace("Address '{}' is not configured in openHAB", destination);
+            final String type = switch (event.getServiceCode()) {
+                case 0x80 -> " GROUP_WRITE(";
+                case 0x40 -> " GROUP_RESPONSE(";
+                case 0x00 -> " GROUP_READ(";
+                default -> " ?(";
+            };
+            final String key = destination.toString() + type + event.getASDU().length + ")";
+            commandExtensionData.unknownGA().compute(key, (k, v) -> v == null ? 1 : v + 1);
+        }
     }
 
     // datapoint is null at end of the list, warning is misleading
index f9dbad6971b1c5c77a055acd0a12c47ba095ff8e..648f66c1ab9dc975c36feee3affc3879bd82f7a0 100644 (file)
@@ -22,6 +22,7 @@ import java.util.concurrent.ScheduledExecutorService;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
 import org.openhab.core.thing.ThingUID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -86,9 +87,10 @@ public class IPClient extends AbstractKNXClient {
             @Nullable InetSocketAddress localEndPoint, boolean useNAT, int autoReconnectPeriod,
             byte[] secureRoutingBackboneGroupKey, long secureRoutingLatencyToleranceMs, byte[] secureTunnelDevKey,
             int secureTunnelUser, byte[] secureTunnelUserKey, ThingUID thingUID, int responseTimeout, int readingPause,
-            int readRetriesLimit, ScheduledExecutorService knxScheduler, StatusUpdateCallback statusUpdateCallback) {
+            int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
+            StatusUpdateCallback statusUpdateCallback) {
         super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
-                statusUpdateCallback);
+                commandExtensionData, statusUpdateCallback);
         this.ipConnectionType = ipConnectionType;
         this.ip = ip;
         this.localSource = localSource;
index 20e269f8300f685a6fc36a4497aa64cfd78a7d15..bf1db21b6482546c068c0da7b625f63c66f8a580 100644 (file)
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
 import org.openhab.core.io.transport.serial.SerialPortManager;
 import org.openhab.core.thing.ThingUID;
@@ -52,9 +53,10 @@ public class SerialClient extends AbstractKNXClient {
 
     public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
             int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
-            SerialPortManager serialPortManager, StatusUpdateCallback statusUpdateCallback) {
+            SerialPortManager serialPortManager, CommandExtensionData commandExtensionData,
+            StatusUpdateCallback statusUpdateCallback) {
         super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
-                statusUpdateCallback);
+                commandExtensionData, statusUpdateCallback);
         this.serialPortManager = serialPortManager;
         this.serialPort = serialPort;
         this.useCemi = useCemi;
diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java
new file mode 100644 (file)
index 0000000..a365f48
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2010-2023 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.knx.internal.console;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.knx.internal.KNXBindingConstants;
+import org.openhab.binding.knx.internal.factory.KNXHandlerFactory;
+import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
+import org.openhab.core.io.console.Console;
+import org.openhab.core.io.console.ConsoleCommandCompleter;
+import org.openhab.core.io.console.StringsCompleter;
+import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
+import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link KNXCommandExtension} is responsible for handling console commands
+ *
+ * @author Holger Friedrich - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ConsoleCommandExtension.class)
+public class KNXCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
+
+    private static final String CMD_LIST_UNKNOWN_GA = "list-unknown-ga";
+    private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_LIST_UNKNOWN_GA), false);
+
+    private final KNXHandlerFactory knxHandlerFactory;
+
+    @Activate
+    public KNXCommandExtension(final @Reference KNXHandlerFactory knxHandlerFactory) {
+        super(KNXBindingConstants.BINDING_ID, "Interact with KNX devices.");
+        this.knxHandlerFactory = knxHandlerFactory;
+    }
+
+    @Override
+    public void execute(String[] args, Console console) {
+        if (args.length == 1 && CMD_LIST_UNKNOWN_GA.equalsIgnoreCase(args[0])) {
+            for (KNXBridgeBaseThingHandler bridgeHandler : knxHandlerFactory.getBridges()) {
+                console.println("KNX bridge \"" + bridgeHandler.getThing().getLabel()
+                        + "\": group address, type, number of bytes, and number of occurence since last reload of binding:");
+                // console.println(handler.getCommandExtensionData().unknownGA().toString());
+                for (Entry<String, Long> entry : bridgeHandler.getCommandExtensionData().unknownGA().entrySet()) {
+                    console.println(entry.getKey() + " " + entry.getValue());
+                }
+            }
+            return;
+        }
+        printUsage(console);
+    }
+
+    @Override
+    public List<String> getUsages() {
+        return Arrays.asList(
+                buildCommandUsage(CMD_LIST_UNKNOWN_GA, "list group addresses which are not configured in openHAB"));
+    }
+
+    @Override
+    public @Nullable ConsoleCommandCompleter getCompleter() {
+        return this;
+    }
+
+    @Override
+    public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
+        if (cursorArgumentIndex <= 0) {
+            return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
+        }
+        return false;
+    }
+}
index b5265982c99e3730411dde69d6b6f72e44f38e77..bc9a288ebdff367fd70bc416321df136b2f0d313 100644 (file)
@@ -17,12 +17,14 @@ import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.knx.internal.client.SerialTransportAdapter;
 import org.openhab.binding.knx.internal.handler.DeviceThingHandler;
 import org.openhab.binding.knx.internal.handler.IPBridgeThingHandler;
+import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
 import org.openhab.binding.knx.internal.handler.SerialBridgeThingHandler;
 import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
 import org.openhab.core.config.core.Configuration;
@@ -49,7 +51,7 @@ import org.osgi.service.component.annotations.Reference;
  * @author Simon Kaufmann - Initial contribution and API
  */
 @NonNullByDefault
-@Component(service = ThingHandlerFactory.class, configurationPid = "binding.knx")
+@Component(service = { ThingHandlerFactory.class, KNXHandlerFactory.class }, configurationPid = "binding.knx")
 public class KNXHandlerFactory extends BaseThingHandlerFactory {
 
     public static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_DEVICE,
@@ -58,6 +60,7 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
     @Nullable
     private final NetworkAddressService networkAddressService;
     private final SerialPortManager serialPortManager;
+    private final Map<ThingUID, KNXBridgeBaseThingHandler> bridges = new ConcurrentHashMap<>();
 
     @Activate
     public KNXHandlerFactory(final @Reference NetworkAddressService networkAddressService, Map<String, Object> config,
@@ -99,16 +102,27 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
 
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
-        if (thing.getThingTypeUID().equals(THING_TYPE_IP_BRIDGE)) {
-            return new IPBridgeThingHandler((Bridge) thing, networkAddressService);
-        } else if (thing.getThingTypeUID().equals(THING_TYPE_SERIAL_BRIDGE)) {
-            return new SerialBridgeThingHandler((Bridge) thing, serialPortManager);
-        } else if (thing.getThingTypeUID().equals(THING_TYPE_DEVICE)) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+        if (thingTypeUID.equals(THING_TYPE_IP_BRIDGE)) {
+            KNXBridgeBaseThingHandler bridgeHandler = new IPBridgeThingHandler((Bridge) thing, networkAddressService);
+            bridges.put(thing.getUID(), bridgeHandler);
+            return bridgeHandler;
+        } else if (thingTypeUID.equals(THING_TYPE_SERIAL_BRIDGE)) {
+            KNXBridgeBaseThingHandler bridgeHandler = new SerialBridgeThingHandler((Bridge) thing, serialPortManager);
+            bridges.put(thing.getUID(), bridgeHandler);
+            return bridgeHandler;
+        } else if (thingTypeUID.equals(THING_TYPE_DEVICE)) {
             return new DeviceThingHandler(thing);
         }
         return null;
     }
 
+    @Override
+    public void unregisterHandler(Thing thing) {
+        bridges.remove(thing.getUID());
+        super.unregisterHandler(thing);
+    }
+
     private ThingUID getIPBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
             Configuration configuration) {
         if (thingUID != null) {
@@ -126,4 +140,8 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
         String serialPort = (String) configuration.get(SERIAL_PORT);
         return new ThingUID(thingTypeUID, serialPort);
     }
+
+    public Collection<KNXBridgeBaseThingHandler> getBridges() {
+        return Set.copyOf(bridges.values());
+    }
 }
index dbd2f1dfde32a1aa6dd52a3f36ea984971976186..dcf6ad80fd78e4fb6b9cf82c89d4d4934db81783 100644 (file)
@@ -185,7 +185,8 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
         client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,
                 secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
                 secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout(),
-                config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), this);
+                config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), getCommandExtensionData(),
+                this);
 
         IPClient tmpClient = client;
         if (tmpClient != null) {
index c669248db0ea5d4a71b822d9cf283b212e18ea1b..eda5d51e32ab9089592edc76d122c0e4e5415368 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.knx.internal.handler;
 
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -65,20 +67,33 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
         public long latencyToleranceMs = 0;
     }
 
+    /**
+     * Helper class to carry information which can be used by the
+     * command line extension (openHAB console).
+     */
+    public record CommandExtensionData(Map<String, Long> unknownGA) {
+    }
+
     protected ConcurrentHashMap<IndividualAddress, Destination> destinations = new ConcurrentHashMap<>();
     private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
     private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
     protected SecureRoutingConfig secureRouting;
     protected SecureTunnelConfig secureTunnel;
+    private CommandExtensionData commandExtensionData;
 
     public KNXBridgeBaseThingHandler(Bridge bridge) {
         super(bridge);
         secureRouting = new SecureRoutingConfig();
         secureTunnel = new SecureTunnelConfig();
+        commandExtensionData = new CommandExtensionData(new TreeMap<>());
     }
 
     protected abstract KNXClient getClient();
 
+    public CommandExtensionData getCommandExtensionData() {
+        return commandExtensionData;
+    }
+
     /***
      * Initialize KNX secure if configured (full interface)
      *
index 0c66934ac853602dcc7b400ddff7dbc799809999..bcd776abf066daf2050e2d58c84bdc6ff720420a 100644 (file)
@@ -57,7 +57,7 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
         SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
         client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(), config.getResponseTimeout(),
                 config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), config.getSerialPort(),
-                config.useCemi(), serialPortManager, this);
+                config.useCemi(), serialPortManager, getCommandExtensionData(), this);
 
         updateStatus(ThingStatus.UNKNOWN);
         // delay actual initialization, allow for longer runtime of actual initialization