2 * Copyright (c) 2010-2020 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.openthermgateway.internal;
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.PrintWriter;
19 import java.net.InetSocketAddress;
20 import java.net.Socket;
21 import java.util.AbstractMap;
23 import java.util.Map.Entry;
24 import java.util.concurrent.ConcurrentHashMap;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.types.State;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
35 * The {@link OpenThermGatewaySocketConnector} is responsible for handling the socket connection
37 * @author Arjen Korevaar - Initial contribution
38 * @author Arjan Mels - Improved robustness by re-sending commands, handling all message types (not only Boiler)
41 public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnector {
42 private static final int COMMAND_RESPONSE_TIME_MILLISECONDS = 100;
43 private static final int COMMAND_TIMEOUT_MILLISECONDS = 5000;
45 private final Logger logger = LoggerFactory.getLogger(OpenThermGatewaySocketConnector.class);
47 private final OpenThermGatewayCallback callback;
48 private final String ipaddress;
49 private final int port;
51 private @Nullable PrintWriter writer;
53 private volatile boolean stopping;
54 private boolean connected;
56 private Map<String, Entry<Long, GatewayCommand>> pendingCommands = new ConcurrentHashMap<>();
58 public OpenThermGatewaySocketConnector(OpenThermGatewayCallback callback, String ipaddress, int port) {
59 this.callback = callback;
60 this.ipaddress = ipaddress;
69 logger.debug("Connecting OpenThermGatewaySocketConnector to {}:{}", this.ipaddress, this.port);
71 callback.connecting();
73 try (Socket socket = new Socket()) {
74 socket.connect(new InetSocketAddress(this.ipaddress, this.port), COMMAND_TIMEOUT_MILLISECONDS);
75 socket.setSoTimeout(COMMAND_TIMEOUT_MILLISECONDS);
81 logger.debug("OpenThermGatewaySocketConnector connected");
83 try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
84 PrintWriter wrt = new PrintWriter(socket.getOutputStream(), true)) {
85 // Make writer accessible on class level
88 sendCommand(GatewayCommand.parse(GatewayCommandCode.PrintReport, "A"));
89 // Set the OTGW to report every message it receives and transmits
90 sendCommand(GatewayCommand.parse(GatewayCommandCode.PrintSummary, "0"));
92 while (!stopping && !Thread.currentThread().isInterrupted()) {
94 String message = reader.readLine();
96 if (message != null) {
97 handleMessage(message);
99 logger.debug("Connection closed by OpenTherm Gateway");
104 logger.debug("Stopping OpenThermGatewaySocketConnector");
108 logger.debug("OpenThermGatewaySocketConnector disconnected");
109 callback.disconnected();
111 } catch (IOException ex) {
112 logger.warn("Unable to connect to the OpenTherm Gateway.", ex);
118 logger.debug("Stopping OpenThermGatewaySocketConnector");
123 public boolean isConnected() {
128 public void sendCommand(GatewayCommand command) {
130 PrintWriter wrtr = writer;
132 String msg = command.toFullString();
134 pendingCommands.put(command.getCode(),
135 new AbstractMap.SimpleImmutableEntry<>(System.currentTimeMillis(), command));
138 logger.debug("Sending message: {}", msg);
140 wrtr.print(msg + "\r\n");
144 logger.debug("Unable to send message: {}. OpenThermGatewaySocketConnector is not connected.", msg);
148 private void handleMessage(String message) {
149 if (message.length() > 2 && message.charAt(2) == ':') {
150 String code = message.substring(0, 2);
151 String value = message.substring(3);
153 logger.debug("Received command confirmation: {}: {}", code, value);
154 pendingCommands.remove(code);
158 long currentTime = System.currentTimeMillis();
160 for (Entry<Long, GatewayCommand> timeAndCommand : pendingCommands.values()) {
161 long responseTime = timeAndCommand.getKey() + COMMAND_RESPONSE_TIME_MILLISECONDS;
162 long timeoutTime = timeAndCommand.getKey() + COMMAND_TIMEOUT_MILLISECONDS;
164 if (currentTime > responseTime && currentTime <= timeoutTime) {
165 logger.debug("Resending command: {}", timeAndCommand.getValue());
166 sendCommand(timeAndCommand.getValue());
167 } else if (currentTime > timeoutTime) {
168 pendingCommands.remove(timeAndCommand.getValue().getCode());
172 Message msg = Message.parse(message);
175 logger.trace("Received message: {}, (unknown)", message);
178 logger.trace("Received message: {}, {} {} {}", message, msg.getID(), msg.getCode(), msg.getMessageType());
181 if (DataItemGroup.dataItemGroups.containsKey(msg.getID())) {
182 DataItem[] dataItems = DataItemGroup.dataItemGroups.get(msg.getID());
184 for (DataItem dataItem : dataItems) {
187 switch (dataItem.getDataType()) {
189 state = OnOffType.from(msg.getBit(dataItem.getByteType(), dataItem.getBitPos()));
193 state = new DecimalType(msg.getUInt(dataItem.getByteType()));
197 state = new DecimalType(msg.getInt(dataItem.getByteType()));
200 state = new DecimalType(msg.getFloat());
206 logger.trace(" Data: {} {} {} {}", dataItem.getID(), dataItem.getSubject(), dataItem.getDataType(),
207 state == null ? "" : state);
211 if (msg.getMessageType() == MessageType.READACK || msg.getMessageType() == MessageType.WRITEDATA) {
216 private void receiveMessage(Message message) {
217 callback.receiveMessage(message);