]> git.basschouten.com Git - openhab-addons.git/blob
6b1f4518762e1500c534237a3279327a0f6202b1
[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.yioremote.internal;
14
15 import static org.openhab.binding.yioremote.internal.YIOremoteBindingConstants.*;
16
17 import java.net.URI;
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;
23
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;
49
50 import com.google.gson.JsonElement;
51 import com.google.gson.JsonObject;
52 import com.google.gson.JsonParser;
53
54 /**
55  * The {@link YIOremoteDockHandler} is responsible for handling commands, which are
56  * sent to one of the channels.
57  *
58  * @author Michael Loercher - Initial contribution
59  */
60 @NonNullByDefault
61 public class YIOremoteDockHandler extends BaseThingHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(YIOremoteDockHandler.class);
64
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();
82
83     public YIOremoteDockHandler(Thing thing) {
84         super(thing);
85     }
86
87     @Override
88     public void initialize() {
89         updateStatus(ThingStatus.UNKNOWN);
90         scheduler.execute(() -> {
91             try {
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());
97             }
98
99             yioremoteDockwebSocketClient.addMessageHandler(new WebsocketInterface() {
100
101                 @Override
102                 public void onConnect(boolean connected) {
103                     if (connected) {
104                         yioRemoteDockActualStatus = YioRemoteDockHandleStatus.CONNECTION_ESTABLISHED;
105                     } else {
106                         yioRemoteDockActualStatus = YioRemoteDockHandleStatus.CONNECTION_FAILED;
107                     }
108                 }
109
110                 @Override
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:
122                                     authenticate();
123                                     break;
124                                 default:
125                                     break;
126                             }
127                             logger.debug("Message {} decoded", receivedMessage);
128                         } else {
129                             logger.debug("Error during message {} decoding", receivedMessage);
130                         }
131                     }
132                 }
133
134                 @Override
135                 public void onError() {
136                     if (webSocketPollingJob != null) {
137                         webSocketPollingJob.cancel(true);
138                     }
139                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
140                             "Connection lost no ping from YIO DOCK");
141                     updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
142                 }
143             });
144
145             try {
146                 webSocketClient.start();
147
148                 webSocketClient.connect(yioremoteDockwebSocketClient, websocketAddress,
149                         yioremoteDockwebSocketClientrequest);
150             } catch (Exception e) {
151                 logger.debug("Connection error {}", e.getMessage());
152             }
153
154         });
155     }
156
157     private boolean decodeReceivedMessage(JsonObject message) {
158         boolean success = false;
159
160         if (message.has("type")) {
161             if (message.get("type").toString().equalsIgnoreCase("\"auth_required\"")) {
162                 heartBeat = true;
163                 success = true;
164                 receivedStatus = "Authentication required";
165             } else if (message.get("type").toString().equalsIgnoreCase("\"auth_ok\"")) {
166                 authenticationOk = true;
167                 heartBeat = true;
168                 success = 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";
174                         heartBeat = true;
175                         success = true;
176                     } else {
177                         if (irCodeSendHandler.getCode().equalsIgnoreCase("\"0;0x0;0;0\"")) {
178                             logger.debug("Send heartBeat Code success");
179                         } else {
180                             receivedStatus = "Send IR Code failure";
181                         }
182                         heartBeat = true;
183                         success = true;
184                     }
185                 } else {
186                     logger.warn("No known message {}", receivedMessage);
187                     heartBeat = false;
188                     success = false;
189                 }
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("\"", ""));
194                 } else {
195                     irCodeReceivedHandler.setCode("");
196                 }
197                 logger.debug("ir_receive message {}", irCodeReceivedHandler.getCode());
198                 heartBeat = true;
199                 success = true;
200             } else {
201                 logger.warn("No known message {}", irCodeReceivedHandler.getCode());
202                 heartBeat = false;
203                 success = false;
204             }
205         } else {
206             logger.warn("No known message {}", irCodeReceivedHandler.getCode());
207             heartBeat = false;
208             success = false;
209         }
210         return success;
211     }
212
213     private JsonObject convertStringToJsonObject(String jsonString) {
214         try {
215             JsonParser parser = new JsonParser();
216             JsonElement jsonElement = parser.parse(jsonString);
217             JsonObject result;
218
219             if (jsonElement instanceof JsonObject) {
220                 result = jsonElement.getAsJsonObject();
221             } else {
222                 logger.debug("{} is not valid JSON stirng", jsonString);
223                 result = new JsonObject();
224                 throw new IllegalArgumentException(jsonString + "{} is not valid JSON stirng");
225             }
226             return result;
227         } catch (IllegalArgumentException e) {
228             JsonObject result = new JsonObject();
229             return result;
230         }
231     }
232
233     public void updateState(String group, String channelId, State value) {
234         ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
235         updateState(id, value);
236     }
237
238     @Override
239     public Collection<Class<? extends ThingHandlerService>> getServices() {
240         return Collections.singleton(YIOremoteDockActions.class);
241     }
242
243     @Override
244     public void dispose() {
245         if (webSocketPollingJob != null) {
246             webSocketPollingJob.cancel(true);
247         }
248     }
249
250     @Override
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, "");
261                     } else {
262                         logger.debug("YIODOCKRECEIVERSWITCH no procedure");
263                     }
264                     break;
265                 default:
266                     break;
267             }
268         }
269     }
270
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);
275             } else {
276                 logger.warn("Wrong ir code format {}", irCode);
277             }
278         }
279     }
280
281     private ChannelUID getChannelUuid(String group, String typeId) {
282         return new ChannelUID(getThing().getUID(), group, typeId);
283     }
284
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));
288     }
289
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;
296                 break;
297             case AUTHENTICATION_PROCESS:
298                 if (authenticationOk) {
299                     yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_COMPLETE;
300                     updateStatus(ThingStatus.ONLINE);
301                     webSocketPollingJob = scheduler.scheduleWithFixedDelay(this::pollingWebsocket, 0, 30,
302                             TimeUnit.SECONDS);
303                 } else {
304                     yioRemoteDockActualStatus = YioRemoteDockHandleStatus.AUTHENTICATION_FAILED;
305                 }
306                 break;
307             default:
308                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
309                         "Connection lost no ping from YIO DOCK");
310                 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
311                 break;
312         }
313     }
314
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, "");
323                 } else {
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);
330                     }
331                 }
332                 break;
333             default:
334                 if (webSocketPollingJob != null) {
335                     webSocketPollingJob.cancel(true);
336                 }
337                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
338                         "Connection lost no ping from YIO DOCK");
339                 updateState(GROUP_OUTPUT, STATUS_STRING_CHANNEL, UnDefType.UNDEF);
340                 break;
341         }
342     }
343
344     public boolean getAndResetHeartbeat() {
345         boolean result = heartBeat;
346         heartBeat = false;
347         return result;
348     }
349
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());
356                 break;
357             case HEARTBEAT_MESSAGE:
358                 irCodeSendHandler.setCode("0;0x0;0;0");
359                 yioremoteDockwebSocketClient.sendMessage(irCodeSendMessageHandler.getIRcodeSendMessageString());
360                 logger.debug("sending heartBeat message: {}", irCodeSendMessageHandler.getIRcodeSendMessageString());
361                 break;
362             case IR_RECEIVER_ON:
363                 irReceiverMessageHandler.setOn();
364                 yioremoteDockwebSocketClient.sendMessage(irReceiverMessageHandler.getIRreceiverMessageString());
365                 logger.debug("sending IR receiver on message: {}",
366                         irReceiverMessageHandler.getIRreceiverMessageString());
367                 break;
368             case IR_RECEIVER_OFF:
369                 irReceiverMessageHandler.setOff();
370                 yioremoteDockwebSocketClient.sendMessage(irReceiverMessageHandler.getIRreceiverMessageString());
371                 logger.debug("sending IR receiver on message: {}",
372                         irReceiverMessageHandler.getIRreceiverMessageString());
373                 break;
374             case IR_SEND:
375                 irCodeSendHandler.setCode(messagePayload);
376                 yioremoteDockwebSocketClient.sendMessage(irCodeSendMessageHandler.getIRcodeSendMessageString());
377                 logger.debug("sending heartBeat message: {}", irCodeSendMessageHandler.getIRcodeSendMessageString());
378                 break;
379         }
380     }
381 }