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