2 * Copyright (c) 2010-2021 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.vektiva.internal.handler;
15 import static org.openhab.binding.vektiva.internal.VektivaBindingConstants.*;
17 import java.io.IOException;
20 import java.util.concurrent.*;
21 import java.util.stream.Collectors;
22 import java.util.stream.Stream;
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;
48 * The {@link VektivaSmarwiHandler} is responsible for handling commands, which are
49 * sent to one of the channels.
51 * @author Ondrej Pecta - Initial contribution
54 public class VektivaSmarwiHandler extends BaseThingHandler {
56 private final Logger logger = LoggerFactory.getLogger(VektivaSmarwiHandler.class);
58 private VektivaSmarwiConfiguration config = new VektivaSmarwiConfiguration();
60 private final HttpClient httpClient;
62 private final WebSocketClient webSocketClient;
64 private @Nullable Session session;
66 private @Nullable ScheduledFuture<?> future = null;
68 private int lastPosition = -1;
70 public VektivaSmarwiHandler(Thing thing, HttpClient httpClient, WebSocketClient webSocketClient) {
72 this.httpClient = httpClient;
73 this.webSocketClient = webSocketClient;
77 public void handleCommand(ChannelUID channelUID, Command command) {
78 if (CHANNEL_STATUS.equals(channelUID.getId()) && command instanceof RefreshType) {
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;
93 if (command instanceof PercentType) {
94 if (RESPONSE_OK.equals(sendCommand(COMMAND_OPEN + "/" + cmd))) {
95 lastPosition = Integer.parseInt(cmd);
102 public void dispose() {
104 if (future != null && !(future.isCancelled() || future.isDone())) {
110 private void closeSession() {
111 if (session != null && session.isOpen()) {
116 private String getSmarwiCommand(Command command) {
117 if (UpDownType.UP.equals(command)) {
120 if (UpDownType.DOWN.equals(command)) {
121 return COMMAND_CLOSE;
123 if (StopMoveType.STOP.equals(command)) {
126 return command.toString();
129 private @Nullable String sendCommand(String cmd) {
130 String url = "http://" + config.ip + "/cmd/" + cmd;
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);
141 updateStatus(ThingStatus.OFFLINE);
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());
161 public void initialize() {
162 config = getConfigAs(VektivaSmarwiConfiguration.class);
163 logger.debug("IP address: {}", config.ip);
165 future = scheduler.scheduleWithFixedDelay(this::checkStatus, 0, config.refreshInterval, TimeUnit.SECONDS);
168 private synchronized void initializeWebSocketSession() {
169 if (config.useWebSockets) {
171 session = createSession();
172 if (session != null) {
173 logger.debug("WebSocket connected!");
178 private void checkStatus() {
179 String url = "http://" + config.ip + "/statusn";
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);
188 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
189 "got response code: " + resp.getStatus());
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();
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());
214 public synchronized void processStatusResponse(String content) {
215 if (ThingStatus.ONLINE != getThing().getStatus()) {
216 updateStatus(ThingStatus.ONLINE);
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]));
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));
227 String statusMessage = "Stopped";
228 if (!"250".equals(values.getOrDefault("s", NA))) {
229 statusMessage = "Moving";
232 if ("1".equals(values.getOrDefault("ro", NA))) {
233 statusMessage = "Not ready";
236 if ("10".equals(values.getOrDefault("e", NA))) {
237 statusMessage = "Blocked";
240 int position = values.getOrDefault("pos", NA).equals("o") ? 0 : 100;
241 if (position == 0 && lastPosition != -1) {
242 position = lastPosition;
244 updateState(CHANNEL_CONTROL, new PercentType(position));
245 updateState(CHANNEL_STATUS, new StringType(statusMessage));
248 private @Nullable Session createSession() {
249 String url = "ws://" + config.ip + "/ws";
250 URI uri = URI.create(url);
253 // The socket that receives events
254 VektivaSmarwiSocket socket = new VektivaSmarwiSocket(this);
256 Future<Session> fut = webSocketClient.connect(socket, uri);
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");