]> git.basschouten.com Git - openhab-addons.git/blob
38bbe0061d6cb23db5458367176649b958b56579
[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.Objects;
21 import java.util.TooManyListenersException;
22 import java.util.stream.Collectors;
23
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.openhab.core.util.StringUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * The {@link SerialOceanicThingHandler} implements {@link OceanicThingHandler} for an Oceanic water softener that is
42  * directly connected to a serial port of the openHAB host
43  *
44  * @author Karel Goderis - Initial contribution
45  */
46 public class SerialOceanicThingHandler extends OceanicThingHandler implements SerialPortEventListener {
47
48     private static final long REQUEST_TIMEOUT = 10000;
49     private static final int BAUD = 19200;
50
51     private final Logger logger = LoggerFactory.getLogger(SerialOceanicThingHandler.class);
52
53     private final SerialPortManager serialPortManager;
54     private SerialPort serialPort;
55     private InputStream inputStream;
56     private OutputStream outputStream;
57     private SerialPortReader readerThread;
58
59     public SerialOceanicThingHandler(Thing thing, SerialPortManager serialPortManager) {
60         super(thing);
61         this.serialPortManager = serialPortManager;
62     }
63
64     @Override
65     public void initialize() {
66         super.initialize();
67
68         SerialOceanicBindingConfiguration config = getConfigAs(SerialOceanicBindingConfiguration.class);
69
70         if (serialPort == null && config.port != null) {
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 ("".equals(lastLineReceived)) {
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 = Objects.requireNonNull(
277                                             StringUtils.chomp(new String(Arrays.copyOf(dataBuffer, index))));
278                                     line = line.replace(",", ".");
279                                     line = line.trim();
280                                     index = 0;
281
282                                     lastLineReceived = line;
283                                     break;
284                                 }
285                             }
286
287                             if (index == bufferSize) {
288                                 index = 0;
289                             }
290                         }
291                     }
292
293                     try {
294                         Thread.sleep(sleep);
295                     } catch (InterruptedException e) {
296                         // Move on silently
297                     }
298                 }
299             } catch (Exception e) {
300                 logger.error("An exception occurred while reading serial port  : {}", e.getMessage(), e);
301                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
302             }
303         }
304     }
305 }