]> git.basschouten.com Git - openhab-addons.git/blob
4f4d0731f0bfa4a6b0c3ee2d65b5f0ac969e4864
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.net.Socket;
19 import java.net.UnknownHostException;
20 import java.util.Arrays;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.apache.commons.lang.StringUtils;
25 import org.openhab.binding.oceanic.internal.NetworkOceanicBindingConfiguration;
26 import org.openhab.binding.oceanic.internal.Throttler;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * The {@link NetworkOceanicThingHandler} implements {@link OceanicThingHandler} for a Oceanic water softener that is
35  * reached using a socat TCP proxy
36  *
37  * @author Karel Goderis - Initial contribution
38  */
39 public class NetworkOceanicThingHandler extends OceanicThingHandler {
40
41     private static final int REQUEST_TIMEOUT = 3000;
42     private static final int RECONNECT_INTERVAL = 15;
43
44     private final Logger logger = LoggerFactory.getLogger(NetworkOceanicThingHandler.class);
45
46     private Socket socket;
47     private InputStream inputStream;
48     private OutputStream outputStream;
49     protected ScheduledFuture<?> reconnectJob;
50
51     public NetworkOceanicThingHandler(Thing thing) {
52         super(thing);
53     }
54
55     @Override
56     public void initialize() {
57         super.initialize();
58
59         NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
60
61         try {
62             socket = new Socket(config.ipAddress, config.portNumber);
63             socket.setSoTimeout(REQUEST_TIMEOUT);
64             outputStream = socket.getOutputStream();
65             inputStream = socket.getInputStream();
66             updateStatus(ThingStatus.ONLINE);
67         } catch (UnknownHostException e) {
68             logger.error("An exception occurred while resolving host {}:{} : '{}'", config.ipAddress, config.portNumber,
69                     e.getMessage());
70             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
71                     "Could not resolve host " + config.ipAddress + ": " + e.getMessage());
72         } catch (IOException e) {
73             logger.debug("An exception occurred while connecting to host {}:{} : '{}'", config.ipAddress,
74                     config.portNumber, e.getMessage());
75             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
76                     "Could not connect to host " + config.ipAddress + ": " + e.getMessage());
77             reconnectJob = scheduler.schedule(reconnectRunnable, RECONNECT_INTERVAL, TimeUnit.SECONDS);
78         }
79     }
80
81     @Override
82     public void dispose() {
83         NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
84
85         if (socket != null) {
86             try {
87                 socket.close();
88             } catch (IOException e) {
89                 logger.error("An exception occurred while disconnecting to host {}:{} : '{}'", config.ipAddress,
90                         config.portNumber, e.getMessage());
91             } finally {
92                 socket = null;
93                 outputStream = null;
94                 inputStream = null;
95             }
96         }
97
98         super.dispose();
99     }
100
101     @Override
102     protected String requestResponse(String commandAsString) {
103         synchronized (this) {
104             if (getThing().getStatus() == ThingStatus.ONLINE) {
105                 NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
106                 Throttler.lock(config.ipAddress);
107
108                 String request = commandAsString + "\r";
109
110                 byte[] dataBuffer = new byte[bufferSize];
111                 byte[] tmpData = new byte[bufferSize];
112                 String line;
113                 int len = -1;
114                 int index = 0;
115                 boolean sequenceFound = false;
116
117                 final byte lineFeed = (byte) '\n';
118                 final byte carriageReturn = (byte) '\r';
119                 final byte nullChar = (byte) '\0';
120                 final byte eChar = (byte) 'E';
121                 final byte rChar = (byte) 'R';
122
123                 try {
124                     logger.debug("Sending request '{}'", request);
125
126                     outputStream.write(request.getBytes());
127                     outputStream.flush();
128
129                     while (true) {
130                         if ((len = inputStream.read(tmpData)) > -1) {
131                             if (logger.isTraceEnabled()) {
132                                 StringBuilder sb = new StringBuilder();
133                                 for (int i = 0; i < len; i++) {
134                                     sb.append(String.format("%02X ", tmpData[i]));
135                                 }
136                                 logger.trace("Read {} bytes : {}", len, sb.toString());
137                             }
138
139                             for (int i = 0; i < len; i++) {
140                                 if (logger.isTraceEnabled()) {
141                                     logger.trace("Byte {} equals '{}' (hex '{}')", i,
142                                             new String(new byte[] { tmpData[i] }), String.format("%02X", tmpData[i]));
143                                 }
144
145                                 if (tmpData[i] == nullChar && !sequenceFound) {
146                                     sequenceFound = true;
147                                     logger.trace("Start of sequence found");
148                                 }
149
150                                 if (sequenceFound && tmpData[i] != lineFeed && tmpData[i] != carriageReturn
151                                         && tmpData[i] != nullChar) {
152                                     dataBuffer[index++] = tmpData[i];
153                                     if (logger.isTraceEnabled()) {
154                                         logger.trace("dataBuffer[{}] set to '{}'(hex '{}')", index - 1,
155                                                 new String(new byte[] { dataBuffer[index - 1] }),
156                                                 String.format("%02X", dataBuffer[index - 1]));
157                                     }
158                                 }
159
160                                 if (sequenceFound && i >= 2) {
161                                     if (tmpData[i - 2] == eChar && tmpData[i - 1] == rChar && tmpData[i] == rChar) {
162                                         // Received ERR from the device.
163                                         return null;
164                                     }
165                                 }
166
167                                 if (sequenceFound && i > 0
168                                         && (tmpData[i - 1] != carriageReturn && tmpData[i] == nullChar)) {
169                                     index = 0;
170                                     // Ignore trash received
171                                     if (logger.isTraceEnabled()) {
172                                         StringBuilder sb = new StringBuilder();
173                                         for (int j = 0; j < i; j++) {
174                                             sb.append(String.format("%02X ", tmpData[j]));
175                                         }
176                                         logger.trace("Ingoring {} bytes : {}", i, sb);
177                                     }
178                                 }
179
180                                 if (sequenceFound && (tmpData[i] == carriageReturn)) {
181                                     if (index > 0) {
182                                         line = new String(Arrays.copyOf(dataBuffer, index));
183                                         logger.debug("Received response '{}'", line);
184                                         line = StringUtils.chomp(line);
185                                         line = line.replace(",", ".");
186                                         line = line.trim();
187                                         index = 0;
188
189                                         return line;
190                                     }
191                                 }
192
193                                 if (index == bufferSize) {
194                                     index = 0;
195                                 }
196                             }
197                         }
198
199                     }
200                 } catch (IOException e) {
201                     logger.debug("An exception occurred while quering host {}:{} : '{}'", config.ipAddress,
202                             config.portNumber, e.getMessage(), e);
203                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
204                     reconnectJob = scheduler.schedule(reconnectRunnable, RECONNECT_INTERVAL, TimeUnit.SECONDS);
205                 } finally {
206                     Throttler.unlock(config.ipAddress);
207                 }
208             }
209             return null;
210         }
211     }
212
213     private Runnable reconnectRunnable = () -> {
214         dispose();
215         initialize();
216     };
217 }