]> git.basschouten.com Git - openhab-addons.git/blob
b2444bcbcba02af78b59579c74ec505c6c28d06f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.oceanic.internal.handler;
14
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;
21
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;
30
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;
37
38 /**
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
41  *
42  * @author Karel Goderis - Initial contribution
43  */
44 public class SerialOceanicThingHandler extends OceanicThingHandler {
45
46     private static final long REQUEST_TIMEOUT = 10000;
47     private static final int BAUD = 19200;
48
49     private final Logger logger = LoggerFactory.getLogger(SerialOceanicThingHandler.class);
50
51     private SerialPort serialPort;
52     private CommPortIdentifier portId;
53     private InputStream inputStream;
54     private OutputStream outputStream;
55     private SerialPortReader readerThread;
56
57     public SerialOceanicThingHandler(Thing thing) {
58         super(thing);
59     }
60
61     @Override
62     public void initialize() {
63         super.initialize();
64
65         SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
66
67         if (serialPort == null && config.port != null) {
68             if (portId == null) {
69                 try {
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,
76                             e.getMessage(), e);
77                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
78                             "Could not setup serial port " + serialPort + ": " + e.getMessage());
79                     return;
80                 }
81             }
82
83             if (portId != null) {
84                 try {
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());
89                     return;
90                 }
91
92                 try {
93                     inputStream = serialPort.getInputStream();
94                 } catch (IOException e) {
95                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
96                             "Could not open serial port " + serialPort + ": " + e.getMessage());
97                     return;
98                 }
99
100                 serialPort.notifyOnDataAvailable(true);
101
102                 try {
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());
109                     return;
110                 }
111
112                 try {
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());
118                     return;
119                 }
120
121                 readerThread = new SerialPortReader(inputStream);
122                 readerThread.start();
123             } else {
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");
131                     }
132                 }
133                 logger.error("Serial port '{}' could not be found. Available ports are:\n {}", config.port, sb);
134                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
135             }
136         }
137     }
138
139     @Override
140     public void dispose() {
141         if (readerThread != null) {
142             try {
143                 readerThread.interrupt();
144                 readerThread.join();
145             } catch (InterruptedException e) {
146                 logger.error("An exception occurred while interrupting the serial port reader thread : {}",
147                         e.getMessage(), e);
148             }
149         }
150
151         if (inputStream != null) {
152             try {
153                 inputStream.close();
154             } catch (IOException e) {
155                 logger.debug("Error while closing the input stream: {}", e.getMessage());
156             }
157         }
158         if (outputStream != null) {
159             try {
160                 outputStream.close();
161             } catch (IOException e) {
162                 logger.debug("Error while closing the output stream: {}", e.getMessage());
163             }
164         }
165         if (serialPort != null) {
166             serialPort.close();
167         }
168
169         readerThread = null;
170         inputStream = null;
171         outputStream = null;
172         serialPort = null;
173
174         super.dispose();
175     }
176
177     @Override
178     protected String requestResponse(String commandAsString) {
179         synchronized (this) {
180             SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
181
182             Throttler.lock(config.port);
183
184             lastLineReceived = "";
185             String request = commandAsString + "\r";
186             String response = null;
187
188             try {
189                 if (logger.isTraceEnabled()) {
190                     logger.trace("Requesting : {} ('{}')", request, request.getBytes());
191                 }
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());
197             }
198
199             long timeStamp = System.currentTimeMillis();
200             while (lastLineReceived.equals("")) {
201                 try {
202                     Thread.sleep(50);
203                 } catch (InterruptedException e) {
204                     logger.error("An exception occurred while putting the thread to sleep: {}", e.getMessage());
205                 }
206                 if (System.currentTimeMillis() - timeStamp > REQUEST_TIMEOUT) {
207                     logger.warn("A timeout occurred while requesting data from the water softener");
208                     readerThread.reset();
209                     break;
210                 }
211             }
212             response = lastLineReceived;
213
214             Throttler.unlock(config.port);
215
216             return response;
217         }
218     }
219
220     public class SerialPortReader extends Thread {
221
222         private boolean interrupted = false;
223         private InputStream inputStream;
224         int index = 0;
225
226         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS");
227
228         public SerialPortReader(InputStream in) {
229             this.inputStream = in;
230             this.setName("SerialPortReader-" + getThing().getUID());
231         }
232
233         public void reset() {
234             logger.trace("Resetting the SerialPortReader");
235             index = 0;
236         }
237
238         @Override
239         public void interrupt() {
240             logger.trace("Interrupting the SerialPortReader");
241             interrupted = true;
242             super.interrupt();
243         }
244
245         @Override
246         public void run() {
247             logger.trace("Starting the serial port reader");
248
249             byte[] dataBuffer = new byte[bufferSize];
250             byte[] tmpData = new byte[bufferSize];
251             String line;
252
253             final byte lineFeed = (byte) '\n';
254             final byte carriageReturn = (byte) '\r';
255             final byte nullChar = (byte) '\0';
256
257             long sleep = 10;
258             int len = -1;
259
260             try {
261                 while (!interrupted) {
262                     logger.trace("Reading the inputStream");
263
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]));
269                             }
270                             logger.trace("Read {} bytes : {}", len, sb.toString());
271                         }
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]));
276                             }
277
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]));
284                                 }
285                             }
286
287                             if (i > 0 && (tmpData[i] == lineFeed || tmpData[i] == carriageReturn
288                                     || tmpData[i] == nullChar)) {
289                                 if (index > 0) {
290                                     if (logger.isTraceEnabled()) {
291                                         logger.trace("The resulting line is '{}'",
292                                                 new String(Arrays.copyOf(dataBuffer, index)));
293                                     }
294                                     line = StringUtils.chomp(new String(Arrays.copyOf(dataBuffer, index)));
295                                     line = line.replace(",", ".");
296                                     line = line.trim();
297                                     index = 0;
298
299                                     lastLineReceived = line;
300                                     break;
301                                 }
302                             }
303
304                             if (index == bufferSize) {
305                                 index = 0;
306                             }
307                         }
308                     }
309
310                     try {
311                         Thread.sleep(sleep);
312                     } catch (InterruptedException e) {
313                         // Move on silently
314                     }
315                 }
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());
319             }
320         }
321     }
322 }