added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
38
bundles/org.openhab.binding.lgwebos/.classpath
Normal file
38
bundles/org.openhab.binding.lgwebos/.classpath
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.lgwebos/.project
Normal file
23
bundles/org.openhab.binding.lgwebos/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.lgwebos</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
19
bundles/org.openhab.binding.lgwebos/NOTICE
Normal file
19
bundles/org.openhab.binding.lgwebos/NOTICE
Normal file
@@ -0,0 +1,19 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
The web socket communication with LGWebOS is derived from Connect-SDK-Java-Core.
|
||||
* License: Apache 2.0 License
|
||||
* Project: https://github.com/ConnectSDK/Connect-SDK-Android-Core
|
||||
355
bundles/org.openhab.binding.lgwebos/README.md
Normal file
355
bundles/org.openhab.binding.lgwebos/README.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# LG webOS Binding
|
||||
|
||||
The binding integrates LG WebOS based smart TVs.
|
||||
This binding is an adoption of LG's [Connect SDK](https://github.com/ConnectSDK/Connect-SDK-Android-Core) library, which is no longer maintained and which was specific to Android.
|
||||
|
||||
## Supported Things
|
||||
|
||||
### LG webOS smart TVs
|
||||
|
||||
LG webOS based smart TVs are supported.
|
||||
|
||||
#### TV Settings
|
||||
|
||||
The TV must be connected to the same network as openHAB.
|
||||
Under network settings allow "LG CONNECT APPS" to connect.
|
||||
|
||||
Note: Under general settings allow mobile applications to turn on the TV, if this option is available.
|
||||
On newer models this setting may also be called "Mobile TV On > Turn On Via WiFi".
|
||||
In combination with the wake on LAN binding this will allow you to start the TV via openHAB. Please see demo.items and demo.rules example below.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has no configuration parameter.
|
||||
|
||||
## Discovery
|
||||
|
||||
TVs are auto discovered through SSDP in the local network.
|
||||
The binding broadcasts a search message via UDP on the network in order to discover and monitor availability of the TV.
|
||||
|
||||
Please note, that if you are running openHAB in a Docker container you need to use macvlan or host networking for this binding to work.
|
||||
If automatic discovery is not possible you may still manually configure a device based on host and access key.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
WebOS TV has three configuration parameters.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|------------|-----------------------------------------------------------------------------------------------------|
|
||||
| host | Hostname or IP address of TV |
|
||||
| key | Key exchanged with TV after pairing (enter it after you paired the device) |
|
||||
| macAddress | The MAC address of your TV to turn on via Wake On Lan (WOL). The binding will attempt to detect it. |
|
||||
|
||||
### Configuration in .things file
|
||||
|
||||
Set host and key parameter as in the following example:
|
||||
|
||||
```
|
||||
Thing lgwebos:WebOSTV:tv1 [host="192.168.2.119", key="6ef1dff6c7c936c8dc5056fc85ea3aef", macAddress="3c:cd:93:c2:20:e0"]
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel Type ID | Item Type | Description | Read/Write |
|
||||
|-----------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|
|
||||
| power | Switch | Current power setting. TV can only be powered off, not on, via the TV's API. Turning on is implemented via Wake On Lan, for which the MAC address must be set in the thing configuration. | RW |
|
||||
| mute | Switch | Current mute setting. | RW |
|
||||
| volume | Dimmer | Current volume setting. Setting and reporting absolute percent values only works when using internal speakers. When connected to an external amp, the volume should be controlled using increase and decrease commands. | RW |
|
||||
| channel | String | Current channel. Use the channel number or channel id as command to update the channel. | RW |
|
||||
| toast | String | Displays a short message on the TV screen. See also rules section. | W |
|
||||
| mediaPlayer | Player | Media control player | W |
|
||||
| mediaStop | Switch | Media control stop | W |
|
||||
| appLauncher | String | Application ID of currently running application. This also allows to start applications on the TV by sending a specific Application ID to this channel. | RW |
|
||||
| rcButton | String | Simulates pressing of a button on the TV's remote control. See below for a list of button names. | W |
|
||||
|
||||
The available application IDs for your TV can be listed using a console command (see below).
|
||||
You have to use one of these IDs as command for the appLauncher channel.
|
||||
Here are examples of values that could be available for your TV: airplay, amazon, com.apple.appletv, com.webos.app.browser, com.webos.app.externalinput.av1, com.webos.app.externalinput.av2, com.webos.app.externalinput.component, com.webos.app.hdmi1, com.webos.app.hdmi2, com.webos.app.hdmi3, com.webos.app.hdmi4, com.webos.app.homeconnect, com.webos.app.igallery, com.webos.app.livetv, com.webos.app.music, com.webos.app.photovideo, com.webos.app.recordings, com.webos.app.screensaver, googleplaymovieswebos, netflix, youtube.leanback.v4.
|
||||
|
||||
### Remote Control Buttons
|
||||
|
||||
The rcButton channel has only been tested on an LGUJ657A TV. and this is a list of button codes that are known to work with this device.
|
||||
This list has been compiled mostly through trial and error. Your mileage may vary.
|
||||
|
||||
| Code String | Description |
|
||||
|-------------|----------------------------------------------------------|
|
||||
| LEFT | Left button in cursor control group |
|
||||
| RIGHT | Right button in cursor control group |
|
||||
| UP | Up button in cursor control group |
|
||||
| DOWN | Down button in cursor control group |
|
||||
| ENTER | "OK" button in the center of the cursor control group |
|
||||
| BACK | "BACK" button |
|
||||
| EXIT | "EXIT" button |
|
||||
| 0-9 | Number buttons |
|
||||
| HOME | "HOME" button |
|
||||
| RED | "RED" button |
|
||||
| GREEN | "GREEN" button |
|
||||
| YELLOW | "YELLOW" button |
|
||||
| BLUE | "BLUE" button |
|
||||
| PLAY | "PLAY" button |
|
||||
| PAUSE | "PAUSE" button |
|
||||
| STOP | "STOP" button |
|
||||
|
||||
|
||||
A sample HABPanel remote control widget can be found [in this GitHub repository.](https://github.com/bbrodt/openhab2-misc)
|
||||
|
||||
## Console Commands
|
||||
|
||||
The binding provides a few commands you can use in the console.
|
||||
Enter the command `lgwebos` to get the usage.
|
||||
|
||||
```
|
||||
openhab> lgwebos
|
||||
Usage: smarthome:lgwebos <thingUID> applications - list applications
|
||||
Usage: smarthome:lgwebos <thingUID> channels - list channels
|
||||
Usage: smarthome:lgwebos <thingUID> accesskey - show the access key
|
||||
```
|
||||
|
||||
The command `applications` reports in the console the list of all applications with their id and name.
|
||||
The command `channels` reports in the console the list of all channels with their id, number and name.
|
||||
The command `accesskey` reports in the console the access key used to connect to your TV.
|
||||
|
||||
## Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Thing lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46 [host="192.168.2.119", key="6ef1dff6c7c936c8dc5056fc85ea3aef", macAddress="3c:cd:93:c2:20:e0"]
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Switch LG_TV0_Power "TV Power" <television> { autoupdate="false", channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:power" }
|
||||
Switch LG_TV0_Mute "TV Mute" { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:mute"}
|
||||
Dimmer LG_TV0_Volume "Volume [%d]" { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:volume" }
|
||||
Number LG_TV0_VolDummy "VolumeUpDown"
|
||||
String LG_TV0_Channel "Channel [%s]" { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:channel" }
|
||||
Number LG_TV0_ChannelDummy "ChannelUpDown"
|
||||
String LG_TV0_Toast { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:toast"}
|
||||
Switch LG_TV0_Stop "Stop" { autoupdate="false", channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:mediaStop" }
|
||||
String LG_TV0_Application "Application [%s]" { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:appLauncher"}
|
||||
Player LG_TV0_Player { channel="lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46:mediaPlayer"}
|
||||
|
||||
```
|
||||
|
||||
demo.sitemap:
|
||||
|
||||
```
|
||||
sitemap demo label="Main Menu"
|
||||
{
|
||||
Frame label="TV" {
|
||||
Switch item=LG_TV0_Power
|
||||
Switch item=LG_TV0_Mute
|
||||
Text item=LG_TV0_Volume
|
||||
Switch item=LG_TV0_VolDummy icon="soundvolume" label="Volume" mappings=[1="▲", 0="▼"]
|
||||
Default item=LG_TV0_Channel
|
||||
Switch item=LG_TV0_ChannelDummy icon="television" label="Channel" mappings=[1="▲", 0="▼"]
|
||||
Default item=LG_TV0_Player
|
||||
Default item=LG_TV0_Application
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
demo.rules:
|
||||
|
||||
```
|
||||
// for relative volume changes
|
||||
rule "VolumeUpDown"
|
||||
when Item LG_TV0_VolDummy received command
|
||||
then
|
||||
switch receivedCommand{
|
||||
case 0: LG_TV0_Volume.sendCommand(DECREASE)
|
||||
case 1: LG_TV0_Volume.sendCommand(INCREASE)
|
||||
}
|
||||
end
|
||||
|
||||
// for relative channel changes
|
||||
rule "ChannelUpDown"
|
||||
when Item LG_TV0_ChannelDummy received command
|
||||
then
|
||||
val actions = getActions("lgwebos","lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46")
|
||||
if(null === actions) {
|
||||
logInfo("actions", "Actions not found, check thing ID")
|
||||
return
|
||||
}
|
||||
|
||||
switch receivedCommand{
|
||||
case 0: actions.decreaseChannel()
|
||||
case 1: actions.increaseChannel()
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
Example of a toast message.
|
||||
|
||||
```
|
||||
LG_TV0_Toast.sendCommand("Hello World")
|
||||
```
|
||||
|
||||
## Rule Actions
|
||||
|
||||
Multiple actions are supported by this binding. In classic rules these are accessible as shown in this example (adjust getActions with your ThingId):
|
||||
|
||||
Example
|
||||
|
||||
```
|
||||
val actions = getActions("lgwebos","lgwebos:WebOSTV:3aab9eea-953b-4272-bdbd-f0cd0ecf4a46")
|
||||
if(null === actions) {
|
||||
logInfo("actions", "Actions not found, check thing ID")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### showToast(text)
|
||||
|
||||
Sends a toast message to a WebOS device with openHAB icon.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|----------------------------------------------------------------------|
|
||||
| text | The text to display |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.showToast("Hello World")
|
||||
```
|
||||
|
||||
### showToast(icon, text)
|
||||
|
||||
Sends a toast message to a WebOS device with custom icon.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|----------------------------------------------------------------------|
|
||||
| icon | The URL to the icon to display |
|
||||
| text | The text to display |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.showToast("http://localhost:8080/icon/energy?format=png","Hello World")
|
||||
```
|
||||
|
||||
### launchBrowser(url)
|
||||
|
||||
Opens the given URL in the TV's browser application.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|----------------------------------------------------------------------|
|
||||
| url | The URL to open |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.launchBrowser("https://www.openhab.org")
|
||||
```
|
||||
|
||||
### launchApplication(appId)
|
||||
|
||||
Opens the application with given Application ID.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|--------------------------------------------------------------------------------|
|
||||
| appId | The Application ID. getApplications provides available apps and their appIds. |
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
actions.launchApplication("com.webos.app.tvguide") // TV Guide
|
||||
actions.launchApplication("com.webos.app.livetv") // TV
|
||||
actions.launchApplication("com.webos.app.hdmi1") // HDMI1
|
||||
actions.launchApplication("com.webos.app.hdmi2") // HDMI2
|
||||
actions.launchApplication("com.webos.app.hdmi3") // HDMI3
|
||||
```
|
||||
|
||||
### launchApplication(appId, params)
|
||||
|
||||
Opens the application with given Application ID and passes an additional parameter.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|-------------------------------------------------------------------------------|
|
||||
| appId | The Application ID. Console command lgwebos <thingUID> applications provides available apps and their appIds. |
|
||||
| params | The parameters to hand over to the application in JSON format |
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
actions.launchApplication("appId","{\"key\":\"value\"}")
|
||||
```
|
||||
|
||||
(Unfortunately, there is currently no information on supported parameters per application available.)
|
||||
|
||||
### sendText(text)
|
||||
|
||||
Sends a text input to a WebOS device.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|----------------------------------------------------------------------|
|
||||
| text | The text to input |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.sendText("Some text")
|
||||
```
|
||||
|
||||
### sendButton(button)
|
||||
|
||||
Sends a button press event to a WebOS device.
|
||||
|
||||
Parameters:
|
||||
|
||||
| Name | Description |
|
||||
|---------|------------------------------------------------------------------------|
|
||||
| button | Can be one of UP, DOWN, LEFT, RIGHT, BACK, DELETE, ENTER, HOME, or OK |
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.sendButton("OK")
|
||||
```
|
||||
|
||||
### increaseChannel()
|
||||
|
||||
TV will switch one channel up in the current channel list.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.increaseChannel
|
||||
```
|
||||
|
||||
### decreaseChannel()
|
||||
|
||||
TV will switch one channel down in the current channel list.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
actions.decreaseChannel
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
In case of issues you may find it helpful to enable debug level logging and check you log file. Log into openHAB console and enable debug logging for this binding:
|
||||
|
||||
```
|
||||
log:set debug org.openhab.binding.lgwebos
|
||||
```
|
||||
|
||||
17
bundles/org.openhab.binding.lgwebos/pom.xml
Normal file
17
bundles/org.openhab.binding.lgwebos/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.lgwebos</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: LG webOS Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -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