]> git.basschouten.com Git - openhab-addons.git/blob
78a4a2d3d120a4f6e715b5cd3432707f08891265
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.vektiva.internal.handler;
14
15 import static org.openhab.binding.vektiva.internal.VektivaBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.URI;
19 import java.util.Map;
20 import java.util.concurrent.*;
21 import java.util.stream.Collectors;
22 import java.util.stream.Stream;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.websocket.api.Session;
30 import org.eclipse.jetty.websocket.client.WebSocketClient;
31 import org.openhab.binding.vektiva.internal.config.VektivaSmarwiConfiguration;
32 import org.openhab.binding.vektiva.internal.net.VektivaSmarwiSocket;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.core.library.types.StopMoveType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.library.types.UpDownType;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link VektivaSmarwiHandler} is responsible for handling commands, which are
49  * sent to one of the channels.
50  *
51  * @author Ondrej Pecta - Initial contribution
52  */
53 @NonNullByDefault
54 public class VektivaSmarwiHandler extends BaseThingHandler {
55
56     private final Logger logger = LoggerFactory.getLogger(VektivaSmarwiHandler.class);
57
58     private VektivaSmarwiConfiguration config = new VektivaSmarwiConfiguration();
59
60     private final HttpClient httpClient;
61
62     private final WebSocketClient webSocketClient;
63
64     private @Nullable Session session;
65
66     private @Nullable ScheduledFuture<?> future = null;
67
68     private int lastPosition = -1;
69
70     public VektivaSmarwiHandler(Thing thing, HttpClient httpClient, WebSocketClient webSocketClient) {
71         super(thing);
72         this.httpClient = httpClient;
73         this.webSocketClient = webSocketClient;
74     }
75
76     @Override
77     public void handleCommand(ChannelUID channelUID, Command command) {
78         if (CHANNEL_STATUS.equals(channelUID.getId()) && command instanceof RefreshType) {
79             checkStatus();
80             return;
81         }
82
83         if (CHANNEL_CONTROL.equals(channelUID.getId())) {
84             logger.trace("Received command: {}", command);
85             String cmd = getSmarwiCommand(command);
86             if (COMMAND_OPEN.equals(cmd) || COMMAND_CLOSE.equals(cmd) || COMMAND_STOP.equals(cmd)) {
87                 if (RESPONSE_OK.equals(sendCommand(cmd)) && !COMMAND_STOP.equals(cmd)) {
88                     lastPosition = COMMAND_OPEN.equals(cmd) ? 0 : 100;
89                 } else {
90                     lastPosition = -1;
91                 }
92             }
93             if (command instanceof PercentType) {
94                 if (RESPONSE_OK.equals(sendCommand(COMMAND_OPEN + "/" + cmd))) {
95                     lastPosition = Integer.parseInt(cmd);
96                 }
97             }
98         }
99     }
100
101     @Override
102     public void dispose() {
103         super.dispose();
104         if (future != null && !(future.isCancelled() || future.isDone())) {
105             future.cancel(true);
106         }
107         closeSession();
108     }
109
110     private void closeSession() {
111         if (session != null && session.isOpen()) {
112             session.close();
113         }
114     }
115
116     private String getSmarwiCommand(Command command) {
117         if (UpDownType.UP.equals(command)) {
118             return COMMAND_OPEN;
119         }
120         if (UpDownType.DOWN.equals(command)) {
121             return COMMAND_CLOSE;
122         }
123         if (StopMoveType.STOP.equals(command)) {
124             return COMMAND_STOP;
125         }
126         return command.toString();
127     }
128
129     private @Nullable String sendCommand(String cmd) {
130         String url = "http://" + config.ip + "/cmd/" + cmd;
131
132         try {
133             ContentResponse resp = httpClient.newRequest(url).method(HttpMethod.GET).send();
134             final String response = resp.getContentAsString();
135             logger.trace("Response: {}", response);
136             if (resp.getStatus() == 200) {
137                 if (ThingStatus.ONLINE != getThing().getStatus()) {
138                     updateStatus(ThingStatus.ONLINE);
139                 }
140             } else {
141                 updateStatus(ThingStatus.OFFLINE);
142             }
143             return response;
144         } catch (InterruptedException e) {
145             logger.debug("API execution has been interrupted", e);
146             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
147                     "API execution has been interrupted");
148             Thread.currentThread().interrupt();
149         } catch (TimeoutException e) {
150             logger.debug("Timeout during API execution", e);
151             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Timeout during API execution");
152         } catch (ExecutionException e) {
153             logger.debug("Exception during API execution", e);
154             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
155                     "Exception during API execution: " + e.getMessage());
156         }
157         return null;
158     }
159
160     @Override
161     public void initialize() {
162         config = getConfigAs(VektivaSmarwiConfiguration.class);
163         logger.debug("IP address: {}", config.ip);
164
165         future = scheduler.scheduleWithFixedDelay(this::checkStatus, 0, config.refreshInterval, TimeUnit.SECONDS);
166     }
167
168     private synchronized void initializeWebSocketSession() {
169         if (config.useWebSockets) {
170             closeSession();
171             session = createSession();
172             if (session != null) {
173                 logger.debug("WebSocket connected!");
174             }
175         }
176     }
177
178     private void checkStatus() {
179         String url = "http://" + config.ip + "/statusn";
180
181         try {
182             ContentResponse resp = httpClient.newRequest(url).method(HttpMethod.GET).send();
183             final String response = resp.getContentAsString();
184             logger.debug("status values: {}", response);
185             if (resp.getStatus() == 200) {
186                 processStatusResponse(response);
187             } else {
188                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
189                         "got response code: " + resp.getStatus());
190             }
191             // reconnect web socket if not connected
192             if (config.useWebSockets && (session == null || !session.isOpen())
193                     && ThingStatus.ONLINE == getThing().getStatus()) {
194                 logger.debug("Initializing WebSocket session");
195                 initializeWebSocketSession();
196                 return;
197             }
198         } catch (InterruptedException e) {
199             logger.debug("API execution has been interrupted", e);
200             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
201                     "API execution has been interrupted");
202             Thread.currentThread().interrupt();
203         } catch (TimeoutException e) {
204             logger.debug("Timeout during status update", e);
205             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Timeout during status update");
206         } catch (ExecutionException e) {
207             logger.debug("Exception during status update", e);
208             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
209                     "Exception during status update: " + e.getMessage());
210         }
211         session = null;
212     }
213
214     public synchronized void processStatusResponse(String content) {
215         if (ThingStatus.ONLINE != getThing().getStatus()) {
216             updateStatus(ThingStatus.ONLINE);
217         }
218
219         Map<String, String> values = Stream.of(content.split("\n")).map(s -> s.split(":")).filter(s -> s.length == 2)
220                 .collect(Collectors.toMap(s -> s[0], s -> s[1]));
221
222         updateProperty("Product type", values.getOrDefault("t", NA));
223         updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, values.getOrDefault("fw", NA));
224         updateProperty("Wifi signal", values.getOrDefault("rssi", NA));
225         updateProperty("Product name", values.getOrDefault("cid", NA));
226
227         String statusMessage = "Stopped";
228         if (!"250".equals(values.getOrDefault("s", NA))) {
229             statusMessage = "Moving";
230         }
231
232         if ("1".equals(values.getOrDefault("ro", NA))) {
233             statusMessage = "Not ready";
234         }
235
236         if ("10".equals(values.getOrDefault("e", NA))) {
237             statusMessage = "Blocked";
238         }
239
240         int position = values.getOrDefault("pos", NA).equals("o") ? 0 : 100;
241         if (position == 0 && lastPosition != -1) {
242             position = lastPosition;
243         }
244         updateState(CHANNEL_CONTROL, new PercentType(position));
245         updateState(CHANNEL_STATUS, new StringType(statusMessage));
246     }
247
248     private @Nullable Session createSession() {
249         String url = "ws://" + config.ip + "/ws";
250         URI uri = URI.create(url);
251
252         try {
253             // The socket that receives events
254             VektivaSmarwiSocket socket = new VektivaSmarwiSocket(this);
255             // Attempt Connect
256             Future<Session> fut = webSocketClient.connect(socket, uri);
257             // Wait for Connect
258             return fut.get();
259         } catch (IOException ex) {
260             logger.debug("Cannot connect websocket client", ex);
261             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot connect websocket client");
262         } catch (InterruptedException ex) {
263             logger.debug("Cannot create websocket session", ex);
264             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot create websocket session");
265             Thread.currentThread().interrupt();
266         } catch (ExecutionException ex) {
267             logger.debug("Cannot create websocket session", ex);
268             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot create websocket session");
269         }
270         return null;
271     }
272 }