]> git.basschouten.com Git - openhab-addons.git/blob
125747e2bb356c8ff40d95f4d72113d0508b140a
[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.TooManyListenersException;
21 import java.util.stream.Collectors;
22
23 import org.apache.commons.lang3.StringUtils;
24 import org.openhab.binding.oceanic.internal.SerialOceanicBindingConfiguration;
25 import org.openhab.binding.oceanic.internal.Throttler;
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.SerialPortEvent;
29 import org.openhab.core.io.transport.serial.SerialPortEventListener;
30 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
31 import org.openhab.core.io.transport.serial.SerialPortManager;
32 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The {@link SerialOceanicThingHandler} implements {@link OceanicThingHandler} for an Oceanic water softener that is
41  * directly connected to a serial port of the openHAB host
42  *
43  * @author Karel Goderis - Initial contribution
44  */
45 public class SerialOceanicThingHandler extends OceanicThingHandler implements SerialPortEventListener {
46
47     private static final long REQUEST_TIMEOUT = 10000;
48     private static final int BAUD = 19200;
49
50     private final Logger logger = LoggerFactory.getLogger(SerialOceanicThingHandler.class);
51
52     private final SerialPortManager serialPortManager;
53     private SerialPort serialPort;
54     private InputStream inputStream;
55     private OutputStream outputStream;
56     private SerialPortReader readerThread;
57
58     public SerialOceanicThingHandler(Thing thing, SerialPortManager serialPortManager) {
59         super(thing);
60         this.serialPortManager = serialPortManager;
61     }
62
63     @Override
64     public void initialize() {
65         super.initialize();
66
67         SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
68
69         if (serialPort == null && config.port != null) {
70
71             SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(config.port);
72
73             if (portIdentifier == null) {
74                 String availablePorts = serialPortManager.getIdentifiers().map(id -> id.getName())
75                         .collect(Collectors.joining(System.lineSeparator()));
76                 String description = String.format("Serial port '%s' could not be found. Available ports are:%n%s",
77                         config.port, availablePorts);
78                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, description);
79                 return;
80             }
81
82             try {
83                 logger.info("Connecting to the Oceanic water softener using {}.", config.port);
84                 serialPort = portIdentifier.open(this.getThing().getUID().getBindingId(), 2000);
85
86                 serialPort.setSerialPortParams(BAUD, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
87                         SerialPort.PARITY_NONE);
88                 serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
89                 serialPort.enableReceiveThreshold(1);
90                 serialPort.disableReceiveTimeout();
91
92                 inputStream = serialPort.getInputStream();
93                 outputStream = serialPort.getOutputStream();
94
95                 serialPort.addEventListener(this);
96                 serialPort.notifyOnDataAvailable(true);
97
98                 readerThread = new SerialPortReader(inputStream);
99                 readerThread.start();
100
101                 updateStatus(ThingStatus.ONLINE);
102
103             } catch (PortInUseException portInUseException) {
104                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port in use: " + config.port);
105             } catch (UnsupportedCommOperationException | IOException e) {
106                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication error");
107             } catch (TooManyListenersException e) {
108                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
109                         "Too many listeners to serial port.");
110             }
111         }
112     }
113
114     @Override
115     public void dispose() {
116         if (readerThread != null) {
117             try {
118                 readerThread.interrupt();
119                 readerThread.join();
120             } catch (InterruptedException e) {
121                 logger.error("An exception occurred while interrupting the serial port reader thread : {}",
122                         e.getMessage(), e);
123             }
124         }
125
126         if (inputStream != null) {
127             try {
128                 inputStream.close();
129             } catch (IOException e) {
130                 logger.debug("Error while closing the input stream: {}", e.getMessage());
131             }
132         }
133         if (outputStream != null) {
134             try {
135                 outputStream.close();
136             } catch (IOException e) {
137                 logger.debug("Error while closing the output stream: {}", e.getMessage());
138             }
139         }
140         if (serialPort != null) {
141             serialPort.close();
142         }
143
144         readerThread = null;
145         inputStream = null;
146         outputStream = null;
147         serialPort = null;
148
149         super.dispose();
150     }
151
152     @Override
153     protected String requestResponse(String commandAsString) {
154         synchronized (this) {
155             SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
156
157             Throttler.lock(config.port);
158
159             lastLineReceived = "";
160             String request = commandAsString + "\r";
161             String response = null;
162
163             try {
164                 if (logger.isTraceEnabled()) {
165                     logger.trace("Requesting : {} ('{}')", request, request.getBytes());
166                 }
167                 outputStream.write(request.getBytes());
168                 outputStream.flush();
169             } catch (IOException e) {
170                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
171                         "Error writing '" + request + "' to serial port " + config.port + " : " + e.getMessage());
172             }
173
174             long timeStamp = System.currentTimeMillis();
175             while (lastLineReceived.equals("")) {
176                 try {
177                     Thread.sleep(50);
178                 } catch (InterruptedException e) {
179                     logger.error("An exception occurred while putting the thread to sleep: {}", e.getMessage());
180                 }
181                 if (System.currentTimeMillis() - timeStamp > REQUEST_TIMEOUT) {
182                     logger.warn("A timeout occurred while requesting data from the water softener");
183                     readerThread.reset();
184                     break;
185                 }
186             }
187             response = lastLineReceived;
188
189             Throttler.unlock(config.port);
190
191             return response;
192         }
193     }
194
195     @Override
196     public void serialEvent(SerialPortEvent serialPortEvent) {
197         if (logger.isTraceEnabled()) {
198             logger.trace("Received a serial port event : {}", serialPortEvent.getEventType());
199         }
200     }
201
202     public class SerialPortReader extends Thread {
203
204         private boolean interrupted = false;
205         private InputStream inputStream;
206         int index = 0;
207
208         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,SSS");
209
210         public SerialPortReader(InputStream in) {
211             this.inputStream = in;
212             this.setName("SerialPortReader-" + getThing().getUID());
213         }
214
215         public void reset() {
216             logger.trace("Resetting the SerialPortReader");
217             index = 0;
218         }
219
220         @Override
221         public void interrupt() {
222             logger.trace("Interrupting the SerialPortReader");
223             interrupted = true;
224             super.interrupt();
225         }
226
227         @Override
228         public void run() {
229             logger.trace("Starting the serial port reader");
230
231             byte[] dataBuffer = new byte[bufferSize];
232             byte[] tmpData = new byte[bufferSize];
233             String line;
234
235             final byte lineFeed = (byte) '\n';
236             final byte carriageReturn = (byte) '\r';
237             final byte nullChar = (byte) '\0';
238
239             long sleep = 10;
240             int len = -1;
241
242             try {
243                 while (!interrupted) {
244                     logger.trace("Reading the inputStream");
245
246                     if ((len = inputStream.read(tmpData)) > -1) {
247                         if (logger.isTraceEnabled()) {
248                             StringBuilder sb = new StringBuilder();
249                             for (int i = 0; i < len; i++) {
250                                 sb.append(String.format("%02X ", tmpData[i]));
251                             }
252                             logger.trace("Read {} bytes : {}", len, sb.toString());
253                         }
254                         for (int i = 0; i < len; i++) {
255                             if (logger.isTraceEnabled()) {
256                                 logger.trace("Byte {} equals '{}' (hex '{}')", i, new String(new byte[] { tmpData[i] }),
257                                         String.format("%02X", tmpData[i]));
258                             }
259
260                             if (tmpData[i] != lineFeed && tmpData[i] != carriageReturn && tmpData[i] != nullChar) {
261                                 dataBuffer[index++] = tmpData[i];
262                                 if (logger.isTraceEnabled()) {
263                                     logger.trace("dataBuffer[{}] set to '{}'(hex '{}')", index - 1,
264                                             new String(new byte[] { dataBuffer[index - 1] }),
265                                             String.format("%02X", dataBuffer[index - 1]));
266                                 }
267                             }
268
269                             if (i > 0 && (tmpData[i] == lineFeed || tmpData[i] == carriageReturn
270                                     || tmpData[i] == nullChar)) {
271                                 if (index > 0) {
272                                     if (logger.isTraceEnabled()) {
273                                         logger.trace("The resulting line is '{}'",
274                                                 new String(Arrays.copyOf(dataBuffer, index)));
275                                     }
276                                     line = StringUtils.chomp(new String(Arrays.copyOf(dataBuffer, index)));
277                                     line = line.replace(",", ".");
278                                     line = line.trim();
279                                     index = 0;
280
281                                     lastLineReceived = line;
282                                     break;
283                                 }
284                             }
285
286                             if (index == bufferSize) {
287                                 index = 0;
288                             }
289                         }
290                     }
291
292                     try {
293                         Thread.sleep(sleep);
294                     } catch (InterruptedException e) {
295                         // Move on silently
296                     }
297                 }
298             } catch (Exception e) {
299                 logger.error("An exception occurred while reading serial port  : {}", e.getMessage(), e);
300                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
301             }
302         }
303     }
304 }