[knx] Implement console commands (#15064)
* [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>
This commit is contained in:
parent
db0132409b
commit
5e010db738
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue