2 * Copyright (c) 2010-2023 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.ventaair.internal;
15 import java.io.BufferedReader;
16 import java.io.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.InputStreamReader;
20 import java.io.OutputStream;
21 import java.math.BigDecimal;
22 import java.net.Socket;
23 import java.nio.charset.StandardCharsets;
24 import java.time.Duration;
25 import java.util.Arrays;
26 import java.util.concurrent.ScheduledExecutorService;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.ventaair.internal.VentaThingHandler.StateUpdatedCallback;
33 import org.openhab.binding.ventaair.internal.message.action.Action;
34 import org.openhab.binding.ventaair.internal.message.dto.CommandMessage;
35 import org.openhab.binding.ventaair.internal.message.dto.DeviceInfoMessage;
36 import org.openhab.binding.ventaair.internal.message.dto.Header;
37 import org.openhab.binding.ventaair.internal.message.dto.Message;
38 import org.openhab.core.thing.binding.ThingHandler;
39 import org.openhab.core.util.HexUtils;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.Gson;
46 * The {@link Communicator} is responsible for sending/receiving commands to/from the device
48 * @author Stefan Triller - Initial contribution
52 public class Communicator {
53 private static final Duration COMMUNICATION_TIMEOUT = Duration.ofSeconds(5);
55 private final Logger logger = LoggerFactory.getLogger(Communicator.class);
57 private @Nullable String ipAddress;
58 private Header header;
59 private int pollingTimeInSeconds;
60 private StateUpdatedCallback callback;
62 private Gson gson = new Gson();
64 private @Nullable ScheduledFuture<?> pollingJob;
66 public Communicator(@Nullable String ipAddress, Header header, @Nullable BigDecimal pollingTime,
67 StateUpdatedCallback callback) {
68 this.ipAddress = ipAddress;
70 if (pollingTime != null) {
71 this.pollingTimeInSeconds = pollingTime.intValue();
73 this.pollingTimeInSeconds = 60;
75 this.callback = callback;
79 * Sends a request message to the device, reads the reply and informs the listener about the current device data
81 public void pollDataFromDevice() {
82 String messageJson = gson.toJson(new Message(header));
84 try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
85 socket.setSoTimeout((int) COMMUNICATION_TIMEOUT.toMillis());
86 InputStream input = socket.getInputStream();
87 OutputStream output = socket.getOutputStream();
89 byte[] dataToSend = buildMessageBytes(messageJson, "GET", "Complete");
90 // we write these lines to the log in order to help users with new/other venta devices, so they only need to
91 // enable debug logging
92 logger.debug("Sending request data message (String):\n{}", new String(dataToSend));
93 logger.debug("Sending request data message (bytes): [{}]", HexUtils.bytesToHex(dataToSend, ", "));
94 output.write(dataToSend);
96 BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
98 while ((reply = br.readLine()) != null) {
99 if (reply.startsWith("{")) {
100 // remove padding byte(s) after JSON data
101 String data = String.valueOf(reply.toCharArray(), 0, reply.length() - 1);
102 // we write this line to the log in order to help users with new/other venta devices, so they only
103 // need to enable debug logging
104 logger.debug("Got Data from device: {}", data);
106 DeviceInfoMessage deviceInfoMessage = gson.fromJson(data, DeviceInfoMessage.class);
107 if (deviceInfoMessage != null) {
108 callback.stateUpdated(deviceInfoMessage);
114 } catch (IOException e) {
115 callback.communicationProblem();
119 private byte[] buildMessageBytes(String message, String method, String endpoint) throws IOException {
120 ByteArrayOutputStream getInfoOutputStream = new ByteArrayOutputStream();
122 .write(createMessageHeader(method, endpoint, message.length()).getBytes(StandardCharsets.UTF_8));
123 getInfoOutputStream.write(message.getBytes(StandardCharsets.UTF_8));
124 getInfoOutputStream.write(new byte[] { 0x1c, 0x00 });
125 return getInfoOutputStream.toByteArray();
128 private String createMessageHeader(String method, String endPoint, int contentLength) {
129 return method + " /" + endPoint + "\n" + "Content-Length: " + contentLength + "\n" + "\n";
133 * Sends and {@link Action} to the device to set for example the FanSpeed or TargetHumidity
135 * @param action - The action to be send to the device
137 public void sendActionToDevice(Action action) throws IOException {
138 CommandMessage message = new CommandMessage(action, header);
140 String messageJson = gson.toJson(message);
142 try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
143 OutputStream output = socket.getOutputStream();
145 byte[] dataToSend = buildMessageBytes(messageJson, "POST", "Action");
147 // we write these lines to the log in order to help users with new/other venta devices, so they only need to
148 // enable debug logging
149 logger.debug("sending: {}", new String(dataToSend));
150 logger.debug("sendingArray: {}", Arrays.toString(dataToSend));
152 output.write(dataToSend);
158 * Starts the polling job to fetch the current device data
160 * @param scheduler - The scheduler of the {@link ThingHandler}
162 public void startPollDataFromDevice(ScheduledExecutorService scheduler) {
163 stopPollDataFromDevice();
164 pollingJob = scheduler.scheduleWithFixedDelay(this::pollDataFromDevice, 2, pollingTimeInSeconds,
169 * Stops the polling for device data
171 public void stopPollDataFromDevice() {
172 ScheduledFuture<?> localPollingJob = pollingJob;
173 if (localPollingJob != null && !localPollingJob.isCancelled()) {
174 localPollingJob.cancel(true);
176 logger.debug("Setting polling job to null");