]> git.basschouten.com Git - openhab-addons.git/blob
479da6f850c42cdfa5ee5e5806b0c20455b98c15
[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.ventaair.internal;
14
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;
29
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;
42
43 import com.google.gson.Gson;
44
45 /**
46  * The {@link Communicator} is responsible for sending/receiving commands to/from the device
47  *
48  * @author Stefan Triller - Initial contribution
49  *
50  */
51 @NonNullByDefault
52 public class Communicator {
53     private static final Duration COMMUNICATION_TIMEOUT = Duration.ofSeconds(5);
54
55     private final Logger logger = LoggerFactory.getLogger(Communicator.class);
56
57     private @Nullable String ipAddress;
58     private Header header;
59     private int pollingTimeInSeconds;
60     private StateUpdatedCallback callback;
61
62     private Gson gson = new Gson();
63
64     private @Nullable ScheduledFuture<?> pollingJob;
65
66     public Communicator(@Nullable String ipAddress, Header header, @Nullable BigDecimal pollingTime,
67             StateUpdatedCallback callback) {
68         this.ipAddress = ipAddress;
69         this.header = header;
70         if (pollingTime != null) {
71             this.pollingTimeInSeconds = pollingTime.intValue();
72         } else {
73             this.pollingTimeInSeconds = 60;
74         }
75         this.callback = callback;
76     }
77
78     /**
79      * Sends a request message to the device, reads the reply and informs the listener about the current device data
80      */
81     public void pollDataFromDevice() {
82         String messageJson = gson.toJson(new Message(header));
83
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();
88
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);
95
96             BufferedReader br = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
97             String reply = "";
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);
105
106                     DeviceInfoMessage deviceInfoMessage = gson.fromJson(data, DeviceInfoMessage.class);
107                     if (deviceInfoMessage != null) {
108                         callback.stateUpdated(deviceInfoMessage);
109                     }
110                 }
111             }
112             br.close();
113         } catch (IOException e) {
114             callback.communicationProblem();
115         }
116     }
117
118     private byte[] buildMessageBytes(String message, String method, String endpoint) throws IOException {
119         ByteArrayOutputStream getInfoOutputStream = new ByteArrayOutputStream();
120         getInfoOutputStream
121                 .write(createMessageHeader(method, endpoint, message.length()).getBytes(StandardCharsets.UTF_8));
122         getInfoOutputStream.write(message.getBytes(StandardCharsets.UTF_8));
123         getInfoOutputStream.write(new byte[] { 0x1c, 0x00 });
124         return getInfoOutputStream.toByteArray();
125     }
126
127     private String createMessageHeader(String method, String endPoint, int contentLength) {
128         return method + " /" + endPoint + "\n" + "Content-Length: " + contentLength + "\n" + "\n";
129     }
130
131     /**
132      * Sends and {@link Action} to the device to set for example the FanSpeed or TargetHumidity
133      *
134      * @param action - The action to be send to the device
135      */
136     public void sendActionToDevice(Action action) throws IOException {
137         CommandMessage message = new CommandMessage(action, header);
138
139         String messageJson = gson.toJson(message);
140
141         try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
142             OutputStream output = socket.getOutputStream();
143
144             byte[] dataToSend = buildMessageBytes(messageJson, "POST", "Action");
145
146             // we write these lines to the log in order to help users with new/other venta devices, so they only need to
147             // enable debug logging
148             logger.debug("sending: {}", new String(dataToSend));
149             logger.debug("sendingArray: {}", Arrays.toString(dataToSend));
150
151             output.write(dataToSend);
152         }
153     }
154
155     /**
156      * Starts the polling job to fetch the current device data
157      *
158      * @param scheduler - The scheduler of the {@link ThingHandler}
159      */
160     public void startPollDataFromDevice(ScheduledExecutorService scheduler) {
161         stopPollDataFromDevice();
162         pollingJob = scheduler.scheduleWithFixedDelay(this::pollDataFromDevice, 2, pollingTimeInSeconds,
163                 TimeUnit.SECONDS);
164     }
165
166     /**
167      * Stops the polling for device data
168      */
169     public void stopPollDataFromDevice() {
170         ScheduledFuture<?> localPollingJob = pollingJob;
171         if (localPollingJob != null && !localPollingJob.isCancelled()) {
172             localPollingJob.cancel(true);
173         }
174         logger.debug("Setting polling job to null");
175         pollingJob = null;
176     }
177 }