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.
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;
private final int readRetriesLimit;
private final StatusUpdateCallback statusUpdateCallback;
private final ScheduledExecutorService knxScheduler;
+ private final CommandExtensionData commandExtensionData;
private @Nullable ProcessCommunicator processCommunicator;
private @Nullable ProcessCommunicationResponder responseCommunicator;
};
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;
this.readRetriesLimit = readRetriesLimit;
this.knxScheduler = knxScheduler;
this.statusUpdateCallback = statusUpdateCallback;
+ this.commandExtensionData = commandExtensionData;
}
public void initialize() {
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
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;
@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;
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;
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;
--- /dev/null
+/**
+ * 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;
+ }
+}
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;
* @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,
@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,
@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) {
String serialPort = (String) configuration.get(SERIAL_PORT);
return new ThingUID(thingTypeUID, serialPort);
}
+
+ public Collection<KNXBridgeBaseThingHandler> getBridges() {
+ return Set.copyOf(bridges.values());
+ }
}
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) {
*/
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;
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)
*
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