]> git.basschouten.com Git - openhab-addons.git/blob
1722a082fd70adc71638d9d33ff6e6fff96e3424
[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             socket.setSoTimeout(REQUEST_TIMEOUT);
67             outputStream = socket.getOutputStream();
68             inputStream = socket.getInputStream();
69             updateStatus(ThingStatus.ONLINE);
70         } catch (UnknownHostException e) {
71             logger.error("An exception occurred while resolving host {}:{} : '{}'", config.ipAddress, config.portNumber,
72                     e.getMessage());
73             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
74                     "Could not resolve host " + config.ipAddress + ": " + e.getMessage());
75         } catch (IOException e) {
76             logger.debug("An exception occurred while connecting to host {}:{} : '{}'", config.ipAddress,
77                     config.portNumber, e.getMessage());
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
79                     "Could not connect to host " + config.ipAddress + ": " + e.getMessage());
80             reconnectJob = scheduler.schedule(reconnectRunnable, RECONNECT_INTERVAL, TimeUnit.SECONDS);
81         }
82     }
83
84     @Override
85     public void dispose() {
86         NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
87
88         if (socket != null) {
89             try {
90                 socket.close();
91             } catch (IOException e) {
92                 logger.error("An exception occurred while disconnecting to host {}:{} : '{}'", config.ipAddress,
93                         config.portNumber, e.getMessage());
94             } finally {
95                 socket = null;
96                 outputStream = null;
97                 inputStream = null;
98             }
99         }
100
101         super.dispose();
102     }
103
104     @Override
105     protected @Nullable String requestResponse(String commandAsString) {
106         synchronized (this) {
107             if (getThing().getStatus() == ThingStatus.ONLINE) {
108                 NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
109                 Throttler.lock(config.ipAddress);
110
111                 String request = commandAsString + "\r";
112
113                 byte[] dataBuffer = new byte[bufferSize];
114                 byte[] tmpData = new byte[bufferSize];
115                 String line;
116                 int len = -1;
117                 int index = 0;
118                 boolean sequenceFound = false;
119
120                 final byte lineFeed = (byte) '\n';
121                 final byte carriageReturn = (byte) '\r';
122                 final byte nullChar = (byte) '\0';
123                 final byte eChar = (byte) 'E';
124                 final byte rChar = (byte) 'R';
125
126                 try {
127                     logger.debug("Sending request '{}'", request);
128
129                     outputStream.write(request.getBytes());
130                     outputStream.flush();
131
132                     while (true) {
133                         if ((len = inputStream.read(tmpData)) > -1) {
134                             if (logger.isTraceEnabled()) {
135                                 StringBuilder sb = new StringBuilder();
136                                 for (int i = 0; i < len; i++) {
137                                     sb.append(String.format("%02X ", tmpData[i]));
138                                 }
139                                 logger.trace("Read {} bytes : {}", len, sb.toString());
140                             }
141
142                             for (int i = 0; i < len; i++) {
143                                 if (logger.isTraceEnabled()) {
144                                     logger.trace("Byte {} equals '{}' (hex '{}')", i,
145                                             new String(new byte[] { tmpData[i] }), String.format("%02X", tmpData[i]));
146                                 }
147
148                                 if (tmpData[i] == nullChar && !sequenceFound) {
149                                     sequenceFound = true;
150                                     logger.trace("Start of sequence found");
151                                 }
152
153                                 if (sequenceFound && tmpData[i] != lineFeed && tmpData[i] != carriageReturn
154                                         && tmpData[i] != nullChar) {
155                                     dataBuffer[index++] = tmpData[i];
156                                     if (logger.isTraceEnabled()) {
157                                         logger.trace("dataBuffer[{}] set to '{}'(hex '{}')", index - 1,
158                                                 new String(new byte[] { dataBuffer[index - 1] }),
159                                                 String.format("%02X", dataBuffer[index - 1]));
160                                     }
161                                 }
162
163                                 if (sequenceFound && i >= 2) {
164                                     if (tmpData[i - 2] == eChar && tmpData[i - 1] == rChar && tmpData[i] == rChar) {
165                                         // Received ERR from the device.
166                                         return null;
167                                     }
168                                 }
169
170                                 if (sequenceFound && i > 0
171                                         && (tmpData[i - 1] != carriageReturn && tmpData[i] == nullChar)) {
172                                     index = 0;
173                                     // Ignore trash received
174                                     if (logger.isTraceEnabled()) {
175                                         StringBuilder sb = new StringBuilder();
176                                         for (int j = 0; j < i; j++) {
177                                             sb.append(String.format("%02X ", tmpData[j]));
178                                         }
179                                         logger.trace("Ingoring {} bytes : {}", i, sb);
180                                     }
181                                 }
182
183                                 if (sequenceFound && (tmpData[i] == carriageReturn)) {
184                                     if (index > 0) {
185                                         line = new String(Arrays.copyOf(dataBuffer, index));
186                                         logger.debug("Received response '{}'", line);
187                                         line = StringUtils.chomp(line);
188                                         line = line.replace(",", ".");
189                                         line = line.trim();
190                                         index = 0;
191
192                                         return line;
193                                     }
194                                 }
195
196                                 if (index == bufferSize) {
197                                     index = 0;
198                                 }
199                             }
200                         }
201
202                     }
203                 } catch (IOException e) {
204                     logger.debug("An exception occurred while quering host {}:{} : '{}'", config.ipAddress,
205                             config.portNumber, e.getMessage(), e);
206                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
207                     reconnectJob = scheduler.schedule(reconnectRunnable, RECONNECT_INTERVAL, TimeUnit.SECONDS);
208                 } finally {
209                     Throttler.unlock(config.ipAddress);
210                 }
211             }
212             return null;
213         }
214     }
215
216     private Runnable reconnectRunnable = () -> {
217         dispose();
218         initialize();
219     };
220 }