]> git.basschouten.com Git - openhab-addons.git/blob
219ab9076c8734e98a1db638a8ec8b74f42e1c37
[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.lgwebos.internal.action;
14
15 import java.awt.image.BufferedImage;
16 import java.io.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.OutputStream;
19 import java.net.URL;
20 import java.nio.charset.StandardCharsets;
21 import java.util.Base64;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Optional;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27
28 import javax.imageio.ImageIO;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
33 import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVMouseSocket.ButtonType;
34 import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket;
35 import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket.State;
36 import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
37 import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
38 import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
39 import org.openhab.binding.lgwebos.internal.handler.core.TextInputStatusInfo;
40 import org.openhab.core.automation.annotation.ActionInput;
41 import org.openhab.core.automation.annotation.RuleAction;
42 import org.openhab.core.thing.binding.ThingActions;
43 import org.openhab.core.thing.binding.ThingActionsScope;
44 import org.openhab.core.thing.binding.ThingHandler;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.google.gson.JsonObject;
49 import com.google.gson.JsonParseException;
50 import com.google.gson.JsonParser;
51
52 /**
53  * The {@link LGWebOSActions} defines the thing actions for the LGwebOS binding.
54  *
55  * @author Sebastian Prehn - Initial contribution
56  * @author Laurent Garnier - new method invokeMethodOf + interface ILGWebOSActions
57  */
58 @ThingActionsScope(name = "lgwebos")
59 @NonNullByDefault
60 public class LGWebOSActions implements ThingActions {
61     private final Logger logger = LoggerFactory.getLogger(LGWebOSActions.class);
62     private final ResponseListener<TextInputStatusInfo> textInputListener = createTextInputStatusListener();
63     private @Nullable LGWebOSHandler handler;
64
65     @Override
66     public void setThingHandler(@Nullable ThingHandler handler) {
67         this.handler = (LGWebOSHandler) handler;
68     }
69
70     @Override
71     public @Nullable ThingHandler getThingHandler() {
72         return handler;
73     }
74
75     // a NonNull getter for handler
76     private LGWebOSHandler getLGWebOSHandler() {
77         LGWebOSHandler lgWebOSHandler = this.handler;
78         if (lgWebOSHandler == null) {
79             throw new IllegalStateException(
80                     "ThingHandler must be set before any action may be invoked on LGWebOSActions.");
81         }
82         return lgWebOSHandler;
83     }
84
85     private enum Button {
86         UP,
87         DOWN,
88         LEFT,
89         RIGHT,
90         BACK,
91         DELETE,
92         ENTER,
93         HOME,
94         OK
95     }
96
97     @RuleAction(label = "@text/actionShowToastLabel", description = "@text/actionShowToastDesc")
98     public void showToast(
99             @ActionInput(name = "text", label = "@text/actionShowToastInputTextLabel", description = "@text/actionShowToastInputTextDesc") String text)
100             throws IOException {
101         getConnectedSocket().ifPresent(control -> control.showToast(text, createResponseListener()));
102     }
103
104     @RuleAction(label = "@text/actionShowToastWithIconLabel", description = "@text/actionShowToastWithIconLabel")
105     public void showToast(
106             @ActionInput(name = "icon", label = "@text/actionShowToastInputIconLabel", description = "@text/actionShowToastInputIconDesc") String icon,
107             @ActionInput(name = "text", label = "@text/actionShowToastInputTextLabel", description = "@text/actionShowToastInputTextDesc") String text)
108             throws IOException {
109         BufferedImage bi = ImageIO.read(new URL(icon));
110         try (ByteArrayOutputStream os = new ByteArrayOutputStream(); OutputStream b64 = Base64.getEncoder().wrap(os)) {
111             ImageIO.write(bi, "png", b64);
112             String string = os.toString(StandardCharsets.UTF_8.name());
113             getConnectedSocket().ifPresent(control -> control.showToast(text, string, "png", createResponseListener()));
114         }
115     }
116
117     @RuleAction(label = "@text/actionLaunchBrowserLabel", description = "@text/actionLaunchBrowserDesc")
118     public void launchBrowser(
119             @ActionInput(name = "url", label = "@text/actionLaunchBrowserInputUrlLabel", description = "@text/actionLaunchBrowserInputUrlDesc") String url) {
120         getConnectedSocket().ifPresent(control -> control.launchBrowser(url, createResponseListener()));
121     }
122
123     private List<AppInfo> getAppInfos() {
124         LGWebOSHandler lgWebOSHandler = getLGWebOSHandler();
125
126         if (!this.getConnectedSocket().isPresent()) {
127             return Collections.emptyList();
128         }
129
130         List<AppInfo> appInfos = lgWebOSHandler.getLauncherApplication()
131                 .getAppInfos(lgWebOSHandler.getThing().getUID());
132         if (appInfos == null) {
133             logger.warn("No AppInfos found for device with ThingID {}.", lgWebOSHandler.getThing().getUID());
134             return Collections.emptyList();
135         }
136         return appInfos;
137     }
138
139     @RuleAction(label = "@text/actionLaunchApplicationLabel", description = "@text/actionLaunchApplicationDesc")
140     public void launchApplication(
141             @ActionInput(name = "appId", label = "@text/actionLaunchApplicationInputAppIDLabel", description = "@text/actionLaunchApplicationInputAppIDDesc") String appId) {
142         Optional<AppInfo> appInfo = getAppInfos().stream().filter(a -> a.getId().equals(appId)).findFirst();
143         if (appInfo.isPresent()) {
144             getConnectedSocket()
145                     .ifPresent(control -> control.launchAppWithInfo(appInfo.get(), createResponseListener()));
146         } else {
147             logger.warn("Device with ThingID {} does not support any app with id: {}.",
148                     getLGWebOSHandler().getThing().getUID(), appId);
149         }
150     }
151
152     @RuleAction(label = "@text/actionLaunchApplicationWithParamsLabel", description = "@text/actionLaunchApplicationWithParamsDesc")
153     public void launchApplication(
154             @ActionInput(name = "appId", label = "@text/actionLaunchApplicationInputAppIDLabel", description = "@text/actionLaunchApplicationInputAppIDDesc") String appId,
155             @ActionInput(name = "params", label = "@text/actionLaunchApplicationInputParamsLabel", description = "@text/actionLaunchApplicationInputParamsDesc") String params) {
156         try {
157             JsonParser parser = new JsonParser();
158             JsonObject payload = (JsonObject) parser.parse(params);
159
160             Optional<AppInfo> appInfo = getAppInfos().stream().filter(a -> a.getId().equals(appId)).findFirst();
161             if (appInfo.isPresent()) {
162                 getConnectedSocket().ifPresent(
163                         control -> control.launchAppWithInfo(appInfo.get(), payload, createResponseListener()));
164             } else {
165                 logger.warn("Device with ThingID {} does not support any app with id: {}.",
166                         getLGWebOSHandler().getThing().getUID(), appId);
167             }
168
169         } catch (JsonParseException ex) {
170             logger.warn("Parameters value ({}) is not in a valid JSON format. {}", params, ex.getMessage());
171             return;
172         }
173     }
174
175     @RuleAction(label = "@text/actionSendTextLabel", description = "@text/actionSendTextDesc")
176     public void sendText(
177             @ActionInput(name = "text", label = "@text/actionSendTextInputTextLabel", description = "@text/actionSendTextInputTextDesc") String text) {
178         getConnectedSocket().ifPresent(control -> {
179             ServiceSubscription<TextInputStatusInfo> subscription = control.subscribeTextInputStatus(textInputListener);
180             control.sendText(text);
181             control.unsubscribe(subscription);
182         });
183     }
184
185     @RuleAction(label = "@text/actionSendButtonLabel", description = "@text/actionSendButtonDesc")
186     public void sendButton(
187             @ActionInput(name = "text", label = "@text/actionSendButtonInputButtonLabel", description = "@text/actionSendButtonInputButtonDesc") String button) {
188         try {
189             switch (Button.valueOf(button)) {
190                 case UP:
191                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.UP)));
192                     break;
193                 case DOWN:
194                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.DOWN)));
195                     break;
196                 case LEFT:
197                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.LEFT)));
198                     break;
199                 case RIGHT:
200                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.RIGHT)));
201                     break;
202                 case BACK:
203                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.BACK)));
204                     break;
205                 case DELETE:
206                     getConnectedSocket().ifPresent(control -> control.sendDelete());
207                     break;
208                 case ENTER:
209                     getConnectedSocket().ifPresent(control -> control.sendEnter());
210                     break;
211                 case HOME:
212                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button("HOME")));
213                     break;
214                 case OK:
215                     getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.click()));
216                     break;
217             }
218         } catch (IllegalArgumentException ex) {
219             logger.warn("{} is not a valid value for button - available are: {}", button,
220                     Stream.of(Button.values()).map(b -> b.name()).collect(Collectors.joining(", ")));
221         }
222     }
223
224     @RuleAction(label = "@text/actionIncreaseChannelLabel", description = "@text/actionIncreaseChannelDesc")
225     public void increaseChannel() {
226         getConnectedSocket().ifPresent(control -> control.channelUp(createResponseListener()));
227     }
228
229     @RuleAction(label = "@text/actionDecreaseChannelLabel", description = "@text/actionDecreaseChannelDesc")
230     public void decreaseChannel() {
231         getConnectedSocket().ifPresent(control -> control.channelDown(createResponseListener()));
232     }
233
234     @RuleAction(label = "@text/actionSendRCButtonLabel", description = "@text/actionSendRCButtonDesc")
235     public void sendRCButton(
236             @ActionInput(name = "text", label = "@text/actionSendRCButtonInputTextLabel", description = "@text/actionSendRCButtonInputTextDesc") String rcButton) {
237         getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(rcButton)));
238     }
239
240     private Optional<LGWebOSTVSocket> getConnectedSocket() {
241         LGWebOSHandler lgWebOSHandler = getLGWebOSHandler();
242         final LGWebOSTVSocket socket = lgWebOSHandler.getSocket();
243
244         if (socket.getState() != State.REGISTERED) {
245             logger.warn("Device with ThingID {} is currently not connected.", lgWebOSHandler.getThing().getUID());
246             return Optional.empty();
247         }
248
249         return Optional.of(socket);
250     }
251
252     private ResponseListener<TextInputStatusInfo> createTextInputStatusListener() {
253         return new ResponseListener<TextInputStatusInfo>() {
254
255             @Override
256             public void onError(@Nullable String error) {
257                 logger.warn("Response: {}", error);
258             }
259
260             @Override
261             public void onSuccess(@Nullable TextInputStatusInfo info) {
262                 logger.debug("Response: {}", info == null ? "OK" : info.getRawData());
263             }
264         };
265     }
266
267     private <O> ResponseListener<O> createResponseListener() {
268         return new ResponseListener<O>() {
269
270             @Override
271             public void onError(@Nullable String error) {
272                 logger.warn("Response: {}", error);
273             }
274
275             @Override
276             public void onSuccess(@Nullable O object) {
277                 logger.debug("Response: {}", object == null ? "OK" : object.toString());
278             }
279         };
280     }
281
282     // delegation methods for "legacy" rule support
283
284     public static void showToast(ThingActions actions, String text) throws IOException {
285         ((LGWebOSActions) actions).showToast(text);
286     }
287
288     public static void showToast(ThingActions actions, String icon, String text) throws IOException {
289         ((LGWebOSActions) actions).showToast(icon, text);
290     }
291
292     public static void launchBrowser(ThingActions actions, String url) {
293         ((LGWebOSActions) actions).launchBrowser(url);
294     }
295
296     public static void launchApplication(ThingActions actions, String appId) {
297         ((LGWebOSActions) actions).launchApplication(appId);
298     }
299
300     public static void launchApplication(ThingActions actions, String appId, String param) {
301         ((LGWebOSActions) actions).launchApplication(appId, param);
302     }
303
304     public static void sendText(ThingActions actions, String text) {
305         ((LGWebOSActions) actions).sendText(text);
306     }
307
308     public static void sendButton(ThingActions actions, String button) {
309         ((LGWebOSActions) actions).sendButton(button);
310     }
311
312     public static void increaseChannel(ThingActions actions) {
313         ((LGWebOSActions) actions).increaseChannel();
314     }
315
316     public static void decreaseChannel(ThingActions actions) {
317         ((LGWebOSActions) actions).decreaseChannel();
318     }
319
320     public static void sendRCButton(ThingActions actions, String rcButton) {
321         ((LGWebOSActions) actions).sendRCButton(rcButton);
322     }
323 }