2 * Copyright (c) 2010-2020 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.yioremote.internal;
15 import static org.openhab.binding.yioremote.internal.YIOremoteBindingConstants.*;
18 import java.net.URISyntaxException;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.concurrent.Future;
22 import java.util.concurrent.TimeUnit;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
27 import org.eclipse.jetty.websocket.client.WebSocketClient;
28 import org.openhab.binding.yioremote.internal.YIOremoteBindingConstants.YioRemoteDockHandleStatus;
29 import org.openhab.binding.yioremote.internal.YIOremoteBindingConstants.YioRemoteMessages;
30 import org.openhab.binding.yioremote.internal.dto.AuthenticationMessage;
31 import org.openhab.binding.yioremote.internal.dto.IRCode;
32 import org.openhab.binding.yioremote.internal.dto.IRCodeSendMessage;
33 import org.openhab.binding.yioremote.internal.dto.IRReceiverMessage;
34 import org.openhab.binding.yioremote.internal.utils.Websocket;
35 import org.openhab.binding.yioremote.internal.utils.WebsocketInterface;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
50 import com.google.gson.JsonElement;
51 import com.google.gson.JsonObject;
52 import com.google.gson.JsonParser;
55 * The {@link YIOremoteDockHandler} is responsible for handling commands, which are
56 * sent to one of the channels.
58 * @author Michael Loercher - Initial contribution
61 public class YIOremoteDockHandler extends BaseThingHandler {
63 private final Logger logger = LoggerFactory.getLogger(YIOremoteDockHandler.class);
65 YIOremoteConfiguration localConfig = getConfigAs(YIOremoteConfiguration.class);
66 private WebSocketClient webSocketClient = new WebSocketClient();
67 private Websocket yioremoteDockwebSocketClient = new Websocket();
68 private ClientUpgradeRequest yioremoteDockwebSocketClientrequest = new ClientUpgradeRequest();
69 private @Nullable URI websocketAddress;
70 private YioRemoteDockHandleStatus yioRemoteDockActualStatus = YioRemoteDockHandleStatus.UNINITIALIZED_STATE;
71 private @Nullable Future<?> webSocketPollingJob;
72 public String receivedMessage = "";
73 private JsonObject recievedJson = new JsonObject();
74 private boolean heartBeat = false;
75 private boolean authenticationOk = false;
76 private String receivedStatus = "";
77 private IRCode irCodeReceivedHandler = new IRCode();
78 private IRCode irCodeSendHandler = new IRCode();
79 private IRCodeSendMessage irCodeSendMessageHandler = new IRCodeSendMessage(irCodeSendHandler);
80 private AuthenticationMessage authenticationMessageHandler = new AuthenticationMessage();
81 private IRReceiverMessage irReceiverMessageHandler = new IRReceiverMessage();
83 public YIOremoteDockHandler(Thing thing) {
88 public void initialize() {
89 updateStatus(ThingStatus.UNKNOWN);
90 scheduler.execute(() -> {
92 websocketAddress = new URI("ws://" + localConfig.host + ":946");
93 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_PROCESS;
94 } catch (URISyntaxException e) {
95 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
96 "Initialize web socket failed: " + e.getMessage());
99 yioremoteDockwebSocketClient.addMessageHandler(new WebsocketInterface() {
102 public void onConnect(boolean connected) {
104 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.CONNECTION_ESTABLISHED;
106 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.CONNECTION_FAILED;
111 public void onMessage(String message) {
112 receivedMessage = message;
113 logger.debug("Message recieved {}", message);
114 recievedJson = convertStringToJsonObject(receivedMessage);
115 if (recievedJson.size() > 0) {
116 if (decodeReceivedMessage(recievedJson)) {
117 triggerChannel(getChannelUuid(GROUP_OUTPUT, STATUS_STRING_CHANNEL));
118 updateChannelString(GROUP_OUTPUT, STATUS_STRING_CHANNEL, receivedStatus);
119 switch (yioRemoteDockActualStatus) {
120 case CONNECTION_ESTABLISHED:
121 case AUTHENTICATION_PROCESS:
127 logger.debug("Message {} decoded", receivedMessage);
129 logger.debug("Error during message {} decoding", receivedMessage);
135 public void onError() {
136 if (webSocketPollingJob != null) {
137 webSocketPollingJob.cancel(true);
139 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140 "Connection lost no ping from YIO DOCK");
141 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
146 webSocketClient.start();
148 webSocketClient.connect(yioremoteDockwebSocketClient, websocketAddress,
149 yioremoteDockwebSocketClientrequest);
150 } catch (Exception e) {
151 logger.debug("Connection error {}", e.getMessage());
157 private boolean decodeReceivedMessage(JsonObject message) {
158 boolean success = false;
160 if (message.has("type")) {
161 if (message.get("type").toString().equalsIgnoreCase("\"auth_required\"")) {
164 receivedStatus = "Authentication required";
165 } else if (message.get("type").toString().equalsIgnoreCase("\"auth_ok\"")) {
166 authenticationOk = true;
169 receivedStatus = "Authentication ok";
170 } else if (message.get("type").toString().equalsIgnoreCase("\"dock\"") && message.has("message")) {
171 if (message.get("message").toString().equalsIgnoreCase("\"ir_send\"")) {
172 if (message.get("success").toString().equalsIgnoreCase("true")) {
173 receivedStatus = "Send IR Code successfully";
177 if (irCodeSendHandler.getCode().equalsIgnoreCase("\"0;0x0;0;0\"")) {
178 logger.debug("Send heartBeat Code success");
180 receivedStatus = "Send IR Code failure";
186 logger.warn("No known message {}", receivedMessage);
190 } else if (message.get("command").toString().equalsIgnoreCase("\"ir_receive\"")) {
191 receivedStatus = message.get("code").toString().replace("\"", "");
192 if (receivedStatus.matches("[0-9][;]0[xX][0-9a-fA-F]+[;][0-9]+[;][0-9]")) {
193 irCodeReceivedHandler.setCode(message.get("code").toString().replace("\"", ""));
195 irCodeReceivedHandler.setCode("");
197 logger.debug("ir_receive message {}", irCodeReceivedHandler.getCode());
201 logger.warn("No known message {}", irCodeReceivedHandler.getCode());
206 logger.warn("No known message {}", irCodeReceivedHandler.getCode());
213 private JsonObject convertStringToJsonObject(String jsonString) {
215 JsonParser parser = new JsonParser();
216 JsonElement jsonElement = parser.parse(jsonString);
219 if (jsonElement instanceof JsonObject) {
220 result = jsonElement.getAsJsonObject();
222 logger.debug("{} is not valid JSON stirng", jsonString);
223 result = new JsonObject();
224 throw new IllegalArgumentException(jsonString + "{} is not valid JSON stirng");
227 } catch (IllegalArgumentException e) {
228 JsonObject result = new JsonObject();
233 public void updateState(String group, String channelId, State value) {
234 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
235 updateState(id, value);
239 public Collection<Class<? extends ThingHandlerService>> getServices() {
240 return Collections.singleton(YIOremoteDockActions.class);
244 public void dispose() {
245 if (webSocketPollingJob != null) {
246 webSocketPollingJob.cancel(true);
251 public void handleCommand(ChannelUID channelUID, Command command) {
252 if (RECEIVER_SWITCH_CHANNEL.equals(channelUID.getIdWithoutGroup())) {
253 switch (yioRemoteDockActualStatus) {
254 case AUTHENTICATION_COMPLETE:
255 if (command == OnOffType.ON) {
256 logger.debug("YIODOCKRECEIVERSWITCH ON procedure: Switching IR Receiver on");
257 sendMessage(YioRemoteMessages.IR_RECEIVER_ON, "");
258 } else if (command == OnOffType.OFF) {
259 logger.debug("YIODOCKRECEIVERSWITCH OFF procedure: Switching IR Receiver off");
260 sendMessage(YioRemoteMessages.IR_RECEIVER_OFF, "");
262 logger.debug("YIODOCKRECEIVERSWITCH no procedure");
271 public void sendIRCode(@Nullable String irCode) {
272 if (irCode != null && yioRemoteDockActualStatus.equals(YioRemoteDockHandleStatus.AUTHENTICATION_COMPLETE)) {
273 if (irCode.matches("[0-9][;]0[xX][0-9a-fA-F]+[;][0-9]+[;][0-9]")) {
274 sendMessage(YioRemoteMessages.IR_SEND, irCode);
276 logger.warn("Wrong ir code format {}", irCode);
281 private ChannelUID getChannelUuid(String group, String typeId) {
282 return new ChannelUID(getThing().getUID(), group, typeId);
285 private void updateChannelString(String group, String channelId, String value) {
286 ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
287 updateState(id, new StringType(value));
290 private void authenticate() {
291 switch (yioRemoteDockActualStatus) {
292 case CONNECTION_ESTABLISHED:
293 authenticationMessageHandler.setToken(localConfig.accessToken);
294 sendMessage(YioRemoteMessages.AUTHENTICATE_MESSAGE, localConfig.accessToken);
295 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_PROCESS;
297 case AUTHENTICATION_PROCESS:
298 if (authenticationOk) {
299 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_COMPLETE;
300 updateStatus(ThingStatus.ONLINE);
301 webSocketPollingJob = scheduler.scheduleWithFixedDelay(this::pollingWebsocket, 0, 30,
304 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_FAILED;
308 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
309 "Connection lost no ping from YIO DOCK");
310 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
315 private void pollingWebsocket() {
316 switch (yioRemoteDockActualStatus) {
317 case AUTHENTICATION_COMPLETE:
318 if (getAndResetHeartbeat()) {
319 updateChannelString(GROUP_OUTPUT, STATUS_STRING_CHANNEL,
320 irCodeReceivedHandler.getCode() + irCodeReceivedHandler.getFormat());
321 logger.debug("heartBeat ok");
322 sendMessage(YioRemoteMessages.HEARTBEAT_MESSAGE, "");
324 yioRemoteDockActualStatus = YioRemoteDockHandleStatus.CONNECTION_FAILED;
325 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
326 "Connection lost no ping from YIO DOCK");
327 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
328 if (webSocketPollingJob != null) {
329 webSocketPollingJob.cancel(true);
334 if (webSocketPollingJob != null) {
335 webSocketPollingJob.cancel(true);
337 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
338 "Connection lost no ping from YIO DOCK");
339 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
344 public boolean getAndResetHeartbeat() {
345 boolean result = heartBeat;
350 public void sendMessage(YioRemoteMessages messageType, String messagePayload) {
351 switch (messageType) {
352 case AUTHENTICATE_MESSAGE:
353 yioremoteDockwebSocketClient.sendMessage(authenticationMessageHandler.getAuthenticationMessageString());
354 logger.debug("sending authenticating {}",
355 authenticationMessageHandler.getAuthenticationMessageString());
357 case HEARTBEAT_MESSAGE:
358 irCodeSendHandler.setCode("0;0x0;0;0");
359 yioremoteDockwebSocketClient.sendMessage(irCodeSendMessageHandler.getIRcodeSendMessageString());
360 logger.debug("sending heartBeat message: {}", irCodeSendMessageHandler.getIRcodeSendMessageString());
363 irReceiverMessageHandler.setOn();
364 yioremoteDockwebSocketClient.sendMessage(irReceiverMessageHandler.getIRreceiverMessageString());
365 logger.debug("sending IR receiver on message: {}",
366 irReceiverMessageHandler.getIRreceiverMessageString());
368 case IR_RECEIVER_OFF:
369 irReceiverMessageHandler.setOff();
370 yioremoteDockwebSocketClient.sendMessage(irReceiverMessageHandler.getIRreceiverMessageString());
371 logger.debug("sending IR receiver on message: {}",
372 irReceiverMessageHandler.getIRreceiverMessageString());
375 irCodeSendHandler.setCode(messagePayload);
376 yioremoteDockwebSocketClient.sendMessage(irCodeSendMessageHandler.getIRcodeSendMessageString());
377 logger.debug("sending heartBeat message: {}", irCodeSendMessageHandler.getIRcodeSendMessageString());