2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.oceanic.internal.handler;
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;
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;
34 * The {@link NetworkOceanicThingHandler} implements {@link OceanicThingHandler} for a Oceanic water softener that is
35 * reached using a socat TCP proxy
37 * @author Karel Goderis - Initial contribution
39 public class NetworkOceanicThingHandler extends OceanicThingHandler {
41 private static final int REQUEST_TIMEOUT = 3000;
42 private static final int RECONNECT_INTERVAL = 15;
44 private final Logger logger = LoggerFactory.getLogger(NetworkOceanicThingHandler.class);
46 private Socket socket;
47 private InputStream inputStream;
48 private OutputStream outputStream;
49 protected ScheduledFuture<?> reconnectJob;
51 public NetworkOceanicThingHandler(Thing thing) {
56 public void initialize() {
59 NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
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,
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);
82 public void dispose() {
83 NetworkOceanicBindingConfiguration config = getConfigAs(NetworkOceanicBindingConfiguration.class);
88 } catch (IOException e) {
89 logger.error("An exception occurred while disconnecting to host {}:{} : '{}'", config.ipAddress,
90 config.portNumber, e.getMessage());
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);
108 String request = commandAsString + "\r";
110 byte[] dataBuffer = new byte[bufferSize];
111 byte[] tmpData = new byte[bufferSize];
115 boolean sequenceFound = false;
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';
124 logger.debug("Sending request '{}'", request);
126 outputStream.write(request.getBytes());
127 outputStream.flush();
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]));
136 logger.trace("Read {} bytes : {}", len, sb.toString());
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]));
145 if (tmpData[i] == nullChar && !sequenceFound) {
146 sequenceFound = true;
147 logger.trace("Start of sequence found");
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]));
160 if (sequenceFound && i >= 2) {
161 if (tmpData[i - 2] == eChar && tmpData[i - 1] == rChar && tmpData[i] == rChar) {
162 // Received ERR from the device.
167 if (sequenceFound && i > 0
168 && (tmpData[i - 1] != carriageReturn && tmpData[i] == nullChar)) {
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]));
176 logger.trace("Ingoring {} bytes : {}", i, sb);
180 if (sequenceFound && (tmpData[i] == carriageReturn)) {
182 line = new String(Arrays.copyOf(dataBuffer, index));
183 logger.debug("Received response '{}'", line);
184 line = StringUtils.chomp(line);
185 line = line.replace(",", ".");
193 if (index == bufferSize) {
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);
206 Throttler.unlock(config.ipAddress);
213 private Runnable reconnectRunnable = () -> {