added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.lgwebos-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-lgwebos" description="LG webOS Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-upnp</feature>
|
||||
<feature>openhab.tp-httpclient</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.lgwebos/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.action;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ILGWebOSActions} defines the interface for all thing actions supported by the binding.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ILGWebOSActions {
|
||||
|
||||
public void showToast(String text) throws IOException;
|
||||
|
||||
public void showToast(String icon, String text) throws IOException;
|
||||
|
||||
public void launchBrowser(String url);
|
||||
|
||||
public void launchApplication(String appId);
|
||||
|
||||
public void launchApplication(String appId, String params);
|
||||
|
||||
public void sendText(String text);
|
||||
|
||||
public void sendButton(String button);
|
||||
|
||||
public void increaseChannel();
|
||||
|
||||
public void decreaseChannel();
|
||||
|
||||
public void sendRCButton(String rcButton);
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.action;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVMouseSocket.ButtonType;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket.State;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.TextInputStatusInfo;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* The {@link LGWebOSActions} defines the thing actions for the LGwebOS binding.
|
||||
* <p>
|
||||
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
|
||||
* the test <i>actions instanceof LGWebOSActions</i> fails. This test can fail
|
||||
* due to an issue in openHAB core v2.5.0 where the {@link LGWebOSActions} class
|
||||
* can be loaded by a different classloader than the <i>actions</i> instance.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
* @author Laurent Garnier - new method invokeMethodOf + interface ILGWebOSActions
|
||||
*/
|
||||
@ThingActionsScope(name = "lgwebos")
|
||||
@NonNullByDefault
|
||||
public class LGWebOSActions implements ThingActions, ILGWebOSActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSActions.class);
|
||||
private final ResponseListener<TextInputStatusInfo> textInputListener = createTextInputStatusListener();
|
||||
private @Nullable LGWebOSHandler handler;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (LGWebOSHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
// a NonNull getter for handler
|
||||
private LGWebOSHandler getLGWebOSHandler() {
|
||||
LGWebOSHandler lgWebOSHandler = this.handler;
|
||||
if (lgWebOSHandler == null) {
|
||||
throw new IllegalStateException(
|
||||
"ThingHandler must be set before any action may be invoked on LGWebOSActions.");
|
||||
}
|
||||
return lgWebOSHandler;
|
||||
}
|
||||
|
||||
private enum Button {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
BACK,
|
||||
DELETE,
|
||||
ENTER,
|
||||
HOME,
|
||||
OK
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionShowToastLabel", description = "@text/actionShowToastDesc")
|
||||
public void showToast(
|
||||
@ActionInput(name = "text", label = "@text/actionShowToastInputTextLabel", description = "@text/actionShowToastInputTextDesc") String text)
|
||||
throws IOException {
|
||||
getConnectedSocket().ifPresent(control -> control.showToast(text, createResponseListener()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionShowToastWithIconLabel", description = "@text/actionShowToastWithIconLabel")
|
||||
public void showToast(
|
||||
@ActionInput(name = "icon", label = "@text/actionShowToastInputIconLabel", description = "@text/actionShowToastInputIconDesc") String icon,
|
||||
@ActionInput(name = "text", label = "@text/actionShowToastInputTextLabel", description = "@text/actionShowToastInputTextDesc") String text)
|
||||
throws IOException {
|
||||
BufferedImage bi = ImageIO.read(new URL(icon));
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream(); OutputStream b64 = Base64.getEncoder().wrap(os)) {
|
||||
ImageIO.write(bi, "png", b64);
|
||||
String string = os.toString(StandardCharsets.UTF_8.name());
|
||||
getConnectedSocket().ifPresent(control -> control.showToast(text, string, "png", createResponseListener()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionLaunchBrowserLabel", description = "@text/actionLaunchBrowserDesc")
|
||||
public void launchBrowser(
|
||||
@ActionInput(name = "url", label = "@text/actionLaunchBrowserInputUrlLabel", description = "@text/actionLaunchBrowserInputUrlDesc") String url) {
|
||||
getConnectedSocket().ifPresent(control -> control.launchBrowser(url, createResponseListener()));
|
||||
}
|
||||
|
||||
private List<AppInfo> getAppInfos() {
|
||||
LGWebOSHandler lgWebOSHandler = getLGWebOSHandler();
|
||||
|
||||
if (!this.getConnectedSocket().isPresent()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AppInfo> appInfos = lgWebOSHandler.getLauncherApplication()
|
||||
.getAppInfos(lgWebOSHandler.getThing().getUID());
|
||||
if (appInfos == null) {
|
||||
logger.warn("No AppInfos found for device with ThingID {}.", lgWebOSHandler.getThing().getUID());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return appInfos;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionLaunchApplicationLabel", description = "@text/actionLaunchApplicationDesc")
|
||||
public void launchApplication(
|
||||
@ActionInput(name = "appId", label = "@text/actionLaunchApplicationInputAppIDLabel", description = "@text/actionLaunchApplicationInputAppIDDesc") String appId) {
|
||||
Optional<AppInfo> appInfo = getAppInfos().stream().filter(a -> a.getId().equals(appId)).findFirst();
|
||||
if (appInfo.isPresent()) {
|
||||
getConnectedSocket()
|
||||
.ifPresent(control -> control.launchAppWithInfo(appInfo.get(), createResponseListener()));
|
||||
} else {
|
||||
logger.warn("Device with ThingID {} does not support any app with id: {}.",
|
||||
getLGWebOSHandler().getThing().getUID(), appId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionLaunchApplicationWithParamsLabel", description = "@text/actionLaunchApplicationWithParamsDesc")
|
||||
public void launchApplication(
|
||||
@ActionInput(name = "appId", label = "@text/actionLaunchApplicationInputAppIDLabel", description = "@text/actionLaunchApplicationInputAppIDDesc") String appId,
|
||||
@ActionInput(name = "params", label = "@text/actionLaunchApplicationInputParamsLabel", description = "@text/actionLaunchApplicationInputParamsDesc") String params) {
|
||||
try {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject payload = (JsonObject) parser.parse(params);
|
||||
|
||||
Optional<AppInfo> appInfo = getAppInfos().stream().filter(a -> a.getId().equals(appId)).findFirst();
|
||||
if (appInfo.isPresent()) {
|
||||
getConnectedSocket().ifPresent(
|
||||
control -> control.launchAppWithInfo(appInfo.get(), payload, createResponseListener()));
|
||||
} else {
|
||||
logger.warn("Device with ThingID {} does not support any app with id: {}.",
|
||||
getLGWebOSHandler().getThing().getUID(), appId);
|
||||
}
|
||||
|
||||
} catch (JsonParseException ex) {
|
||||
logger.warn("Parameters value ({}) is not in a valid JSON format. {}", params, ex.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionSendTextLabel", description = "@text/actionSendTextDesc")
|
||||
public void sendText(
|
||||
@ActionInput(name = "text", label = "@text/actionSendTextInputTextLabel", description = "@text/actionSendTextInputTextDesc") String text) {
|
||||
getConnectedSocket().ifPresent(control -> {
|
||||
ServiceSubscription<TextInputStatusInfo> subscription = control.subscribeTextInputStatus(textInputListener);
|
||||
control.sendText(text);
|
||||
control.unsubscribe(subscription);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionSendButtonLabel", description = "@text/actionSendButtonDesc")
|
||||
public void sendButton(
|
||||
@ActionInput(name = "text", label = "@text/actionSendButtonInputButtonLabel", description = "@text/actionSendButtonInputButtonDesc") String button) {
|
||||
try {
|
||||
switch (Button.valueOf(button)) {
|
||||
case UP:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.UP)));
|
||||
break;
|
||||
case DOWN:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.DOWN)));
|
||||
break;
|
||||
case LEFT:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.LEFT)));
|
||||
break;
|
||||
case RIGHT:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.RIGHT)));
|
||||
break;
|
||||
case BACK:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(ButtonType.BACK)));
|
||||
break;
|
||||
case DELETE:
|
||||
getConnectedSocket().ifPresent(control -> control.sendDelete());
|
||||
break;
|
||||
case ENTER:
|
||||
getConnectedSocket().ifPresent(control -> control.sendEnter());
|
||||
break;
|
||||
case HOME:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button("HOME")));
|
||||
break;
|
||||
case OK:
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.click()));
|
||||
break;
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
logger.warn("{} is not a valid value for button - available are: {}", button,
|
||||
Stream.of(Button.values()).map(b -> b.name()).collect(Collectors.joining(", ")));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionIncreaseChannelLabel", description = "@text/actionIncreaseChannelDesc")
|
||||
public void increaseChannel() {
|
||||
getConnectedSocket().ifPresent(control -> control.channelUp(createResponseListener()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionDecreaseChannelLabel", description = "@text/actionDecreaseChannelDesc")
|
||||
public void decreaseChannel() {
|
||||
getConnectedSocket().ifPresent(control -> control.channelDown(createResponseListener()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "@text/actionSendRCButtonLabel", description = "@text/actionSendRCButtonDesc")
|
||||
public void sendRCButton(
|
||||
@ActionInput(name = "text", label = "@text/actionSendRCButtonInputTextLabel", description = "@text/actionSendRCButtonInputTextDesc") String rcButton) {
|
||||
getConnectedSocket().ifPresent(control -> control.executeMouse(s -> s.button(rcButton)));
|
||||
}
|
||||
|
||||
private Optional<LGWebOSTVSocket> getConnectedSocket() {
|
||||
LGWebOSHandler lgWebOSHandler = getLGWebOSHandler();
|
||||
final LGWebOSTVSocket socket = lgWebOSHandler.getSocket();
|
||||
|
||||
if (socket.getState() != State.REGISTERED) {
|
||||
logger.warn("Device with ThingID {} is currently not connected.", lgWebOSHandler.getThing().getUID());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(socket);
|
||||
}
|
||||
|
||||
private ResponseListener<TextInputStatusInfo> createTextInputStatusListener() {
|
||||
return new ResponseListener<TextInputStatusInfo>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.warn("Response: {}", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable TextInputStatusInfo info) {
|
||||
logger.debug("Response: {}", info == null ? "OK" : info.getRawData());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private <O> ResponseListener<O> createResponseListener() {
|
||||
return new ResponseListener<O>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.warn("Response: {}", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable O object) {
|
||||
logger.debug("Response: {}", object == null ? "OK" : object.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// delegation methods for "legacy" rule support
|
||||
|
||||
private static ILGWebOSActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(LGWebOSActions.class.getName())) {
|
||||
if (actions instanceof ILGWebOSActions) {
|
||||
return (ILGWebOSActions) actions;
|
||||
} else {
|
||||
return (ILGWebOSActions) Proxy.newProxyInstance(ILGWebOSActions.class.getClassLoader(),
|
||||
new Class[] { ILGWebOSActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of LGWebOSActions");
|
||||
}
|
||||
|
||||
public static void showToast(@Nullable ThingActions actions, String text) throws IOException {
|
||||
invokeMethodOf(actions).showToast(text);
|
||||
}
|
||||
|
||||
public static void showToast(@Nullable ThingActions actions, String icon, String text) throws IOException {
|
||||
invokeMethodOf(actions).showToast(icon, text);
|
||||
}
|
||||
|
||||
public static void launchBrowser(@Nullable ThingActions actions, String url) {
|
||||
invokeMethodOf(actions).launchBrowser(url);
|
||||
}
|
||||
|
||||
public static void launchApplication(@Nullable ThingActions actions, String appId) {
|
||||
invokeMethodOf(actions).launchApplication(appId);
|
||||
}
|
||||
|
||||
public static void launchApplication(@Nullable ThingActions actions, String appId, String param) {
|
||||
invokeMethodOf(actions).launchApplication(appId, param);
|
||||
}
|
||||
|
||||
public static void sendText(@Nullable ThingActions actions, String text) {
|
||||
invokeMethodOf(actions).sendText(text);
|
||||
}
|
||||
|
||||
public static void sendButton(@Nullable ThingActions actions, String button) {
|
||||
invokeMethodOf(actions).sendButton(button);
|
||||
}
|
||||
|
||||
public static void increaseChannel(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).increaseChannel();
|
||||
}
|
||||
|
||||
public static void decreaseChannel(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).decreaseChannel();
|
||||
}
|
||||
|
||||
public static void sendRCButton(@Nullable ThingActions actions, String rcButton) {
|
||||
invokeMethodOf(actions).sendRCButton(rcButton);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* An abstract implementation of ChannelHander which serves as a base class for all concrete instances.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class BaseChannelHandler<T> implements ChannelHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(BaseChannelHandler.class);
|
||||
|
||||
private final ResponseListener<T> defaultResponseListener = createResponseListener();
|
||||
|
||||
protected <Y> ResponseListener<Y> createResponseListener() {
|
||||
return new ResponseListener<Y>() {
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
logger.debug("{} received error response: {}", BaseChannelHandler.this.getClass().getSimpleName(),
|
||||
error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Y object) {
|
||||
logger.debug("{} received: {}.", BaseChannelHandler.this.getClass().getSimpleName(), object);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// IP to Subscriptions map
|
||||
private Map<ThingUID, ServiceSubscription<T>> subscriptions = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onDeviceReady(String channelId, LGWebOSHandler handler) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(String channelId, LGWebOSHandler handler) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void refreshSubscription(String channelId, LGWebOSHandler handler) {
|
||||
removeAnySubscription(handler);
|
||||
|
||||
Optional<ServiceSubscription<T>> listener = getSubscription(channelId, handler);
|
||||
if (listener.isPresent()) {
|
||||
logger.debug("Subscribed {} on Thing: {}", this.getClass().getName(), handler.getThing().getUID());
|
||||
subscriptions.put(handler.getThing().getUID(), listener.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a subscription instance for this device if subscription is supported.
|
||||
*
|
||||
* @param device device to which state changes to subscribe to
|
||||
* @param channelID channel ID
|
||||
* @param handler
|
||||
* @return an {@code Optional} containing the ServiceSubscription, or an empty {@code Optional} if subscription is
|
||||
* not supported.
|
||||
*/
|
||||
protected Optional<ServiceSubscription<T>> getSubscription(String channelId, LGWebOSHandler handler) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void removeAnySubscription(LGWebOSHandler handler) {
|
||||
ServiceSubscription<T> l = subscriptions.remove(handler.getThing().getUID());
|
||||
if (l != null) {
|
||||
handler.getSocket().unsubscribe(l);
|
||||
logger.debug("Unsubscribed {} on Thing: {}", this.getClass().getName(), handler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
protected ResponseListener<T> getDefaultResponseListener() {
|
||||
return defaultResponseListener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Channel Handler mediates between connect sdk device state changes and openhab channel events.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelHandler {
|
||||
|
||||
/**
|
||||
* This method will be called whenever a command is received for this handler.
|
||||
* All implementations provide custom logic here.
|
||||
*
|
||||
* @param channelId must not be <code>null</code>
|
||||
* @param handler must not be <code>null</code>
|
||||
* @param command must not be <code>null</code>
|
||||
*/
|
||||
void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command);
|
||||
|
||||
/**
|
||||
* Handle underlying subscription status if device changes online state, capabilities or channel gets linked or
|
||||
* unlinked.
|
||||
*
|
||||
* Implementation first removes any subscription via removeAnySubscription and subsequently establishes any required
|
||||
* subscription on this device channel handler.
|
||||
*
|
||||
* @param channelId must not be <code>null</code>
|
||||
* @param handler must not be <code>null</code>
|
||||
*/
|
||||
void refreshSubscription(String channelId, LGWebOSHandler handler);
|
||||
|
||||
/**
|
||||
* Removes subscriptions if there are any.
|
||||
*
|
||||
* @param handler must not be <code>null</code>
|
||||
*/
|
||||
void removeAnySubscription(LGWebOSHandler handler);
|
||||
|
||||
/**
|
||||
* Callback method whenever a device disappears.
|
||||
*
|
||||
* @param channelId must not be <code>null</code>
|
||||
* @param handler must not be <code>null</code>
|
||||
*/
|
||||
void onDeviceRemoved(String channelId, LGWebOSHandler handler);
|
||||
|
||||
/**
|
||||
* Callback method whenever a device is discovered and ready to operate.
|
||||
*
|
||||
* @param channelId must not be <code>null</code>
|
||||
* @param handler must not be <code>null</code>
|
||||
*/
|
||||
void onDeviceReady(String channelId, LGWebOSHandler handler);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.jupnp.model.types.ServiceType;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* This class defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LGWebOSBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "lgwebos";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_WEBOSTV = new ThingTypeUID(BINDING_ID, "WebOSTV");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_WEBOSTV);
|
||||
|
||||
public static final ServiceType UPNP_SERVICE_TYPE = new ServiceType("lge-com", "webos-second-screen", 1);
|
||||
|
||||
/*
|
||||
* Config names must match property names in
|
||||
* - WebOSConfiguration
|
||||
* - parameter names in OH-INF/config/config.xml
|
||||
* - property names in OH-INF/thing/thing-types.xml
|
||||
*/
|
||||
public static final String CONFIG_HOST = "host";
|
||||
public static final String CONFIG_KEY = "key";
|
||||
public static final String CONFIG_MAC_ADDRESS = "macAddress";
|
||||
|
||||
/*
|
||||
* Property names must match property names in
|
||||
* - property names in OH-INF/thing/thing-types.xml
|
||||
*/
|
||||
public static final String PROPERTY_DEVICE_ID = "deviceId";
|
||||
public static final String PROPERTY_DEVICE_OS = "deviceOS";
|
||||
public static final String PROPERTY_DEVICE_OS_VERSION = "deviceOSVersion";
|
||||
public static final String PROPERTY_DEVICE_OS_RELEASE_VERSION = "deviceOSReleaseVersion";
|
||||
public static final String PROPERTY_LAST_CONNECTED = "lastConnected";
|
||||
|
||||
/*
|
||||
* List of all Channel ids.
|
||||
* Values have to match ids in thing-types.xml
|
||||
*/
|
||||
public static final String CHANNEL_VOLUME = "volume";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_MUTE = "mute";
|
||||
public static final String CHANNEL_CHANNEL = "channel";
|
||||
public static final String CHANNEL_TOAST = "toast";
|
||||
public static final String CHANNEL_MEDIA_PLAYER = "mediaPlayer";
|
||||
public static final String CHANNEL_MEDIA_STOP = "mediaStop";
|
||||
public static final String CHANNEL_APP_LAUNCHER = "appLauncher";
|
||||
public static final String CHANNEL_RCBUTTON = "rcButton";
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import static org.openhab.binding.lgwebos.internal.LGWebOSBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.core.io.net.http.WebSocketFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LGWebOSHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.lgwebos")
|
||||
public class LGWebOSHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSHandlerFactory.class);
|
||||
|
||||
private final WebSocketClient webSocketClient;
|
||||
|
||||
private final LGWebOSStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public LGWebOSHandlerFactory(final @Reference WebSocketFactory webSocketFactory,
|
||||
final @Reference LGWebOSStateDescriptionOptionProvider stateDescriptionProvider) {
|
||||
/*
|
||||
* Cannot use openHAB's shared web socket client (webSocketFactory.getCommonWebSocketClient()) as we have to
|
||||
* change client settings.
|
||||
*/
|
||||
this.webSocketClient = webSocketFactory.createWebSocketClient("lgwebos");
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (thingTypeUID.equals(THING_TYPE_WEBOSTV)) {
|
||||
return new LGWebOSHandler(thing, webSocketClient, stateDescriptionProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate(ComponentContext componentContext) {
|
||||
super.activate(componentContext);
|
||||
// LGWebOS TVs only support WEAK cipher suites, thus not using SSL.
|
||||
// SslContextFactory sslContextFactory = new SslContextFactory(true);
|
||||
// sslContextFactory.addExcludeProtocols("tls/1.3");
|
||||
|
||||
// reduce timeout from default 15sec
|
||||
this.webSocketClient.setConnectTimeout(1000);
|
||||
|
||||
// channel and app listing are json docs up to 3MB
|
||||
this.webSocketClient.getPolicy().setMaxTextMessageSize(3 * 1024 * 1024);
|
||||
|
||||
// since this is not using openHAB's shared web socket client we need to start and stop
|
||||
try {
|
||||
this.webSocketClient.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to to start websocket client.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate(ComponentContext componentContext) {
|
||||
super.deactivate(componentContext);
|
||||
try {
|
||||
this.webSocketClient.stop();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to to stop websocket client.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of state options while leaving other state description fields as original.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, LGWebOSStateDescriptionOptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class LGWebOSStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.LaunchSession;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides ability to launch an application on the TV.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LauncherApplication extends BaseChannelHandler<AppInfo> {
|
||||
private final Logger logger = LoggerFactory.getLogger(LauncherApplication.class);
|
||||
private final Map<ThingUID, @Nullable List<AppInfo>> applicationListCache = new HashMap<>();
|
||||
private final ResponseListener<LaunchSession> launchSessionResponseListener = createResponseListener();
|
||||
|
||||
@Override
|
||||
public void onDeviceReady(String channelId, LGWebOSHandler handler) {
|
||||
super.onDeviceReady(channelId, handler);
|
||||
|
||||
handler.getSocket().getAppList(new ResponseListener<List<AppInfo>>() {
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
logger.warn("Error requesting application list: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void onSuccess(List<AppInfo> appInfos) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
for (AppInfo a : appInfos) {
|
||||
logger.debug("AppInfo {} - {}", a.getId(), a.getName());
|
||||
}
|
||||
}
|
||||
applicationListCache.put(handler.getThing().getUID(), appInfos);
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (AppInfo appInfo : appInfos) {
|
||||
options.add(new StateOption(appInfo.getId(), appInfo.getName()));
|
||||
}
|
||||
handler.setOptions(channelId, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(String channelId, LGWebOSHandler handler) {
|
||||
super.onDeviceRemoved(channelId, handler);
|
||||
applicationListCache.remove(handler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
handler.getSocket().getRunningApp(createResponseListener(channelId, handler));
|
||||
return;
|
||||
}
|
||||
|
||||
final String value = command.toString();
|
||||
|
||||
List<AppInfo> appInfos = applicationListCache.get(handler.getThing().getUID());
|
||||
if (appInfos == null) {
|
||||
logger.warn("No application list cached for this device {}, ignoring command.",
|
||||
handler.getThing().getUID());
|
||||
} else {
|
||||
Optional<AppInfo> appInfo = appInfos.stream().filter(a -> a.getId().equals(value)).findFirst();
|
||||
if (appInfo.isPresent()) {
|
||||
handler.getSocket().launchAppWithInfo(appInfo.get(), launchSessionResponseListener);
|
||||
} else {
|
||||
logger.warn("TV does not support any app with id: {}.", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ServiceSubscription<AppInfo>> getSubscription(String channelId, LGWebOSHandler handler) {
|
||||
return Optional.of(handler.getSocket().subscribeRunningApp(createResponseListener(channelId, handler)));
|
||||
}
|
||||
|
||||
private ResponseListener<AppInfo> createResponseListener(String channelId, LGWebOSHandler handler) {
|
||||
return new ResponseListener<AppInfo>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.debug("Error in retrieving application: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable AppInfo appInfo) {
|
||||
if (appInfo == null || appInfo.getId().isEmpty()) {
|
||||
handler.postUpdate(channelId, UnDefType.UNDEF);
|
||||
} else {
|
||||
handler.postUpdate(channelId, new StringType(appInfo.getId()));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public @Nullable List<AppInfo> getAppInfos(ThingUID key) {
|
||||
return applicationListCache.get(key);
|
||||
}
|
||||
|
||||
public List<String> reportApplications(ThingUID thingUID) {
|
||||
List<String> report = new ArrayList<>();
|
||||
List<AppInfo> appInfos = applicationListCache.get(thingUID);
|
||||
if (appInfos != null) {
|
||||
for (AppInfo a : appInfos) {
|
||||
report.add(a.getId() + " : " + a.getName());
|
||||
}
|
||||
}
|
||||
return report;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles commands of a Player Item.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MediaControlPlayer extends BaseChannelHandler<CommandConfirmation> {
|
||||
private final Logger logger = LoggerFactory.getLogger(MediaControlPlayer.class);
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
// nothing to do
|
||||
} else if (PlayPauseType.PLAY == command) {
|
||||
handler.getSocket().play(getDefaultResponseListener());
|
||||
} else if (PlayPauseType.PAUSE == command) {
|
||||
handler.getSocket().pause(getDefaultResponseListener());
|
||||
} else if (RewindFastforwardType.FASTFORWARD == command) {
|
||||
handler.getSocket().fastForward(getDefaultResponseListener());
|
||||
} else if (RewindFastforwardType.REWIND == command) {
|
||||
handler.getSocket().rewind(getDefaultResponseListener());
|
||||
} else {
|
||||
logger.info("Only accept PlayPauseType, RewindFastforwardType, RefreshType. Type was {}.",
|
||||
command.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: playstatesubscription
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
/**
|
||||
* Handles Media Control Command Stop.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MediaControlStop extends BaseChannelHandler<CommandConfirmation> {
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
return;
|
||||
}
|
||||
handler.getSocket().stop(getDefaultResponseListener());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket.State;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles Power Control Command.
|
||||
* Note: Connect SDK only supports powering OFF for most devices.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PowerControlPower extends BaseChannelHandler<CommandConfirmation> {
|
||||
private static final int WOL_PACKET_RETRY_COUNT = 10;
|
||||
private static final int WOL_PACKET_RETRY_DELAY_MILLIS = 100;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PowerControlPower.class);
|
||||
private final ConfigProvider configProvider;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
public PowerControlPower(ConfigProvider configProvider, ScheduledExecutorService scheduler) {
|
||||
this.configProvider = configProvider;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
final State state = handler.getSocket().getState();
|
||||
if (RefreshType.REFRESH == command) {
|
||||
handler.postUpdate(channelId, state == State.REGISTERED ? OnOffType.ON : OnOffType.OFF);
|
||||
} else if (OnOffType.ON == command) {
|
||||
switch (state) {
|
||||
case CONNECTING:
|
||||
case REGISTERING:
|
||||
logger.debug("Received ON - TV is currently connecting.");
|
||||
handler.postUpdate(channelId, OnOffType.OFF);
|
||||
break;
|
||||
case REGISTERED:
|
||||
logger.debug("Received ON - TV is already on.");
|
||||
break;
|
||||
case DISCONNECTING: // WOL will not stop the shutdown process, but we must not update the state to ON
|
||||
case DISCONNECTED:
|
||||
String macAddress = configProvider.getMacAddress();
|
||||
if (macAddress.isEmpty()) {
|
||||
logger.debug("Received ON - Turning TV on via API is not supported by LG WebOS TVs. "
|
||||
+ "You may succeed using wake on lan (WOL). "
|
||||
+ "Please set the macAddress config value in Thing configuration to enable this.");
|
||||
handler.postUpdate(channelId, OnOffType.OFF);
|
||||
} else {
|
||||
for (int i = 0; i < WOL_PACKET_RETRY_COUNT; i++) {
|
||||
scheduler.schedule(() -> {
|
||||
try {
|
||||
WakeOnLanUtility.sendWOLPacket(macAddress);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Failed to send WOL packet: {}", e.getMessage());
|
||||
}
|
||||
}, i * WOL_PACKET_RETRY_DELAY_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (OnOffType.OFF == command) {
|
||||
switch (state) {
|
||||
case CONNECTING:
|
||||
case REGISTERING:
|
||||
// in both states no message will sent to TV, thus the operation won't have an effect
|
||||
logger.debug("Received OFF - TV is currently connecting.");
|
||||
break;
|
||||
case REGISTERED:
|
||||
handler.getSocket().powerOff(getDefaultResponseListener());
|
||||
break;
|
||||
case DISCONNECTING:
|
||||
case DISCONNECTED:
|
||||
logger.debug("Received OFF - TV is already off.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.info("Only accept OnOffType, RefreshType. Type was {}.", command.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceReady(String channelId, LGWebOSHandler handler) {
|
||||
handler.postUpdate(channelId, OnOffType.ON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(String channelId, LGWebOSHandler handler) {
|
||||
handler.postUpdate(channelId, OnOffType.OFF);
|
||||
}
|
||||
|
||||
public interface ConfigProvider {
|
||||
String getMacAddress();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
/**
|
||||
* Handles rcButton Control Command. This allows to send IR Remote Control button presses to the TV.
|
||||
*
|
||||
* @author Robert Brodt - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RCButtonControl extends BaseChannelHandler<CommandConfirmation> {
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
return;
|
||||
}
|
||||
handler.getSocket().sendRCButton(command.toString(), getDefaultResponseListener());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ChannelInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles TV Control Channel Command.
|
||||
* Allows to set a channel to an absolute channel number.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TVControlChannel extends BaseChannelHandler<ChannelInfo> {
|
||||
private final Logger logger = LoggerFactory.getLogger(TVControlChannel.class);
|
||||
private final Map<ThingUID, @Nullable List<ChannelInfo>> channelListCache = new HashMap<>();
|
||||
private final ResponseListener<CommandConfirmation> objResponseListener = createResponseListener();
|
||||
|
||||
@Override
|
||||
public void onDeviceReady(String channelId, LGWebOSHandler handler) {
|
||||
super.onDeviceReady(channelId, handler);
|
||||
handler.getSocket().getChannelList(new ResponseListener<List<ChannelInfo>>() {
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.warn("error requesting channel list: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void onSuccess(List<ChannelInfo> channels) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
channels.forEach(c -> logger.debug("Channel {} - {}", c.getChannelNumber(), c.getName()));
|
||||
}
|
||||
channelListCache.put(handler.getThing().getUID(), channels);
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (ChannelInfo channel : channels) {
|
||||
String name = channel.getName() == null ? "" : channel.getName();
|
||||
options.add(new StateOption(channel.getId(), channel.getChannelNumber() + " - " + name));
|
||||
}
|
||||
handler.setOptions(channelId, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceRemoved(String channelId, LGWebOSHandler handler) {
|
||||
super.onDeviceRemoved(channelId, handler);
|
||||
channelListCache.remove(handler.getThing().getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
handler.getSocket().getCurrentChannel(createResponseListener(channelId, handler));
|
||||
return;
|
||||
}
|
||||
|
||||
final String value = command.toString();
|
||||
|
||||
List<ChannelInfo> channels = channelListCache.get(handler.getThing().getUID());
|
||||
if (channels == null) {
|
||||
logger.warn("No channel list cached for this device {}, ignoring command.",
|
||||
handler.getThing().getUID().toString());
|
||||
} else {
|
||||
Optional<ChannelInfo> channelInfo = channels.stream()
|
||||
.filter(c -> c.getId().equals(value) || c.getChannelNumber().equals(value)).findFirst();
|
||||
if (channelInfo.isPresent()) {
|
||||
handler.getSocket().setChannel(channelInfo.get(), objResponseListener);
|
||||
} else {
|
||||
logger.info("TV does not have a channel: {}.", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ServiceSubscription<ChannelInfo>> getSubscription(String channelId, LGWebOSHandler handler) {
|
||||
return Optional.of(handler.getSocket().subscribeCurrentChannel(createResponseListener(channelId, handler)));
|
||||
}
|
||||
|
||||
private ResponseListener<ChannelInfo> createResponseListener(String channelId, LGWebOSHandler handler) {
|
||||
return new ResponseListener<ChannelInfo>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.debug("Error in retrieving channel: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable ChannelInfo channelInfo) {
|
||||
if (channelInfo == null) {
|
||||
return;
|
||||
}
|
||||
handler.postUpdate(channelId, new StringType(channelInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<String> reportChannels(ThingUID thingUID) {
|
||||
List<String> report = new ArrayList<>();
|
||||
List<ChannelInfo> channels = channelListCache.get(thingUID);
|
||||
if (channels != null) {
|
||||
for (ChannelInfo channel : channels) {
|
||||
String name = channel.getName() == null ? "" : channel.getName();
|
||||
report.add(channel.getId() + " : " + channel.getChannelNumber() + " - " + name);
|
||||
}
|
||||
}
|
||||
return report;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
/**
|
||||
* Handles Toast Control Command. This allows to send messages to the TV screen.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ToastControlToast extends BaseChannelHandler<CommandConfirmation> {
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
return;
|
||||
}
|
||||
handler.getSocket().showToast(command.toString(), getDefaultResponseListener());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles TV Control Mute Command.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumeControlMute extends BaseChannelHandler<Boolean> {
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumeControlMute.class);
|
||||
|
||||
private final ResponseListener<CommandConfirmation> objResponseListener = createResponseListener();
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
handler.getSocket().getMute(createResponseListener(channelId, handler));
|
||||
} else if (OnOffType.ON == command || OnOffType.OFF == command) {
|
||||
handler.getSocket().setMute(OnOffType.ON == command, objResponseListener);
|
||||
} else {
|
||||
logger.info("Only accept OnOffType, RefreshType. Type was {}.", command.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ServiceSubscription<Boolean>> getSubscription(String channelId, LGWebOSHandler handler) {
|
||||
return Optional.of(handler.getSocket().subscribeMute(createResponseListener(channelId, handler)));
|
||||
}
|
||||
|
||||
private ResponseListener<Boolean> createResponseListener(String channelId, LGWebOSHandler handler) {
|
||||
return new ResponseListener<Boolean>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.debug("Error in retrieving mute: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable Boolean value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
handler.postUpdate(channelId, OnOffType.from(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handles TV Control Volume Commands. Allows to set a volume to an absolute number or increment and decrement the
|
||||
* volume. If used with On Off type commands it will mute volume when receiving OFF and unmute when receiving ON.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumeControlVolume extends BaseChannelHandler<Float> {
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumeControlVolume.class);
|
||||
|
||||
private final ResponseListener<CommandConfirmation> objResponseListener = createResponseListener();
|
||||
|
||||
@Override
|
||||
public void onReceiveCommand(String channelId, LGWebOSHandler handler, Command command) {
|
||||
final PercentType percent;
|
||||
if (RefreshType.REFRESH == command) {
|
||||
handler.getSocket().getVolume(createResponseListener(channelId, handler));
|
||||
return;
|
||||
}
|
||||
if (command instanceof PercentType) {
|
||||
percent = (PercentType) command;
|
||||
} else if (command instanceof DecimalType) {
|
||||
percent = new PercentType(((DecimalType) command).toBigDecimal());
|
||||
} else if (command instanceof StringType) {
|
||||
percent = new PercentType(((StringType) command).toString());
|
||||
} else {
|
||||
percent = null;
|
||||
}
|
||||
|
||||
if (percent != null) {
|
||||
handler.getSocket().setVolume(percent.floatValue() / 100.0f, objResponseListener);
|
||||
} else if (IncreaseDecreaseType.INCREASE == command) {
|
||||
handler.getSocket().volumeUp(objResponseListener);
|
||||
} else if (IncreaseDecreaseType.DECREASE == command) {
|
||||
handler.getSocket().volumeDown(objResponseListener);
|
||||
} else if (OnOffType.OFF == command || OnOffType.ON == command) {
|
||||
handler.getSocket().setMute(OnOffType.OFF == command, objResponseListener);
|
||||
} else {
|
||||
logger.info("Only accept PercentType, DecimalType, StringType, RefreshType. Type was {}.",
|
||||
command.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ServiceSubscription<Float>> getSubscription(String channelUID, LGWebOSHandler handler) {
|
||||
return Optional.of(handler.getSocket().subscribeVolume(createResponseListener(channelUID, handler)));
|
||||
}
|
||||
|
||||
private ResponseListener<Float> createResponseListener(String channelUID, LGWebOSHandler handler) {
|
||||
return new ResponseListener<Float>() {
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable String error) {
|
||||
logger.debug("Error in retrieving volume: {}.", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable Float value) {
|
||||
if (value != null && !Float.isNaN(value)) {
|
||||
handler.postUpdate(channelUID, new PercentType(Math.round(value * 100)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.net.exec.ExecUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class with utility functions to support Wake On Lan (WOL)
|
||||
*
|
||||
* @author Arjan Mels - Initial contribution
|
||||
* @author Sebastian Prehn - Modification to getMACAddress
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WakeOnLanUtility {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WakeOnLanUtility.class);
|
||||
private static final Pattern MAC_REGEX = Pattern.compile("(([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2})");
|
||||
private static final int CMD_TIMEOUT_MS = 1000;
|
||||
|
||||
private static final String COMMAND;
|
||||
static {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
LOGGER.debug("os: {}", os);
|
||||
if ((os.indexOf("win") >= 0)) {
|
||||
COMMAND = "arp -a %s";
|
||||
} else if ((os.indexOf("mac") >= 0)) {
|
||||
COMMAND = "arp %s";
|
||||
} else { // linux
|
||||
if (checkIfLinuxCommandExists("arp")) {
|
||||
COMMAND = "arp %s";
|
||||
} else if (checkIfLinuxCommandExists("arping")) { // typically OH provided docker image
|
||||
COMMAND = "arping -r -c 1 -C 1 %s";
|
||||
} else {
|
||||
COMMAND = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MAC address for host
|
||||
*
|
||||
* @param hostName Host Name (or IP address) of host to retrieve MAC address for
|
||||
* @return MAC address
|
||||
*/
|
||||
public static @Nullable String getMACAddress(String hostName) {
|
||||
if (COMMAND.isEmpty()) {
|
||||
LOGGER.debug("MAC address detection not possible. No command to identify MAC found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String cmd = String.format(COMMAND, hostName);
|
||||
String response = ExecUtil.executeCommandLineAndWaitResponse(cmd, CMD_TIMEOUT_MS);
|
||||
Matcher matcher = MAC_REGEX.matcher(response);
|
||||
String macAddress = null;
|
||||
|
||||
while (matcher.find()) {
|
||||
String group = matcher.group();
|
||||
|
||||
if (group.length() == 17) {
|
||||
macAddress = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (macAddress != null) {
|
||||
LOGGER.debug("MAC address of host {} is {}", hostName, macAddress);
|
||||
} else {
|
||||
LOGGER.debug("Problem executing command {} to retrieve MAC address for {}: {}", cmd, hostName, response);
|
||||
}
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send single WOL (Wake On Lan) package on all interfaces
|
||||
*
|
||||
* @macAddress MAC address to send WOL package to
|
||||
*/
|
||||
public static void sendWOLPacket(String macAddress) {
|
||||
byte[] bytes = getWOLPackage(macAddress);
|
||||
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback()) {
|
||||
continue; // Do not want to use the loopback interface.
|
||||
}
|
||||
for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress broadcast = interfaceAddress.getBroadcast();
|
||||
if (broadcast == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, broadcast, 9);
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
socket.send(packet);
|
||||
LOGGER.trace("Sent WOL packet to {} {}", broadcast, macAddress);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Problem sending WOL packet to {} {}", broadcast, macAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Problem with interface while sending WOL packet to {}", macAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create WOL UDP package: 6 bytes 0xff and then 16 times the 6 byte mac address repeated
|
||||
*
|
||||
* @param macStr String representation of the MAC address (either with : or -)
|
||||
* @return byte array with the WOL package
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
private static byte[] getWOLPackage(String macStr) throws IllegalArgumentException {
|
||||
byte[] macBytes = new byte[6];
|
||||
String[] hex = macStr.split("(\\:|\\-)");
|
||||
if (hex.length != 6) {
|
||||
throw new IllegalArgumentException("Invalid MAC address.");
|
||||
}
|
||||
try {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
macBytes[i] = (byte) Integer.parseInt(hex[i], 16);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid hex digit in MAC address.");
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[6 + 16 * macBytes.length];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
bytes[i] = (byte) 0xff;
|
||||
}
|
||||
for (int i = 6; i < bytes.length; i += macBytes.length) {
|
||||
System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static boolean checkIfLinuxCommandExists(String cmd) {
|
||||
try {
|
||||
return 0 == Runtime.getRuntime().exec(String.format("which %s", cmd)).waitFor();
|
||||
} catch (InterruptedException | IOException e) {
|
||||
LOGGER.debug("Error trying to check if command {} exists: {}", cmd, e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.console;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSHandler;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link LGWebOSCommandExtension} is responsible for handling console commands
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class LGWebOSCommandExtension extends AbstractConsoleCommandExtension {
|
||||
|
||||
private static final String APPLICATIONS = "applications";
|
||||
private static final String CHANNELS = "channels";
|
||||
private static final String ACCESS_KEY = "accesskey";
|
||||
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public LGWebOSCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
||||
super("lgwebos", "Interact with the LG webOS binding.");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length == 2) {
|
||||
Thing thing = null;
|
||||
try {
|
||||
ThingUID thingUID = new ThingUID(args[0]);
|
||||
thing = thingRegistry.get(thingUID);
|
||||
} catch (IllegalArgumentException e) {
|
||||
thing = null;
|
||||
}
|
||||
ThingHandler thingHandler = null;
|
||||
LGWebOSHandler handler = null;
|
||||
if (thing != null) {
|
||||
thingHandler = thing.getHandler();
|
||||
if (thingHandler instanceof LGWebOSHandler) {
|
||||
handler = (LGWebOSHandler) thingHandler;
|
||||
}
|
||||
}
|
||||
if (thing == null) {
|
||||
console.println("Bad thing id '" + args[0] + "'");
|
||||
printUsage(console);
|
||||
} else if (thingHandler == null) {
|
||||
console.println("No handler initialized for the thing id '" + args[0] + "'");
|
||||
printUsage(console);
|
||||
} else if (handler == null) {
|
||||
console.println("'" + args[0] + "' is not a LG webOS thing id");
|
||||
printUsage(console);
|
||||
} else {
|
||||
switch (args[1]) {
|
||||
case APPLICATIONS:
|
||||
handler.reportApplications().forEach(console::println);
|
||||
break;
|
||||
case CHANNELS:
|
||||
handler.reportChannels().forEach(console::println);
|
||||
break;
|
||||
case ACCESS_KEY:
|
||||
console.println("Your access key is " + handler.getKey());
|
||||
break;
|
||||
default:
|
||||
printUsage(console);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(new String[] { buildCommandUsage("<thingUID> " + APPLICATIONS, "list applications"),
|
||||
buildCommandUsage("<thingUID> " + CHANNELS, "list channels"),
|
||||
buildCommandUsage("<thingUID> " + ACCESS_KEY, "show the access key") });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.lgwebos.internal.LGWebOSBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.jupnp.model.meta.RemoteDevice;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This Upnp Discovery participant add the ability to auto discover LG Web OS devices on the network.
|
||||
* Some users choose to not use upnp. Therefore this can only play an optional role and help discover the device and its
|
||||
* ip.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = UpnpDiscoveryParticipant.class, immediate = true, configurationPid = "discovery.lgwebos.upnp")
|
||||
public class LGWebOSUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSUpnpDiscoveryParticipant.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
|
||||
ThingUID thingUID = getThingUID(device);
|
||||
|
||||
if (thingUID == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String modelName = device.getDetails().getModelDetails().getModelName();
|
||||
if (device.getDetails().getModelDetails().getModelNumber() != null) {
|
||||
modelName += " " + device.getDetails().getModelDetails().getModelNumber();
|
||||
}
|
||||
|
||||
return DiscoveryResultBuilder.create(thingUID).withLabel(device.getDetails().getFriendlyName())
|
||||
.withProperty(PROPERTY_DEVICE_ID, device.getIdentity().getUdn().getIdentifierString())
|
||||
.withProperty(CONFIG_HOST, device.getIdentity().getDescriptorURL().getHost())
|
||||
.withLabel(device.getDetails().getFriendlyName()).withProperty(Thing.PROPERTY_MODEL_ID, modelName)
|
||||
.withProperty(Thing.PROPERTY_VENDOR, device.getDetails().getManufacturerDetails().getManufacturer())
|
||||
.withRepresentationProperty(PROPERTY_DEVICE_ID).withThingType(THING_TYPE_WEBOSTV).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(RemoteDevice device) {
|
||||
logger.trace("Discovered remote device {}", device);
|
||||
if (device.findService(UPNP_SERVICE_TYPE) != null) {
|
||||
logger.debug("Found LG WebOS TV: {}", device);
|
||||
return new ThingUID(THING_TYPE_WEBOSTV, device.getIdentity().getUdn().getIdentifierString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link LGWebOSConfiguration} class contains the thing configuration
|
||||
* parameters for LGWebOS devices
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LGWebOSConfiguration {
|
||||
@Nullable
|
||||
String host; // name has to match LGWebOSBindingConstants.CONFIG_HOST
|
||||
int port = 3000; // 3001 for TLS
|
||||
@Nullable
|
||||
String key; // name has to match LGWebOSBindingConstants.CONFIG_KEY
|
||||
@Nullable
|
||||
String macAddress; // name has to match LGWebOSBindingConstants.CONFIG_MAC_ADDRESS
|
||||
|
||||
public String getHost() {
|
||||
String h = host;
|
||||
return h == null ? "" : h;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
String k = key;
|
||||
return k == null ? "" : k;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getMacAddress() {
|
||||
String m = macAddress;
|
||||
return m == null ? "" : m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WebOSConfiguration [host=" + host + ", port=" + port + ", key.length=" + getKey().length()
|
||||
+ ", macAddress=" + macAddress + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lgwebos.internal.LGWebOSBindingConstants.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.lgwebos.action.LGWebOSActions;
|
||||
import org.openhab.binding.lgwebos.internal.ChannelHandler;
|
||||
import org.openhab.binding.lgwebos.internal.LGWebOSBindingConstants;
|
||||
import org.openhab.binding.lgwebos.internal.LGWebOSStateDescriptionOptionProvider;
|
||||
import org.openhab.binding.lgwebos.internal.LauncherApplication;
|
||||
import org.openhab.binding.lgwebos.internal.MediaControlPlayer;
|
||||
import org.openhab.binding.lgwebos.internal.MediaControlStop;
|
||||
import org.openhab.binding.lgwebos.internal.PowerControlPower;
|
||||
import org.openhab.binding.lgwebos.internal.RCButtonControl;
|
||||
import org.openhab.binding.lgwebos.internal.TVControlChannel;
|
||||
import org.openhab.binding.lgwebos.internal.ToastControlToast;
|
||||
import org.openhab.binding.lgwebos.internal.VolumeControlMute;
|
||||
import org.openhab.binding.lgwebos.internal.VolumeControlVolume;
|
||||
import org.openhab.binding.lgwebos.internal.WakeOnLanUtility;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket.WebOSTVSocketListener;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LGWebOSHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Sebastian Prehn - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LGWebOSHandler extends BaseThingHandler
|
||||
implements LGWebOSTVSocket.ConfigProvider, WebOSTVSocketListener, PowerControlPower.ConfigProvider {
|
||||
|
||||
/*
|
||||
* constants for device polling
|
||||
*/
|
||||
private static final int RECONNECT_INTERVAL_SECONDS = 10;
|
||||
private static final int RECONNECT_START_UP_DELAY_SECONDS = 0;
|
||||
private static final int CHANNEL_SUBSCRIPTION_DELAY_SECONDS = 1;
|
||||
private static final String APP_ID_LIVETV = "com.webos.app.livetv";
|
||||
|
||||
/*
|
||||
* error messages
|
||||
*/
|
||||
private static final String MSG_MISSING_PARAM = "Missing parameter \"host\"";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSHandler.class);
|
||||
|
||||
// ChannelID to CommandHandler Map
|
||||
private final Map<String, ChannelHandler> channelHandlers;
|
||||
|
||||
private final LauncherApplication appLauncher = new LauncherApplication();
|
||||
|
||||
private final WebSocketClient webSocketClient;
|
||||
|
||||
private final LGWebOSStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
|
||||
private @Nullable LGWebOSTVSocket socket;
|
||||
|
||||
private @Nullable ScheduledFuture<?> reconnectJob;
|
||||
private @Nullable ScheduledFuture<?> keepAliveJob;
|
||||
private @Nullable ScheduledFuture<?> channelSubscriptionJob;
|
||||
|
||||
private @Nullable LGWebOSConfiguration config;
|
||||
|
||||
public LGWebOSHandler(Thing thing, WebSocketClient webSocketClient,
|
||||
LGWebOSStateDescriptionOptionProvider stateDescriptionProvider) {
|
||||
super(thing);
|
||||
this.webSocketClient = webSocketClient;
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
|
||||
Map<String, ChannelHandler> handlers = new HashMap<>();
|
||||
handlers.put(CHANNEL_VOLUME, new VolumeControlVolume());
|
||||
handlers.put(CHANNEL_POWER, new PowerControlPower(this, scheduler));
|
||||
handlers.put(CHANNEL_MUTE, new VolumeControlMute());
|
||||
handlers.put(CHANNEL_CHANNEL, new TVControlChannel());
|
||||
handlers.put(CHANNEL_APP_LAUNCHER, appLauncher);
|
||||
handlers.put(CHANNEL_MEDIA_STOP, new MediaControlStop());
|
||||
handlers.put(CHANNEL_TOAST, new ToastControlToast());
|
||||
handlers.put(CHANNEL_MEDIA_PLAYER, new MediaControlPlayer());
|
||||
handlers.put(CHANNEL_RCBUTTON, new RCButtonControl());
|
||||
channelHandlers = Collections.unmodifiableMap(handlers);
|
||||
}
|
||||
|
||||
private LGWebOSConfiguration getLGWebOSConfig() {
|
||||
LGWebOSConfiguration c = config;
|
||||
if (c == null) {
|
||||
c = getConfigAs(LGWebOSConfiguration.class);
|
||||
config = c;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for thing {}", getThing().getUID());
|
||||
LGWebOSConfiguration c = getLGWebOSConfig();
|
||||
logger.trace("Handler initialized with config {}", c);
|
||||
String host = c.getHost();
|
||||
if (host.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, MSG_MISSING_PARAM);
|
||||
return;
|
||||
}
|
||||
|
||||
LGWebOSTVSocket s = new LGWebOSTVSocket(webSocketClient, this, host, c.getPort(), scheduler);
|
||||
s.setListener(this);
|
||||
socket = s;
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "TV is off");
|
||||
|
||||
startReconnectJob();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing handler for thing {}", getThing().getUID());
|
||||
stopKeepAliveJob();
|
||||
stopReconnectJob();
|
||||
stopChannelSubscriptionJob();
|
||||
|
||||
LGWebOSTVSocket s = socket;
|
||||
if (s != null) {
|
||||
s.setListener(null);
|
||||
s.disconnect();
|
||||
}
|
||||
socket = null;
|
||||
config = null; // ensure config gets actually refreshed during re-initialization
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void startReconnectJob() {
|
||||
ScheduledFuture<?> job = reconnectJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
getSocket().disconnect();
|
||||
getSocket().connect();
|
||||
}, RECONNECT_START_UP_DELAY_SECONDS, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopReconnectJob() {
|
||||
ScheduledFuture<?> job = reconnectJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
reconnectJob = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep alive ensures that the web socket connection is used and does not time out.
|
||||
*/
|
||||
private void startKeepAliveJob() {
|
||||
ScheduledFuture<?> job = keepAliveJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
// half of idle time out setting
|
||||
long keepAliveInterval = this.webSocketClient.getMaxIdleTimeout() / 2;
|
||||
|
||||
// it is irrelevant which service is queried. Only need to send some packets over the wire
|
||||
|
||||
keepAliveJob = scheduler
|
||||
.scheduleWithFixedDelay(() -> getSocket().getRunningApp(new ResponseListener<AppInfo>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(AppInfo responseObject) {
|
||||
// ignore - actual response is not relevant here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
// ignore
|
||||
}
|
||||
}), keepAliveInterval, keepAliveInterval, TimeUnit.MILLISECONDS);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void stopKeepAliveJob() {
|
||||
ScheduledFuture<?> job = keepAliveJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
keepAliveJob = null;
|
||||
}
|
||||
|
||||
public LGWebOSTVSocket getSocket() {
|
||||
LGWebOSTVSocket s = this.socket;
|
||||
if (s == null) {
|
||||
throw new IllegalStateException("Component called before it was initialized or already disposed.");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public LauncherApplication getLauncherApplication() {
|
||||
return appLauncher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("handleCommand({},{})", channelUID, command);
|
||||
ChannelHandler handler = channelHandlers.get(channelUID.getId());
|
||||
if (handler == null) {
|
||||
logger.warn(
|
||||
"Unable to handle command {}. No handler found for channel {}. This must not happen. Please report as a bug.",
|
||||
command, channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
handler.onReceiveCommand(channelUID.getId(), this, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMacAddress() {
|
||||
return getLGWebOSConfig().getMacAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return getLGWebOSConfig().getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeKey(@Nullable String key) {
|
||||
if (!getKey().equals(key)) {
|
||||
logger.debug("Store new access Key in the thing configuration");
|
||||
// store it current configuration and avoiding complete re-initialization via handleConfigurationUpdate
|
||||
getLGWebOSConfig().key = key;
|
||||
|
||||
// persist the configuration change
|
||||
Configuration configuration = editConfiguration();
|
||||
configuration.put(LGWebOSBindingConstants.CONFIG_KEY, key);
|
||||
updateConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeProperties(Map<String, String> properties) {
|
||||
logger.debug("storeProperties {}", properties);
|
||||
Map<String, String> map = editProperties();
|
||||
map.putAll(properties);
|
||||
updateProperties(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(LGWebOSTVSocket.State state) {
|
||||
switch (state) {
|
||||
case DISCONNECTING:
|
||||
postUpdate(CHANNEL_POWER, OnOffType.OFF);
|
||||
break;
|
||||
case DISCONNECTED:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "TV is off");
|
||||
channelHandlers.forEach((k, v) -> {
|
||||
v.onDeviceRemoved(k, this);
|
||||
v.removeAnySubscription(this);
|
||||
});
|
||||
|
||||
stopKeepAliveJob();
|
||||
startReconnectJob();
|
||||
break;
|
||||
case CONNECTING:
|
||||
stopReconnectJob();
|
||||
break;
|
||||
case REGISTERING:
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE,
|
||||
"Registering - You may need to confirm pairing on TV.");
|
||||
findMacAddress();
|
||||
break;
|
||||
case REGISTERED:
|
||||
startKeepAliveJob();
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "Connected");
|
||||
|
||||
channelHandlers.forEach((k, v) -> {
|
||||
// refresh subscriptions except on channel, which can only be subscribe in livetv app. see
|
||||
// postUpdate method
|
||||
if (!CHANNEL_CHANNEL.equals(k)) {
|
||||
v.refreshSubscription(k, this);
|
||||
}
|
||||
v.onDeviceReady(k, this);
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
logger.debug("Connection failed - error: {}", error);
|
||||
|
||||
switch (getSocket().getState()) {
|
||||
case DISCONNECTING:
|
||||
case DISCONNECTED:
|
||||
break;
|
||||
case CONNECTING:
|
||||
case REGISTERING:
|
||||
case REGISTERED:
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection Failed: " + error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOptions(String channelId, List<StateOption> options) {
|
||||
logger.debug("setOptions channelId={} options.size()={}", channelId, options.size());
|
||||
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId), options);
|
||||
}
|
||||
|
||||
public void postUpdate(String channelId, State state) {
|
||||
if (isLinked(channelId)) {
|
||||
updateState(channelId, state);
|
||||
}
|
||||
|
||||
// channel subscription only works when livetv app is started,
|
||||
// therefore we need to slightly delay the subscription
|
||||
if (CHANNEL_APP_LAUNCHER.equals(channelId)) {
|
||||
if (APP_ID_LIVETV.equals(state.toString())) {
|
||||
scheduleChannelSubscriptionJob();
|
||||
} else {
|
||||
stopChannelSubscriptionJob();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleChannelSubscriptionJob() {
|
||||
ScheduledFuture<?> job = channelSubscriptionJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
logger.debug("Schedule channel subscription job");
|
||||
channelSubscriptionJob = scheduler.schedule(
|
||||
() -> channelHandlers.get(CHANNEL_CHANNEL).refreshSubscription(CHANNEL_CHANNEL, this),
|
||||
CHANNEL_SUBSCRIPTION_DELAY_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopChannelSubscriptionJob() {
|
||||
ScheduledFuture<?> job = channelSubscriptionJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
logger.debug("Stop channel subscription job");
|
||||
job.cancel(true);
|
||||
}
|
||||
channelSubscriptionJob = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(LGWebOSActions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a best effort to automatically detect the MAC address of the TV.
|
||||
* If this does not work automatically, users can still set it manually in the Thing config.
|
||||
*/
|
||||
private void findMacAddress() {
|
||||
LGWebOSConfiguration c = getLGWebOSConfig();
|
||||
String host = c.getHost();
|
||||
if (!host.isEmpty()) {
|
||||
try {
|
||||
// validate host, so that no command can be injected
|
||||
String macAddress = WakeOnLanUtility.getMACAddress(InetAddress.getByName(host).getHostAddress());
|
||||
if (macAddress != null && !macAddress.equals(c.macAddress)) {
|
||||
c.macAddress = macAddress;
|
||||
// persist the configuration change
|
||||
Configuration configuration = editConfiguration();
|
||||
configuration.put(LGWebOSBindingConstants.CONFIG_MAC_ADDRESS, macAddress);
|
||||
updateConfiguration(configuration);
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("Unable to determine MAC address: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> reportApplications() {
|
||||
return appLauncher.reportApplications(getThing().getUID());
|
||||
}
|
||||
|
||||
public List<String> reportChannels() {
|
||||
return ((TVControlChannel) channelHandlers.get(CHANNEL_CHANNEL)).reportChannels(getThing().getUID());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* WebOSTVKeyboardInput
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceCommand;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.TextInputStatusInfo;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* {@link LGWebOSTVKeyboardInput} handles WebOSTV keyboard api.
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
public class LGWebOSTVKeyboardInput {
|
||||
|
||||
private LGWebOSTVSocket service;
|
||||
private boolean waiting;
|
||||
private final List<String> toSend;
|
||||
|
||||
private static final String KEYBOARD_INPUT = "ssap://com.webos.service.ime/registerRemoteKeyboard";
|
||||
private static final String ENTER = "ENTER";
|
||||
private static final String DELETE = "DELETE";
|
||||
|
||||
public LGWebOSTVKeyboardInput(LGWebOSTVSocket service) {
|
||||
this.service = service;
|
||||
waiting = false;
|
||||
toSend = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void sendText(String input) {
|
||||
toSend.add(input);
|
||||
if (!waiting) { // TODO: use a latch,and send in any case
|
||||
sendData();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendEnter() {
|
||||
sendText(ENTER);
|
||||
}
|
||||
|
||||
public void sendDel() {
|
||||
if (toSend.isEmpty()) {
|
||||
toSend.add(DELETE);
|
||||
if (!waiting) {
|
||||
sendData();
|
||||
}
|
||||
} else {
|
||||
toSend.remove(toSend.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendData() {
|
||||
waiting = true;
|
||||
|
||||
String uri;
|
||||
String typeTest = toSend.get(0);
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
|
||||
if (typeTest.equals(ENTER)) {
|
||||
toSend.remove(0);
|
||||
uri = "ssap://com.webos.service.ime/sendEnterKey";
|
||||
} else if (typeTest.equals(DELETE)) {
|
||||
uri = "ssap://com.webos.service.ime/deleteCharacters";
|
||||
|
||||
int count = 0;
|
||||
while (!toSend.isEmpty() && toSend.get(0).equals(DELETE)) {
|
||||
toSend.remove(0);
|
||||
count++;
|
||||
}
|
||||
|
||||
payload.addProperty("count", count);
|
||||
} else {
|
||||
uri = "ssap://com.webos.service.ime/insertText";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (!toSend.isEmpty() && !(toSend.get(0).equals(DELETE) || toSend.get(0).equals(ENTER))) {
|
||||
String text = toSend.get(0);
|
||||
sb.append(text);
|
||||
toSend.remove(0);
|
||||
}
|
||||
|
||||
payload.addProperty("text", sb.toString());
|
||||
payload.addProperty("replace", 0);
|
||||
}
|
||||
|
||||
ResponseListener<JsonObject> responseListener = new ResponseListener<JsonObject>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(JsonObject response) {
|
||||
waiting = false;
|
||||
if (!toSend.isEmpty()) {
|
||||
sendData();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
waiting = false;
|
||||
if (!toSend.isEmpty()) {
|
||||
sendData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ServiceCommand<JsonObject> request = new ServiceCommand<>(uri, payload, x -> x, responseListener);
|
||||
service.sendCommand(request);
|
||||
}
|
||||
|
||||
public ServiceSubscription<TextInputStatusInfo> connect(final ResponseListener<TextInputStatusInfo> listener) {
|
||||
ServiceSubscription<TextInputStatusInfo> subscription = new ServiceSubscription<>(KEYBOARD_INPUT, null,
|
||||
rawData -> parseRawKeyboardData(rawData), listener);
|
||||
service.sendCommand(subscription);
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private TextInputStatusInfo parseRawKeyboardData(JsonObject rawData) {
|
||||
boolean focused = false;
|
||||
String contentType = null;
|
||||
boolean predictionEnabled = false;
|
||||
boolean correctionEnabled = false;
|
||||
boolean autoCapitalization = false;
|
||||
boolean hiddenText = false;
|
||||
boolean focusChanged = false;
|
||||
|
||||
TextInputStatusInfo keyboard = new TextInputStatusInfo();
|
||||
keyboard.setRawData(rawData);
|
||||
|
||||
if (rawData.has("currentWidget")) {
|
||||
JsonObject currentWidget = (JsonObject) rawData.get("currentWidget");
|
||||
focused = currentWidget.get("focus").getAsBoolean();
|
||||
|
||||
if (currentWidget.has("contentType")) {
|
||||
contentType = currentWidget.get("contentType").getAsString();
|
||||
}
|
||||
if (currentWidget.has("predictionEnabled")) {
|
||||
predictionEnabled = currentWidget.get("predictionEnabled").getAsBoolean();
|
||||
}
|
||||
if (currentWidget.has("correctionEnabled")) {
|
||||
correctionEnabled = currentWidget.get("correctionEnabled").getAsBoolean();
|
||||
}
|
||||
if (currentWidget.has("autoCapitalization")) {
|
||||
autoCapitalization = currentWidget.get("autoCapitalization").getAsBoolean();
|
||||
}
|
||||
if (currentWidget.has("hiddenText")) {
|
||||
hiddenText = currentWidget.get("hiddenText").getAsBoolean();
|
||||
}
|
||||
}
|
||||
if (rawData.has("focusChanged")) {
|
||||
focusChanged = rawData.get("focusChanged").getAsBoolean();
|
||||
}
|
||||
|
||||
keyboard.setFocused(focused);
|
||||
keyboard.setContentType(contentType);
|
||||
keyboard.setPredictionEnabled(predictionEnabled);
|
||||
keyboard.setCorrectionEnabled(correctionEnabled);
|
||||
keyboard.setAutoCapitalization(autoCapitalization);
|
||||
keyboard.setHiddenText(hiddenText);
|
||||
keyboard.setFocusChanged(focusChanged);
|
||||
|
||||
return keyboard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* WebSocket implementation to connect to WebOSTV mouse api.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*
|
||||
*/
|
||||
@WebSocket()
|
||||
@NonNullByDefault
|
||||
public class LGWebOSTVMouseSocket {
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSTVMouseSocket.class);
|
||||
|
||||
public enum State {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
}
|
||||
|
||||
public enum ButtonType {
|
||||
HOME,
|
||||
BACK,
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
}
|
||||
|
||||
private State state = State.DISCONNECTED;
|
||||
private final WebSocketClient client;
|
||||
private @Nullable Session session;
|
||||
private @Nullable WebOSTVMouseSocketListener listener;
|
||||
|
||||
public LGWebOSTVMouseSocket(WebSocketClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
private void setState(State state) {
|
||||
State oldState = this.state;
|
||||
this.state = state;
|
||||
Optional.ofNullable(this.listener).ifPresent(l -> l.onStateChanged(oldState, this.state));
|
||||
}
|
||||
|
||||
public interface WebOSTVMouseSocketListener {
|
||||
|
||||
public void onStateChanged(State oldState, State newState);
|
||||
|
||||
public void onError(String errorMessage);
|
||||
}
|
||||
|
||||
public void setListener(@Nullable WebOSTVMouseSocketListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void connect(URI destUri) {
|
||||
synchronized (this) {
|
||||
if (state != State.DISCONNECTED) {
|
||||
logger.debug("Already connecting; not trying to connect again: {}", state);
|
||||
return;
|
||||
}
|
||||
setState(State.CONNECTING);
|
||||
}
|
||||
|
||||
try {
|
||||
this.client.connect(this, destUri);
|
||||
logger.debug("Connecting to: {}", destUri);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Unable to connect.", e);
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
setState(State.DISCONNECTING);
|
||||
try {
|
||||
Optional.ofNullable(this.session).ifPresent(s -> s.close());
|
||||
} catch (Exception e) {
|
||||
logger.debug("Error connecting to device.", e);
|
||||
}
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
setState(State.DISCONNECTED);
|
||||
logger.debug("WebSocket Closed - Code: {}, Reason: {}", statusCode, reason);
|
||||
this.session = null;
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
logger.debug("WebSocket Connected to: {}", session.getRemoteAddress().getAddress());
|
||||
this.session = session;
|
||||
setState(State.CONNECTED);
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String message) {
|
||||
logger.debug("Message [in]: {}", message);
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onError(Throwable cause) {
|
||||
Optional.ofNullable(this.listener).ifPresent(l -> l.onError(cause.getMessage()));
|
||||
logger.debug("Connection Error.", cause);
|
||||
}
|
||||
|
||||
private void sendMessage(String msg) {
|
||||
Session s = this.session;
|
||||
try {
|
||||
if (s != null) {
|
||||
logger.debug("Message [out]: {}", msg);
|
||||
s.getRemote().sendString(msg);
|
||||
} else {
|
||||
logger.warn("No Connection to TV, skipping [out]: {}", msg);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to send message.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void click() {
|
||||
sendMessage("type:click\n" + "\n");
|
||||
}
|
||||
|
||||
public void button(ButtonType type) {
|
||||
String keyName;
|
||||
switch (type) {
|
||||
case HOME:
|
||||
keyName = "HOME";
|
||||
break;
|
||||
case BACK:
|
||||
keyName = "BACK";
|
||||
break;
|
||||
case UP:
|
||||
keyName = "UP";
|
||||
break;
|
||||
case DOWN:
|
||||
keyName = "DOWN";
|
||||
break;
|
||||
case LEFT:
|
||||
keyName = "LEFT";
|
||||
break;
|
||||
case RIGHT:
|
||||
keyName = "RIGHT";
|
||||
break;
|
||||
|
||||
default:
|
||||
keyName = "NONE";
|
||||
break;
|
||||
}
|
||||
|
||||
button(keyName);
|
||||
}
|
||||
|
||||
public void button(String keyName) {
|
||||
sendMessage("type:button\n" + "name:" + keyName + "\n" + "\n");
|
||||
}
|
||||
|
||||
public void move(double dx, double dy) {
|
||||
sendMessage("type:move\n" + "dx:" + dx + "\n" + "dy:" + dy + "\n" + "down:0\n" + "\n");
|
||||
}
|
||||
|
||||
public void move(double dx, double dy, boolean drag) {
|
||||
sendMessage("type:move\n" + "dx:" + dx + "\n" + "dy:" + dy + "\n" + "down:" + (drag ? 1 : 0) + "\n" + "\n");
|
||||
}
|
||||
|
||||
public void scroll(double dx, double dy) {
|
||||
sendMessage("type:scroll\n" + "dx:" + dx + "\n" + "dy:" + dy + "\n" + "\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,951 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* This file is based on:
|
||||
*
|
||||
* WebOSTVService
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lgwebos.internal.LGWebOSBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVMouseSocket.WebOSTVMouseSocketListener;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceCommand;
|
||||
import org.openhab.binding.lgwebos.internal.handler.command.ServiceSubscription;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.AppInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ChannelInfo;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.CommandConfirmation;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.LaunchSession;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.LaunchSession.LaunchSessionType;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.Response;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.TextInputStatusInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* WebSocket to handle the communication with WebOS device.
|
||||
*
|
||||
* @author Hyun Kook Khang - Initial contribution
|
||||
* @author Sebastian Prehn - Web Socket implementation and adoption for openHAB
|
||||
*/
|
||||
@WebSocket()
|
||||
@NonNullByDefault
|
||||
public class LGWebOSTVSocket {
|
||||
|
||||
private static final String FOREGROUND_APP = "ssap://com.webos.applicationManager/getForegroundAppInfo";
|
||||
// private static final String APP_STATUS = "ssap://com.webos.service.appstatus/getAppStatus";
|
||||
// private static final String APP_STATE = "ssap://system.launcher/getAppState";
|
||||
private static final String VOLUME = "ssap://audio/getVolume";
|
||||
private static final String MUTE = "ssap://audio/getMute";
|
||||
// private static final String VOLUME_STATUS = "ssap://audio/getStatus";
|
||||
private static final String CHANNEL_LIST = "ssap://tv/getChannelList";
|
||||
private static final String CHANNEL = "ssap://tv/getCurrentChannel";
|
||||
// private static final String PROGRAM = "ssap://tv/getChannelProgramInfo";
|
||||
// private static final String CURRENT_PROGRAM = "ssap://tv/getChannelCurrentProgramInfo";
|
||||
// private static final String THREED_STATUS = "ssap://com.webos.service.tv.display/get3DStatus";
|
||||
|
||||
private static final int DISCONNECTING_DELAY_SECONDS = 2;
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LGWebOSTVSocket.class);
|
||||
|
||||
private final ConfigProvider config;
|
||||
private final WebSocketClient client;
|
||||
private final URI destUri;
|
||||
private final LGWebOSTVKeyboardInput keyboardInput;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
public enum State {
|
||||
DISCONNECTING,
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
REGISTERING,
|
||||
REGISTERED
|
||||
}
|
||||
|
||||
private State state = State.DISCONNECTED;
|
||||
|
||||
private @Nullable Session session;
|
||||
private @Nullable Future<?> sessionFuture;
|
||||
private @Nullable WebOSTVSocketListener listener;
|
||||
|
||||
/**
|
||||
* Requests to which we are awaiting response.
|
||||
*/
|
||||
private HashMap<Integer, ServiceCommand<?>> requests = new HashMap<>();
|
||||
|
||||
private int nextRequestId = 0;
|
||||
|
||||
private @Nullable ScheduledFuture<?> disconnectingJob;
|
||||
|
||||
public LGWebOSTVSocket(WebSocketClient client, ConfigProvider config, String host, int port,
|
||||
ScheduledExecutorService scheduler) {
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
this.keyboardInput = new LGWebOSTVKeyboardInput(this);
|
||||
|
||||
try {
|
||||
this.destUri = new URI("ws://" + host + ":" + port);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("IP address or hostname provided is invalid: " + host);
|
||||
}
|
||||
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
private void setState(State state) {
|
||||
logger.debug("setState new {} - current {}", state, this.state);
|
||||
State oldState = this.state;
|
||||
if (oldState != state) {
|
||||
this.state = state;
|
||||
Optional.ofNullable(this.listener).ifPresent(l -> l.onStateChanged(this.state));
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(@Nullable WebOSTVSocketListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void clearRequests() {
|
||||
requests.clear();
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
try {
|
||||
sessionFuture = this.client.connect(this, this.destUri);
|
||||
logger.debug("Connecting to: {}", this.destUri);
|
||||
} catch (IOException e) {
|
||||
logger.debug("Unable to connect.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
Optional.ofNullable(this.session).ifPresent(s -> s.close());
|
||||
Future<?> future = sessionFuture;
|
||||
if (future != null && !future.isDone()) {
|
||||
future.cancel(true);
|
||||
}
|
||||
stopDisconnectingJob();
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
|
||||
private void disconnecting() {
|
||||
logger.debug("disconnecting");
|
||||
if (state == State.REGISTERED) {
|
||||
setState(State.DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleDisconectingJob() {
|
||||
ScheduledFuture<?> job = disconnectingJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
logger.debug("Schedule disconecting job");
|
||||
disconnectingJob = scheduler.schedule(this::disconnecting, DISCONNECTING_DELAY_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopDisconnectingJob() {
|
||||
ScheduledFuture<?> job = disconnectingJob;
|
||||
if (job != null && !job.isCancelled()) {
|
||||
logger.debug("Stop disconnecting job");
|
||||
job.cancel(true);
|
||||
}
|
||||
disconnectingJob = null;
|
||||
}
|
||||
|
||||
/*
|
||||
* WebSocket Callbacks
|
||||
*/
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
logger.debug("WebSocket Connected to: {}", session.getRemoteAddress().getAddress());
|
||||
this.session = session;
|
||||
sendHello();
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onError(Throwable cause) {
|
||||
logger.trace("Connection Error", cause);
|
||||
if (cause instanceof SocketTimeoutException && "Connect Timeout".equals(cause.getMessage())) {
|
||||
// this is expected during connection attempts while TV is off
|
||||
setState(State.DISCONNECTED);
|
||||
return;
|
||||
}
|
||||
if (cause instanceof ConnectException && "Connection refused".equals(cause.getMessage())) {
|
||||
// this is expected during TV startup or shutdown
|
||||
return;
|
||||
}
|
||||
|
||||
Optional.ofNullable(this.listener).ifPresent(l -> l.onError(cause.getMessage()));
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
logger.debug("WebSocket Closed - Code: {}, Reason: {}", statusCode, reason);
|
||||
this.requests.clear();
|
||||
this.session = null;
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* WebOS WebSocket API specific Communication
|
||||
*/
|
||||
void sendHello() {
|
||||
setState(State.CONNECTING);
|
||||
|
||||
JsonObject packet = new JsonObject();
|
||||
packet.addProperty("id", nextRequestId());
|
||||
packet.addProperty("type", "hello");
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("appId", "org.openhab");
|
||||
payload.addProperty("appName", "openHAB");
|
||||
payload.addProperty("appRegion", Locale.getDefault().getDisplayCountry());
|
||||
packet.add("payload", payload);
|
||||
// the hello response will not contain id, therefore not registering in requests
|
||||
sendMessage(packet);
|
||||
}
|
||||
|
||||
void sendRegister() {
|
||||
setState(State.REGISTERING);
|
||||
|
||||
JsonObject packet = new JsonObject();
|
||||
int id = nextRequestId();
|
||||
packet.addProperty("id", id);
|
||||
packet.addProperty("type", "register");
|
||||
|
||||
JsonObject manifest = new JsonObject();
|
||||
manifest.addProperty("manifestVersion", 1);
|
||||
|
||||
String[] permissions = { "LAUNCH", "LAUNCH_WEBAPP", "APP_TO_APP", "CONTROL_AUDIO",
|
||||
"CONTROL_INPUT_MEDIA_PLAYBACK", "CONTROL_POWER", "READ_INSTALLED_APPS", "CONTROL_DISPLAY",
|
||||
"CONTROL_INPUT_JOYSTICK", "CONTROL_INPUT_MEDIA_RECORDING", "CONTROL_INPUT_TV", "READ_INPUT_DEVICE_LIST",
|
||||
"READ_NETWORK_STATE", "READ_TV_CHANNEL_LIST", "WRITE_NOTIFICATION_TOAST", "CONTROL_INPUT_TEXT",
|
||||
"CONTROL_MOUSE_AND_KEYBOARD", "READ_CURRENT_CHANNEL", "READ_RUNNING_APPS" };
|
||||
|
||||
manifest.add("permissions", GSON.toJsonTree(permissions));
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
String key = config.getKey();
|
||||
if (!key.isEmpty()) {
|
||||
payload.addProperty("client-key", key);
|
||||
}
|
||||
payload.addProperty("pairingType", "PROMPT"); // PIN, COMBINED
|
||||
payload.add("manifest", manifest);
|
||||
packet.add("payload", payload);
|
||||
ResponseListener<JsonObject> dummyListener = new ResponseListener<JsonObject>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable JsonObject payload) {
|
||||
// Noting to do here. TV shows PROMPT dialog.
|
||||
// Waiting for message of type error or registered
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
logger.debug("Registration failed with message: {}", message);
|
||||
disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
this.requests.put(id, new ServiceSubscription<>("dummy", payload, x -> x, dummyListener));
|
||||
sendMessage(packet, !key.isEmpty());
|
||||
}
|
||||
|
||||
private int nextRequestId() {
|
||||
int requestId;
|
||||
do {
|
||||
requestId = nextRequestId++;
|
||||
} while (requests.containsKey(requestId));
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public void sendCommand(ServiceCommand<?> command) {
|
||||
switch (state) {
|
||||
case REGISTERED:
|
||||
int requestId = nextRequestId();
|
||||
requests.put(requestId, command);
|
||||
JsonObject packet = new JsonObject();
|
||||
packet.addProperty("type", command.getType());
|
||||
packet.addProperty("id", requestId);
|
||||
packet.addProperty("uri", command.getTarget());
|
||||
JsonElement payload = command.getPayload();
|
||||
if (payload != null) {
|
||||
packet.add("payload", payload);
|
||||
}
|
||||
this.sendMessage(packet);
|
||||
|
||||
break;
|
||||
case CONNECTING:
|
||||
case REGISTERING:
|
||||
case DISCONNECTING:
|
||||
case DISCONNECTED:
|
||||
logger.debug("Skipping {} command {} for {} in state {}", command.getType(), command,
|
||||
command.getTarget(), state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void unsubscribe(ServiceSubscription<?> subscription) {
|
||||
Optional<Entry<Integer, ServiceCommand<?>>> entry = this.requests.entrySet().stream()
|
||||
.filter(e -> e.getValue().equals(subscription)).findFirst();
|
||||
if (entry.isPresent()) {
|
||||
int requestId = entry.get().getKey();
|
||||
this.requests.remove(requestId);
|
||||
JsonObject packet = new JsonObject();
|
||||
packet.addProperty("type", "unsubscribe");
|
||||
packet.addProperty("id", requestId);
|
||||
sendMessage(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(JsonObject json) {
|
||||
sendMessage(json, false);
|
||||
}
|
||||
|
||||
private void sendMessage(JsonObject json, boolean checkKey) {
|
||||
String msg = GSON.toJson(json);
|
||||
Session s = this.session;
|
||||
try {
|
||||
if (s != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Message [out]: {}", checkKey ? GSON.toJson(maskKeyInJson(json)) : msg);
|
||||
}
|
||||
s.getRemote().sendString(msg);
|
||||
} else {
|
||||
logger.warn("No Connection to TV, skipping [out]: {}",
|
||||
checkKey ? GSON.toJson(maskKeyInJson(json)) : msg);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Unable to send message.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonObject maskKeyInJson(JsonObject json) {
|
||||
if (json.has("payload") && json.getAsJsonObject("payload").has("client-key")) {
|
||||
JsonObject jsonCopy = json.deepCopy();
|
||||
JsonObject payload = jsonCopy.getAsJsonObject("payload");
|
||||
payload.remove("client-key");
|
||||
payload.addProperty("client-key", "***");
|
||||
return jsonCopy;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String message) {
|
||||
Response response = GSON.fromJson(message, Response.class);
|
||||
JsonElement payload = response.getPayload();
|
||||
JsonObject jsonPayload = payload == null ? null : payload.getAsJsonObject();
|
||||
String messageToLog = (jsonPayload != null && jsonPayload.has("client-key")) ? "***" : message;
|
||||
logger.trace("Message [in]: {}", messageToLog);
|
||||
ServiceCommand<?> request = null;
|
||||
|
||||
if (response.getId() != null) {
|
||||
request = requests.get(response.getId());
|
||||
if (request == null) {
|
||||
logger.warn("Received a response with id {}, for which no request was found. This should not happen.",
|
||||
response.getId());
|
||||
} else {
|
||||
// for subscriptions we want to keep the original
|
||||
// message, so that we have a reference to the response listener
|
||||
if (!(request instanceof ServiceSubscription<?>)) {
|
||||
requests.remove(response.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (response.getType()) {
|
||||
case "response":
|
||||
if (request == null) {
|
||||
logger.debug("No matching request found for response message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
if (payload == null) {
|
||||
logger.debug("No payload in response message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
request.processResponse(jsonPayload);
|
||||
} catch (RuntimeException ex) {
|
||||
// An uncaught runtime exception in @OnWebSocketMessage annotated method will cause the web socket
|
||||
// implementation to call @OnWebSocketError callback in which we would reset the connection.
|
||||
// Users have the ability to create miss-configurations in which IllegalArgumentException could be
|
||||
// thrown
|
||||
logger.warn("Error while processing message: {} - in response to request: {} - Error Message: {}",
|
||||
messageToLog, request, ex.getMessage());
|
||||
}
|
||||
break;
|
||||
case "error":
|
||||
logger.debug("Error: {}", messageToLog);
|
||||
|
||||
if (request == null) {
|
||||
logger.warn("No matching request found for error message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
if (payload == null) {
|
||||
logger.warn("No payload in error message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
request.processError(response.getError());
|
||||
} catch (RuntimeException ex) {
|
||||
// An uncaught runtime exception in @OnWebSocketMessage annotated method will cause the web socket
|
||||
// implementation to call @OnWebSocketError callback in which we would reset the connection.
|
||||
// Users have the ability to create miss-configurations in which IllegalArgumentException could be
|
||||
// thrown
|
||||
logger.warn("Error while processing error: {} - in response to request: {} - Error Message: {}",
|
||||
messageToLog, request, ex.getMessage());
|
||||
}
|
||||
break;
|
||||
case "hello":
|
||||
if (state != State.CONNECTING) {
|
||||
logger.debug("Skipping response {}, not in CONNECTING state, state was {}", messageToLog, state);
|
||||
break;
|
||||
}
|
||||
if (jsonPayload == null) {
|
||||
logger.warn("No payload in error message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(PROPERTY_DEVICE_OS, jsonPayload.get("deviceOS").getAsString());
|
||||
map.put(PROPERTY_DEVICE_OS_VERSION, jsonPayload.get("deviceOSVersion").getAsString());
|
||||
map.put(PROPERTY_DEVICE_OS_RELEASE_VERSION, jsonPayload.get("deviceOSReleaseVersion").getAsString());
|
||||
map.put(PROPERTY_LAST_CONNECTED, Instant.now().toString());
|
||||
config.storeProperties(map);
|
||||
sendRegister();
|
||||
break;
|
||||
case "registered":
|
||||
if (state != State.REGISTERING) {
|
||||
logger.debug("Skipping response {}, not in REGISTERING state, state was {}", messageToLog, state);
|
||||
break;
|
||||
}
|
||||
if (jsonPayload == null) {
|
||||
logger.warn("No payload in registered message: {}", messageToLog);
|
||||
break;
|
||||
}
|
||||
this.requests.remove(response.getId());
|
||||
config.storeKey(jsonPayload.get("client-key").getAsString());
|
||||
setState(State.REGISTERED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public interface WebOSTVSocketListener {
|
||||
|
||||
public void onStateChanged(State state);
|
||||
|
||||
public void onError(String errorMessage);
|
||||
}
|
||||
|
||||
public ServiceSubscription<Boolean> subscribeMute(ResponseListener<Boolean> listener) {
|
||||
ServiceSubscription<Boolean> request = new ServiceSubscription<>(MUTE, null,
|
||||
(jsonObj) -> jsonObj.get("mute").getAsBoolean(), listener);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public ServiceCommand<Boolean> getMute(ResponseListener<Boolean> listener) {
|
||||
ServiceCommand<Boolean> request = new ServiceCommand<>(MUTE, null,
|
||||
(jsonObj) -> jsonObj.get("mute").getAsBoolean(), listener);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public ServiceSubscription<Float> subscribeVolume(ResponseListener<Float> listener) {
|
||||
ServiceSubscription<Float> request = new ServiceSubscription<>(VOLUME, null,
|
||||
jsonObj -> jsonObj.get("volume").getAsInt() >= 0 ? (float) (jsonObj.get("volume").getAsInt() / 100.0)
|
||||
: Float.NaN,
|
||||
listener);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public ServiceCommand<Float> getVolume(ResponseListener<Float> listener) {
|
||||
ServiceCommand<Float> request = new ServiceCommand<>(VOLUME, null,
|
||||
jsonObj -> jsonObj.get("volume").getAsInt() >= 0 ? (float) (jsonObj.get("volume").getAsInt() / 100.0)
|
||||
: Float.NaN,
|
||||
listener);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setMute(boolean isMute, ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://audio/setMute";
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("mute", isMute);
|
||||
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, payload,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void setVolume(float volume, ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://audio/setVolume";
|
||||
JsonObject payload = new JsonObject();
|
||||
int intVolume = Math.round(volume * 100.0f);
|
||||
payload.addProperty("volume", intVolume);
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, payload,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void volumeUp(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://audio/volumeUp";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void volumeDown(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://audio/volumeDown";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public ServiceSubscription<ChannelInfo> subscribeCurrentChannel(ResponseListener<ChannelInfo> listener) {
|
||||
ServiceSubscription<ChannelInfo> request = new ServiceSubscription<>(CHANNEL, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj, ChannelInfo.class), listener);
|
||||
sendCommand(request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public ServiceCommand<ChannelInfo> getCurrentChannel(ResponseListener<ChannelInfo> listener) {
|
||||
ServiceCommand<ChannelInfo> request = new ServiceCommand<>(CHANNEL, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj, ChannelInfo.class), listener);
|
||||
sendCommand(request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setChannel(ChannelInfo channelInfo, ResponseListener<CommandConfirmation> listener) {
|
||||
JsonObject payload = new JsonObject();
|
||||
if (channelInfo.getId() != null) {
|
||||
payload.addProperty("channelId", channelInfo.getId());
|
||||
}
|
||||
if (channelInfo.getChannelNumber() != null) {
|
||||
payload.addProperty("channelNumber", channelInfo.getChannelNumber());
|
||||
}
|
||||
setChannel(payload, listener);
|
||||
}
|
||||
|
||||
private void setChannel(JsonObject payload, ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://tv/openChannel";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, payload,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void channelUp(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://tv/channelUp";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void channelDown(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://tv/channelDown";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void getChannelList(ResponseListener<List<ChannelInfo>> listener) {
|
||||
ServiceCommand<List<ChannelInfo>> request = new ServiceCommand<>(CHANNEL_LIST, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj.get("channelList"), new TypeToken<ArrayList<ChannelInfo>>() {
|
||||
}.getType()), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
// TOAST
|
||||
|
||||
public void showToast(String message, ResponseListener<CommandConfirmation> listener) {
|
||||
showToast(message, null, null, listener);
|
||||
}
|
||||
|
||||
public void showToast(String message, @Nullable String iconData, @Nullable String iconExtension,
|
||||
ResponseListener<CommandConfirmation> listener) {
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("message", message);
|
||||
|
||||
if (iconData != null && iconExtension != null) {
|
||||
payload.addProperty("iconData", iconData);
|
||||
payload.addProperty("iconExtension", iconExtension);
|
||||
}
|
||||
|
||||
sendToast(payload, listener);
|
||||
}
|
||||
|
||||
private void sendToast(JsonObject payload, ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://system.notifications/createToast";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, payload,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
// POWER
|
||||
public void powerOff(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://system/turnOff";
|
||||
|
||||
ResponseListener<CommandConfirmation> interceptor = new ResponseListener<CommandConfirmation>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(CommandConfirmation confirmation) {
|
||||
if (confirmation.getReturnValue()) {
|
||||
disconnecting();
|
||||
}
|
||||
listener.onSuccess(confirmation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
listener.onError(message);
|
||||
}
|
||||
};
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), interceptor);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
// MEDIA CONTROL
|
||||
public void play(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://media.controls/play";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void pause(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://media.controls/pause";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void stop(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://media.controls/stop";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void rewind(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://media.controls/rewind";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void fastForward(ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://media.controls/fastForward";
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, null,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
// APPS
|
||||
|
||||
public void getAppList(final ResponseListener<List<AppInfo>> listener) {
|
||||
String uri = "ssap://com.webos.applicationManager/listApps";
|
||||
|
||||
ServiceCommand<List<AppInfo>> request = new ServiceCommand<>(uri, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj.get("apps"), new TypeToken<ArrayList<AppInfo>>() {
|
||||
}.getType()), listener);
|
||||
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void launchAppWithInfo(AppInfo appInfo, ResponseListener<LaunchSession> listener) {
|
||||
launchAppWithInfo(appInfo, null, listener);
|
||||
}
|
||||
|
||||
public void launchAppWithInfo(final AppInfo appInfo, @Nullable JsonObject params,
|
||||
final ResponseListener<LaunchSession> listener) {
|
||||
String uri = "ssap://system.launcher/launch";
|
||||
JsonObject payload = new JsonObject();
|
||||
|
||||
final String appId = appInfo.getId();
|
||||
|
||||
String contentId = null;
|
||||
|
||||
if (params != null) {
|
||||
contentId = params.get("contentId").getAsString();
|
||||
}
|
||||
|
||||
payload.addProperty("id", appId);
|
||||
|
||||
if (contentId != null) {
|
||||
payload.addProperty("contentId", contentId);
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
payload.add("params", params);
|
||||
}
|
||||
|
||||
ServiceCommand<LaunchSession> request = new ServiceCommand<>(uri, payload, obj -> {
|
||||
LaunchSession launchSession = new LaunchSession();
|
||||
launchSession.setService(this);
|
||||
launchSession.setAppId(appId); // note that response uses id to mean appId
|
||||
if (obj.has("sessionId")) {
|
||||
launchSession.setSessionId(obj.get("sessionId").getAsString());
|
||||
launchSession.setSessionType(LaunchSessionType.App);
|
||||
} else {
|
||||
launchSession.setSessionType(LaunchSessionType.Unknown);
|
||||
}
|
||||
return launchSession;
|
||||
}, listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void launchBrowser(String url, final ResponseListener<LaunchSession> listener) {
|
||||
String uri = "ssap://system.launcher/open";
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("target", url);
|
||||
|
||||
ServiceCommand<LaunchSession> request = new ServiceCommand<>(uri, payload, obj -> {
|
||||
LaunchSession launchSession = new LaunchSession();
|
||||
launchSession.setService(this);
|
||||
launchSession.setAppId(obj.get("id").getAsString()); // note that response uses id to mean appId
|
||||
if (obj.has("sessionId")) {
|
||||
launchSession.setSessionId(obj.get("sessionId").getAsString());
|
||||
launchSession.setSessionType(LaunchSessionType.App);
|
||||
} else {
|
||||
launchSession.setSessionType(LaunchSessionType.Unknown);
|
||||
}
|
||||
return launchSession;
|
||||
}, listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
public void closeLaunchSession(LaunchSession launchSession, ResponseListener<CommandConfirmation> listener) {
|
||||
LGWebOSTVSocket service = launchSession.getService();
|
||||
|
||||
switch (launchSession.getSessionType()) {
|
||||
case App:
|
||||
case ExternalInputPicker:
|
||||
service.closeApp(launchSession, listener);
|
||||
break;
|
||||
|
||||
/*
|
||||
* If we want to extend support for MediaPlayer or WebAppLauncher at some point, this is how it was handeled
|
||||
* in connectsdk:
|
||||
*
|
||||
* case Media:
|
||||
* if (service instanceof MediaPlayer) {
|
||||
* ((MediaPlayer) service).closeMedia(launchSession, listener);
|
||||
* }
|
||||
* break;
|
||||
*
|
||||
*
|
||||
* case WebApp:
|
||||
* if (service instanceof WebAppLauncher) {
|
||||
* ((WebAppLauncher) service).closeWebApp(launchSession, listener);
|
||||
* }
|
||||
* break;
|
||||
* case Unknown:
|
||||
*/
|
||||
default:
|
||||
listener.onError("This DeviceService does not know ho to close this LaunchSession");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void closeApp(LaunchSession launchSession, ResponseListener<CommandConfirmation> listener) {
|
||||
String uri = "ssap://system.launcher/close";
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.addProperty("id", launchSession.getAppId());
|
||||
payload.addProperty("sessionId", launchSession.getSessionId());
|
||||
|
||||
ServiceCommand<CommandConfirmation> request = new ServiceCommand<>(uri, payload,
|
||||
x -> GSON.fromJson(x, CommandConfirmation.class), listener);
|
||||
launchSession.getService().sendCommand(request);
|
||||
}
|
||||
|
||||
public ServiceSubscription<AppInfo> subscribeRunningApp(ResponseListener<AppInfo> listener) {
|
||||
ResponseListener<AppInfo> interceptor = new ResponseListener<AppInfo>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(AppInfo appInfo) {
|
||||
if (appInfo.getId().isEmpty()) {
|
||||
scheduleDisconectingJob();
|
||||
} else {
|
||||
stopDisconnectingJob();
|
||||
if (state == State.DISCONNECTING) {
|
||||
setState(State.REGISTERED);
|
||||
}
|
||||
}
|
||||
listener.onSuccess(appInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
listener.onError(message);
|
||||
}
|
||||
};
|
||||
ServiceSubscription<AppInfo> request = new ServiceSubscription<>(FOREGROUND_APP, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj, AppInfo.class), interceptor);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
public ServiceCommand<AppInfo> getRunningApp(ResponseListener<AppInfo> listener) {
|
||||
ServiceCommand<AppInfo> request = new ServiceCommand<>(FOREGROUND_APP, null,
|
||||
jsonObj -> GSON.fromJson(jsonObj, AppInfo.class), listener);
|
||||
sendCommand(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
// KEYBOARD
|
||||
|
||||
public ServiceSubscription<TextInputStatusInfo> subscribeTextInputStatus(
|
||||
ResponseListener<TextInputStatusInfo> listener) {
|
||||
return keyboardInput.connect(listener);
|
||||
}
|
||||
|
||||
public void sendText(String input) {
|
||||
keyboardInput.sendText(input);
|
||||
}
|
||||
|
||||
public void sendEnter() {
|
||||
keyboardInput.sendEnter();
|
||||
}
|
||||
|
||||
public void sendDelete() {
|
||||
keyboardInput.sendDel();
|
||||
}
|
||||
|
||||
// MOUSE
|
||||
|
||||
public void executeMouse(Consumer<LGWebOSTVMouseSocket> onConnected) {
|
||||
LGWebOSTVMouseSocket mouseSocket = new LGWebOSTVMouseSocket(this.client);
|
||||
mouseSocket.setListener(new WebOSTVMouseSocketListener() {
|
||||
|
||||
@Override
|
||||
public void onStateChanged(LGWebOSTVMouseSocket.State oldState, LGWebOSTVMouseSocket.State newState) {
|
||||
switch (newState) {
|
||||
case CONNECTED:
|
||||
onConnected.accept(mouseSocket);
|
||||
mouseSocket.disconnect();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String errorMessage) {
|
||||
logger.debug("Error in communication with Mouse Socket: {}", errorMessage);
|
||||
}
|
||||
});
|
||||
|
||||
String uri = "ssap://com.webos.service.networkinput/getPointerInputSocket";
|
||||
|
||||
ResponseListener<JsonObject> listener = new ResponseListener<JsonObject>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable JsonObject jsonObj) {
|
||||
if (jsonObj != null) {
|
||||
String socketPath = jsonObj.get("socketPath").getAsString().replace("wss:", "ws:").replace(":3001/",
|
||||
":3000/");
|
||||
try {
|
||||
mouseSocket.connect(new URI(socketPath));
|
||||
} catch (URISyntaxException e) {
|
||||
logger.warn("Connect mouse error: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
logger.warn("Connect mouse error: {}", error);
|
||||
}
|
||||
};
|
||||
|
||||
ServiceCommand<JsonObject> request = new ServiceCommand<>(uri, null, x -> x, listener);
|
||||
sendCommand(request);
|
||||
}
|
||||
|
||||
// Simulate Remote Control Button press
|
||||
|
||||
public void sendRCButton(String rcButton, ResponseListener<CommandConfirmation> listener) {
|
||||
executeMouse(s -> s.button(rcButton));
|
||||
}
|
||||
|
||||
public interface ConfigProvider {
|
||||
void storeKey(String key);
|
||||
|
||||
void storeProperties(Map<String, String> properties);
|
||||
|
||||
String getKey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* This file is based on:
|
||||
*
|
||||
* ServiceCommand
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.command;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Internal implementation of ServiceCommand for URL-based commands
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
public class ServiceCommand<T> {
|
||||
|
||||
protected enum Type {
|
||||
request,
|
||||
subscribe
|
||||
}
|
||||
|
||||
protected Type type;
|
||||
protected JsonObject payload;
|
||||
protected String target;
|
||||
protected Function<JsonObject, T> converter;
|
||||
|
||||
ResponseListener<T> responseListener;
|
||||
|
||||
public ServiceCommand(String targetURL, JsonObject payload, Function<JsonObject, T> converter,
|
||||
ResponseListener<T> listener) {
|
||||
this.target = targetURL;
|
||||
this.payload = payload;
|
||||
this.converter = converter;
|
||||
this.responseListener = listener;
|
||||
this.type = Type.request;
|
||||
}
|
||||
|
||||
public JsonElement getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type.name();
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void processResponse(JsonObject response) {
|
||||
this.getResponseListener().onSuccess(this.converter.apply(response));
|
||||
}
|
||||
|
||||
public void processError(String error) {
|
||||
this.getResponseListener().onError(error);
|
||||
}
|
||||
|
||||
public ResponseListener<T> getResponseListener() {
|
||||
return responseListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServiceCommand [type=" + type + ", target=" + target + ", payload=" + payload + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* This file is based on URLServiceSubscription:
|
||||
*
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.command;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.openhab.binding.lgwebos.internal.handler.core.ResponseListener;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Internal implementation of ServiceSubscription for URL-based commands.
|
||||
*
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
public class ServiceSubscription<T> extends ServiceCommand<T> {
|
||||
|
||||
public ServiceSubscription(String uri, JsonObject payload, Function<JsonObject, T> converter,
|
||||
ResponseListener<T> listener) {
|
||||
super(uri, payload, converter, listener);
|
||||
type = Type.subscribe;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* This file is based on:
|
||||
*
|
||||
* AppInfo
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link AppInfo} is a value object to describe an application on WebOSTV.
|
||||
* The id value is mandatory when starting an application. The name is a human readable friendly name, which is not
|
||||
* further interpreted by the TV.
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB, made immutable
|
||||
*/
|
||||
public class AppInfo {
|
||||
|
||||
@SerializedName(value = "id", alternate = "appId")
|
||||
private String id;
|
||||
@SerializedName(value = "name", alternate = { "appName", "title" })
|
||||
private String name;
|
||||
|
||||
public AppInfo() {
|
||||
// no-argument constructor for gson
|
||||
}
|
||||
|
||||
public AppInfo(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AppInfo [id=" + id + ", name=" + name + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AppInfo other = (AppInfo) obj;
|
||||
if (id == null) {
|
||||
if (other.id != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!id.equals(other.id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/*
|
||||
* This file is based on:
|
||||
*
|
||||
* ChannelInfo
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
/**
|
||||
* {@link ChannelInfo} is a value object to describe a channel on WebOSTV.
|
||||
* The id value is mandatory when starting an channel. The channelName is a human readable friendly name, which is not
|
||||
* further interpreted by the TV.
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB, removed minor major number, made immutable
|
||||
*/
|
||||
public class ChannelInfo {
|
||||
|
||||
private String channelName;
|
||||
private String channelId;
|
||||
private String channelNumber;
|
||||
private String channelType;
|
||||
|
||||
public ChannelInfo() {
|
||||
// no-argument constructor for gson
|
||||
}
|
||||
|
||||
public ChannelInfo(String channelName, String channelId, String channelNumber, String channelType) {
|
||||
this.channelId = channelId;
|
||||
this.channelNumber = channelNumber;
|
||||
this.channelName = channelName;
|
||||
this.channelType = channelType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public String getChannelNumber() {
|
||||
return channelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChannelInfo [channelId=" + channelId + ", channelNumber=" + channelNumber + ", channelName="
|
||||
+ channelName + ", channelType=" + channelType + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((channelId == null) ? 0 : channelId.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ChannelInfo other = (ChannelInfo) obj;
|
||||
if (channelId == null) {
|
||||
if (other.channelId != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!channelId.equals(other.channelId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
/**
|
||||
* {@link CommandConfirmation} represents payload in response from TV were it only confirms the result of an operation.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
public class CommandConfirmation {
|
||||
private boolean returnValue;
|
||||
|
||||
public CommandConfirmation() {
|
||||
// no-argument constructor for gson
|
||||
}
|
||||
|
||||
public CommandConfirmation(boolean returnValue) {
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
|
||||
public boolean getReturnValue() {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommandConfirmation [returnValue=" + returnValue + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/* This file is based on:
|
||||
*
|
||||
* LaunchSession
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Jeffrey Glenn on 07 Mar 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
import org.openhab.binding.lgwebos.internal.handler.LGWebOSTVSocket;
|
||||
|
||||
/**
|
||||
* {@link LaunchSession} is a value object to describe a session with an application running on WebOSTV.
|
||||
*
|
||||
* Any time anything is launched onto a first screen device, there will be important session information that needs to
|
||||
* be tracked. {@link LaunchSession} tracks this data, and must be retained to perform certain actions within the
|
||||
* session.
|
||||
*
|
||||
* @author Jeffrey Glenn - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
public class LaunchSession {
|
||||
|
||||
private String appId;
|
||||
private String appName;
|
||||
private String sessionId;
|
||||
|
||||
private transient LGWebOSTVSocket socket;
|
||||
private transient LaunchSessionType sessionType;
|
||||
|
||||
/**
|
||||
* LaunchSession type is used to help DeviceService's know how to close a LaunchSession.
|
||||
*
|
||||
*/
|
||||
public enum LaunchSessionType {
|
||||
/** Unknown LaunchSession type, may be unable to close this launch session */
|
||||
Unknown,
|
||||
/** LaunchSession represents a launched app */
|
||||
App,
|
||||
/** LaunchSession represents an external input picker that was launched */
|
||||
ExternalInputPicker,
|
||||
/** LaunchSession represents a media app */
|
||||
Media,
|
||||
/** LaunchSession represents a web app */
|
||||
WebApp
|
||||
}
|
||||
|
||||
public LaunchSession() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a LaunchSession object for a given app ID.
|
||||
*
|
||||
* @param appId System-specific, unique ID of the app
|
||||
* @return the launch session
|
||||
*/
|
||||
public static LaunchSession launchSessionForAppId(String appId) {
|
||||
LaunchSession launchSession = new LaunchSession();
|
||||
launchSession.appId = appId;
|
||||
return launchSession;
|
||||
}
|
||||
|
||||
/** @return System-specific, unique ID of the app (ex. youtube.leanback.v4, 0000134, hulu) */
|
||||
public String getAppId() {
|
||||
return appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system-specific, unique ID of the app (ex. youtube.leanback.v4, 0000134, hulu)
|
||||
*
|
||||
* @param appId Id of the app
|
||||
*/
|
||||
public void setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
/** @return User-friendly name of the app (ex. YouTube, Browser, Hulu) */
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user-friendly name of the app (ex. YouTube, Browser, Hulu)
|
||||
*
|
||||
* @param appName Name of the app
|
||||
*/
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
/** @return Unique ID for the session (only provided by certain protocols) */
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session id (only provided by certain protocols)
|
||||
*
|
||||
* @param sessionId Id of the current session
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/** @return WebOSTVSocket responsible for launching the session. */
|
||||
public LGWebOSTVSocket getService() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* DeviceService responsible for launching the session.
|
||||
*
|
||||
* @param service Sets the DeviceService
|
||||
*/
|
||||
public void setService(LGWebOSTVSocket service) {
|
||||
this.socket = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return When closing a LaunchSession, the DeviceService relies on the sessionType to determine the method of
|
||||
* closing the session.
|
||||
*/
|
||||
public LaunchSessionType getSessionType() {
|
||||
return sessionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the LaunchSessionType of this LaunchSession.
|
||||
*
|
||||
* @param sessionType The type of LaunchSession
|
||||
*/
|
||||
public void setSessionType(LaunchSessionType sessionType) {
|
||||
this.sessionType = sessionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the app/media associated with the session.
|
||||
*
|
||||
* @param listener the response listener
|
||||
*/
|
||||
public void close(ResponseListener<CommandConfirmation> listener) {
|
||||
socket.closeLaunchSession(this, listener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* {@link Response} is a value object for a response message from WebOSTV.
|
||||
*
|
||||
* @author Sebastian Prehn - Initial contribution
|
||||
*/
|
||||
public class Response {
|
||||
/** Required response type */
|
||||
private String type;
|
||||
/** Optional payload */
|
||||
private JsonElement payload;
|
||||
/**
|
||||
* Message ID to which this is a response to.
|
||||
* This is optional.
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
public Response() {
|
||||
// no-argument constructor for gson
|
||||
}
|
||||
|
||||
/** Optional error message. */
|
||||
private String error;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public JsonElement getPayload() {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/* This file is based on:
|
||||
*
|
||||
* ResponseListener
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Generic asynchronous operation response success handler block. If there is any response data to be processed, it will
|
||||
* be provided via the responseObject parameter.
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ResponseListener<T> {
|
||||
|
||||
/**
|
||||
* Returns the success of the call of type T.
|
||||
* Contains the output data as a generic object reference.
|
||||
* This value may be any of a number of types as defined by T in subclasses of ResponseListener.
|
||||
*
|
||||
* @param responseObject Response object, can be any number of object types, depending on the
|
||||
* protocol/capability/etc
|
||||
*/
|
||||
void onSuccess(T responseObject);
|
||||
|
||||
/**
|
||||
* Method to return the error message that was generated.
|
||||
*
|
||||
*/
|
||||
void onError(String message);
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
/* This file is based on:
|
||||
*
|
||||
* TextInputStatusInfo
|
||||
* Connect SDK
|
||||
*
|
||||
* Copyright (c) 2014 LG Electronics.
|
||||
* Created by Hyun Kook Khang on 19 Jan 2014
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openhab.binding.lgwebos.internal.handler.core;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Normalized reference object for information about a text input event.
|
||||
*
|
||||
* @author Hyun Kook Khang - Connect SDK initial contribution
|
||||
* @author Sebastian Prehn - Adoption for openHAB
|
||||
*/
|
||||
public class TextInputStatusInfo {
|
||||
// @cond INTERNAL
|
||||
public enum TextInputType {
|
||||
DEFAULT,
|
||||
URL,
|
||||
NUMBER,
|
||||
PHONE_NUMBER,
|
||||
EMAIL
|
||||
}
|
||||
|
||||
boolean focused = false;
|
||||
String contentType = null;
|
||||
boolean predictionEnabled = false;
|
||||
boolean correctionEnabled = false;
|
||||
boolean autoCapitalization = false;
|
||||
boolean hiddenText = false;
|
||||
boolean focusChanged = false;
|
||||
|
||||
JsonObject rawData;
|
||||
// @endcond
|
||||
|
||||
public TextInputStatusInfo() {
|
||||
}
|
||||
|
||||
public boolean isFocused() {
|
||||
return focused;
|
||||
}
|
||||
|
||||
public void setFocused(boolean focused) {
|
||||
this.focused = focused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of keyboard that should be displayed to the user.
|
||||
*
|
||||
* @return the keyboard type
|
||||
*/
|
||||
public TextInputType getTextInputType() {
|
||||
TextInputType textInputType = TextInputType.DEFAULT;
|
||||
|
||||
if (contentType != null) {
|
||||
if (contentType.equals("number")) {
|
||||
textInputType = TextInputType.NUMBER;
|
||||
} else if (contentType.equals("phonenumber")) {
|
||||
textInputType = TextInputType.PHONE_NUMBER;
|
||||
} else if (contentType.equals("url")) {
|
||||
textInputType = TextInputType.URL;
|
||||
} else if (contentType.equals("email")) {
|
||||
textInputType = TextInputType.EMAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return textInputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of keyboard that should be displayed to the user.
|
||||
*
|
||||
* @param textInputType the keyboard type
|
||||
*/
|
||||
public void setTextInputType(TextInputType textInputType) {
|
||||
switch (textInputType) {
|
||||
case NUMBER:
|
||||
contentType = "number";
|
||||
break;
|
||||
case PHONE_NUMBER:
|
||||
contentType = "phonenumber";
|
||||
break;
|
||||
case URL:
|
||||
contentType = "url";
|
||||
break;
|
||||
case EMAIL:
|
||||
contentType = "number";
|
||||
break;
|
||||
case DEFAULT:
|
||||
default:
|
||||
contentType = "email";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public boolean isPredictionEnabled() {
|
||||
return predictionEnabled;
|
||||
}
|
||||
|
||||
public void setPredictionEnabled(boolean predictionEnabled) {
|
||||
this.predictionEnabled = predictionEnabled;
|
||||
}
|
||||
|
||||
public boolean isCorrectionEnabled() {
|
||||
return correctionEnabled;
|
||||
}
|
||||
|
||||
public void setCorrectionEnabled(boolean correctionEnabled) {
|
||||
this.correctionEnabled = correctionEnabled;
|
||||
}
|
||||
|
||||
public boolean isAutoCapitalization() {
|
||||
return autoCapitalization;
|
||||
}
|
||||
|
||||
public void setAutoCapitalization(boolean autoCapitalization) {
|
||||
this.autoCapitalization = autoCapitalization;
|
||||
}
|
||||
|
||||
public boolean isHiddenText() {
|
||||
return hiddenText;
|
||||
}
|
||||
|
||||
public void setHiddenText(boolean hiddenText) {
|
||||
this.hiddenText = hiddenText;
|
||||
}
|
||||
|
||||
/** @return the raw data from the first screen device about the text input status. */
|
||||
public JsonObject getRawData() {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
/** @param data the raw data from the first screen device about the text input status. */
|
||||
public void setRawData(JsonObject data) {
|
||||
rawData = data;
|
||||
}
|
||||
|
||||
public boolean isFocusChanged() {
|
||||
return focusChanged;
|
||||
}
|
||||
|
||||
public void setFocusChanged(boolean focusChanged) {
|
||||
this.focusChanged = focusChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="lgwebos" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>LG webOS Binding</name>
|
||||
<description>Binding to connect LG's WebOS based smart TVs</description>
|
||||
<author>Sebastian Prehn</author>
|
||||
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:lgwebos:WebOSTV">
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host</label>
|
||||
<description>Hostname or IP address of TV.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="key" type="text" required="false">
|
||||
<label>Access Key</label>
|
||||
<description>Key exchanged with TV after pairing.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="macAddress" type="text" required="false">
|
||||
<label>MAC Address</label>
|
||||
<description>If MAC Address of TV is entered here, the binding will attempt to power on the device via Wake On Lan
|
||||
(WOL), when it receives command ON on channel power. Accepted value is six groups of two hexadecimal digits,
|
||||
separated by hyphens or colons, e.g '3c:cd:93:c2:20:e0'.)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,45 @@
|
||||
actionShowToastLabel=Show Toast
|
||||
actionShowToastDesc=Sends a toast message to a WebOS device with openHAB icon.
|
||||
actionShowToastInputTextLabel=Text
|
||||
actionShowToastInputTextDesc=The text to display
|
||||
|
||||
actionShowToastWithIconLabel=Show Toast with Icon
|
||||
actionShowToastWithIconLabel=Sends a toast message to a WebOS device with custom icon.
|
||||
actionShowToastInputIconLabel=Icon
|
||||
actionShowToastInputIconDesc=The URL to the icon to display
|
||||
|
||||
actionLaunchBrowserLabel=Launch Browser
|
||||
actionLaunchBrowserDesc=Opens the given URL in the TV's browser application.
|
||||
actionLaunchBrowserInputUrlLabel=URL
|
||||
actionLaunchBrowserInputUrlDesc=The URL to open
|
||||
|
||||
actionLaunchApplicationLabel=Launch Application
|
||||
actionLaunchApplicationDesc=Opens the application with given Application ID.
|
||||
actionLaunchApplicationInputAppIDLabel=Application ID
|
||||
actionLaunchApplicationInputAppIDDesc=The Application ID
|
||||
|
||||
actionLaunchApplicationWithParamsLabel=Launch Application with Parameters
|
||||
actionLaunchApplicationWithParamsDesc=Opens the application with given Application ID and passes additional parameters.
|
||||
actionLaunchApplicationInputParamsLabel=JSON Parameters
|
||||
actionLaunchApplicationInputParamsDesc=The parameters to hand over to the application in JSON format
|
||||
|
||||
actionSendTextLabel=Send Text
|
||||
actionSendTextDesc=Sends a text input to a WebOS device.
|
||||
actionSendTextInputTextLabel=Text
|
||||
actionSendTextInputTextDesc=The text to input
|
||||
|
||||
actionSendButtonLabel=Send Button
|
||||
actionSendButtonDesc=Sends a button press event to a WebOS device.
|
||||
actionSendButtonInputButtonLabel=Button
|
||||
actionSendButtonInputButtonDesc=Can be one of UP, DOWN, LEFT, RIGHT, BACK, DELETE, ENTER, HOME, or OK
|
||||
|
||||
actionIncreaseChannelLabel=Channel Up
|
||||
actionIncreaseChannelDesc=TV will switch one channel up in the current channel list.
|
||||
|
||||
actionDecreaseChannelLabel=Channel Down
|
||||
actionDecreaseChannelDesc=TV will switch one channel down in the current channel list.
|
||||
|
||||
actionSendRCButtonLabel=Remote Control button press
|
||||
actionSendRCButtonDesc=Simulates pressing of a Remote Control Button.
|
||||
actionSendRCButtonInputTextLabel=Remote Control button name
|
||||
actionSendRCButtonInputTextDesc=The Remote Control button name to send to the WebOS device.
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="lgwebos"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="WebOSTV">
|
||||
<label>WebOS TV</label>
|
||||
<description>WebOS based smart TV</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="powerType"/>
|
||||
<channel id="mute" typeId="muteType"/>
|
||||
<channel id="volume" typeId="volumeType"/>
|
||||
<channel id="channel" typeId="channelType"/>
|
||||
<channel id="toast" typeId="toastType"/>
|
||||
<channel id="mediaPlayer" typeId="mediaPlayerType"/>
|
||||
<channel id="mediaStop" typeId="mediaStopType"/>
|
||||
<channel id="appLauncher" typeId="appLauncherChannelType"/>
|
||||
<channel id="rcButton" typeId="rcButtonType"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="deviceId"/>
|
||||
<property name="lastConnected"/>
|
||||
<property name="deviceOS"/>
|
||||
<property name="deviceOSVersion"/>
|
||||
<property name="deviceOSReleaseVersion"/>
|
||||
</properties>
|
||||
<representation-property>deviceId</representation-property>
|
||||
|
||||
|
||||
<config-description-ref uri="thing-type:lgwebos:WebOSTV"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="powerType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power</label>
|
||||
<description>Via this binding TV can only be powered off, not on.</description>
|
||||
</channel-type>
|
||||
<channel-type id="muteType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mute</label>
|
||||
<description>Current Mute Setting</description>
|
||||
<category>SoundVolume</category>
|
||||
</channel-type>
|
||||
<channel-type id="volumeType">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Current Volume Setting</description>
|
||||
<category>SoundVolume</category>
|
||||
<state min="0" max="100" step="1"></state>
|
||||
</channel-type>
|
||||
<channel-type id="channelType">
|
||||
<item-type>String</item-type>
|
||||
<label>Channel</label>
|
||||
<description>Current Channel</description>
|
||||
</channel-type>
|
||||
<channel-type id="toastType">
|
||||
<item-type>String</item-type>
|
||||
<label>Toast</label>
|
||||
<description>Send a message onto the TV screen.</description>
|
||||
</channel-type>
|
||||
<channel-type id="mediaPlayerType">
|
||||
<item-type>Player</item-type>
|
||||
<label>Media Control</label>
|
||||
<description>Control media (e.g. audio or video) playback</description>
|
||||
<category>MediaControl</category>
|
||||
</channel-type>
|
||||
<channel-type id="mediaStopType">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Stop</label>
|
||||
<description>Stop Playback</description>
|
||||
</channel-type>
|
||||
<channel-type id="appLauncherChannelType">
|
||||
<item-type>String</item-type>
|
||||
<label>Application</label>
|
||||
<description>Start application and monitor running applications.</description>
|
||||
</channel-type>
|
||||
<channel-type id="rcButtonType">
|
||||
<item-type>String</item-type>
|
||||
<label>RCButton</label>
|
||||
<description>Simulate a Remote Control button press</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,3 @@
|
||||
org.slf4j.simpleLogger.defaultLogLevel=info
|
||||
org.slf4j.simpleLogger.log.org.openhab.binding.lgwebos=trace
|
||||
org.slf4j.simpleLogger.log.org.eclipse.jetty.websocket=info
|
||||
Reference in New Issue
Block a user