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