2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.client;
15 import java.io.ByteArrayInputStream;
16 import java.io.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.stream.Collectors;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.core.io.transport.serial.PortInUseException;
27 import org.openhab.core.io.transport.serial.SerialPort;
28 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
29 import org.openhab.core.io.transport.serial.SerialPortManager;
30 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import aQute.bnd.annotation.spi.ServiceProvider;
35 import tuwien.auto.calimero.KNXException;
36 import tuwien.auto.calimero.serial.spi.SerialCom;
39 * The {@link SerialTransportAdapter} provides org.openhab.core.io.transport.serial
40 * services to the Calimero library.
42 * {@literal @}ServiceProvider annotation (biz.aQute.bnd.annotation) automatically creates the file
43 * /META-INF/services/tuwien.auto.calimero.serial.spi.SerialCom
44 * to register SerialTransportAdapter to the service loader.
45 * Additional attributes for SerialTansportAdapter can be specified as well, e.g.
46 * attribute = { "position=1" }
47 * and will be part of MANIFEST.MF
49 * @author Holger Friedrich - Initial contribution
51 @ServiceProvider(value = SerialCom.class)
53 public class SerialTransportAdapter implements SerialCom {
54 private static final int OPEN_TIMEOUT_MS = 200;
55 private static final int RECEIVE_TIMEOUT_MS = 5;
56 private static final int RECEIVE_THRESHOLD = 1024;
57 private static final int BAUDRATE = 19200;
59 private Logger logger = LoggerFactory.getLogger(SerialTransportAdapter.class);
61 private static SerialPortManager serialPortManager = null;
63 private SerialPort serialPort = null;
65 public static void setSerialPortManager(SerialPortManager serialPortManager) {
66 SerialTransportAdapter.serialPortManager = serialPortManager;
69 public SerialTransportAdapter() {
73 public void open(@Nullable String portId) throws IOException, KNXException {
75 throw new IOException("Port not available");
77 logger = LoggerFactory.getLogger("SerialTransportAdapter:" + portId);
79 final @Nullable SerialPortManager tmpSerialPortManager = serialPortManager;
80 if (tmpSerialPortManager == null) {
81 throw new IOException("PortManager not available");
84 SerialPortIdentifier portIdentifier = tmpSerialPortManager.getIdentifier(portId);
85 if (portIdentifier != null) {
86 logger.trace("Trying to open port {}", portId);
87 SerialPort serialPort = portIdentifier.open(this.getClass().getName(), OPEN_TIMEOUT_MS);
88 // apply default settings for com port, may be overwritten by caller
89 serialPort.setSerialPortParams(BAUDRATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
90 SerialPort.PARITY_EVEN);
91 serialPort.enableReceiveThreshold(RECEIVE_THRESHOLD);
92 serialPort.enableReceiveTimeout(RECEIVE_TIMEOUT_MS);
93 this.serialPort = serialPort;
95 // Notification / event listeners are available and may be used to log/trace com failures
96 // serialPort.notifyOnDataAvailable(true);
97 logger.trace("Port opened successfully");
99 logger.info("Port {} not available", portId);
100 throw new IOException("Port " + portId + " not available");
102 } catch (PortInUseException e) {
103 logger.info("Port {} already in use", portId);
104 throw new IOException("Port " + portId + " already in use", e);
105 } catch (UnsupportedCommOperationException e) {
106 logger.info("Port {} unsupported com operation", portId);
107 throw new IOException("Port " + portId + " unsupported com operation", e);
111 // SerialCom extends AutoCloseable, close() throws Exception
113 public void close() {
114 logger.trace("Closing serial port");
115 final @Nullable SerialPort tmpSerialPort = serialPort;
116 if (tmpSerialPort != null) {
117 tmpSerialPort.close();
123 public @Nullable InputStream inputStream() {
124 final @Nullable SerialPort tmpSerialPort = serialPort;
125 if (tmpSerialPort != null) {
127 return tmpSerialPort.getInputStream();
128 } catch (IOException e) {
129 logger.info("Cannot open input stream");
132 // should not throw, create a dummy return value
134 return new ByteArrayInputStream(buf);
138 public @Nullable OutputStream outputStream() {
139 final @Nullable SerialPort tmpSerialPort = serialPort;
140 if (tmpSerialPort != null) {
142 return tmpSerialPort.getOutputStream();
143 } catch (IOException e) {
144 logger.info("Cannot open output stream");
147 // should not throw, create a dummy return value
148 return new ByteArrayOutputStream(0);
151 // disable NonNullByDefault for this function, legacy interface List<String>
152 @NonNullByDefault({})
154 public List<String> portIdentifiers() {
155 final @Nullable SerialPortManager tmpSerialPortManager = serialPortManager;
156 if (tmpSerialPortManager == null) {
157 return Collections.emptyList();
159 // typecast only required to avoid warning about less-annotated type
160 return (List<String>) tmpSerialPortManager.getIdentifiers().map(SerialPortIdentifier::getName)
161 .collect(Collectors.toList());
165 public int baudRate() throws IOException {
166 final @Nullable SerialPort tmpSerialPort = serialPort;
167 if (tmpSerialPort == null) {
168 throw new IOException("Port not available");
170 return tmpSerialPort.getBaudRate();
174 public void setSerialPortParams(final int baudrate, final int databits, @Nullable StopBits stopbits,
175 @Nullable Parity parity) throws IOException {
176 final @Nullable SerialPort tmpSerialPort = serialPort;
177 if (tmpSerialPort == null) {
178 throw new IOException("Port not available");
180 if ((stopbits == null) || (parity == null)) {
181 throw new IOException("Invalid parameters");
184 tmpSerialPort.setSerialPortParams(baudrate, databits, stopbits.value(), parity.value());
185 } catch (final UnsupportedCommOperationException e) {
186 throw new IOException("Setting serial port parameters for " + tmpSerialPort.getName() + " failed", e);
191 public void setFlowControlMode(@Nullable FlowControl mode) throws IOException {
192 final @Nullable SerialPort tmpSerialPort = serialPort;
193 if (tmpSerialPort == null) {
194 throw new IOException("Port not available");
197 throw new IOException("Invalid parameters");
199 if (mode == FlowControl.None) {
201 tmpSerialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
202 } catch (final UnsupportedCommOperationException e) {
203 throw new IOException("Setting flow control parameters for " + tmpSerialPort.getName() + " failed", e);
206 logger.warn("Unknown FlowControl mode {}", mode);
207 throw new IOException("Invalid flow mode");