]> git.basschouten.com Git - openhab-addons.git/blob
f4ddbbe7a4e5b3d1f46d9549065d21e4ee110cba
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.rme.internal.handler;
14
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;
23
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;
39
40 /**
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
47  *
48  * @author Karel Goderis - Initial contribution
49  */
50 public abstract class SerialThingHandler extends BaseThingHandler implements SerialPortEventListener {
51
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";
56
57     private final Logger logger = LoggerFactory.getLogger(SerialThingHandler.class);
58
59     private SerialPort serialPort;
60     private SerialPortIdentifier portId;
61     private final SerialPortManager serialPortManager;
62     private InputStream inputStream;
63     private OutputStream outputStream;
64     protected int baud;
65     protected String port;
66     protected int bufferSize;
67     protected long sleep = 100;
68     protected long interval = 0;
69     private Thread readerThread = null;
70
71     public SerialThingHandler(Thing thing, SerialPortManager serialPortManager) {
72         super(thing);
73         this.serialPortManager = serialPortManager;
74     }
75
76     /**
77      * Called when data is received on the serial port
78      *
79      * @param line the received data as a String
80      *
81      **/
82     public abstract void onDataReceived(String line);
83
84     /**
85      * Write data to the serial port
86      *
87      * @param msg the received data as a String
88      *
89      **/
90     public void writeString(String msg) {
91         String port = (String) this.getConfig().get(PORT);
92
93         try {
94             // write string to serial port
95             outputStream.write(msg.getBytes());
96             outputStream.flush();
97         } catch (IOException e) {
98             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
99                     "Error writing '" + msg + "' to serial port " + port + " : " + e.getMessage());
100         }
101     }
102
103     @Override
104     public void serialEvent(SerialPortEvent arg0) {
105         try {
106             /*
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
111              * directly in java
112              */
113             logger.trace("RXTX library CPU load workaround, sleep forever");
114             Thread.sleep(Long.MAX_VALUE);
115         } catch (InterruptedException e) {
116         }
117     }
118
119     @Override
120     public void dispose() {
121         logger.debug("Disposing serial thing handler.");
122         if (serialPort != null) {
123             serialPort.removeEventListener();
124         }
125         if (readerThread != null) {
126             try {
127                 readerThread.interrupt();
128                 readerThread.join();
129             } catch (InterruptedException e) {
130             }
131         }
132         if (inputStream != null) {
133             try {
134                 inputStream.close();
135             } catch (IOException e) {
136                 logger.debug("Error while closing the input stream: {}", e.getMessage());
137             }
138         }
139         if (outputStream != null) {
140             try {
141                 outputStream.close();
142             } catch (IOException e) {
143                 logger.debug("Error while closing the output stream: {}", e.getMessage());
144             }
145         }
146         if (serialPort != null) {
147             serialPort.close();
148         }
149         readerThread = null;
150         inputStream = null;
151         outputStream = null;
152         serialPort = null;
153     }
154
155     @Override
156     public void initialize() {
157         logger.debug("Initializing serial thing handler.");
158
159         if (baud == 0) {
160             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Baud rate is not configured");
161             return;
162         } else if (port == null || port.isEmpty()) {
163             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured");
164             return;
165         }
166
167         portId = serialPortManager.getIdentifier(port);
168
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,
173                     availablePorts);
174             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
175             return;
176
177         }
178
179         // initialize serial port
180         try {
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());
185             return;
186         }
187
188         try {
189             inputStream = serialPort.getInputStream();
190         } catch (IOException e) {
191             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
192                     "Could not open serial port " + port + ": " + e.getMessage());
193             return;
194         }
195
196         try {
197             serialPort.addEventListener(this);
198         } catch (TooManyListenersException e) {
199             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
200                     "Could not open serial port " + port + ": " + e.getMessage());
201             return;
202         }
203
204         // activate the DATA_AVAILABLE notifier
205         serialPort.notifyOnDataAvailable(true);
206
207         try {
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());
213             return;
214         }
215
216         try {
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());
223             return;
224         }
225
226         readerThread = new SerialPortReader(inputStream);
227         readerThread.start();
228     }
229
230     @Override
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());
234     }
235
236     public class SerialPortReader extends Thread {
237
238         private static final byte LINE_FEED = (byte) '\n';
239         private static final byte CARRIAGE_RETURN = (byte) '\r';
240
241         private boolean interrupted = false;
242         private InputStream inputStream;
243         private boolean hasInterval = interval == 0 ? false : true;
244
245         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS");
246
247         public SerialPortReader(InputStream in) {
248             this.inputStream = in;
249             this.setName("SerialPortReader-" + getThing().getUID());
250         }
251
252         @Override
253         public void interrupt() {
254             interrupted = true;
255             super.interrupt();
256         }
257
258         @Override
259         public void run() {
260             byte[] dataBuffer = new byte[bufferSize];
261             byte[] tmpData = new byte[bufferSize];
262             int index = 0;
263             int len = -1;
264             boolean foundStart = false;
265
266             logger.debug("Serial port listener for serial port '{}' has started", port);
267
268             try {
269                 while (!interrupted) {
270                     long startOfRead = System.currentTimeMillis();
271
272                     if ((len = inputStream.read(tmpData)) > 0) {
273                         foundStart = false;
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) {
278                                         index = 0;
279                                         foundStart = true;
280                                     }
281                                 }
282                             }
283
284                             if (tmpData[i] != LINE_FEED && tmpData[i] != CARRIAGE_RETURN) {
285                                 dataBuffer[index++] = tmpData[i];
286                             }
287
288                             if (tmpData[i] == LINE_FEED || tmpData[i] == CARRIAGE_RETURN) {
289                                 if (index > 1) {
290                                     if (hasInterval) {
291                                         if (foundStart) {
292                                             onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
293                                             break;
294                                         } else {
295                                             index = 0;
296                                             foundStart = true;
297                                         }
298                                     } else {
299                                         onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
300                                         index = 0;
301                                     }
302                                 }
303                             }
304
305                             if (index == bufferSize) {
306                                 if (!hasInterval) {
307                                     onDataReceived(new String(Arrays.copyOf(dataBuffer, index)));
308                                 }
309                                 index = 0;
310                             }
311                         }
312
313                         if (hasInterval) {
314                             try {
315                                 Thread.sleep(Math.max(interval - (System.currentTimeMillis() - startOfRead), 0));
316                             } catch (InterruptedException e) {
317                             }
318                         }
319                     }
320
321                     try {
322                         Thread.sleep(sleep);
323                     } catch (InterruptedException e) {
324                     }
325
326                 }
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);
331             }
332
333             logger.debug("Serial port listener for serial port '{}' has stopped", port);
334         }
335     }
336 }