]> git.basschouten.com Git - openhab-addons.git/blob
638fb8b183b79099b6451490122ece817479a785
[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             socket.close();
114         } catch (IOException e) {
115             callback.communicationProblem();
116         }
117     }
118
119     private byte[] buildMessageBytes(String message, String method, String endpoint) throws IOException {
120         ByteArrayOutputStream getInfoOutputStream = new ByteArrayOutputStream();
121         getInfoOutputStream
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();
126     }
127
128     private String createMessageHeader(String method, String endPoint, int contentLength) {
129         return method + " /" + endPoint + "\n" + "Content-Length: " + contentLength + "\n" + "\n";
130     }
131
132     /**
133      * Sends and {@link Action} to the device to set for example the FanSpeed or TargetHumidity
134      *
135      * @param action - The action to be send to the device
136      */
137     public void sendActionToDevice(Action action) throws IOException {
138         CommandMessage message = new CommandMessage(action, header);
139
140         String messageJson = gson.toJson(message);
141
142         try (Socket socket = new Socket(ipAddress, VentaAirBindingConstants.PORT)) {
143             OutputStream output = socket.getOutputStream();
144
145             byte[] dataToSend = buildMessageBytes(messageJson, "POST", "Action");
146
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));
151
152             output.write(dataToSend);
153             socket.close();
154         }
155     }
156
157     /**
158      * Starts the polling job to fetch the current device data
159      *
160      * @param scheduler - The scheduler of the {@link ThingHandler}
161      */
162     public void startPollDataFromDevice(ScheduledExecutorService scheduler) {
163         stopPollDataFromDevice();
164         pollingJob = scheduler.scheduleWithFixedDelay(this::pollDataFromDevice, 2, pollingTimeInSeconds,
165                 TimeUnit.SECONDS);
166     }
167
168     /**
169      * Stops the polling for device data
170      */
171     public void stopPollDataFromDevice() {
172         ScheduledFuture<?> localPollingJob = pollingJob;
173         if (localPollingJob != null && !localPollingJob.isCancelled()) {
174             localPollingJob.cancel(true);
175         }
176         logger.debug("Setting polling job to null");
177         pollingJob = null;
178     }
179 }