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.rme.internal.handler;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.InterruptedIOException;
18 import java.io.OutputStream;
19 import java.text.SimpleDateFormat;
20 import java.util.Arrays;
21 import java.util.TooManyListenersException;
22 import java.util.stream.Collectors;
24 import org.openhab.core.io.transport.serial.PortInUseException;
25 import org.openhab.core.io.transport.serial.SerialPort;
26 import org.openhab.core.io.transport.serial.SerialPortEvent;
27 import org.openhab.core.io.transport.serial.SerialPortEventListener;
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.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link SerialThingHandler} is responsible for handling commands, which
42 * are sent to one of the channels. Thing Handler classes that use serial
43 * communications can extend/implement this class, but must make sure they
44 * supplement the configuration parameters into the {@link SerialConfiguration}
45 * Configuration of the underlying Thing, if not already specified in the
46 * thing.xml definition
48 * @author Karel Goderis - Initial contribution
50 public abstract class SerialThingHandler extends BaseThingHandler implements SerialPortEventListener {
52 // List of all Configuration parameters
53 public static final String PORT = "port";
54 public static final String BAUD_RATE = "baud";
55 public static final String BUFFER_SIZE = "buffer";
57 private final Logger logger = LoggerFactory.getLogger(SerialThingHandler.class);
59 private SerialPort serialPort;
60 private SerialPortIdentifier portId;
61 private final SerialPortManager serialPortManager;
62 private InputStream inputStream;
63 private OutputStream outputStream;
65 protected String port;
66 protected int bufferSize;
67 protected long sleep = 100;
68 protected long interval = 0;
69 private Thread readerThread = null;
71 public SerialThingHandler(Thing thing, SerialPortManager serialPortManager) {
73 this.serialPortManager = serialPortManager;
77 * Called when data is received on the serial port
79 * @param line the received data as a String
82 public abstract void onDataReceived(String line);
85 * Write data to the serial port
87 * @param msg the received data as a String
90 public void writeString(String msg) {
91 String port = (String) this.getConfig().get(PORT);
94 // write string to serial port
95 outputStream.write(msg.getBytes());
97 } catch (IOException e) {
98 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
99 "Error writing '" + msg + "' to serial port " + port + " : " + e.getMessage());
104 public void serialEvent(SerialPortEvent arg0) {
107 * The short select() timeout in the native code of the nrjavaserial lib does cause a high CPU load, despite
108 * the fix published (see https://github.com/NeuronRobotics/nrjavaserial/issues/22). A workaround for this
109 * problem is to (1) put the Thread initiated by the nrjavaserial library to sleep forever, so that the
110 * number of calls to the select() function gets minimized, and (2) implement a Threaded streamreader
113 logger.trace("RXTX library CPU load workaround, sleep forever");
114 Thread.sleep(Long.MAX_VALUE);
115 } catch (InterruptedException e) {
120 public void dispose() {
121 logger.debug("Disposing serial thing handler.");
122 if (serialPort != null) {
123 serialPort.removeEventListener();
125 if (readerThread != null) {
127 readerThread.interrupt();
129 } catch (InterruptedException e) {
132 if (inputStream != null) {
135 } catch (IOException e) {
136 logger.debug("Error while closing the input stream: {}", e.getMessage());
139 if (outputStream != null) {
141 outputStream.close();
142 } catch (IOException e) {
143 logger.debug("Error while closing the output stream: {}", e.getMessage());
146 if (serialPort != null) {
156 public void initialize() {
157 logger.debug("Initializing serial thing handler.");
160 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Baud rate is not configured");
162 } else if (port == null || port.isEmpty()) {
163 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured");
167 portId = serialPortManager.getIdentifier(port);
169 if (portId == null) {
170 String availablePorts = serialPortManager.getIdentifiers().map(id -> id.getName())
171 .collect(Collectors.joining(System.lineSeparator()));
172 String description = String.format("Serial port '%s' could not be found. Available ports are:%n%s", port,
174 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
179 // initialize serial port
181 serialPort = portId.open("openHAB", 2000);
182 } catch (PortInUseException e) {
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
184 "Could not open serial port " + port + ": " + e.getMessage());
189 inputStream = serialPort.getInputStream();
190 } catch (IOException e) {
191 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
192 "Could not open serial port " + port + ": " + e.getMessage());
197 serialPort.addEventListener(this);
198 } catch (TooManyListenersException e) {
199 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
200 "Could not open serial port " + port + ": " + e.getMessage());
204 // activate the DATA_AVAILABLE notifier
205 serialPort.notifyOnDataAvailable(true);
208 // set port parameters
209 serialPort.setSerialPortParams(baud, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
210 } catch (UnsupportedCommOperationException e) {
211 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
212 "Could not configure serial port " + port + ": " + e.getMessage());
217 // get the output stream
218 outputStream = serialPort.getOutputStream();
219 updateStatus(ThingStatus.ONLINE);
220 } catch (IOException e) {
221 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
222 "Could not communicate with the serial port " + port + ": " + e.getMessage());
226 readerThread = new SerialPortReader(inputStream);
227 readerThread.start();
231 public void handleCommand(ChannelUID channelUID, Command command) {
232 // by default, we write anything we received as a string to the serial port
233 writeString(command.toString());
236 public class SerialPortReader extends Thread {
238 private static final byte LINE_FEED = (byte) '\n';
239 private static final byte CARRIAGE_RETURN = (byte) '\r';
241 private boolean interrupted = false;
242 private InputStream inputStream;
243 private boolean hasInterval = interval == 0 ? false : true;
245 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS");
247 public SerialPortReader(InputStream in) {
248 this.inputStream = in;
249 this.setName("SerialPortReader-" + getThing().getUID());
253 public void interrupt() {
260 byte[] dataBuffer = new byte[bufferSize];
261 byte[] tmpData = new byte[bufferSize];
264 boolean foundStart = false;
266 logger.debug("Serial port listener for serial port '{}' has started", port);
269 while (!interrupted) {
270 long startOfRead = System.currentTimeMillis();
272 if ((len = inputStream.read(tmpData)) > 0) {
274 for (int i = 0; i < len; i++) {
275 if (hasInterval && i > 0) {
276 if (tmpData[i] != LINE_FEED && tmpData[i] != CARRIAGE_RETURN) {
277 if (tmpData[i - 1] == LINE_FEED || tmpData[i - 1] == CARRIAGE_RETURN) {
284 if (tmpData[i] != LINE_FEED && tmpData[i] != CARRIAGE_RETURN) {
285 dataBuffer[index++] = tmpData[i];
288 if (tmpData[i] == LINE_FEED || tmpData[i] == CARRIAGE_RETURN) {
292 onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
299 onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
305 if (index == bufferSize) {
307 onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
315 Thread.sleep(Math.max(interval - (System.currentTimeMillis() - startOfRead), 0));
316 } catch (InterruptedException e) {
323 } catch (InterruptedException e) {
327 } catch (InterruptedIOException e) {
328 Thread.currentThread().interrupt();
329 } catch (IOException e) {
330 logger.error("An exception occurred while reading serial port '{}' : {}", port, e.getMessage(), e);
333 logger.debug("Serial port listener for serial port '{}' has stopped", port);