2 * Copyright (c) 2010-2023 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.oceanic.internal.handler;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.text.SimpleDateFormat;
19 import java.util.Arrays;
20 import java.util.Enumeration;
22 import org.apache.commons.lang3.StringUtils;
23 import org.openhab.binding.oceanic.internal.SerialOceanicBindingConfiguration;
24 import org.openhab.binding.oceanic.internal.Throttler;
25 import org.openhab.core.thing.Thing;
26 import org.openhab.core.thing.ThingStatus;
27 import org.openhab.core.thing.ThingStatusDetail;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import gnu.io.CommPortIdentifier;
32 import gnu.io.NoSuchPortException;
33 import gnu.io.PortInUseException;
34 import gnu.io.RXTXCommDriver;
35 import gnu.io.SerialPort;
36 import gnu.io.UnsupportedCommOperationException;
39 * The {@link SerialOceanicThingHandler} implements {@link OceanicThingHandler} for an Oceanic water softener that is
40 * directly connected to a serial port of the openHAB host
42 * @author Karel Goderis - Initial contribution
44 public class SerialOceanicThingHandler extends OceanicThingHandler {
46 private static final long REQUEST_TIMEOUT = 10000;
47 private static final int BAUD = 19200;
49 private final Logger logger = LoggerFactory.getLogger(SerialOceanicThingHandler.class);
51 private SerialPort serialPort;
52 private CommPortIdentifier portId;
53 private InputStream inputStream;
54 private OutputStream outputStream;
55 private SerialPortReader readerThread;
57 public SerialOceanicThingHandler(Thing thing) {
62 public void initialize() {
65 SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
67 if (serialPort == null && config.port != null) {
70 RXTXCommDriver rxtxCommDriver = new RXTXCommDriver();
71 rxtxCommDriver.initialize();
72 CommPortIdentifier.addPortName(config.port, CommPortIdentifier.PORT_RAW, rxtxCommDriver);
73 portId = CommPortIdentifier.getPortIdentifier(config.port);
74 } catch (NoSuchPortException e) {
75 logger.error("An exception occurred while setting up serial port '{}' : '{}'", config.port,
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
78 "Could not setup serial port " + serialPort + ": " + e.getMessage());
85 serialPort = portId.open(this.getThing().getUID().getBindingId(), 2000);
86 } catch (PortInUseException e) {
87 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
88 "Could not open serial port " + serialPort + ": " + e.getMessage());
93 inputStream = serialPort.getInputStream();
94 } catch (IOException e) {
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
96 "Could not open serial port " + serialPort + ": " + e.getMessage());
100 serialPort.notifyOnDataAvailable(true);
103 serialPort.setSerialPortParams(BAUD, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
104 SerialPort.PARITY_NONE);
105 serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
106 } catch (UnsupportedCommOperationException e) {
107 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
108 "Could not configure serial port " + serialPort + ": " + e.getMessage());
113 outputStream = serialPort.getOutputStream();
114 updateStatus(ThingStatus.ONLINE);
115 } catch (IOException e) {
116 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
117 "Could not communicate with the serial port " + serialPort + ": " + e.getMessage());
121 readerThread = new SerialPortReader(inputStream);
122 readerThread.start();
124 StringBuilder sb = new StringBuilder();
125 @SuppressWarnings("rawtypes")
126 Enumeration portList = CommPortIdentifier.getPortIdentifiers();
127 while (portList.hasMoreElements()) {
128 CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
129 if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
130 sb.append(id.getName() + "\n");
133 logger.error("Serial port '{}' could not be found. Available ports are:\n {}", config.port, sb);
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
140 public void dispose() {
141 if (readerThread != null) {
143 readerThread.interrupt();
145 } catch (InterruptedException e) {
146 logger.error("An exception occurred while interrupting the serial port reader thread : {}",
151 if (inputStream != null) {
154 } catch (IOException e) {
155 logger.debug("Error while closing the input stream: {}", e.getMessage());
158 if (outputStream != null) {
160 outputStream.close();
161 } catch (IOException e) {
162 logger.debug("Error while closing the output stream: {}", e.getMessage());
165 if (serialPort != null) {
178 protected String requestResponse(String commandAsString) {
179 synchronized (this) {
180 SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
182 Throttler.lock(config.port);
184 lastLineReceived = "";
185 String request = commandAsString + "\r";
186 String response = null;
189 if (logger.isTraceEnabled()) {
190 logger.trace("Requesting : {} ('{}')", request, request.getBytes());
192 outputStream.write(request.getBytes());
193 outputStream.flush();
194 } catch (IOException e) {
195 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
196 "Error writing '" + request + "' to serial port " + config.port + " : " + e.getMessage());
199 long timeStamp = System.currentTimeMillis();
200 while (lastLineReceived.equals("")) {
203 } catch (InterruptedException e) {
204 logger.error("An exception occurred while putting the thread to sleep: {}", e.getMessage());
206 if (System.currentTimeMillis() - timeStamp > REQUEST_TIMEOUT) {
207 logger.warn("A timeout occurred while requesting data from the water softener");
208 readerThread.reset();
212 response = lastLineReceived;
214 Throttler.unlock(config.port);
220 public class SerialPortReader extends Thread {
222 private boolean interrupted = false;
223 private InputStream inputStream;
226 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS");
228 public SerialPortReader(InputStream in) {
229 this.inputStream = in;
230 this.setName("SerialPortReader-" + getThing().getUID());
233 public void reset() {
234 logger.trace("Resetting the SerialPortReader");
239 public void interrupt() {
240 logger.trace("Interrupting the SerialPortReader");
247 logger.trace("Starting the serial port reader");
249 byte[] dataBuffer = new byte[bufferSize];
250 byte[] tmpData = new byte[bufferSize];
253 final byte lineFeed = (byte) '\n';
254 final byte carriageReturn = (byte) '\r';
255 final byte nullChar = (byte) '\0';
261 while (!interrupted) {
262 logger.trace("Reading the inputStream");
264 if ((len = inputStream.read(tmpData)) > -1) {
265 if (logger.isTraceEnabled()) {
266 StringBuilder sb = new StringBuilder();
267 for (int i = 0; i < len; i++) {
268 sb.append(String.format("%02X ", tmpData[i]));
270 logger.trace("Read {} bytes : {}", len, sb.toString());
272 for (int i = 0; i < len; i++) {
273 if (logger.isTraceEnabled()) {
274 logger.trace("Byte {} equals '{}' (hex '{}')", i, new String(new byte[] { tmpData[i] }),
275 String.format("%02X", tmpData[i]));
278 if (tmpData[i] != lineFeed && tmpData[i] != carriageReturn && tmpData[i] != nullChar) {
279 dataBuffer[index++] = tmpData[i];
280 if (logger.isTraceEnabled()) {
281 logger.trace("dataBuffer[{}] set to '{}'(hex '{}')", index - 1,
282 new String(new byte[] { dataBuffer[index - 1] }),
283 String.format("%02X", dataBuffer[index - 1]));
287 if (i > 0 && (tmpData[i] == lineFeed || tmpData[i] == carriageReturn
288 || tmpData[i] == nullChar)) {
290 if (logger.isTraceEnabled()) {
291 logger.trace("The resulting line is '{}'",
292 new String(Arrays.copyOf(dataBuffer, index)));
294 line = StringUtils.chomp(new String(Arrays.copyOf(dataBuffer, index)));
295 line = line.replace(",", ".");
299 lastLineReceived = line;
304 if (index == bufferSize) {
312 } catch (InterruptedException e) {
316 } catch (Exception e) {
317 logger.error("An exception occurred while reading serial port : {}", e.getMessage(), e);
318 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());