[knx] Replace gnu.io dependency with serial transport (#14051)

Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
Holger Friedrich 2023-02-19 15:45:42 +01:00 committed by GitHub
parent 1da2694a17
commit f795abbce7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 256 additions and 32 deletions

View File

@ -15,10 +15,16 @@
<name>openHAB Add-ons :: Bundles :: KNX Binding</name> <name>openHAB Add-ons :: Bundles :: KNX Binding</name>
<properties> <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> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>biz.aQute.bnd</groupId>
<artifactId>biz.aQute.bnd.annotation</artifactId>
<version>${bnd.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>com.github.calimero</groupId> <groupId>com.github.calimero</groupId>
<artifactId>calimero-core</artifactId> <artifactId>calimero-core</artifactId>
@ -43,23 +49,28 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.github.calimero</groupId>
<artifactId>calimero-rxtx</artifactId>
<version>2.5.1</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>

View File

@ -12,16 +12,16 @@
*/ */
package org.openhab.binding.knx.internal.client; package org.openhab.binding.knx.internal.client;
import java.util.Enumeration;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.openhab.core.thing.ThingUID;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import gnu.io.CommPortIdentifier;
import gnu.io.RXTXVersion;
import tuwien.auto.calimero.KNXException; import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.link.KNXNetworkLink; import tuwien.auto.calimero.link.KNXNetworkLink;
import tuwien.auto.calimero.link.KNXNetworkLinkFT12; 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 Logger logger = LoggerFactory.getLogger(SerialClient.class);
private final SerialPortManager serialPortManager;
private final String serialPort; private final String serialPort;
private final boolean useCemi; private final boolean useCemi;
public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause, public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi, int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
StatusUpdateCallback statusUpdateCallback) { SerialPortManager serialPortManager, StatusUpdateCallback statusUpdateCallback) {
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler, super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
statusUpdateCallback); statusUpdateCallback);
this.serialPortManager = serialPortManager;
this.serialPort = serialPort; this.serialPort = serialPort;
this.useCemi = useCemi; this.useCemi = useCemi;
} }
@ -55,7 +57,6 @@ public class SerialClient extends AbstractKNXClient {
@Override @Override
protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException { protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
try { try {
RXTXVersion.getVersion();
logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}.", serialPort, logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}.", serialPort,
(useCemi ? " using CEMI" : "")); (useCemi ? " using CEMI" : ""));
// CEMI support by Calimero library, userful for newer serial devices like KNX RF sticks, kBerry, // 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(); final String msg = e.getMessage();
// TODO add a test for this string match; error message might change in later version of Calimero library // 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))) { if ((msg != null) && (msg.startsWith(CALIMERO_ERROR_CANNOT_OPEN_PORT))) {
StringBuilder sb = new StringBuilder("Available ports are:\n"); String availablePorts = serialPortManager.getIdentifiers().map(SerialPortIdentifier::getName)
Enumeration<?> portList = CommPortIdentifier.getPortIdentifiers(); .collect(Collectors.joining("\n"));
while (portList.hasMoreElements()) { if (!availablePorts.isEmpty()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement(); availablePorts = " Available ports are:\n" + availablePorts;
if ((id != null) && (id.getPortType() == CommPortIdentifier.PORT_SERIAL)) {
sb.append(id.getName());
sb.append("\n");
}
} }
sb.deleteCharAt(sb.length() - 1); throw new KNXException("Serial port '" + serialPort + "' could not be opened." + availablePorts);
throw new KNXException("Serial port '" + serialPort + "' could not be opened. " + sb.toString());
} else { } else {
throw e; throw e;
} }

View File

@ -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");
}
}
}

View File

@ -19,6 +19,7 @@ import java.util.Collection;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.DeviceThingHandler;
import org.openhab.binding.knx.internal.handler.IPBridgeThingHandler; import org.openhab.binding.knx.internal.handler.IPBridgeThingHandler;
import org.openhab.binding.knx.internal.handler.SerialBridgeThingHandler; 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.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.net.NetworkAddressService; import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -53,11 +55,14 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
@Nullable @Nullable
private NetworkAddressService networkAddressService; private NetworkAddressService networkAddressService;
private final SerialPortManager serialPortManager;
@Activate @Activate
public KNXHandlerFactory(final @Reference TranslationProvider translationProvider, public KNXHandlerFactory(final @Reference TranslationProvider translationProvider,
final @Reference LocaleProvider localeProvider) { final @Reference LocaleProvider localeProvider, final @Reference SerialPortManager serialPortManager) {
KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider); KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider);
this.serialPortManager = serialPortManager;
SerialTransportAdapter.setSerialPortManager(serialPortManager);
} }
@Override @Override
@ -87,7 +92,7 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory {
if (thing.getThingTypeUID().equals(THING_TYPE_IP_BRIDGE)) { if (thing.getThingTypeUID().equals(THING_TYPE_IP_BRIDGE)) {
return new IPBridgeThingHandler((Bridge) thing, networkAddressService); return new IPBridgeThingHandler((Bridge) thing, networkAddressService);
} else if (thing.getThingTypeUID().equals(THING_TYPE_SERIAL_BRIDGE)) { } 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)) { } else if (thing.getThingTypeUID().equals(THING_TYPE_DEVICE)) {
return new DeviceThingHandler(thing); return new DeviceThingHandler(thing);
} }

View 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.NoOpClient;
import org.openhab.binding.knx.internal.client.SerialClient; import org.openhab.binding.knx.internal.client.SerialClient;
import org.openhab.binding.knx.internal.config.SerialBridgeConfiguration; 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.Bridge;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -42,8 +43,11 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SerialBridgeThingHandler.class); 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); super(bridge);
this.serialPortManager = serialPortManager;
} }
@Override @Override
@ -53,7 +57,7 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class); SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(), config.getResponseTimeout(), client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(), config.getResponseTimeout(),
config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), config.getSerialPort(), config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), config.getSerialPort(),
config.useCemi(), this); config.useCemi(), serialPortManager, this);
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
// delay actual initialization, allow for longer runtime of actual initialization // delay actual initialization, allow for longer runtime of actual initialization