From f795abbce77072cd71b1af82a1be6c6c72f0960a Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 19 Feb 2023 15:45:42 +0100 Subject: [PATCH] [knx] Replace gnu.io dependency with serial transport (#14051) Signed-off-by: Holger Friedrich --- bundles/org.openhab.binding.knx/pom.xml | 37 ++-- .../knx/internal/client/SerialClient.java | 26 +-- .../client/SerialTransportAdapter.java | 208 ++++++++++++++++++ .../internal/factory/KNXHandlerFactory.java | 9 +- .../handler/SerialBridgeThingHandler.java | 8 +- 5 files changed, 256 insertions(+), 32 deletions(-) create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java diff --git a/bundles/org.openhab.binding.knx/pom.xml b/bundles/org.openhab.binding.knx/pom.xml index 65d508c0e..acab25a95 100644 --- a/bundles/org.openhab.binding.knx/pom.xml +++ b/bundles/org.openhab.binding.knx/pom.xml @@ -15,10 +15,16 @@ openHAB Add-ons :: Bundles :: KNX Binding - gnu.io;version="[3.12,6)",javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional" + javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional" + + biz.aQute.bnd + biz.aQute.bnd.annotation + ${bnd.version} + provided + com.github.calimero calimero-core @@ -43,23 +49,28 @@ - - com.github.calimero - calimero-rxtx - 2.5.1 - compile - - - org.slf4j - slf4j-api - - - + + biz.aQute.bnd + bnd-maven-plugin + + + + + org.apache.maven.plugins maven-javadoc-plugin diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java index 6e20fa676..24a5d6f5f 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialClient.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.knx.internal.client; -import java.util.Enumeration; import java.util.concurrent.ScheduledExecutorService; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.transport.serial.SerialPortIdentifier; +import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.thing.ThingUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import gnu.io.CommPortIdentifier; -import gnu.io.RXTXVersion; import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.link.KNXNetworkLink; import tuwien.auto.calimero.link.KNXNetworkLinkFT12; @@ -40,14 +40,16 @@ public class SerialClient extends AbstractKNXClient { private final Logger logger = LoggerFactory.getLogger(SerialClient.class); + private final SerialPortManager serialPortManager; private final String serialPort; private final boolean useCemi; public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause, int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi, - StatusUpdateCallback statusUpdateCallback) { + SerialPortManager serialPortManager, StatusUpdateCallback statusUpdateCallback) { super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler, statusUpdateCallback); + this.serialPortManager = serialPortManager; this.serialPort = serialPort; this.useCemi = useCemi; } @@ -55,7 +57,6 @@ public class SerialClient extends AbstractKNXClient { @Override protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException { try { - RXTXVersion.getVersion(); logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}.", serialPort, (useCemi ? " using CEMI" : "")); // CEMI support by Calimero library, userful for newer serial devices like KNX RF sticks, kBerry, @@ -73,17 +74,12 @@ public class SerialClient extends AbstractKNXClient { final String msg = e.getMessage(); // TODO add a test for this string match; error message might change in later version of Calimero library if ((msg != null) && (msg.startsWith(CALIMERO_ERROR_CANNOT_OPEN_PORT))) { - StringBuilder sb = new StringBuilder("Available ports are:\n"); - Enumeration portList = CommPortIdentifier.getPortIdentifiers(); - while (portList.hasMoreElements()) { - CommPortIdentifier id = (CommPortIdentifier) portList.nextElement(); - if ((id != null) && (id.getPortType() == CommPortIdentifier.PORT_SERIAL)) { - sb.append(id.getName()); - sb.append("\n"); - } + String availablePorts = serialPortManager.getIdentifiers().map(SerialPortIdentifier::getName) + .collect(Collectors.joining("\n")); + if (!availablePorts.isEmpty()) { + availablePorts = " Available ports are:\n" + availablePorts; } - sb.deleteCharAt(sb.length() - 1); - throw new KNXException("Serial port '" + serialPort + "' could not be opened. " + sb.toString()); + throw new KNXException("Serial port '" + serialPort + "' could not be opened." + availablePorts); } else { throw e; } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java new file mode 100644 index 000000000..125c2e2ea --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/SerialTransportAdapter.java @@ -0,0 +1,208 @@ +/** + * 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.client; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.io.transport.serial.PortInUseException; +import org.openhab.core.io.transport.serial.SerialPort; +import org.openhab.core.io.transport.serial.SerialPortIdentifier; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.io.transport.serial.UnsupportedCommOperationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import aQute.bnd.annotation.spi.ServiceProvider; +import tuwien.auto.calimero.KNXException; +import tuwien.auto.calimero.serial.spi.SerialCom; + +/** + * The {@link SerialTransportAdapter} provides org.openhab.core.io.transport.serial + * services to the Calimero library. + * + * @ServiceProvider annotation (biz.aQute.bnd.annotation) automatically creates the file + * /META-INF/services/tuwien.auto.calimero.serial.spi.SerialCom + * to register SerialTransportAdapter to the service loader. + * Additional attributes for SerialTansportAdapter can be specified as well, e.g. + * attribute = { "position=1" } + * and will be part of MANIFEST.MF + * + * @author Holger Friedrich - Initial contribution + */ +@ServiceProvider(value = SerialCom.class) +@NonNullByDefault +public class SerialTransportAdapter implements SerialCom { + private static final int OPEN_TIMEOUT_MS = 200; + private static final int RECEIVE_TIMEOUT_MS = 5; + private static final int RECEIVE_THRESHOLD = 1024; + private static final int BAUDRATE = 19200; + + private Logger logger = LoggerFactory.getLogger(SerialTransportAdapter.class); + @Nullable + private static SerialPortManager serialPortManager = null; + @Nullable + private SerialPort serialPort = null; + + public static void setSerialPortManager(SerialPortManager serialPortManager) { + SerialTransportAdapter.serialPortManager = serialPortManager; + } + + public SerialTransportAdapter() { + } + + @Override + public void open(@Nullable String portId) throws IOException, KNXException { + if (portId == null) { + throw new IOException("Port not available"); + } + logger = LoggerFactory.getLogger("SerialTransportAdapter:" + portId); + + final @Nullable SerialPortManager tmpSerialPortManager = serialPortManager; + if (tmpSerialPortManager == null) { + throw new IOException("PortManager not available"); + } + try { + SerialPortIdentifier portIdentifier = tmpSerialPortManager.getIdentifier(portId); + if (portIdentifier != null) { + logger.trace("Trying to open port {}", portId); + SerialPort serialPort = portIdentifier.open(this.getClass().getName(), OPEN_TIMEOUT_MS); + // apply default settings for com port, may be overwritten by caller + serialPort.setSerialPortParams(BAUDRATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, + SerialPort.PARITY_EVEN); + serialPort.enableReceiveThreshold(RECEIVE_THRESHOLD); + serialPort.enableReceiveTimeout(RECEIVE_TIMEOUT_MS); + this.serialPort = serialPort; + + // Notification / event listeners are available and may be used to log/trace com failures + // serialPort.notifyOnDataAvailable(true); + logger.trace("Port opened successfully"); + } else { + logger.info("Port {} not available", portId); + throw new IOException("Port " + portId + " not available"); + } + } catch (PortInUseException e) { + logger.info("Port {} already in use", portId); + throw new IOException("Port " + portId + " already in use", e); + } catch (UnsupportedCommOperationException e) { + logger.info("Port {} unsupported com operation", portId); + throw new IOException("Port " + portId + " unsupported com operation", e); + } + } + + // SerialCom extends AutoCloseable, close() throws Exception + @Override + public void close() { + logger.trace("Closing serial port"); + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort != null) { + tmpSerialPort.close(); + serialPort = null; + } + } + + @Override + public @Nullable InputStream inputStream() { + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort != null) { + try { + return tmpSerialPort.getInputStream(); + } catch (IOException e) { + logger.info("Cannot open input stream"); + } + } + // should not throw, create a dummy return value + byte buf[] = {}; + return new ByteArrayInputStream(buf); + } + + @Override + public @Nullable OutputStream outputStream() { + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort != null) { + try { + return tmpSerialPort.getOutputStream(); + } catch (IOException e) { + logger.info("Cannot open output stream"); + } + } + // should not throw, create a dummy return value + return new ByteArrayOutputStream(0); + } + + // disable NonNullByDefault for this function, legacy interface List + @NonNullByDefault({}) + @Override + public List portIdentifiers() { + final @Nullable SerialPortManager tmpSerialPortManager = serialPortManager; + if (tmpSerialPortManager == null) { + return Collections.emptyList(); + } + return tmpSerialPortManager.getIdentifiers().map(SerialPortIdentifier::getName).collect(Collectors.toList()); + } + + @Override + public int baudRate() throws IOException { + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort == null) { + throw new IOException("Port not available"); + } + return tmpSerialPort.getBaudRate(); + } + + @Override + public void setSerialPortParams(final int baudrate, final int databits, @Nullable StopBits stopbits, + @Nullable Parity parity) throws IOException { + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort == null) { + throw new IOException("Port not available"); + } + if ((stopbits == null) || (parity == null)) { + throw new IOException("Invalid parameters"); + } + try { + tmpSerialPort.setSerialPortParams(baudrate, databits, stopbits.value(), parity.value()); + } catch (final UnsupportedCommOperationException e) { + throw new IOException("Setting serial port parameters for " + tmpSerialPort.getName() + " failed", e); + } + } + + @Override + public void setFlowControlMode(@Nullable FlowControl mode) throws IOException { + final @Nullable SerialPort tmpSerialPort = serialPort; + if (tmpSerialPort == null) { + throw new IOException("Port not available"); + } + if (mode == null) { + throw new IOException("Invalid parameters"); + } + if (mode == FlowControl.None) { + try { + tmpSerialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE); + } catch (final UnsupportedCommOperationException e) { + throw new IOException("Setting flow control parameters for " + tmpSerialPort.getName() + " failed", e); + } + } else { + logger.warn("Unknown FlowControl mode {}", mode); + throw new IOException("Invalid flow mode"); + } + } +} diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java index a510355f2..460d23620 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java @@ -19,6 +19,7 @@ import java.util.Collection; 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.SerialBridgeThingHandler; @@ -26,6 +27,7 @@ import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider; import org.openhab.core.config.core.Configuration; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.net.NetworkAddressService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -53,11 +55,14 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory { @Nullable private NetworkAddressService networkAddressService; + private final SerialPortManager serialPortManager; @Activate public KNXHandlerFactory(final @Reference TranslationProvider translationProvider, - final @Reference LocaleProvider localeProvider) { + final @Reference LocaleProvider localeProvider, final @Reference SerialPortManager serialPortManager) { KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider); + this.serialPortManager = serialPortManager; + SerialTransportAdapter.setSerialPortManager(serialPortManager); } @Override @@ -87,7 +92,7 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory { 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); + return new SerialBridgeThingHandler((Bridge) thing, serialPortManager); } else if (thing.getThingTypeUID().equals(THING_TYPE_DEVICE)) { return new DeviceThingHandler(thing); } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/SerialBridgeThingHandler.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/SerialBridgeThingHandler.java index 4c598f1fe..a97538165 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/SerialBridgeThingHandler.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/SerialBridgeThingHandler.java @@ -20,6 +20,7 @@ import org.openhab.binding.knx.internal.client.KNXClient; import org.openhab.binding.knx.internal.client.NoOpClient; import org.openhab.binding.knx.internal.client.SerialClient; import org.openhab.binding.knx.internal.config.SerialBridgeConfiguration; +import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingStatus; import org.slf4j.Logger; @@ -42,8 +43,11 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler { private final Logger logger = LoggerFactory.getLogger(SerialBridgeThingHandler.class); - public SerialBridgeThingHandler(Bridge bridge) { + private final SerialPortManager serialPortManager; + + public SerialBridgeThingHandler(Bridge bridge, final SerialPortManager serialPortManager) { super(bridge); + this.serialPortManager = serialPortManager; } @Override @@ -53,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(), this); + config.useCemi(), serialPortManager, this); updateStatus(ThingStatus.UNKNOWN); // delay actual initialization, allow for longer runtime of actual initialization