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