]> git.basschouten.com Git - openhab-addons.git/commitdiff
[knx] Replace gnu.io dependency with serial transport (#14051)
authorHolger Friedrich <holgerfriedrich@users.noreply.github.com>
Sun, 19 Feb 2023 14:45:42 +0000 (15:45 +0100)
committerGitHub <noreply@github.com>
Sun, 19 Feb 2023 14:45:42 +0000 (15:45 +0100)
Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
bundles/org.openhab.binding.knx/pom.xml
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/client/SerialTransportAdapter.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/SerialBridgeThingHandler.java

index 65d508c0e5f1ed175b9259b06e148c960271e81e..acab25a9550da2eca9baa3b57e9ff5ba2d55785b 100644 (file)
   <name>openHAB Add-ons :: Bundles :: KNX Binding</name>
 
   <properties>
-    <bnd.importpackage>gnu.io;version="[3.12,6)",javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional"</bnd.importpackage>
+    <bnd.importpackage>javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional"</bnd.importpackage>
   </properties>
 
   <dependencies>
     <dependency>
-      <groupId>com.github.calimero</groupId>
-      <artifactId>calimero-core</artifactId>
-      <version>2.5.1</version>
-      <scope>compile</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>org.slf4j</groupId>
-          <artifactId>slf4j-api</artifactId>
-        </exclusion>
-      </exclusions>
+      <groupId>biz.aQute.bnd</groupId>
+      <artifactId>biz.aQute.bnd.annotation</artifactId>
+      <version>${bnd.version}</version>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>com.github.calimero</groupId>
-      <artifactId>calimero-device</artifactId>
+      <artifactId>calimero-core</artifactId>
       <version>2.5.1</version>
       <scope>compile</scope>
       <exclusions>
@@ -45,7 +39,7 @@
     </dependency>
     <dependency>
       <groupId>com.github.calimero</groupId>
-      <artifactId>calimero-rxtx</artifactId>
+      <artifactId>calimero-device</artifactId>
       <version>2.5.1</version>
       <scope>compile</scope>
       <exclusions>
   <build>
     <pluginManagement>
       <plugins>
+        <plugin>
+          <groupId>biz.aQute.bnd</groupId>
+          <artifactId>bnd-maven-plugin</artifactId>
+          <configuration>
+            <bnd><![CDATA[${oh.bndDefaults}
+Require-Capability:
+    osgi.extender:=
+      filter:="(osgi.extender=osgi.serviceloader.processor)",
+    osgi.serviceloader:=
+      filter:="(osgi.serviceloader=tuwien.auto.calimero.serial.spi.SerialCom)";
+      cardinality:=multiple
+SPI-Provider: tuwien.auto.calimero.serial.spi.SerialCom
+SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.serial.spi.SerialCom])
+            ]]>
+            </bnd>
+          </configuration>
+        </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
index 6e20fa676ff72baaa16e9bc308eed3d86d80e938..24a5d6f5f25df1692c56f2d59cde16402a597749 100644 (file)
  */
 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 (file)
index 0000000..125c2e2
--- /dev/null
@@ -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<String>
+    @NonNullByDefault({})
+    @Override
+    public List<String> 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");
+        }
+    }
+}
index a510355f2bd3f65d2dd0b290094e8d88f363d31a..460d2362067106b8c9b20fd5c8ed414086deacfc 100644 (file)
@@ -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);
         }
index 4c598f1fe7d36a2ee60525f14a5879286df620a5..a97538165891d11ea9e2c40f3c5da18fa4078ce4 100644 (file)
@@ -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