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