[androidtv] Add PhilipsTV protocol

Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: morph166955 <53797132+morph166955@users.noreply.github.com>
This commit is contained in:
morph166955 2024-02-09 03:05:48 -06:00 committed by GitHub
parent dd6d8c1bd2
commit e4d5ae0a87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 5795 additions and 41 deletions

View File

@ -11,3 +11,21 @@ https://www.eclipse.org/legal/epl-2.0/.
== Source Code == Source Code
https://github.com/openhab/openhab-addons https://github.com/openhab/openhab-addons
== Third-party Content
httpclient
* License: Apache 2.0 License
* Project: https://hc.apache.org/httpcomponents-client-ga
* Source: https://hc.apache.org/httpcomponents-client-ga
httpcore
* License: Apache 2.0 License
* Project: http://hc.apache.org/httpcomponents-core-ga
* Source: http://hc.apache.org/httpcomponents-core-ga
jackson
* License: Apache 2.0 License
* Project: https://github.com/FasterXML/jackson
* Source: https://github.com/FasterXML/jackson

View File

@ -1,8 +1,9 @@
# AndroidTV Binding # AndroidTV Binding
This binding is designed to emulate different protocols to interact with the AndroidTV platform. This binding is designed to emulate different protocols to interact with the AndroidTV platform.
Currently it emulates both the Google Video App to interact with a variety of AndroidTVs for purposes of remote control. Currently it emulates the Google Video App to interact with a variety of AndroidTVs for purposes of remote control.
It also currently emulates the Nvidia ShieldTV Android App to interact with an Nvidia ShieldTV for purposes of remote control. It also currently emulates the Nvidia ShieldTV Android App to interact with an Nvidia ShieldTV for purposes of remote control.
It also currently emulates the PhilipsTV App to interact with a 2016+ PhilipsTV for purposes of remote control.
## Supported Things ## Supported Things
@ -10,14 +11,15 @@ This binding supports two thing types:
- **googletv** - An AndroidTV running Google Video - **googletv** - An AndroidTV running Google Video
- **shieldtv** - An Nvidia ShieldTV - **shieldtv** - An Nvidia ShieldTV
- **philipstv** - A 2016+ Philips TV
## Discovery ## Discovery
Both GoogleTVs and ShieldTVs should be added automatically to the inbox through the mDNS discovery process. All relevant thing types should be added automatically to the inbox through the mDNS discovery process.
In the case of the ShieldTV, openHAB will likely create an inbox entry for both a GoogleTV and a ShieldTV device. In the case of the ShieldTV or PhilipsTV, openHAB will likely create an inbox entry for both a GoogleTV and a ShieldTV or PhilipsTV device.
Only the ShieldTV device should be configured, the GoogleTV can be ignored. Only the ShieldTV or PhilipsTV device should be configured, the GoogleTV can be ignored.
There is no benefit to configuring two things for a ShieldTV device. There is no benefit to configuring two things for a ShieldTV or PhilipsTV device.
This could cause undesired effects. This could cause undesired effects.
## Binding Configuration ## Binding Configuration
@ -30,37 +32,58 @@ This binding requires GoogleTV to be installed on the device (https://play.googl
## Thing Configuration ## Thing Configuration
There are three required fields to connect successfully to a ShieldTV. The is one required field to connect to the devices. All other fields are optional.
| Name | Type | Description | Default | Required | Advanced | | Name | Type | Description | Default | Required | Advanced |
|------------------|---------|---------------------------------------|---------|----------|----------| |------------------|---------|---------------------------------------|---------|----------|----------|
| ipAddress | text | IP address of the device | N/A | yes | no | | ipAddress | text | IP address of the device | N/A | yes | no |
| googletvPort | text | TCP Port for GoogleTV | 6466 | no | no | | googletvPort | text | TCP Port for GoogleTV | 6466 | no | yes |
| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | no | | shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | yes |
| keystore | text | Location of the Java Keystore | N/A | no | no | | philipstvPort | text | TCP Port for PhilipsTV | 1926 | no | yes |
| keystorePassword | text | Password of the Java Keystore | N/A | no | no | | keystoreFileName | text | Location of the Java Keystore | N/A | no | yes |
| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | no | | keystorePassword | text | Password of the Java Keystore | N/A | no | yes |
| reconnect | text | Delay between reconnections | 60 | no | yes |
| heartbeat | text | Frequency of heartbeats | 5 | no | yes |
| delay | text | Delay between messages | 0 | no | yes |
| refreshRate | text | Refresh interval of PhilipsTV | 10 | no | yes |
| useUpnpDiscovery | boolean | Enables UPnP Discovery for PhilipsTV | true | no | yes |
| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | yes |
```java ```java
Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ]
Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ]
Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ]
``` ```
## Channels ## Channels
| Channel | Type | Description | GoogleTV | ShieldTV | | Channel | Type | Description | GoogleTV | ShieldTV | PhilipsTV |
|------------|--------|-----------------------------|----------|----------| |----------------------|--------|--------------------------------------|----------|----------|-----------|
| keyboard | String | Keyboard Data Entry | RW | RW | | keyboard | String | Keyboard Data Entry | RW | RW | RW |
| keypress | String | Manual Key Press Entry | RW | RW | | keypress | String | Manual Key Press Entry | RW | RW | RW |
| keycode | String | Direct KEYCODE Entry | RW | RW | | keycode | String | Direct KEYCODE Entry | RW | RW | RW |
| pincode | String | PIN Code Entry | RW | RW | | pincode | String | PIN Code Entry | RW | RW | RW |
| app | String | App Control | RO | RW | | app | String | App Control | RO | RW | RW |
| appname | String | App Name | N/A | RW | | appname | String | App Name | N/A | RW | RW |
| appurl | String | App URL | N/A | RW | | appurl | String | App URL | N/A | RO | N/A |
| player | Player | Player Control | RW | RW | | appicon | Image | App Icon | N/A | N/A | RO |
| power | Switch | Power Control | RW | RW | | player | Player | Player Control | RW | RW | RW |
| volume | Dimmer | Volume Control | RO | RO | | power | Switch | Power Control | RW | RW | RW |
| mute | Switch | Mute Control | RW | RW | | volume | Dimmer | Volume Control | RO | RO | RW |
| mute | Switch | Mute Control | RW | RW | RW |
| tvChannel | String | TV Channel Control | N/A | N/A | RW |
| brightness | Dimmer | Brightness Control | N/A | N/A | RW |
| contrast | Dimmer | Contrast Control | N/A | N/A | RW |
| sharpness | Dimmer | Sharpness Control | N/A | N/A | RW |
| searchContent | String | Google Assistant search | N/A | N/A | RW |
| ambilightPower | Switch | Ambilight power control | N/A | N/A | RW |
| ambilightHuePower | Switch | Ambilight + Hue power control | N/A | N/A | RW |
| ambilightStyle | String | Ambilight Style plus algorithm used | N/A | N/A | RW |
| ambilightColor | Color | Color for all Ambilight Sides | N/A | N/A | RW |
| ambilightLeftColor | Color | Color for left Ambilight Side | N/A | N/A | RW |
| ambilightRightColor | Color | Color for right Ambilight Side | N/A | N/A | RW |
| ambilightTopColor | Color | Color for top Ambilight Side | N/A | N/A | RW |
| ambilightBottomColor | Color | Color for bottom Ambilight Side | N/A | N/A | RW |
```java ```java
@ -76,6 +99,32 @@ Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:po
Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" } Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" }
Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" } Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" }
String PhilipsTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:philipstv:bedroom:keyboard" }
String PhilipsTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:philipstv:bedroom:keypress" }
String PhilipsTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:philipstv:bedroom:keycode" }
String PhilipsTV_PINCODE "PINCODE [%s]" { channel = "androidtv:philipstv:bedroom:pincode" }
String PhilipsTV_APP "APP [%s]" { channel = "androidtv:philipstv:bedroom:app" }
String PhilipsTV_APPNAME "APPNAME [%s]" { channel = "androidtv:philipstv:bedroom:appname" }
Image PhilipsTV_APPICON "APPICON [%s]" { channel = "androidtv:philipstv:bedroom:appicon" }
Player PhilipsTV_PLAYER "PLAYER [%s]" { channel = "androidtv:philipstv:bedroom:player" }
Switch PhilipsTV_POWER "POWER" { channel = "androidtv:philipstv:bedroom:power" }
Dimmer PhilipsTV_VOLUME "VOLUME [%s]" { channel = "androidtv:philipstv:bedroom:volume" }
Switch PhilipsTV_MUTE "MUTE [%s]" { channel = "androidtv:philipstv:bedroom:mute" }
String PhilipsTV_TVCHANNEL "TVCHANNEL [%s]" { channel = "androidtv:philipstv:bedroom:tvChannel" }
String PhilipsTV_SEARCHCONTENT "SEARCH CONTENT [%s]" { channel = "androidtv:philipstv:bedroom:searchContent" }
Switch PhilipsTV_AMBILIGHTPOWER "AMBILIGHT POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightPower" }
Switch PhilipsTV_AMBILIGHTHUEPOWER "AMBILIGHT HUE POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightHuePower" }
Switch PhilipsTV_AMBILIGHTLOUNGEPOWER "AMBILIGHT LOUNGE POWER" { channel = "androidtv:philipstv:bedroom:ambilightLoungePower" }
String PhilipsTV_AMBILIGHTSTYLE "AMBILIGHT STYLE" { channel = "androidtv:philipstv:bedroom:ambilightStyle" }
Color PhilipsTV_AMBILIGHTALLCOLOR "ALL SIDES AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightColor" }
Color PhilipsTV_AMBILIGHTLEFTCOLOR "LEFT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightLeftColor" }
Color PhilipsTV_AMBILIGHTRIGHTCOLOR "RIGHT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightRightColor" }
Color PhilipsTV_AMBILIGHTTOPCOLOR "TOP SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightTopColor" }
Color PhilipsTV_AMBILIGHTBOTTOMCOLOR "BOTTOM SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightBottomColor" }
Dimmer PhilipsTV_BRIGHTNESS "BRIGHTNESS [%s]" { channel = "androidtv:philipstv:bedroom:brightness" }
Dimmer PhilipsTV_CONTRAST "CONTRAST [%s]" { channel = "androidtv:philipstv:bedroom:contrast" }
Dimmer PhilipsTV_SHARPNESS "SHARPNESS [%s]" { channel = "androidtv:philipstv:bedroom:sharpness" }
String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" } String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" }
String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" }
String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" } String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" }
@ -168,7 +217,7 @@ openhab> openhab:androidtv androidtv:googletv:theater pincode abc123
The display should return back to where it was originally. The display should return back to where it was originally.
If you are on a ShieldTV you must run that process a second time to authenticate the GoogleTV protocol stack. If you are on a ShieldTV or PhilipsTV you must run that process a second time to authenticate the GoogleTV protocol stack.
This completes the PIN process. This completes the PIN process.
@ -179,6 +228,7 @@ Upon reconnection (either from reconfiguration or a restart of OpenHAB), you sho
```java ```java
Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ]
Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ]
Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ]
``` ```
```java ```java
@ -194,6 +244,32 @@ Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:po
Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" } Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" }
Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" } Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" }
String PhilipsTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:philipstv:bedroom:keyboard" }
String PhilipsTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:philipstv:bedroom:keypress" }
String PhilipsTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:philipstv:bedroom:keycode" }
String PhilipsTV_PINCODE "PINCODE [%s]" { channel = "androidtv:philipstv:bedroom:pincode" }
String PhilipsTV_APP "APP [%s]" { channel = "androidtv:philipstv:bedroom:app" }
String PhilipsTV_APPNAME "APPNAME [%s]" { channel = "androidtv:philipstv:bedroom:appname" }
Image PhilipsTV_APPICON "APPICON [%s]" { channel = "androidtv:philipstv:bedroom:appicon" }
Player PhilipsTV_PLAYER "PLAYER [%s]" { channel = "androidtv:philipstv:bedroom:player" }
Switch PhilipsTV_POWER "POWER" { channel = "androidtv:philipstv:bedroom:power" }
Dimmer PhilipsTV_VOLUME "VOLUME [%s]" { channel = "androidtv:philipstv:bedroom:volume" }
Switch PhilipsTV_MUTE "MUTE [%s]" { channel = "androidtv:philipstv:bedroom:mute" }
String PhilipsTV_TVCHANNEL "TVCHANNEL [%s]" { channel = "androidtv:philipstv:bedroom:tvChannel" }
String PhilipsTV_SEARCHCONTENT "SEARCH CONTENT [%s]" { channel = "androidtv:philipstv:bedroom:searchContent" }
Switch PhilipsTV_AMBILIGHTPOWER "AMBILIGHT POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightPower" }
Switch PhilipsTV_AMBILIGHTHUEPOWER "AMBILIGHT HUE POWER [%s]" { channel = "androidtv:philipstv:bedroom:ambilightHuePower" }
Switch PhilipsTV_AMBILIGHTLOUNGEPOWER "AMBILIGHT LOUNGE POWER" { channel = "androidtv:philipstv:bedroom:ambilightLoungePower" }
String PhilipsTV_AMBILIGHTSTYLE "AMBILIGHT STYLE" { channel = "androidtv:philipstv:bedroom:ambilightStyle" }
Color PhilipsTV_AMBILIGHTALLCOLOR "ALL SIDES AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightColor" }
Color PhilipsTV_AMBILIGHTLEFTCOLOR "LEFT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightLeftColor" }
Color PhilipsTV_AMBILIGHTRIGHTCOLOR "RIGHT SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightRightColor" }
Color PhilipsTV_AMBILIGHTTOPCOLOR "TOP SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightTopColor" }
Color PhilipsTV_AMBILIGHTBOTTOMCOLOR "BOTTOM SIDE AMBILIGHT COLOR [%s]" { channel = "androidtv:philipstv:bedroom:ambilightBottomColor" }
Dimmer PhilipsTV_BRIGHTNESS "BRIGHTNESS [%s]" { channel = "androidtv:philipstv:bedroom:brightness" }
Dimmer PhilipsTV_CONTRAST "CONTRAST [%s]" { channel = "androidtv:philipstv:bedroom:contrast" }
Dimmer PhilipsTV_SHARPNESS "SHARPNESS [%s]" { channel = "androidtv:philipstv:bedroom:sharpness" }
String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" } String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" }
String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" }
String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" } String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" }

View File

@ -14,6 +14,10 @@
<name>openHAB Add-ons :: Bundles :: AndroidTV Binding</name> <name>openHAB Add-ons :: Bundles :: AndroidTV Binding</name>
<properties>
<bnd.importpackage>!net.sf.ehcache.*,!net.spy.*</bnd.importpackage>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
@ -33,6 +37,36 @@
<version>1.75</version> <version>1.75</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-osgi</artifactId>
<version>4.5.14</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-osgi</artifactId>
<version>4.4.16</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -4,6 +4,7 @@
<feature name="openhab-binding-androidtv" description="AndroidTV Binding" version="${project.version}"> <feature name="openhab-binding-androidtv" description="AndroidTV Binding" version="${project.version}">
<feature>openhab-runtime-base</feature> <feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version}</bundle> <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version}</bundle>
</feature> </feature>
</features> </features>

View File

@ -31,8 +31,9 @@ public class AndroidTVBindingConstants {
// List of all Thing Type UIDs // List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_GOOGLETV = new ThingTypeUID(BINDING_ID, "googletv"); public static final ThingTypeUID THING_TYPE_GOOGLETV = new ThingTypeUID(BINDING_ID, "googletv");
public static final ThingTypeUID THING_TYPE_SHIELDTV = new ThingTypeUID(BINDING_ID, "shieldtv"); public static final ThingTypeUID THING_TYPE_SHIELDTV = new ThingTypeUID(BINDING_ID, "shieldtv");
public static final ThingTypeUID THING_TYPE_PHILIPSTV = new ThingTypeUID(BINDING_ID, "philipstv");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV); public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV,
THING_TYPE_PHILIPSTV);
// List of all Channel ids // List of all Channel ids
public static final String CHANNEL_DEBUG = "debug"; public static final String CHANNEL_DEBUG = "debug";
@ -43,15 +44,32 @@ public class AndroidTVBindingConstants {
public static final String CHANNEL_APP = "app"; public static final String CHANNEL_APP = "app";
public static final String CHANNEL_APPNAME = "appname"; public static final String CHANNEL_APPNAME = "appname";
public static final String CHANNEL_APPURL = "appurl"; public static final String CHANNEL_APPURL = "appurl";
public static final String CHANNEL_APP_ICON = "appicon";
public static final String CHANNEL_POWER = "power"; public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_VOLUME = "volume"; public static final String CHANNEL_VOLUME = "volume";
public static final String CHANNEL_MUTE = "mute"; public static final String CHANNEL_MUTE = "mute";
public static final String CHANNEL_PLAYER = "player"; public static final String CHANNEL_PLAYER = "player";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_CONTRAST = "contrast";
public static final String CHANNEL_SHARPNESS = "sharpness";
public static final String CHANNEL_TV_CHANNEL = "tvChannel";
public static final String CHANNEL_SEARCH_CONTENT = "searchContent";
public static final String CHANNEL_AMBILIGHT = "ambilight";
public static final String CHANNEL_AMBILIGHT_POWER = "ambilightPower";
public static final String CHANNEL_AMBILIGHT_HUE_POWER = "ambilightHuePower";
public static final String CHANNEL_AMBILIGHT_LOUNGE_POWER = "ambilightLoungePower";
public static final String CHANNEL_AMBILIGHT_STYLE = "ambilightStyle";
public static final String CHANNEL_AMBILIGHT_COLOR = "ambilightColor";
public static final String CHANNEL_AMBILIGHT_LEFT_COLOR = "ambilightLeftColor";
public static final String CHANNEL_AMBILIGHT_RIGHT_COLOR = "ambilightRightColor";
public static final String CHANNEL_AMBILIGHT_TOP_COLOR = "ambilightTopColor";
public static final String CHANNEL_AMBILIGHT_BOTTOM_COLOR = "ambilightBottomColor";
// List of all config properties // List of all config properties
public static final String PARAMETER_IP_ADDRESS = "ipAddress"; public static final String PARAMETER_IP_ADDRESS = "ipAddress";
public static final String PARAMETER_GOOGLETV_PORT = "googletvPort"; public static final String PARAMETER_GOOGLETV_PORT = "googletvPort";
public static final String PARAMETER_SHIELDTV_PORT = "shieldtvPort"; public static final String PARAMETER_SHIELDTV_PORT = "shieldtvPort";
public static final String PARAMETER_PHILIPSTV_PORT = "philipstvPort";
public static final String PARAMETER_GTV_ENABLED = "gtvEnabled"; public static final String PARAMETER_GTV_ENABLED = "gtvEnabled";
// List of all static String literals // List of all static String literals

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.StateOption;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Benjamin Meyer - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class,
AndroidTVDynamicStateDescriptionProvider.class }, immediate = true)
@NonNullByDefault
public class AndroidTVDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
private final Map<ChannelUID, List<@NonNull StateOption>> channelOptionsMap = new ConcurrentHashMap<>();
public void setStateOptions(ChannelUID channelUID, List<StateOption> options) {
channelOptionsMap.put(channelUID, options);
}
@Override
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
@Nullable Locale locale) {
List<StateOption> options = channelOptionsMap.get(channel.getUID());
if (options == null) {
return null;
}
if (original != null) {
return StateDescriptionFragmentBuilder.create(original).withOptions(options).build().toStateDescription();
}
return StateDescriptionFragmentBuilder.create().withOptions(options).build().toStateDescription();
}
@Deactivate
public void deactivate() {
channelOptionsMap.clear();
}
}

View File

@ -25,14 +25,19 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConfiguration; import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConfiguration;
import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConnectionManager; import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConfiguration;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConfiguration; import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConfiguration;
import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConnectionManager; import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConnectionManager;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption; import org.openhab.core.types.CommandOption;
@ -55,6 +60,7 @@ public class AndroidTVHandler extends BaseThingHandler {
private @Nullable ShieldTVConnectionManager shieldtvConnectionManager; private @Nullable ShieldTVConnectionManager shieldtvConnectionManager;
private @Nullable GoogleTVConnectionManager googletvConnectionManager; private @Nullable GoogleTVConnectionManager googletvConnectionManager;
private @Nullable PhilipsTVConnectionManager philipstvConnectionManager;
private @Nullable ScheduledFuture<?> monitorThingStatusJob; private @Nullable ScheduledFuture<?> monitorThingStatusJob;
private final Object monitorThingStatusJobLock = new Object(); private final Object monitorThingStatusJobLock = new Object();
@ -68,13 +74,20 @@ public class AndroidTVHandler extends BaseThingHandler {
private String currentThingStatus = ""; private String currentThingStatus = "";
private boolean currentThingFailed = false; private boolean currentThingFailed = false;
private DiscoveryServiceRegistry discoveryServiceRegistry;
private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider;
public AndroidTVHandler(Thing thing, AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider, public AndroidTVHandler(Thing thing, AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider,
AndroidTVTranslationProvider translationProvider, ThingTypeUID thingTypeUID) { AndroidTVTranslationProvider translationProvider, DiscoveryServiceRegistry discoveryServiceRegistry,
AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider, ThingTypeUID thingTypeUID) {
super(thing); super(thing);
this.commandDescriptionProvider = commandDescriptionProvider; this.commandDescriptionProvider = commandDescriptionProvider;
this.translationProvider = translationProvider; this.translationProvider = translationProvider;
this.thingTypeUID = thingTypeUID; this.thingTypeUID = thingTypeUID;
this.thingID = this.getThing().getUID().getId(); this.thingID = this.getThing().getUID().getId();
this.discoveryServiceRegistry = discoveryServiceRegistry;
this.stateDescriptionProvider = stateDescriptionProvider;
} }
public void setThingProperty(String property, String value) { public void setThingProperty(String property, String value) {
@ -89,6 +102,22 @@ public class AndroidTVHandler extends BaseThingHandler {
return this.thingID; return this.thingID;
} }
public Configuration getThingConfig() {
return getConfig();
}
public DiscoveryServiceRegistry getDiscoveryServiceRegistry() {
return discoveryServiceRegistry;
}
public AndroidTVDynamicStateDescriptionProvider getStateDescriptionProvider() {
return stateDescriptionProvider;
}
public ThingUID getThingUID() {
return getThing().getUID();
}
public void updateChannelState(String channel, State state) { public void updateChannelState(String channel, State state) {
updateState(channel, state); updateState(channel, state);
} }
@ -122,6 +151,7 @@ public class AndroidTVHandler extends BaseThingHandler {
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager;
if (googletvConnectionManager != null) { if (googletvConnectionManager != null) {
if (!googletvConnectionManager.getLoggedIn()) { if (!googletvConnectionManager.getLoggedIn()) {
@ -143,6 +173,15 @@ public class AndroidTVHandler extends BaseThingHandler {
} }
} }
if (THING_TYPE_PHILIPSTV.equals(thingTypeUID)) {
if (philipstvConnectionManager != null) {
if (!philipstvConnectionManager.getLoggedIn()) {
failed = true;
}
statusMessage = statusMessage + "PhilipsTV: " + philipstvConnectionManager.getStatusMessage();
}
}
if (!currentThingStatus.equals(statusMessage) || (currentThingFailed != failed)) { if (!currentThingStatus.equals(statusMessage) || (currentThingFailed != failed)) {
if (failed) { if (failed) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, statusMessage); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, statusMessage);
@ -186,14 +225,30 @@ public class AndroidTVHandler extends BaseThingHandler {
shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig); shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig);
} }
if (THING_TYPE_PHILIPSTV.equals(thingTypeUID)) {
PhilipsTVConfiguration philipstvConfig = getConfigAs(PhilipsTVConfiguration.class);
ipAddress = philipstvConfig.ipAddress;
if (ipAddress.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.philipstv-address-not-specified");
return;
}
philipstvConnectionManager = new PhilipsTVConnectionManager(this, philipstvConfig);
}
monitorThingStatusJob = scheduler.schedule(this::monitorThingStatus, THING_STATUS_FREQUENCY, monitorThingStatusJob = scheduler.schedule(this::monitorThingStatus, THING_STATUS_FREQUENCY,
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS);
} }
public void sendCommandToProtocol(ChannelUID channelUID, Command command) { public void sendCommandToProtocol(ChannelUID channelUID, Command command) {
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager;
if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) { if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) {
shieldtvConnectionManager.handleCommand(channelUID, command); shieldtvConnectionManager.handleCommand(channelUID, command);
} else if (THING_TYPE_PHILIPSTV.equals(thingTypeUID) && (philipstvConnectionManager != null)) {
philipstvConnectionManager.handleCommand(channelUID, command);
} }
} }
@ -208,6 +263,7 @@ public class AndroidTVHandler extends BaseThingHandler {
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager;
if (CHANNEL_DEBUG.equals(channelUID.getId())) { if (CHANNEL_DEBUG.equals(channelUID.getId())) {
if (command instanceof StringType) { if (command instanceof StringType) {
@ -231,10 +287,18 @@ public class AndroidTVHandler extends BaseThingHandler {
ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class); ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class);
shieldtvConfig.shim = true; shieldtvConfig.shim = true;
shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig); shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig);
} else if (command.toString().equals("PHILIPSTV_HALT") && (philipstvConnectionManager != null)) {
philipstvConnectionManager.dispose();
philipstvConnectionManager = null;
} else if (command.toString().equals("PHILIPSTV_START")) {
PhilipsTVConfiguration philipstvConfig = getConfigAs(PhilipsTVConfiguration.class);
philipstvConnectionManager = new PhilipsTVConnectionManager(this, philipstvConfig);
} else if (command.toString().startsWith("GOOGLETV") && (googletvConnectionManager != null)) { } else if (command.toString().startsWith("GOOGLETV") && (googletvConnectionManager != null)) {
googletvConnectionManager.handleCommand(channelUID, command); googletvConnectionManager.handleCommand(channelUID, command);
} else if (command.toString().startsWith("SHIELDTV") && (shieldtvConnectionManager != null)) { } else if (command.toString().startsWith("SHIELDTV") && (shieldtvConnectionManager != null)) {
shieldtvConnectionManager.handleCommand(channelUID, command); shieldtvConnectionManager.handleCommand(channelUID, command);
} else if (command.toString().startsWith("PHILIPSTV") && (philipstvConnectionManager != null)) {
philipstvConnectionManager.handleCommand(channelUID, command);
} }
} }
return; return;
@ -259,6 +323,33 @@ public class AndroidTVHandler extends BaseThingHandler {
} }
} }
if (THING_TYPE_PHILIPSTV.equals(thingTypeUID) && (philipstvConnectionManager != null)) {
if (googletvConnectionManager != null) {
if (CHANNEL_PINCODE.equals(channelUID.getId())) {
if (command instanceof StringType) {
if (!philipstvConnectionManager.getLoggedIn()) {
philipstvConnectionManager.handleCommand(channelUID, command);
return;
}
}
} else if (CHANNEL_POWER.equals(channelUID.getId()) && !googletvConnectionManager.getLoggedIn()) {
philipstvConnectionManager.handleCommand(channelUID, command);
return;
} else if (CHANNEL_APP.equals(channelUID.getId()) || CHANNEL_APPNAME.equals(channelUID.getId())
|| CHANNEL_TV_CHANNEL.equals(channelUID.getId()) || CHANNEL_VOLUME.equals(channelUID.getId())
|| CHANNEL_MUTE.equals(channelUID.getId()) || CHANNEL_SEARCH_CONTENT.equals(channelUID.getId())
|| CHANNEL_BRIGHTNESS.equals(channelUID.getId()) || CHANNEL_SHARPNESS.equals(channelUID.getId())
|| CHANNEL_CONTRAST.equals(channelUID.getId())
|| channelUID.getId().startsWith(CHANNEL_AMBILIGHT)) {
philipstvConnectionManager.handleCommand(channelUID, command);
return;
}
} else {
philipstvConnectionManager.handleCommand(channelUID, command);
return;
}
}
if (googletvConnectionManager != null) { if (googletvConnectionManager != null) {
googletvConnectionManager.handleCommand(channelUID, command); googletvConnectionManager.handleCommand(channelUID, command);
return; return;
@ -279,11 +370,16 @@ public class AndroidTVHandler extends BaseThingHandler {
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager;
if (shieldtvConnectionManager != null) { if (shieldtvConnectionManager != null) {
shieldtvConnectionManager.dispose(); shieldtvConnectionManager.dispose();
} }
if (philipstvConnectionManager != null) {
philipstvConnectionManager.dispose();
}
if (googletvConnectionManager != null) { if (googletvConnectionManager != null) {
googletvConnectionManager.dispose(); googletvConnectionManager.dispose();
} }

View File

@ -18,6 +18,7 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -39,18 +40,24 @@ import org.osgi.service.component.annotations.Reference;
@Component(configurationPid = "binding.androidtv", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.androidtv", service = ThingHandlerFactory.class)
public class AndroidTVHandlerFactory extends BaseThingHandlerFactory { public class AndroidTVHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV,
THING_TYPE_SHIELDTV); THING_TYPE_PHILIPSTV);
private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider; private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider;
private final AndroidTVTranslationProvider translationProvider; private final AndroidTVTranslationProvider translationProvider;
private final DiscoveryServiceRegistry discoveryServiceRegistry;
private final AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider;
@Activate @Activate
public AndroidTVHandlerFactory( public AndroidTVHandlerFactory(
final @Reference AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider, final @Reference AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider,
final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) { final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider,
final @Reference DiscoveryServiceRegistry discoveryServiceRegistry,
final @Reference AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider) {
this.commandDescriptionProvider = commandDescriptionProvider; this.commandDescriptionProvider = commandDescriptionProvider;
this.translationProvider = new AndroidTVTranslationProvider(i18nProvider, localeProvider); this.translationProvider = new AndroidTVTranslationProvider(i18nProvider, localeProvider);
this.discoveryServiceRegistry = discoveryServiceRegistry;
this.stateDescriptionProvider = stateDescriptionProvider;
} }
@Override @Override
@ -61,6 +68,7 @@ public class AndroidTVHandlerFactory extends BaseThingHandlerFactory {
@Override @Override
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, thingTypeUID); return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, discoveryServiceRegistry,
stateDescriptionProvider, thingTypeUID);
} }
} }

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* The {@link ConnectionManager} is responsible for handling https GETs and POSTs to the Philips
* TVs.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class ConnectionManager {
private static final String TARGET_URI_MSG = "Target Uri is: {}";
private final Logger logger = LoggerFactory.getLogger(getClass());
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
// Cannot use jetty in OH2.4 due to 9.4.11.v20180605 version with digest auth bug
// https://github.com/eclipse/jetty.project/issues/1555
private final CloseableHttpClient httpClient;
private final HttpHost httpHost;
public ConnectionManager(CloseableHttpClient httpClient, HttpHost httpHost) {
this.httpClient = httpClient;
this.httpHost = httpHost;
OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public String doHttpsGet(String path) throws IOException {
String uri = httpHost.toURI() + path;
logger.debug(TARGET_URI_MSG, uri);
HttpGet httpGet = new HttpGet(uri);
String jsonContent = "";
try (CloseableHttpClient client = httpClient; //
CloseableHttpResponse response = client.execute(httpHost, httpGet)) {
validateResponse(response, uri);
jsonContent = getJsonFromResponse(response);
} catch (HttpHostConnectException e) {
logger.debug("HttpHostConnectException when getting {}", uri);
}
return jsonContent;
}
public String doHttpsPost(String path, String json) throws IOException {
String uri = httpHost.toURI() + path;
logger.debug(TARGET_URI_MSG, uri);
HttpPost httpPost = new HttpPost(uri);
httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(new StringEntity(json));
String jsonContent = "";
try (CloseableHttpClient client = httpClient; //
CloseableHttpResponse response = client.execute(httpHost, httpPost)) {
validateResponse(response, uri);
jsonContent = getJsonFromResponse(response);
} catch (HttpHostConnectException e) {
logger.debug("HttpHostConnectException when getting {}", uri);
}
return jsonContent;
}
private void validateResponse(CloseableHttpResponse response, String uri) throws HttpResponseException {
if (response == null) {
throw new HttpResponseException(0, String.format("The response for the request to %s was empty.", uri));
} else if (response.getStatusLine().getStatusCode() == 401) {
throw new HttpResponseException(401, "The given username/password combination is invalid.");
}
}
private String getJsonFromResponse(HttpResponse response) throws IOException {
String jsonContent = EntityUtils.toString(response.getEntity());
logger.trace("----------------------------------------");
logger.trace("{}", response.getStatusLine());
logger.trace("{}", jsonContent);
return jsonContent;
}
public byte[] doHttpsGetForImage(String path) throws IOException {
String uri = httpHost.toURI() + path;
logger.debug(TARGET_URI_MSG, uri);
HttpGet httpGet = new HttpGet(uri);
try (CloseableHttpClient client = httpClient;
CloseableHttpResponse response = client.execute(httpHost, httpGet)) {
if ((response != null) && (response.getStatusLine().getStatusCode() == 401)) {
throw new HttpResponseException(401, "The given username/password combination is invalid.");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (response != null) {
response.getEntity().writeTo(baos);
}
return baos.toByteArray();
}
}
}

View File

@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.CONNECT_TIMEOUT_MILLISECONDS;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.HTTPS;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.MAX_REQUEST_RETRIES;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.SOCKET_TIMEOUT_MILLISECONDS;
import java.net.NoRouteToHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ConnectionManagerUtil} is offering methods for connection specific processes.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public final class ConnectionManagerUtil {
private ConnectionManagerUtil() {
}
public static CloseableHttpClient createSharedHttpClient(HttpHost target, String username, String password)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
CredentialsProvider credProvider = new BasicCredentialsProvider();
credProvider.setCredentials(new AuthScope(target.getHostName(), target.getPort()),
new UsernamePasswordCredentials(username, password));
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS)
.setSocketTimeout(SOCKET_TIMEOUT_MILLISECONDS).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(getSslConnectionWithoutCertValidation(),
NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register(HTTPS, sslsf).build();
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
HttpRequestRetryHandler requestRetryHandler = (exception, executionCount, context) -> {
if (exception instanceof NoRouteToHostException) {
return false;
}
String message = Optional.ofNullable(exception.getMessage()).orElse("");
if (!message.isEmpty()) {
if ((exception instanceof HttpHostConnectException) && message.contains("Connection refused")) {
return false;
}
}
return executionCount < MAX_REQUEST_RETRIES;
};
return HttpClients.custom().setDefaultRequestConfig(requestConfig).setSSLSocketFactory(sslsf)
.setDefaultCredentialsProvider(credProvider).setConnectionManager(connManager)
.setRetryHandler(requestRetryHandler).setConnectionManagerShared(true).build();
}
private static SSLContext getSslConnectionWithoutCertValidation()
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
return new SSLContextBuilder().loadTrustMaterial(null, (certificate, authType) -> true).build();
}
}

View File

@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PhilipsTVBindingConstants} class defines common constants, which are used across the
* whole binding.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PhilipsTVBindingConstants {
// Config Parameters
public static final String HOST = "host";
public static final String PORT = "philipstvPort";
public static final String MAC_ADDRESS = "macAddress";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String HTTPS = "https";
// Connection specific values
static final int CONNECT_TIMEOUT_MILLISECONDS = 3 * 1000;
static final int SOCKET_TIMEOUT_MILLISECONDS = 1000;
static final int MAX_REQUEST_RETRIES = 3;
// Default port for jointspace v6
public static final int DEFAULT_PORT = 1926;
// Powerstates
public static final String POWER_ON = "On";
public static final String POWER_OFF = "Off";
public static final String STANDBY = "Standby";
public static final String STANDBYKEEP = "StandbyKeep";
public static final String STANDBY_MSG = "online.standby";
public static final String EMPTY = "";
// REST Paths
public static final String SLASH = "/";
private static final String API_VERSION = "6";
public static final String BASE_PATH = SLASH + API_VERSION + SLASH;
public static final String VOLUME_PATH = BASE_PATH + "audio" + SLASH + "volume";
public static final String KEY_CODE_PATH = BASE_PATH + "input" + SLASH + "key";
public static final String TV_POWERSTATE_PATH = BASE_PATH + "powerstate";
public static final String GET_AVAILABLE_APP_LIST_PATH = BASE_PATH + "applications";
public static final String GET_NETWORK_DEVICES_PATH = BASE_PATH + "network" + SLASH + "devices";
private static final String ACTIVITIES_BASE_PATH = BASE_PATH + "activities" + SLASH;
public static final String GET_AVAILABLE_TV_CHANNEL_LIST_PATH = BASE_PATH + "channeldb" + SLASH + "tv" + SLASH
+ "channelLists" + SLASH + "all";
public static final String TV_CHANNEL_PATH = ACTIVITIES_BASE_PATH + "tv";
public static final String GET_CURRENT_APP_PATH = ACTIVITIES_BASE_PATH + "current";
public static final String LAUNCH_APP_PATH = ACTIVITIES_BASE_PATH + "launch";
private static final String AMBILIGHT_BASE_PATH = BASE_PATH + "ambilight" + SLASH;
public static final String AMBILIGHT_POWERSTATE_PATH = AMBILIGHT_BASE_PATH + "power";
public static final String AMBILIGHT_CONFIG_PATH = AMBILIGHT_BASE_PATH + "currentconfiguration";
public static final String AMBILIGHT_MODE_PATH = AMBILIGHT_BASE_PATH + "mode";
public static final String AMBILIGHT_CACHED_PATH = AMBILIGHT_BASE_PATH + "cached";
public static final String AMBILIGHT_TOPOLOGY_PATH = AMBILIGHT_BASE_PATH + "topology";
public static final String AMBILIGHT_LOUNGE_PATH = AMBILIGHT_BASE_PATH + "lounge";
private static final String SETTINGS_BASE_PATH = BASE_PATH + "menuitems" + SLASH + "settings" + SLASH;
public static final String UPDATE_SETTINGS_PATH = SETTINGS_BASE_PATH + "update";
public static final String CURRENT_SETTINGS_PATH = SETTINGS_BASE_PATH + "current";
public static final String STRUCTURE_SETTINGS_PATH = SETTINGS_BASE_PATH + "structure";
// Logging messages
public static final String TV_OFFLINE_MSG = "offline.tv-is-not-reachable-and-should-therefore-be-off";
public static final String TV_NOT_LISTENING_MSG = "offline.tv-does-not-accept-commands-at-the-moment";
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PhilipsTVConfiguration} class contains fields for mapping thing configuration parameters.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PhilipsTVConfiguration {
public String ipAddress = "";
public Integer philipstvPort = 1926;
public Integer refreshRate = 10;
public boolean useUpnpDiscovery = true;
public String pairingCode = "";
}

View File

@ -0,0 +1,696 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import org.apache.http.HttpHost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androidtv.internal.AndroidTVDynamicStateDescriptionProvider;
import org.openhab.binding.androidtv.internal.AndroidTVHandler;
import org.openhab.binding.androidtv.internal.AndroidTVTranslationProvider;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.PhilipsTVPairing;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AmbilightService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AppService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPressService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.PowerService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.SearchContentService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvChannelService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvPictureService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.VolumeService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.core.OpenHAB;
import org.openhab.core.config.discovery.DiscoveryListener;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* The {@link PhilipsTVHandler} is responsible for handling commands, which are sent to one of the
* channels.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PhilipsTVConnectionManager implements DiscoveryListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private AndroidTVHandler handler;
public PhilipsTVConfiguration config;
private ScheduledExecutorService scheduler;
private final AndroidTVTranslationProvider translationProvider;
private DiscoveryServiceRegistry discoveryServiceRegistry;
private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider;
private @Nullable ThingUID upnpThingUID;
private @Nullable ScheduledFuture<?> refreshScheduler;
private final Predicate<ScheduledFuture<?>> isRefreshSchedulerRunning = r -> (r != null) && !r.isCancelled();
private final ReentrantLock lock = new ReentrantLock();
private boolean isLoggedIn = false;
private String statusMessage = "";
private HttpHost target;
private String username = "";
private String password = "";
private String macAddress = "";
private @Nullable ScheduledFuture<?> deviceHealthJob;
private boolean isOnline = true;
private boolean pendingPowerOn = false;
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/* Philips TV services */
private Map<String, PhilipsTVService> channelServices = new HashMap<>();
public PhilipsTVConnectionManager(AndroidTVHandler handler, PhilipsTVConfiguration config) {
logger.debug("Create a Philips TV Handler for thing '{}'", handler.getThingUID());
this.handler = handler;
this.config = config;
this.scheduler = handler.getScheduler();
this.translationProvider = handler.getTranslationProvider();
this.discoveryServiceRegistry = handler.getDiscoveryServiceRegistry();
this.stateDescriptionProvider = handler.getStateDescriptionProvider();
this.target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS);
initialize();
}
private void setStatus(boolean isLoggedIn) {
if (isLoggedIn) {
setStatus(isLoggedIn, "online.online");
} else {
setStatus(isLoggedIn, "offline.unknown");
}
}
private void setStatus(boolean isLoggedIn, String statusMessage) {
String translatedMessage = translationProvider.getText(statusMessage);
logger.trace("setStatus to {} {} {}", isLoggedIn, statusMessage, translatedMessage);
if ((this.isLoggedIn != isLoggedIn) || (!this.statusMessage.equals(translatedMessage))) {
this.isLoggedIn = isLoggedIn;
this.statusMessage = translatedMessage;
handler.checkThingStatus();
}
}
public String getStatusMessage() {
return statusMessage;
}
public void setLoggedIn(boolean isLoggedIn) {
if (this.isLoggedIn != isLoggedIn) {
setStatus(isLoggedIn);
}
}
public boolean getLoggedIn() {
return isLoggedIn;
}
public void updateStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String thingStatusMessage) {
if (thingStatus == ThingStatus.ONLINE) {
setLoggedIn(true);
} else {
logger.trace("Updating status to {} {} {}", thingStatus, thingStatusDetail, thingStatusMessage);
setStatus(false, thingStatusMessage);
}
}
public String getMacAddress() {
return this.macAddress;
}
public void saveConfigs() {
String folderName = OpenHAB.getUserDataFolder() + "/androidtv";
File folder = new File(folderName);
if (!folder.exists()) {
logger.debug("Creating directory {}", folderName);
folder.mkdirs();
}
String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config";
Map<String, String> configMap = new HashMap<>();
configMap.put("username", username);
configMap.put("password", password);
configMap.put("macAddress", macAddress);
try {
String configJson = OBJECT_MAPPER.writeValueAsString(configMap);
logger.debug("Writing configJson \"{}\" to {}", configJson, fileName);
Files.write(Paths.get(fileName), configJson.getBytes());
} catch (JsonProcessingException e) {
logger.warn("JsonProcessingException trying to save configMap: {}", e.getMessage(), e);
} catch (IOException ex) {
logger.debug("IOException when writing configJson to file {}", ex.getMessage());
}
}
private void readConfigs() {
String folderName = OpenHAB.getUserDataFolder() + "/androidtv";
String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config";
File file = new File(fileName);
if (!file.exists()) {
return;
}
try {
final byte[] contents = Files.readAllBytes(Paths.get(fileName));
String configJson = new String(contents);
logger.debug("Read configJson \"{}\" from {}", configJson, fileName);
Map<String, String> configMap = OBJECT_MAPPER.readValue(configJson,
new TypeReference<HashMap<String, String>>() {
});
this.username = Optional.ofNullable(configMap.get("username")).orElse("");
this.password = Optional.ofNullable(configMap.get("password")).orElse("");
this.macAddress = Optional.ofNullable(configMap.get("macAddress")).orElse("");
logger.debug("Processed configJson as {} {} {}", this.username, this.password, this.macAddress);
} catch (IOException ex) {
logger.debug("IOException when reading configJson from file {}", ex.getMessage());
}
}
public void setCreds(String username, String password) {
this.username = username;
this.password = password;
saveConfigs();
}
private boolean servicePing() {
int timeout = 500;
SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.philipstvPort);
try (Socket socket = new Socket()) {
socket.connect(socketAddress, timeout);
return true;
} catch (ConnectException | SocketTimeoutException | NoRouteToHostException ignored) {
return false;
} catch (IOException ignored) {
// IOException is thrown by automatic close() of the socket.
// This should actually never return a value as we should return true above already
return true;
}
}
private void checkHealth() {
boolean isOnline = servicePing();
logger.debug("{} - Device Health - Online: {} - Logged In: {}", handler.getThingID(), isOnline, isLoggedIn);
if (isOnline != this.isOnline) {
this.isOnline = isOnline;
if (isOnline) {
logger.debug("{} - Device is back online. Attempting reconnection.", handler.getThingID());
connect();
} else {
logger.debug("{} - Device is offline.", handler.getThingID());
postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"offline.communication-error-will-try-to-reconnect");
}
}
}
public void checkPendingPowerOn() {
if (pendingPowerOn) {
@Nullable
PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
if (powerService != null) {
powerService.handleCommand(CHANNEL_POWER, OnOffType.ON);
}
pendingPowerOn = false;
startDeviceHealthJob(5, TimeUnit.SECONDS);
}
}
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Received channel: {}, command: {}", channelUID, command);
String username = this.username;
String password = this.password;
if (channelUID.getId().equals(CHANNEL_PINCODE)) {
if (command instanceof StringType) {
HttpHost target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS);
if (command.toString().equals("REQUEST")) {
try {
initPairingCodeRetrieval(target);
} catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"offline.error-occured-while-presenting-pairing-code");
}
} else {
boolean hasFailed = initCredentialsRetrieval(target, command.toString());
if (hasFailed) {
postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"offline.error-occured-during-retrieval-of-credentials");
return;
}
readConfigs();
username = this.username;
password = this.password;
if ((username.isEmpty()) || (password.isEmpty())) {
postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"offline.pairing-was-unsuccessful");
return;
}
}
}
return;
}
if ((username.isEmpty()) || (password.isEmpty())) {
return; // pairing process is not finished
}
boolean isLoggedIn = this.isLoggedIn;
Map<String, PhilipsTVService> channelServices = this.channelServices;
if ((!isLoggedIn) && (!channelUID.getId().equals(CHANNEL_POWER)
& !channelUID.getId().equals(CHANNEL_AMBILIGHT_LOUNGE_POWER))) {
// Check if tv turned on meanwhile
@Nullable
PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
if (powerService != null) {
powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
}
isLoggedIn = this.isLoggedIn;
if (!isLoggedIn) {
// still offline
logger.warn(
"Cannot execute command {} for channel {}: PowerState of TV was checked and resolved to offline.",
command, channelUID.getId());
return;
}
}
String channel = channelUID.getId();
long startTime = System.currentTimeMillis();
// Delegate the other commands to correct channel service
@Nullable
PhilipsTVService philipsTvService = channelServices.get(channel);
if (philipsTvService == null) {
logger.warn("Unknown channel for Philips TV Binding: {}", channel);
return;
}
if ((!isLoggedIn) && (channelUID.getId().equals(CHANNEL_POWER)) && (command.equals(OnOffType.ON))) {
startDeviceHealthJob(1, TimeUnit.SECONDS);
pendingPowerOn = true;
}
philipsTvService.handleCommand(channel, command);
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
logger.trace("The command {} took : {} nanoseconds", command.toFullString(), elapsedTime);
}
public void initialize() {
logger.debug("Init of handler for Thing: {}", handler.getThingID());
readConfigs();
String username = this.username;
String password = this.password;
if ((username.isEmpty()) || (password.isEmpty())) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"offline.pairing-is-not-configured-yet");
return;
}
connect();
startDeviceHealthJob(5, TimeUnit.SECONDS);
}
private void startDeviceHealthJob(int interval, TimeUnit unit) {
ScheduledFuture<?> deviceHealthJob = this.deviceHealthJob;
if (deviceHealthJob != null) {
deviceHealthJob.cancel(true);
}
this.deviceHealthJob = scheduler.scheduleWithFixedDelay(this::checkHealth, interval, interval, unit);
}
private void connect() {
HttpHost target = this.target;
String username = this.username;
String password = this.password;
String macAddress = this.macAddress;
logger.debug("Starting connection to {} {} {}", username, password, macAddress);
if (!config.useUpnpDiscovery && isSchedulerInitializable()) {
logger.debug("connect starting refresh scheduler");
startRefreshScheduler();
}
CloseableHttpClient httpClient;
try {
httpClient = ConnectionManagerUtil.createSharedHttpClient(target, username, password);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
String.format("offline.error-occurred-during-creation-of-http-client: %s", e.getMessage()));
return;
}
ConnectionManager connectionManager = new ConnectionManager(httpClient, target);
if (macAddress.isEmpty()) {
try {
Optional<String> wolAddress = WakeOnLanUtil.getMacFromEnabledInterface(connectionManager);
if (wolAddress.isPresent()) {
this.macAddress = wolAddress.get();
saveConfigs();
} else {
logger.debug("MAC Address could not be determined for Wake-On-LAN support, "
+ "because Wake-On-LAN is not enabled on the TV.");
}
} catch (IOException e) {
logger.debug("Error occurred during retrieval of MAC Address: {}", e.getMessage());
}
}
startServices(connectionManager);
discoveryServiceRegistry.addDiscoveryListener(this);
// Thing is initialized, check power state and available communication of the TV and set ONLINE or OFFLINE
postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online");
Map<String, PhilipsTVService> channelServices = this.channelServices;
@Nullable
PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
if (powerService != null) {
powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
}
}
private void startServices(ConnectionManager connectionManager) {
Map<String, PhilipsTVService> services = new HashMap<>();
PhilipsTVService volumeService = new VolumeService(this, connectionManager);
services.put(CHANNEL_VOLUME, volumeService);
services.put(CHANNEL_MUTE, volumeService);
PhilipsTVService tvPictureService = new TvPictureService(this, connectionManager);
services.put(CHANNEL_BRIGHTNESS, tvPictureService);
services.put(CHANNEL_SHARPNESS, tvPictureService);
services.put(CHANNEL_CONTRAST, tvPictureService);
PhilipsTVService keyPressService = new KeyPressService(this, connectionManager);
services.put(CHANNEL_KEYPRESS, keyPressService);
services.put(CHANNEL_PLAYER, keyPressService);
PhilipsTVService appService = new AppService(this, connectionManager);
services.put(CHANNEL_APP, appService);
services.put(CHANNEL_APPNAME, appService);
services.put(CHANNEL_APP_ICON, appService);
PhilipsTVService ambilightService = new AmbilightService(this, connectionManager);
services.put(CHANNEL_AMBILIGHT_POWER, ambilightService);
services.put(CHANNEL_AMBILIGHT_HUE_POWER, ambilightService);
services.put(CHANNEL_AMBILIGHT_LOUNGE_POWER, ambilightService);
services.put(CHANNEL_AMBILIGHT_STYLE, ambilightService);
services.put(CHANNEL_AMBILIGHT_COLOR, ambilightService);
services.put(CHANNEL_AMBILIGHT_LEFT_COLOR, ambilightService);
services.put(CHANNEL_AMBILIGHT_RIGHT_COLOR, ambilightService);
services.put(CHANNEL_AMBILIGHT_TOP_COLOR, ambilightService);
services.put(CHANNEL_AMBILIGHT_BOTTOM_COLOR, ambilightService);
services.put(CHANNEL_TV_CHANNEL, new TvChannelService(this, connectionManager));
services.put(CHANNEL_POWER, new PowerService(this, connectionManager));
services.put(CHANNEL_SEARCH_CONTENT, new SearchContentService(this, connectionManager));
channelServices = Collections.unmodifiableMap(services);
}
/**
* Starts the pairing Process with the TV, which results in a Pairing Code shown on TV.
*/
private void initPairingCodeRetrieval(HttpHost target)
throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
logger.info("Pairing code for tv authentication is missing. "
+ "Starting initial pairing process. Please provide manually the pairing code shown on the tv at the configuration of the tv thing.");
PhilipsTVPairing pairing = new PhilipsTVPairing();
pairing.requestPairingPin(target);
}
private boolean initCredentialsRetrieval(HttpHost target, String pincode) {
boolean hasFailed = false;
logger.info(
"Pairing code is available, but username and/or password is missing. Therefore we try to grant authorization and retrieve username and password.");
PhilipsTVPairing pairing = new PhilipsTVPairing();
try {
if (pincode.isEmpty()) {
pairing.finishPairingWithTv(config.pairingCode, this, target);
} else {
pairing.finishPairingWithTv(pincode, this, target);
}
postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv");
} catch (Exception e) {
postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"offline.could-not-successfully-finish-pairing-process-with-the-tv");
logger.warn("Error during finishing pairing process with the TV: {}", e.getMessage(), e);
hasFailed = true;
}
return hasFailed;
}
// callback methods for channel services
public void postUpdateChannel(String channelUID, State state) {
handler.updateChannelState(channelUID, state);
}
public synchronized void postUpdateThing(ThingStatus status, ThingStatusDetail statusDetail, String msg) {
logger.trace("postUpdateThing {} {} {}", status, statusDetail, msg);
if (status == ThingStatus.ONLINE) {
if (msg.equalsIgnoreCase(STANDBY_MSG)) {
handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF);
} else {
handler.updateChannelState(CHANNEL_POWER, OnOffType.ON);
startDeviceHealthJob(5, TimeUnit.SECONDS);
pendingPowerOn = false;
}
if (isSchedulerInitializable()) { // Init refresh scheduler only, if pairing is completed
startRefreshScheduler();
}
} else if (status == ThingStatus.OFFLINE) {
handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF);
if (!TV_NOT_LISTENING_MSG.equals(msg)) { // avoid cancelling refresh if TV is temporarily not available
ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
if (refreshScheduler != null) {
if (config.useUpnpDiscovery && isRefreshSchedulerRunning.test(refreshScheduler)) {
stopRefreshScheduler();
}
}
// Reset app and channel list (if existing) for new retrieval during next startup
Map<String, PhilipsTVService> channelServices = this.channelServices;
@Nullable
PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME);
if (appnameService != null) {
((AppService) appnameService).clearAvailableAppList();
}
@Nullable
PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL);
if (tvchannelService != null) {
((TvChannelService) tvchannelService).clearAvailableTvChannelList();
}
}
}
updateStatus(status, statusDetail, msg);
}
private boolean isSchedulerInitializable() {
String username = this.username;
String password = this.password;
boolean schedulerIsDone = false;
ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
if (refreshScheduler != null) {
schedulerIsDone = refreshScheduler.isDone();
}
return (!username.isEmpty()) && (!password.isEmpty()) && ((refreshScheduler == null) || schedulerIsDone);
}
private void startRefreshScheduler() {
int configuredRefreshRateOrDefault = Optional.ofNullable(config.refreshRate).orElse(10);
if (configuredRefreshRateOrDefault > 0) { // If value equals zero, refreshing should not be scheduled
ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
if (refreshScheduler != null) {
logger.debug("Refresh Scheduler already started for Philips TV {}, terminating.", handler.getThingID());
if (isRefreshSchedulerRunning.test(refreshScheduler)) {
stopRefreshScheduler();
}
}
logger.debug("Starting Refresh Scheduler for Philips TV {} with refresh rate of {}.", handler.getThingID(),
configuredRefreshRateOrDefault);
this.refreshScheduler = scheduler.scheduleWithFixedDelay(this::refreshTvProperties, 10,
configuredRefreshRateOrDefault, TimeUnit.SECONDS);
}
}
private void stopRefreshScheduler() {
logger.debug("Stopping Refresh Scheduler for Philips TV: {}", handler.getThingID());
ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
if (refreshScheduler != null) {
refreshScheduler.cancel(true);
}
}
private void refreshTvProperties() {
try {
boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
if (isLockAcquired) {
try {
if (isOnline) {
Map<String, PhilipsTVService> channelServices = this.channelServices;
@Nullable
PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
if (powerService != null) {
powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
}
@Nullable
PhilipsTVService volumeService = channelServices.get(CHANNEL_VOLUME);
if (volumeService != null) {
volumeService.handleCommand(CHANNEL_VOLUME, RefreshType.REFRESH);
}
@Nullable
PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME);
if (appnameService != null) {
appnameService.handleCommand(CHANNEL_APPNAME, RefreshType.REFRESH);
}
@Nullable
PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL);
if (tvchannelService != null) {
tvchannelService.handleCommand(CHANNEL_TV_CHANNEL, RefreshType.REFRESH);
}
}
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
logger.warn("Exception occurred during refreshing the tv properties: {}", e.getMessage());
}
}
public void updateChannelStateDescription(final String channelId, Map<String, String> values) {
AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider = this.stateDescriptionProvider;
List<StateOption> options = new ArrayList<>();
if (!values.isEmpty()) {
values.forEach((key, value) -> options.add(new StateOption(key, value)));
stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThingUID(), channelId), options);
}
}
@Override
public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
logger.debug("thingDiscovered: {}", result);
if (config.useUpnpDiscovery && config.ipAddress.equals(result.getProperties().get(HOST))) {
upnpThingUID = result.getThingUID();
logger.debug("thingDiscovered, thingUID={}, discoveredUID={}", handler.getThingUID(), upnpThingUID);
Map<String, PhilipsTVService> channelServices = this.channelServices;
@Nullable
PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
if (powerService != null) {
powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
}
}
}
@Override
public void thingRemoved(DiscoveryService discoveryService, ThingUID thingUID) {
logger.debug("thingRemoved: {}", thingUID);
if (thingUID.equals(upnpThingUID)) {
postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby");
}
}
@Override
public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService discoveryService, long l,
@Nullable Collection<ThingTypeUID> collection, @Nullable ThingUID thingUID) {
return Collections.emptyList();
}
public void dispose() {
discoveryServiceRegistry.removeDiscoveryListener(this);
ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
if (refreshScheduler != null) {
if (isRefreshSchedulerRunning.test(refreshScheduler)) {
stopRefreshScheduler();
}
}
ScheduledFuture<?> deviceHealthJob = this.deviceHealthJob;
if (deviceHealthJob != null) {
deviceHealthJob.cancel(true);
}
}
}

View File

@ -0,0 +1,136 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.GET_NETWORK_DEVICES_PATH;
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.net.SocketException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
/**
* The {@link WakeOnLanUtil} is offering methods for powering on TVs via Wake-On-LAN.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public final class WakeOnLanUtil {
private static final int MAX_WOL_RETRIES = 10;
private static final int WOL_PORT = 9;
private static final Logger LOGGER = LoggerFactory.getLogger(WakeOnLanUtil.class);
private static final Pattern MAC_PATTERN = Pattern.compile("([:\\-])");
private static final Predicate<JsonNode> IS_WOL_ENABLED = j -> j.get("wake-on-lan").asText()
.equalsIgnoreCase("Enabled");
private static final Predicate<NetworkInterface> IS_NOT_LOOPBACK = ni -> {
try {
return !ni.isLoopback();
} catch (SocketException e) {
return false;
}
};
private WakeOnLanUtil() {
}
public static Optional<String> getMacFromEnabledInterface(ConnectionManager connectionManager) throws IOException {
String jsonContent = connectionManager.doHttpsGet(GET_NETWORK_DEVICES_PATH);
List<JsonNode> jsonNode = OBJECT_MAPPER.readValue(jsonContent, new TypeReference<List<JsonNode>>() {
});
return jsonNode.stream().filter(IS_WOL_ENABLED).map(j -> j.get("mac").asText())
.peek(m -> LOGGER.debug("Mac identified as: {}", m)).findFirst();
}
public static void wakeOnLan(String ip, String mac) throws IOException, InterruptedException {
for (int i = 0; i < MAX_WOL_RETRIES; i++) {
if (isReachable(ip)) {
Thread.sleep(2000);
return;
} else {
Thread.sleep(100);
sendWakeOnLanPackage(mac);
}
}
}
private static void sendWakeOnLanPackage(String mac) throws IOException {
byte[] macBytes = getMacBytes(mac);
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);
}
List<InetAddress> broadcastAddresses = Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
.filter(IS_NOT_LOOPBACK).map(NetworkInterface::getInterfaceAddresses).flatMap(Collection::stream)
.map(InterfaceAddress::getBroadcast).filter(Objects::nonNull).collect(Collectors.toList());
for (InetAddress broadcast : broadcastAddresses) {
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, broadcast, WOL_PORT);
try (DatagramSocket socket = new DatagramSocket()) {
LOGGER.debug("WOL sent to Broadcast-IP {} with MAC {}", broadcast, mac);
socket.send(packet);
}
}
}
private static byte[] getMacBytes(String mac) {
byte[] bytes = new byte[6];
String[] hex = MAC_PATTERN.split(mac);
if (hex.length != 6) {
throw new IllegalArgumentException("Invalid MAC address.");
}
try {
for (int i = 0; i < 6; i++) {
bytes[i] = (byte) Integer.parseInt(hex[i], 16);
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid hex digit in MAC address.");
}
return bytes;
}
public static boolean isReachable(String ipAddress) throws IOException {
InetAddress inetAddress = InetAddress.getByName(ipAddress);
return inetAddress.isReachable(1000);
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.discovery;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.DeviceDetails;
import org.jupnp.model.meta.ModelDetails;
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.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PhilipsTVDiscoveryParticipant} is responsible for discovering Philips TV devices through UPnP.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
@Component(immediate = true)
public class PhilipsTVDiscoveryParticipant implements UpnpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(THING_TYPE_PHILIPSTV);
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
final ThingUID uid = getThingUID(device);
if (uid == null) {
return null;
}
final Map<String, Object> properties = new HashMap<>(2);
String ipAddress = device.getIdentity().getDescriptorURL().getHost();
properties.put(PARAMETER_IP_ADDRESS, ipAddress);
properties.put(PARAMETER_PHILIPSTV_PORT, DEFAULT_PORT);
logger.debug("Philips TV Found: {}, using default port {}", ipAddress, DEFAULT_PORT);
String friendlyName = device.getDetails().getFriendlyName();
if (friendlyName.length() > 0 && Character.isDigit(friendlyName.charAt(0))) {
friendlyName = "_" + friendlyName; // label must not start with a digit
}
return DiscoveryResultBuilder.create(uid).withThingType(THING_TYPE_PHILIPSTV).withProperties(properties)
.withLabel(friendlyName).build();
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
DeviceDetails details = device.getDetails();
if (details != null) {
ModelDetails modelDetails = details.getModelDetails();
if (modelDetails != null) {
String modelName = modelDetails.getModelName();
String modelDescription = modelDetails.getModelDescription();
if (modelName != null && modelDescription != null) {
if (modelName.contains("Philips TV")) {
logger.debug("Device found: {} with desc {}", modelName, modelDescription);
// One Philips TV contains several UPnP devices.
// Create unique Philips TV thing for every Media Renderer
// device and ignore rest of the UPnP devices.
if (modelDescription.contains("Media")) {
// UDN shouldn't contain '-' characters.
String udn = device.getIdentity().getUdn().getIdentifierString().replace("-", "_");
logger.debug("Discovered a Philips TV '{}' model '{}' thing with UDN '{}'",
device.getDetails().getFriendlyName(), modelName, udn);
return new ThingUID(THING_TYPE_PHILIPSTV, udn);
}
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,204 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.BASE_PATH;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.EMPTY;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.SLASH;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Formatter;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.client.AuthCache;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManagerUtil;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.AuthDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.DeviceDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.FinishPairingDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.PairingDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model.RequestCodeDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PhilipsTVPairing} is responsible for the initial pairing process with the Philips TV.
* The outcome of this one-time pairing is a registered user with password, which will be used for
* controlling the tv.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PhilipsTVPairing {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static String authTimestamp = "";
private static String authKey = "";
private static String deviceId = "";
private final String pairingBasePath = BASE_PATH + "pair" + SLASH;
public void requestPairingPin(HttpHost target)
throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RequestCodeDTO requestCodeDTO = new RequestCodeDTO(
Stream.of("read", "write", "control").collect(Collectors.toList()), createDeviceSpecification());
CloseableHttpClient httpClient = ConnectionManagerUtil.createSharedHttpClient(target, EMPTY, EMPTY);
ConnectionManager connectionManager = new ConnectionManager(httpClient, target);
String requestCodeJson = OBJECT_MAPPER.writeValueAsString(requestCodeDTO);
String requestPairingCodePath = pairingBasePath + "request";
logger.debug("Request pairing code with json: {}", requestCodeJson);
PairingDTO pairingDTO = OBJECT_MAPPER
.readValue(connectionManager.doHttpsPost(requestPairingCodePath, requestCodeJson), PairingDTO.class);
authTimestamp = pairingDTO.getTimestamp();
authKey = pairingDTO.getAuthKey();
logger.info("The pairing code is valid for {} seconds.", pairingDTO.getTimeout());
}
public void finishPairingWithTv(String pairingCode, PhilipsTVConnectionManager handler, HttpHost target)
throws NoSuchAlgorithmException, InvalidKeyException, IOException, KeyStoreException,
KeyManagementException {
AuthDTO authDTO = new AuthDTO();
authDTO.setAuthAppId("1");
authDTO.setAuthSignature(calculateRFC2104HMAC(authTimestamp + pairingCode));
authDTO.setAuthTimestamp(authTimestamp);
authDTO.setPin(pairingCode);
FinishPairingDTO finishPairingDTO = new FinishPairingDTO(createDeviceSpecification(), authDTO);
String grantPairingJson = OBJECT_MAPPER.writeValueAsString(finishPairingDTO);
Header challengeHeader = null;
try (CloseableHttpClient httpClient = ConnectionManagerUtil.createSharedHttpClient(target, EMPTY, EMPTY)) {
CloseableHttpResponse response = httpClient
.execute(new HttpGet(target.toURI() + pairingBasePath + "grant"));
challengeHeader = response.getFirstHeader("WWW-Authenticate");
} catch (IOException e) {
logger.debug("finishPairingWithTv: {}", e.getMessage());
throw e;
}
try (CloseableHttpClient client = ConnectionManagerUtil.createSharedHttpClient(target, deviceId, authKey)) {
logger.debug("{} and device id: {} and auth_key: {}", grantPairingJson, deviceId, authKey);
String grantPairingCodePath = pairingBasePath + "grant";
HttpPost httpPost = new HttpPost(grantPairingCodePath);
httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(new StringEntity(grantPairingJson));
DigestScheme digestAuth = new DigestScheme();
digestAuth.processChallenge(challengeHeader);
AuthCache authCache = new BasicAuthCache();
authCache.put(target, digestAuth);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAuthCache(authCache);
try (CloseableHttpResponse response = client.execute(target, httpPost, localContext)) {
String jsonContent = EntityUtils.toString(response.getEntity());
logger.debug("----------------------------------------");
logger.debug("{}", response.getStatusLine());
logger.debug("{}", jsonContent);
if (response.getStatusLine().getStatusCode() != 200) {
throw new IOException("Pairing grant failed");
}
if (jsonContent.contains("INVALID_PIN")) {
throw new IOException("Invalid PIN");
}
}
handler.setCreds(deviceId, authKey);
} catch (MalformedChallengeException e) {
logger.debug("finishPairingWithTv: {}", e.getMessage());
throw new IOException(e.getMessage());
}
}
private String createDeviceId() {
StringBuilder deviceIdBuilder = new StringBuilder();
String chars = "abcdefghkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789";
for (int i = 0; i < 16; i++) {
int index = (int) Math.floor(Math.random() * chars.length());
deviceIdBuilder.append(chars.charAt(index));
}
return deviceIdBuilder.toString();
}
private DeviceDTO createDeviceSpecification() {
DeviceDTO deviceDTO = new DeviceDTO();
deviceDTO.setAppName("openHAB");
deviceDTO.setAppId("app.id");
deviceDTO.setDeviceName("heliotrope");
deviceDTO.setDeviceOs("Android");
deviceDTO.setType("native");
if (deviceId.isEmpty()) {
deviceId = createDeviceId();
}
deviceDTO.setId(deviceId);
return deviceDTO;
}
private String toHexString(byte[] bytes) {
try (Formatter formatter = new Formatter()) {
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
}
private String calculateRFC2104HMAC(String data)
throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
String hmacSHA1 = "HmacSHA1";
// Key used for generated the HMAC signature
String secretKey = "ZmVay1EQVFOaZhwQ4Kv81ypLAZNczV9sG4KkseXWn1NEk6cXmPKO/MCa9sryslvLCFMnNe4Z4CPXzToowvhHvA==";
Key signingKey = new SecretKeySpec(Base64.getDecoder().decode(secretKey), hmacSHA1);
Mac mac = Mac.getInstance(hmacSHA1);
mac.init(signingKey);
return Base64.getEncoder().encodeToString(toHexString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8.name())))
.getBytes(StandardCharsets.UTF_8.name()));
}
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link FinishPairingDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class AuthDTO {
@JsonProperty("auth_signature")
private String authSignature = "";
@JsonProperty("auth_timestamp")
private String authTimestamp = "";
@JsonProperty("pin")
private String pin = "";
@JsonProperty("auth_AppId")
private String authAppId = "";
public void setAuthSignature(String authSignature) {
this.authSignature = authSignature;
}
public String getAuthSignature() {
return authSignature;
}
public void setAuthTimestamp(String authTimestamp) {
this.authTimestamp = authTimestamp;
}
public String getAuthTimestamp() {
return authTimestamp;
}
public void setPin(String pin) {
this.pin = pin;
}
public String getPin() {
return pin;
}
public void setAuthAppId(String authAppId) {
this.authAppId = authAppId;
}
public String getAuthAppId() {
return authAppId;
}
@Override
public String toString() {
return "Auth{" + "auth_signature = '" + authSignature + '\'' + ",auth_timestamp = '" + authTimestamp + '\''
+ ",pin = '" + pin + '\'' + ",auth_AppId = '" + authAppId + '\'' + "}";
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link RequestCodeDTO} and {@link FinishPairingDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class DeviceDTO {
@JsonProperty("app_name")
private String appName = "";
@JsonProperty("device_name")
private String deviceName = "";
@JsonProperty("id")
private String id = "";
@JsonProperty("type")
private String type = "";
@JsonProperty("app_id")
private String appId = "";
@JsonProperty("device_os")
private String deviceOs = "";
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppName() {
return appName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getDeviceName() {
return deviceName;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppId() {
return appId;
}
public void setDeviceOs(String deviceOs) {
this.deviceOs = deviceOs;
}
public String getDeviceOs() {
return deviceOs;
}
@Override
public String toString() {
return "Device{" + "app_name = '" + appName + '\'' + ",device_name = '" + deviceName + '\'' + ",id = '" + id
+ '\'' + ",type = '" + type + '\'' + ",app_id = '" + appId + '\'' + ",device_os = '" + deviceOs + '\''
+ "}";
}
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link FinishPairingDTO} class defines the Data Transfer Object
* for the Philips TV API /pair/grant endpoint to finish pairing.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class FinishPairingDTO {
@JsonProperty("auth")
private AuthDTO auth;
@JsonProperty("device")
private DeviceDTO device;
public FinishPairingDTO(DeviceDTO device, AuthDTO auth) {
this.device = device;
this.auth = auth;
}
public void setAuth(AuthDTO auth) {
this.auth = auth;
}
public AuthDTO getAuth() {
return auth;
}
public void setDevice(DeviceDTO device) {
this.device = device;
}
public DeviceDTO getDevice() {
return device;
}
@Override
public String toString() {
return "FinishPairingDTO{" + "auth = '" + auth + '\'' + ",device = '" + device + '\'' + "}";
}
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Response Data Transfer Object of {@link RequestCodeDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PairingDTO {
@JsonProperty("auth_key")
private String authKey = "";
@JsonProperty("timestamp")
private String timestamp = "";
@JsonProperty("timeout")
private String timeout = "";
public String getTimeout() {
return timeout;
}
public void setTimeout(String timeout) {
this.timeout = timeout;
}
public void setAuthKey(String authKey) {
this.authKey = authKey;
}
public String getAuthKey() {
return authKey;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return "PairingCodeDTO{" + "auth_key = '" + authKey + '\'' + ",timestamp = '" + timestamp + '\'' + "}";
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.pairing.model;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link RequestCodeDTO} class defines the Data Transfer Object
* for the Philips TV API /pair/request endpoint to request a pairing code.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class RequestCodeDTO {
@JsonProperty("scope")
private List<String> scope;
@JsonProperty("device")
private DeviceDTO device;
public RequestCodeDTO(List<String> scope, DeviceDTO device) {
this.scope = scope;
this.device = device;
}
public void setScope(List<String> scope) {
this.scope = scope;
}
public List<String> getScope() {
return scope;
}
public void setDevice(DeviceDTO device) {
this.device = device;
}
public DeviceDTO getDevice() {
return device;
}
@Override
public String toString() {
return "RequestPinDTO{" + "scope = '" + scope + '\'' + ",device = '" + device + '\'' + "}";
}
}

View File

@ -0,0 +1,316 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.WakeOnLanUtil;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.DataDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValueDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValuesDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDeltaDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorSettingsDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightConfigDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightLoungeDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightModeDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightPowerDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightTopologyDTO;
import org.openhab.core.library.types.HSBType;
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.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Service for handling commands regarding Ambilight settings of the TV
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class AmbilightService implements PhilipsTVService {
private static final List<String> AMBILIGHT_COLOR_CHANNELS = Stream
.of(CHANNEL_AMBILIGHT_COLOR, CHANNEL_AMBILIGHT_LEFT_COLOR, CHANNEL_AMBILIGHT_RIGHT_COLOR,
CHANNEL_AMBILIGHT_TOP_COLOR, CHANNEL_AMBILIGHT_BOTTOM_COLOR)
.collect(Collectors.toList());
private static final int AMBILIGHT_HUE_NODE_ID = 2131230774;
private static final int AMBILIGHT_BRIGHTNESS_NODE_ID = 2131230769;
private static final String AMBILIGHT_MODE_MANUAL = "manual";
private static final String AMBILIGHT_STYLE_FOLLOW_VIDEO = "FOLLOW_VIDEO";
private static final String AMBILIGHT_STYLE_FOLLOW_COLOR = "FOLLOW_COLOR";
private static final String AMBILIGHT_ALGORITHM_MANUAL_HUE = "MANUAL HUE";
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final boolean isWakeOnLanEnabled;
private @Nullable AmbilightTopologyDTO ambilightTopology;
private final ConnectionManager connectionManager;
public AmbilightService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
this.isWakeOnLanEnabled = handler.getMacAddress().isEmpty() ? false : true;
}
@Override
public void handleCommand(String channel, Command command) {
try {
if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof OnOffType)) {
setAmbilightPowerState(command);
} else if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof RefreshType)) {
AmbilightPowerDTO ambilightPowerDTO = getAmbilightPowerState();
handler.postUpdateChannel(CHANNEL_AMBILIGHT_POWER,
ambilightPowerDTO.isPoweredOn() ? OnOffType.ON : OnOffType.OFF);
} else if (CHANNEL_AMBILIGHT_HUE_POWER.equals(channel) && (command instanceof OnOffType)) {
setAmbilightHuePowerState(command);
} else if (CHANNEL_AMBILIGHT_LOUNGE_POWER.equals(channel) && (command instanceof OnOffType)) {
setAmbilightLoungePowerState(command);
} else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof StringType)) {
setAmbilightStyle(command.toString());
} else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof RefreshType)) {
AmbilightConfigDTO config = getAmbilightConfig();
String styleWithAlgorithm = String.format("%s %s", config.getStyleName(), config.getMenuSetting());
handler.postUpdateChannel(CHANNEL_AMBILIGHT_STYLE, new StringType(styleWithAlgorithm));
} else if (CHANNEL_AMBILIGHT_COLOR.equals(channel) && (command instanceof HSBType)) {
setAllAmbilightColors((HSBType) command);
} else if ((CHANNEL_AMBILIGHT_LEFT_COLOR.equals(channel) || CHANNEL_AMBILIGHT_RIGHT_COLOR.equals(channel)
|| CHANNEL_AMBILIGHT_TOP_COLOR.equals(channel) || CHANNEL_AMBILIGHT_BOTTOM_COLOR.equals(channel))
&& (command instanceof HSBType)) {
setAmbilightPixel((HSBType) command, channel);
} else if (AMBILIGHT_COLOR_CHANNELS.contains(channel) && (command instanceof PercentType)) {
setAmbilightBrightness(((PercentType) command).intValue());
} else {
if (!(command instanceof RefreshType)) {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error during handling the Ambilight command {} for Channel {}: {}", command, channel,
e.getMessage(), e);
}
}
}
private AmbilightPowerDTO getAmbilightPowerState() throws IOException {
return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_POWERSTATE_PATH),
AmbilightPowerDTO.class);
}
private void setAmbilightPowerState(Command command) throws IOException {
if (command.equals(OnOffType.OFF)) {
AmbilightPowerDTO ambilightPower = new AmbilightPowerDTO();
ambilightPower.setPower(POWER_OFF);
String powerStateJson = OBJECT_MAPPER.writeValueAsString(ambilightPower);
logger.debug("Post Ambilight power state json: {}", powerStateJson);
connectionManager.doHttpsPost(AMBILIGHT_POWERSTATE_PATH, powerStateJson);
} else { // power on via setting FOLLOW_VIDEO instead through POWERSTATE_PATH which sets FOLLOW_COLOR
setAmbilightStyle(String.format("%s %s", AMBILIGHT_STYLE_FOLLOW_VIDEO, "STANDARD"));
}
}
private void setAmbilightHuePowerState(Command command) throws IOException {
DataDTO data = new DataDTO((command.equals(OnOffType.ON) ? "true" : "false"));
ValueDTO value = new ValueDTO(data);
value.setNodeid(AMBILIGHT_HUE_NODE_ID);
value.setAvailable("true");
value.setControllable("true");
ValuesDTO values = new ValuesDTO(value);
TvSettingsUpdateDTO ambilightHuePower = new TvSettingsUpdateDTO(Collections.singletonList(values));
String ambilightHuePowerJson = OBJECT_MAPPER.writeValueAsString(ambilightHuePower);
logger.debug("Post Ambilight hue power state json: {}", ambilightHuePowerJson);
connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightHuePowerJson);
}
private void setAmbilightLoungePowerState(Command command) throws IOException, InterruptedException {
AmbilightColorDTO ambilightColorDTO = new AmbilightColorDTO();
if (command.equals(OnOffType.ON)) {
if (isWakeOnLanEnabled && !WakeOnLanUtil.isReachable(handler.config.ipAddress)) {
WakeOnLanUtil.wakeOnLan(handler.config.ipAddress, handler.getMacAddress());
}
ambilightColorDTO.setHue(0);
} else {
ambilightColorDTO.setHue(255);
}
AmbilightLoungeDTO ambilightLoungeDTO = new AmbilightLoungeDTO(ambilightColorDTO);
String setAmbilightLoungeJson = OBJECT_MAPPER.writeValueAsString(ambilightLoungeDTO);
logger.debug("Setting ambilight lounge power state json: {}", setAmbilightLoungeJson);
connectionManager.doHttpsPost(AMBILIGHT_LOUNGE_PATH, setAmbilightLoungeJson);
}
private void setAmbilightStyle(String styleToSet) throws IOException {
String[] styleWithAlgorithm = styleToSet.split(" ");
if (styleWithAlgorithm.length != 2) {
throw new IllegalStateException("Style and/or algorithm is missing.");
}
String style = styleWithAlgorithm[0];
String algorithm = styleWithAlgorithm[1];
AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO(
new AmbilightColorSettingsDTO(new AmbilightColorDTO(), new AmbilightColorDeltaDTO()));
ambilightConfig.setStyleName(style);
ambilightConfig.setMenuSetting(algorithm);
if (style.equals(AMBILIGHT_STYLE_FOLLOW_COLOR) && algorithm.equals(AMBILIGHT_ALGORITHM_MANUAL_HUE)) {
ambilightConfig.setAlgorithm(algorithm);
ambilightConfig.setIsExpert(true);
AmbilightColorDeltaDTO ambilightColorDeltaDTO = new AmbilightColorDeltaDTO();
ambilightColorDeltaDTO.setHue(0);
ambilightColorDeltaDTO.setBrightness(0);
ambilightColorDeltaDTO.setSaturation(0);
AmbilightColorSettingsDTO ambilightColorSettingsDTO = new AmbilightColorSettingsDTO(new AmbilightColorDTO(),
ambilightColorDeltaDTO);
ambilightColorSettingsDTO.setSpeed(255);
ambilightConfig.setColorSettings(ambilightColorSettingsDTO);
}
String ambilightConfigJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig);
logger.debug("Post config for Ambilight style json: {}", ambilightConfigJson);
connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, ambilightConfigJson);
}
private AmbilightConfigDTO getAmbilightConfig() throws IOException {
return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_CONFIG_PATH), AmbilightConfigDTO.class);
}
private void setAmbilightMode(String mode) throws IOException {
AmbilightModeDTO ambilightMode = new AmbilightModeDTO();
ambilightMode.setCurrent(mode);
String ambilightModeJson = OBJECT_MAPPER.writeValueAsString(ambilightMode);
logger.debug("Post ambilight mode json: {}", ambilightModeJson);
connectionManager.doHttpsPost(AMBILIGHT_MODE_PATH, ambilightModeJson);
}
// private AmbilightModeDTO getAmbilightMode() throws IOException {
// return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_MODE_PATH), AmbilightModeDTO.class);
// }
private void setAmbilightBrightness(int brightnessToSet) throws IOException {
String ambilightBrightnessJson = ServiceUtil.createTvSettingsUpdateJson(AMBILIGHT_BRIGHTNESS_NODE_ID,
brightnessToSet / 10);
logger.debug("Post Ambilight brightness json: {}", ambilightBrightnessJson);
connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightBrightnessJson);
}
private void setAmbilightPixel(HSBType hsb, String channel) throws IOException {
if (ambilightTopology == null) {
ambilightTopology = getAmbilightTopology();
}
setAmbilightMode(AMBILIGHT_MODE_MANUAL); // activates the usage of cached values
String sideToSet = determineAmbilightSide(channel);
int pixelSize = ambilightTopology.getPixelSizeForGivenSide(sideToSet);
ObjectNode rootNode = OBJECT_MAPPER.createObjectNode();
ObjectNode pixel = OBJECT_MAPPER.createObjectNode();
pixel.put("r", hsb.getRed().intValue());
pixel.put("g", hsb.getGreen().intValue());
pixel.put("b", hsb.getBlue().intValue());
ObjectNode sidePixels = OBJECT_MAPPER.createObjectNode();
// pixel declaration in json start with 0
IntStream.range(0, pixelSize).forEach(i -> sidePixels.set(Integer.toString(i), pixel));
IntStream.rangeClosed(1, ambilightTopology.getLayers()).forEach(i -> {
ObjectNode layerX = OBJECT_MAPPER.createObjectNode();
layerX.set(sideToSet, sidePixels);
rootNode.set("layer" + i, layerX);
});
String ambilightPixelJson = OBJECT_MAPPER.writeValueAsString(rootNode);
logger.debug("Sending {} Ambilight pixel json: {}", sideToSet, ambilightPixelJson);
connectionManager.doHttpsPost(AMBILIGHT_CACHED_PATH, ambilightPixelJson);
}
private AmbilightTopologyDTO getAmbilightTopology() throws IOException {
return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_TOPOLOGY_PATH),
AmbilightTopologyDTO.class);
}
private String determineAmbilightSide(String channel) {
String sideToSet;
switch (channel) {
case CHANNEL_AMBILIGHT_LEFT_COLOR:
sideToSet = "left";
break;
case CHANNEL_AMBILIGHT_RIGHT_COLOR:
sideToSet = "right";
break;
case CHANNEL_AMBILIGHT_TOP_COLOR:
sideToSet = "top";
break;
case CHANNEL_AMBILIGHT_BOTTOM_COLOR:
sideToSet = "bottom";
break;
default:
throw new IllegalStateException("Unexpected channel for ambilight pixel set: " + channel);
}
return sideToSet;
}
private void setAllAmbilightColors(HSBType hsb) throws IOException {
AmbilightColorDTO ambilightColor = new AmbilightColorDTO(hsb);
AmbilightColorDeltaDTO ambilightColorDelta = new AmbilightColorDeltaDTO();
ambilightColorDelta.setHue(0);
ambilightColorDelta.setSaturation(0);
ambilightColorDelta.setBrightness(0);
AmbilightColorSettingsDTO ambilightColorSettings = new AmbilightColorSettingsDTO(ambilightColor,
ambilightColorDelta);
ambilightColorSettings.setSpeed(255);
AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO(ambilightColorSettings);
ambilightConfig.setIsExpert(true);
ambilightConfig.setStyleName("FOLLOW_COLOR");
ambilightConfig.setAlgorithm("MANUAL_HUE");
String setAmbilightColorsJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig);
logger.debug("Setting ambilight colors json: {}", setAmbilightColorsJson);
connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, setAmbilightColorsJson);
}
}

View File

@ -0,0 +1,207 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.http.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ApplicationsDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.AvailableAppsDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ComponentDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.CurrentAppDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ExtrasDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.IntentDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.LaunchAppDTO;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AppService} is responsible for handling key code commands, which emulate a button
* press on a remote control.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class AppService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
// Label , Entry<PackageName,ClassName> of App
private @Nullable Map<String, AbstractMap.SimpleEntry<String, String>> availableApps;
private String currentPackageName = "";
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public AppService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
try {
synchronized (this) {
if (isAvailableAppListEmpty()) {
getAvailableAppListFromTv();
handler.updateChannelStateDescription(CHANNEL_APPNAME, availableApps.keySet().stream()
.collect(Collectors.toMap(Function.identity(), Function.identity())));
}
}
if (command instanceof RefreshType) {
// Get current App name
String packageName = getCurrentApp();
if (currentPackageName.equals(packageName)) {
return;
} else {
currentPackageName = packageName;
}
Optional<Map.Entry<String, AbstractMap.SimpleEntry<String, String>>> app = availableApps.entrySet()
.stream().filter(e -> e.getValue().getKey().equalsIgnoreCase(packageName)).findFirst();
if (app.isPresent()) {
handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName));
Map.Entry<String, AbstractMap.SimpleEntry<String, String>> appEntry = app.get();
handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(appEntry.getKey()));
// Get icon for current App
RawType image = getIconForApp(appEntry.getValue().getKey(), appEntry.getValue().getValue());
handler.postUpdateChannel(CHANNEL_APP_ICON, (image != null) ? image : UnDefType.UNDEF);
} else { // NA
handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName));
handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(packageName));
handler.postUpdateChannel(CHANNEL_APP_ICON, UnDefType.UNDEF);
}
} else if (command instanceof StringType) {
String appName = "";
if (CHANNEL_APPNAME.equals(channel) && availableApps.containsKey(command.toString())) {
launchApp(command.toString());
} else if (CHANNEL_APP.equals(channel)) {
launchDNApp(command.toString());
} else {
logger.warn("The given App with Name: {} {} couldn't be found in the local App List from the tv.",
command, appName);
}
} else {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
logger.debug("Could not execute command for apps, the TV is offline.");
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error occurred during handling of command for apps: {}", e.getMessage(), e);
}
}
}
private boolean isAvailableAppListEmpty() {
return (availableApps == null) || availableApps.isEmpty();
}
private void launchDNApp(String appName) throws IOException {
for (Map.Entry<String, AbstractMap.SimpleEntry<String, String>> entry : availableApps.entrySet()) {
Map.Entry<String, String> app = entry.getValue();
if (app.getKey().equals(appName)) {
logger.debug("Found app by dn: {} {} {}", entry.getKey(), app.getKey(), app.getValue());
launchApp(entry.getKey());
return;
}
}
logger.warn("The given App with DN: {} couldn't be found in the local App List from the tv.", appName);
}
private void launchApp(String appName) throws IOException {
Map.Entry<String, String> app = availableApps.get(appName);
ComponentDTO componentDTO = new ComponentDTO();
componentDTO.setPackageName(app.getKey());
componentDTO.setClassName(app.getValue());
IntentDTO intentDTO = new IntentDTO(componentDTO, new ExtrasDTO());
intentDTO.setAction("empty");
LaunchAppDTO launchAppDTO = new LaunchAppDTO(intentDTO);
String appLaunchJson = OBJECT_MAPPER.writeValueAsString(launchAppDTO);
logger.debug("App Launch json: {}", appLaunchJson);
connectionManager.doHttpsPost(LAUNCH_APP_PATH, appLaunchJson);
}
private String getCurrentApp() throws IOException, ParseException {
CurrentAppDTO currentAppDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(GET_CURRENT_APP_PATH),
CurrentAppDTO.class);
return currentAppDTO.getComponent().getPackageName();
}
private @Nullable RawType getIconForApp(String packageName, String className) throws IOException {
String pathForIcon = String.format("%s%s-%s%sicon", SLASH, className, packageName, SLASH);
byte[] icon = connectionManager
.doHttpsGetForImage(String.format("%s%s", GET_AVAILABLE_APP_LIST_PATH, pathForIcon));
if ((icon != null) && (icon.length > 0)) {
return new RawType(icon, "image/png");
} else {
return null;
}
}
private void getAvailableAppListFromTv() throws IOException {
AvailableAppsDTO availableAppsDTO = OBJECT_MAPPER
.readValue(connectionManager.doHttpsGet(GET_AVAILABLE_APP_LIST_PATH), AvailableAppsDTO.class);
ConcurrentMap<String, AbstractMap.SimpleEntry<String, String>> appsMap = availableAppsDTO.getApplications()
.stream()
.collect(Collectors.toConcurrentMap(ApplicationsDTO::getLabel,
a -> new AbstractMap.SimpleEntry<>(a.getIntent().getComponent().getPackageName(),
a.getIntent().getComponent().getClassName()),
(a1, a2) -> a1));
logger.debug("appsMap - Apps added: {}", appsMap.size());
if (logger.isTraceEnabled()) {
appsMap.keySet().forEach(app -> logger.trace("appsMap - App found: {}", app));
}
this.availableApps = appsMap;
}
public void clearAvailableAppList() {
if (availableApps != null) {
availableApps.clear();
}
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* The {@link KeyPress} presents all available key codes of Philips TV.
*
* @see <a
* href=
* "http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html">http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html
* </a>
*
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public enum KeyPress {
KEY_STANDBY("Standby"),
KEY_BACK("Back"),
KEY_FIND("Find"),
KEY_RED_COLOR("RedColour"),
KEY_GREEN_COLOR("GreenColour"),
KEY_YELLOW_COLOR("YellowColour"),
KEY_BLUE_COLOR("BlueColour"),
KEY_HOME("Home"),
KEY_VOLUME_UP("VolumeUp"),
KEY_VOLUME_DOWN("VolumeDown"),
KEY_MUTE("Mute"),
KEY_OPTIONS("Options"),
KEY_DOT("Dot"),
KEY_0("Digit0"),
KEY_1("Digit1"),
KEY_2("Digit2"),
KEY_3("Digit3"),
KEY_4("Digit4"),
KEY_5("Digit5"),
KEY_6("Digit6"),
KEY_7("Digit7"),
KEY_8("Digit8"),
KEY_9("Digit9"),
KEY_INFO("Info"),
KEY_CURSOR_UP("CursorUp"),
KEY_CURSOR_DOWN("CursorDown"),
KEY_CURSOR_LEFT("CursorLeft"),
KEY_CURSOR_RIGHT("CursorRight"),
KEY_CONFIRM("Confirm"),
KEY_NEXT("Next"),
KEY_PREVIOUS("Previous"),
KEY_ADJUST("Adjust"),
KEY_WATCH_TV("WatchTV"),
KEY_VIEW_MODE("Viewmode"),
KEY_TELETEXT("Teletext"),
KEY_SUBTITLE("Subtitle"),
KEY_CHANNEL_STEP_UP("ChannelStepUp"),
KEY_CHANNEL_STEP_DOWN("ChannelStepDown"),
KEY_SOURCE("Source"),
KEY_AMBILIGHT_ON_OFF("AmbilightOnOff"),
KEY_PLAY("Play"),
KEY_PAUSE("Pause"),
KEY_FAST_FORWARD("FastForward"),
KEY_STOP("Stop"),
KEY_REWIND("Rewind"),
KEY_RECORD("Record"),
KEY_ONLINE("Online");
private final String value;
KeyPress(String value) {
this.value = value;
}
public static KeyPress getKeyPressForValue(String value) throws IllegalArgumentException {
return Arrays.stream(values()).filter(v -> v.value.equalsIgnoreCase(value)).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Key code could not be recognized: " + value));
}
@JsonValue
@Override
public String toString() {
return this.value;
}
}

View File

@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.keypress.KeyPressDTO;
import org.openhab.core.library.types.NextPreviousType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.RewindFastforwardType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KeyPressService} is responsible for handling key code commands, which emulate a button
* press on a remote control.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class KeyPressService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public KeyPressService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
KeyPress keyPress = null;
if (isSupportedCommand(command)) {
// Three approaches to resolve the KEY_CODE
try {
keyPress = KeyPress.valueOf(command.toString().toUpperCase());
} catch (IllegalArgumentException e) {
try {
keyPress = KeyPress.valueOf("KEY_" + command.toString().toUpperCase());
} catch (IllegalArgumentException e2) {
try {
keyPress = KeyPress.getKeyPressForValue(command.toString());
} catch (IllegalArgumentException e3) {
logger.trace("KeyPress threw IllegalArgumentException", e3);
}
}
}
if (keyPress != null) {
try {
sendKeyPress(keyPress);
} catch (Exception e) {
if (isTvOfflineException(e)) {
logger.debug("Could not execute command for key code, the TV is offline.");
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Unknown error occurred while sending keyPress code {}: {}", keyPress,
e.getMessage(), e);
}
}
} else {
logger.warn("Command '{}' not a supported keyPress code.", command);
}
}
}
private static boolean isSupportedCommand(Command command) {
return (command instanceof StringType) || (command instanceof NextPreviousType)
|| (command instanceof PlayPauseType) || (command instanceof RewindFastforwardType);
}
private void sendKeyPress(KeyPress key) throws IOException {
KeyPressDTO keyPressDTO = new KeyPressDTO(key);
String keyPressJson = OBJECT_MAPPER.writeValueAsString(keyPressDTO);
logger.debug("KeyPress Json sent: {}", keyPressJson);
connectionManager.doHttpsPost(KEY_CODE_PATH, keyPressJson);
}
}

View File

@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import org.apache.http.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.WakeOnLanUtil;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.power.PowerStateDTO;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PowerService} is responsible for handling power states commands, which are sent to the
* power channel.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class PowerService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
private final boolean isWakeOnLanEnabled;
public PowerService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
this.isWakeOnLanEnabled = handler.getMacAddress().isEmpty() ? false : true;
}
@Override
public void handleCommand(String channel, Command command) {
try {
if (command instanceof RefreshType) {
PowerStateDTO powerStateDTO = getPowerState();
if (powerStateDTO.isPoweredOn()) {
handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online");
} else if (powerStateDTO.isStandby()) {
handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby");
if (powerStateDTO.isStandbyKeep()) {
handler.checkPendingPowerOn();
}
} else {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, EMPTY);
}
} else if (command instanceof OnOffType) {
setPowerState((OnOffType) command);
if (command == OnOffType.ON) {
handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online");
} else {
handler.postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby");
}
} else {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Unexpected Error handling the PowerState command {} for Channel {}: {}", command, channel,
e.getMessage());
}
}
}
private PowerStateDTO getPowerState() throws IOException, ParseException {
return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(TV_POWERSTATE_PATH), PowerStateDTO.class);
}
private void setPowerState(OnOffType onOffType) throws IOException, InterruptedException {
PowerStateDTO powerStateDTO = new PowerStateDTO();
if (onOffType == OnOffType.ON) {
if (isWakeOnLanEnabled && !WakeOnLanUtil.isReachable(handler.config.ipAddress)) {
WakeOnLanUtil.wakeOnLan(handler.config.ipAddress, handler.getMacAddress());
}
powerStateDTO.setPowerState(POWER_ON);
} else {
powerStateDTO.setPowerState(STANDBY);
}
String powerStateJson = OBJECT_MAPPER.writeValueAsString(powerStateDTO);
logger.debug("PowerState Json sent: {}", powerStateJson);
connectionManager.doHttpsPost(TV_POWERSTATE_PATH, powerStateJson);
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ComponentDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ExtrasDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.IntentDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.LaunchAppDTO;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for toggling the Google Assistant on the Philips TV
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class SearchContentService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public SearchContentService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
if (command instanceof StringType) {
try {
searchForContentOnTv(command.toString());
} catch (Exception e) {
if (isTvOfflineException(e)) {
logger.warn("Could not search content on Philips TV: TV is offline.");
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error during the launch of search content on Philips TV: {}", e.getMessage(), e);
}
}
} else if (!(command instanceof RefreshType)) {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
}
private void searchForContentOnTv(String searchContent) throws IOException {
ExtrasDTO extrasDTO = new ExtrasDTO();
extrasDTO.setQuery(searchContent);
IntentDTO intentDTO = new IntentDTO(new ComponentDTO(), extrasDTO);
intentDTO.setAction("android.search.action.GLOBAL_SEARCH");
LaunchAppDTO launchAppDTO = new LaunchAppDTO(intentDTO);
String searchContentLaunch = OBJECT_MAPPER.writeValueAsString(launchAppDTO);
logger.debug("Search Content Launch json: {}", searchContentLaunch);
connectionManager.doHttpsPost(LAUNCH_APP_PATH, searchContentLaunch);
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import java.util.Collections;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.DataDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.NodesDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsCurrentDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValueDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValuesDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
/**
* Util class for common used methods from philips tv services
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
final class ServiceUtil {
private ServiceUtil() {
}
static String createTvSettingsRetrievalJson(int nodeId) throws JsonProcessingException {
NodesDTO nodes = new NodesDTO();
nodes.setNodeid(nodeId);
TvSettingsCurrentDTO tvSettingCurrent = new TvSettingsCurrentDTO(Collections.singletonList(nodes));
return OBJECT_MAPPER.writeValueAsString(tvSettingCurrent);
}
static String createTvSettingsUpdateJson(int nodeId, int valueToSet) throws JsonProcessingException {
DataDTO data = new DataDTO(valueToSet);
ValueDTO value = new ValueDTO(data);
value.setNodeid(nodeId);
ValuesDTO values = new ValuesDTO(value);
values.setValue(value);
TvSettingsUpdateDTO tvSetting = new TvSettingsUpdateDTO(Collections.singletonList(values));
return OBJECT_MAPPER.writeValueAsString(tvSetting);
}
}

View File

@ -0,0 +1,147 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.AvailableTvChannelsDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelListDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.TvChannelDTO;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for handling commands regarding setting or retrieving the TV channel
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class TvChannelService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
// Name , ccid of TV Channel
private @Nullable Map<String, String> availableTvChannels;
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public TvChannelService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
try {
synchronized (this) {
if (isTvChannelListEmpty()) {
availableTvChannels = getAvailableTvChannelListFromTv();
handler.updateChannelStateDescription(CHANNEL_TV_CHANNEL, availableTvChannels.keySet().stream()
.collect(Collectors.toMap(Function.identity(), Function.identity())));
}
}
if (command instanceof RefreshType) {
// Get current tv channel name
String tvChannelName = getCurrentTvChannel();
handler.postUpdateChannel(CHANNEL_TV_CHANNEL, new StringType(tvChannelName));
} else if (command instanceof StringType) {
if (availableTvChannels.containsKey(command.toString())) {
switchTvChannel(command);
} else {
logger.warn(
"The given TV Channel with Name: {} couldn't be found in the local Channel List from the TV.",
command);
}
} else {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
logger.warn("Could not execute command for TV Channels, the TV is offline.");
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error occurred during handling of command for TV Channels: {}", e.getMessage(), e);
}
}
}
private boolean isTvChannelListEmpty() {
return (availableTvChannels == null) || availableTvChannels.isEmpty();
}
private Map<String, String> getAvailableTvChannelListFromTv() throws IOException {
AvailableTvChannelsDTO availableTvChannelsDTO = OBJECT_MAPPER.readValue(
connectionManager.doHttpsGet(GET_AVAILABLE_TV_CHANNEL_LIST_PATH), AvailableTvChannelsDTO.class);
ConcurrentMap<String, String> tvChannelsMap = availableTvChannelsDTO.getChannel().stream()
.collect(Collectors.toConcurrentMap(ChannelDTO::getName, ChannelDTO::getCcid, (c1, c2) -> c1));
logger.debug("TV Channels added: {}", tvChannelsMap.size());
if (logger.isTraceEnabled()) {
tvChannelsMap.keySet().forEach(app -> logger.trace("TV Channel found: {}", app));
}
return tvChannelsMap;
}
private String getCurrentTvChannel() throws IOException {
TvChannelDTO tvChannelDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(TV_CHANNEL_PATH),
TvChannelDTO.class);
return Optional.ofNullable(tvChannelDTO.getChannel()).map(ChannelDTO::getName).orElse("NA");
}
private void switchTvChannel(Command command) throws IOException {
ChannelDTO channelDTO = new ChannelDTO();
channelDTO.setCcid(availableTvChannels.get(command.toString()));
ChannelListDTO channelListDTO = new ChannelListDTO();
channelListDTO.setId("allter");
channelListDTO.setVersion("30");
TvChannelDTO tvChannelDTO = new TvChannelDTO(channelDTO, channelListDTO);
String switchTvChannelJson = OBJECT_MAPPER.writeValueAsString(tvChannelDTO);
logger.debug("Switch TV Channel json: {}", switchTvChannelJson);
connectionManager.doHttpsPost(TV_CHANNEL_PATH, switchTvChannelJson);
}
public void clearAvailableTvChannelList() {
if (availableTvChannels != null) {
availableTvChannels.clear();
}
}
}

View File

@ -0,0 +1,132 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for handling commands regarding the TV picture settings
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class TvPictureService implements PhilipsTVService {
private static final int SHARPNESS_NODE_ID = 2131230851;
private static final int CONTRAST_NODE_ID = 2131230850;
private static final int BRIGHTNESS_NODE_ID = 2131230852;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public TvPictureService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
try {
if (CHANNEL_BRIGHTNESS.equals(channel) && command instanceof PercentType) {
setBrightness(((PercentType) command).intValue());
} else if (CHANNEL_BRIGHTNESS.equals(channel) && command instanceof RefreshType) {
int currentBrightness = getBrightness();
handler.postUpdateChannel(CHANNEL_BRIGHTNESS, new PercentType(currentBrightness));
} else if (CHANNEL_CONTRAST.equals(channel) && command instanceof PercentType) {
setContrast(((PercentType) command).intValue());
} else if (CHANNEL_CONTRAST.equals(channel) && command instanceof RefreshType) {
int currentContrast = getContrast();
handler.postUpdateChannel(CHANNEL_CONTRAST, new PercentType(currentContrast));
} else if (CHANNEL_SHARPNESS.equals(channel) && command instanceof PercentType) {
setSharpness(((PercentType) command).intValue());
} else if (CHANNEL_SHARPNESS.equals(channel) && command instanceof RefreshType) {
int currentSharpness = getSharpness();
handler.postUpdateChannel(CHANNEL_SHARPNESS, new PercentType(currentSharpness));
} else {
if (!(command instanceof RefreshType)) {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error during handling the TvPicture command {} for Channel {}: {}", command, channel,
e.getMessage(), e);
}
}
}
private int getBrightness() throws IOException {
String getBrightnessJson = ServiceUtil.createTvSettingsRetrievalJson(BRIGHTNESS_NODE_ID);
logger.debug("Post Tv Picture retrieval brightness json: {}", getBrightnessJson);
return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getBrightnessJson),
TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue();
}
private void setBrightness(int brightness) throws IOException {
String tvPictureBrightnessJson = ServiceUtil.createTvSettingsUpdateJson(BRIGHTNESS_NODE_ID, brightness);
logger.debug("Post Tv Picture brightness json: {}", tvPictureBrightnessJson);
connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureBrightnessJson);
}
private int getContrast() throws IOException {
String getContrastJson = ServiceUtil.createTvSettingsRetrievalJson(CONTRAST_NODE_ID);
logger.debug("Post Tv Picture retrieval contrast json: {}", getContrastJson);
return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getContrastJson),
TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue();
}
private void setContrast(int contrast) throws IOException {
String tvPictureContrastJson = ServiceUtil.createTvSettingsUpdateJson(CONTRAST_NODE_ID, contrast);
logger.debug("Post Tv Picture contrast json: {}", tvPictureContrastJson);
connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureContrastJson);
}
private int getSharpness() throws IOException {
String getSharpnessJson = ServiceUtil.createTvSettingsRetrievalJson(SHARPNESS_NODE_ID);
logger.debug("Post Tv Picture retrieval sharpness json: {}", getSharpnessJson);
return (int) OBJECT_MAPPER.readValue(connectionManager.doHttpsPost(CURRENT_SETTINGS_PATH, getSharpnessJson),
TvSettingsUpdateDTO.class).getValues().get(0).getValue().getData().getValue();
}
private void setSharpness(int sharpness) throws IOException {
String tvPictureSharpnessJson = ServiceUtil.createTvSettingsUpdateJson(SHARPNESS_NODE_ID, sharpness / 10);
logger.debug("Post Tv Picture brightness json: {}", tvPictureSharpnessJson);
connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, tvPictureSharpnessJson);
}
}

View File

@ -0,0 +1,107 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service;
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPress.KEY_MUTE;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.keypress.KeyPressDTO;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.volume.VolumeDTO;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VolumeService} is responsible for handling volume commands, which are sent to the
* volume channel or mute channel.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public class VolumeService implements PhilipsTVService {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final PhilipsTVConnectionManager handler;
private final ConnectionManager connectionManager;
public VolumeService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
this.handler = handler;
this.connectionManager = connectionManager;
}
@Override
public void handleCommand(String channel, Command command) {
try {
if (command instanceof RefreshType) {
VolumeDTO volumeDTO = getVolume();
handler.postUpdateChannel(CHANNEL_VOLUME, new PercentType(volumeDTO.getCurrentVolume()));
handler.postUpdateChannel(CHANNEL_MUTE, volumeDTO.isMuted() ? OnOffType.ON : OnOffType.OFF);
} else if (CHANNEL_VOLUME.equals(channel) && command instanceof PercentType) {
setVolume((PercentType) command);
handler.postUpdateChannel(CHANNEL_VOLUME, (PercentType) command);
} else if (CHANNEL_MUTE.equals(channel) && command instanceof OnOffType) {
setMute();
} else {
logger.warn("Unknown command: {} for Channel {}", command, channel);
}
} catch (Exception e) {
if (isTvOfflineException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
} else if (isTvNotListeningException(e)) {
handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
TV_NOT_LISTENING_MSG);
} else {
logger.warn("Error during handling the VolumeService command {} for Channel {}: {}", command, channel,
e.getMessage(), e);
}
}
}
private VolumeDTO getVolume() throws IOException {
String jsonContent = connectionManager.doHttpsGet(VOLUME_PATH);
return OBJECT_MAPPER.readValue(jsonContent, VolumeDTO.class);
}
private void setVolume(PercentType volumeToSet) throws IOException {
VolumeDTO volumeDTO = new VolumeDTO();
volumeDTO.setMuted(false);
volumeDTO.setCurrentVolume(volumeToSet.intValue());
String volumeJson = OBJECT_MAPPER.writeValueAsString(volumeDTO);
logger.debug("Set json volume: {}", volumeJson);
connectionManager.doHttpsPost(VOLUME_PATH, volumeJson);
}
private void setMute() throws IOException {
// We just sent the KEY_MUTE and dont bother what was actually requested
KeyPressDTO keyPressDTO = new KeyPressDTO(KEY_MUTE);
String muteJson = OBJECT_MAPPER.writeValueAsString(keyPressDTO);
logger.debug("Set json mute state: {}", muteJson);
connectionManager.doHttpsPost(KEY_CODE_PATH, muteJson);
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.api;
import java.net.NoRouteToHostException;
import java.util.Optional;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.types.Command;
/**
* Interface for Philips TV services.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
@NonNullByDefault
public interface PhilipsTVService {
/**
* Procedure for sending command.
*
* @param channel the channel to which the command applies
* @param command the command to be handled
*/
void handleCommand(String channel, Command command);
default boolean isTvOfflineException(Exception exception) {
String message = Optional.ofNullable(exception.getMessage()).orElse("");
if (!message.isEmpty()) {
if ((exception instanceof NoRouteToHostException) && message.contains("Host unreachable")) {
return true;
} else {
return (exception instanceof ConnectTimeoutException) && message.contains("timed out");
}
} else {
return false;
}
}
default boolean isTvNotListeningException(Exception exception) {
String message = Optional.ofNullable(exception.getMessage()).orElse("");
if (!message.isEmpty()) {
return (exception instanceof HttpHostConnectException) && message.contains("Connection refused");
} else {
return false;
}
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvSettingsUpdateDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class DataDTO {
@JsonProperty
private Object value; // can be int or string
public DataDTO() {
}
public DataDTO(Object value) {
this.value = value;
}
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Data{" + "value = '" + value + '\'' + "}";
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvSettingsCurrentDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class NodesDTO {
@JsonProperty("nodeid")
private int nodeid;
public void setNodeid(int nodeid) {
this.nodeid = nodeid;
}
public int getNodeid() {
return nodeid;
}
@Override
public String toString() {
return "NodesItem{" + "nodeid = '" + nodeid + '\'' + "}";
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link TvSettingsCurrentDTO} class defines the Data Transfer Object
* for the POST Request to Philips TV API /menuitems/settings/current endpoint to retrieve current settings of the tv,
* e.g. the tv picture brightness.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class TvSettingsCurrentDTO {
@JsonProperty("nodes")
private List<NodesDTO> nodes;
public TvSettingsCurrentDTO() {
}
public TvSettingsCurrentDTO(List<NodesDTO> nodes) {
this.nodes = nodes;
}
public void setNodes(List<NodesDTO> nodes) {
this.nodes = nodes;
}
public List<NodesDTO> getNodes() {
return nodes;
}
@Override
public String toString() {
return "TvSettingsCurrentDTO{" + "nodes = '" + nodes + '\'' + "}";
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link TvSettingsUpdateDTO} class defines the Data Transfer Object
* for the Philips TV API /menuitems/settings/update endpoint to update settings of the tv, e.g. turning on/off
* ambilight hue power.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class TvSettingsUpdateDTO {
@JsonProperty
private List<ValuesDTO> values;
public TvSettingsUpdateDTO() {
}
public TvSettingsUpdateDTO(List<ValuesDTO> values) {
this.values = values;
}
public void setValues(List<ValuesDTO> values) {
this.values = values;
}
public List<ValuesDTO> getValues() {
return values;
}
@Override
public String toString() {
return "TvSettingsDTO{" + "values = '" + values + '\'' + "}";
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvSettingsUpdateDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ValueDTO {
@JsonProperty("Controllable")
private String controllable = "";
@JsonProperty
private DataDTO data;
@JsonProperty("Nodeid")
private int nodeid;
@JsonProperty("Available")
private String available = "";
public ValueDTO() {
}
public ValueDTO(DataDTO data) {
this.data = data;
}
public void setControllable(String controllable) {
this.controllable = controllable;
}
public String getControllable() {
return controllable;
}
public void setData(DataDTO data) {
this.data = data;
}
public DataDTO getData() {
return data;
}
public void setNodeid(int nodeid) {
this.nodeid = nodeid;
}
public int getNodeid() {
return nodeid;
}
public void setAvailable(String available) {
this.available = available;
}
public String getAvailable() {
return available;
}
@Override
public String toString() {
return "Value{" + "controllable = '" + controllable + '\'' + ",data = '" + data + '\'' + ",nodeid = '" + nodeid
+ '\'' + ",available = '" + available + '\'' + "}";
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvSettingsUpdateDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ValuesDTO {
@JsonProperty
private ValueDTO value;
public ValuesDTO() {
}
public ValuesDTO(ValueDTO value) {
this.value = value;
}
public void setValue(ValueDTO value) {
this.value = value;
}
public ValueDTO getValue() {
return value;
}
@Override
public String toString() {
return "ValuesItem{" + "value = '" + value + '\'' + "}";
}
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import org.openhab.core.library.types.HSBType;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link AmbilightColorSettingsDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightColorDTO {
@JsonProperty("saturation")
private int saturation;
@JsonProperty("brightness")
private int brightness;
@JsonProperty("hue")
private int hue;
public AmbilightColorDTO() {
}
public AmbilightColorDTO(HSBType hsb) {
hue = hsb.getHue().intValue() * 255 / 360;
saturation = hsb.getSaturation().intValue() * 255 / 100;
brightness = hsb.getBrightness().intValue() * 255 / 100;
}
public void setSaturation(int saturation) {
this.saturation = saturation;
}
public int getSaturation() {
return saturation;
}
public void setBrightness(int brightness) {
this.brightness = brightness;
}
public int getBrightness() {
return brightness;
}
public void setHue(int hue) {
this.hue = hue;
}
public int getHue() {
return hue;
}
@Override
public String toString() {
return "Color{" + "saturation = '" + saturation + '\'' + ",brightness = '" + brightness + '\'' + ",hue = '"
+ hue + '\'' + "}";
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link AmbilightColorSettingsDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightColorDeltaDTO {
@JsonProperty("saturation")
private int saturation;
@JsonProperty("brightness")
private int brightness;
@JsonProperty("hue")
private int hue;
public void setSaturation(int saturation) {
this.saturation = saturation;
}
public int getSaturation() {
return saturation;
}
public void setBrightness(int brightness) {
this.brightness = brightness;
}
public int getBrightness() {
return brightness;
}
public void setHue(int hue) {
this.hue = hue;
}
public int getHue() {
return hue;
}
@Override
public String toString() {
return "ColorDelta{" + "saturation = '" + saturation + '\'' + ",brightness = '" + brightness + '\'' + ",hue = '"
+ hue + '\'' + "}";
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link AmbilightConfigDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightColorSettingsDTO {
@JsonProperty("color")
private AmbilightColorDTO color;
@JsonProperty("colorDelta")
private AmbilightColorDeltaDTO colorDelta;
@JsonProperty("speed")
private int speed;
public AmbilightColorSettingsDTO(AmbilightColorDTO color, AmbilightColorDeltaDTO colorDelta) {
this.color = color;
this.colorDelta = colorDelta;
}
public void setColor(AmbilightColorDTO color) {
this.color = color;
}
public AmbilightColorDTO getColor() {
return color;
}
public void setColorDelta(AmbilightColorDeltaDTO colorDelta) {
this.colorDelta = colorDelta;
}
public AmbilightColorDeltaDTO getColorDelta() {
return colorDelta;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
@Override
public String toString() {
return "ColorSettings{" + "color = '" + color + '\'' + ",colorDelta = '" + colorDelta + '\'' + ",speed = '"
+ speed + '\'' + "}";
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AmbilightConfigDTO} class defines the Data Transfer Object
* for the Philips TV API /ambilight/currentconfiguration endpoint to retrieve or set the current ambilight style.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightConfigDTO {
@JsonProperty("isExpert")
private boolean isExpert;
@JsonProperty("menuSetting")
private String menuSetting = "";
@JsonProperty("styleName")
private String styleName = "";
@JsonProperty("colorSettings")
private AmbilightColorSettingsDTO colorSettings;
@JsonProperty("algorithm")
private String algorithm = "";
public AmbilightConfigDTO() {
}
public AmbilightConfigDTO(AmbilightColorSettingsDTO colorSettings) {
this.colorSettings = colorSettings;
}
public void setMenuSetting(String menuSetting) {
this.menuSetting = menuSetting;
}
public String getMenuSetting() {
return menuSetting;
}
public void setStyleName(String styleName) {
this.styleName = styleName;
}
public String getStyleName() {
return styleName;
}
public boolean isIsExpert() {
return isExpert;
}
public void setIsExpert(boolean isExpert) {
this.isExpert = isExpert;
}
public AmbilightColorSettingsDTO getColorSettings() {
return colorSettings;
}
public void setColorSettings(AmbilightColorSettingsDTO colorSettings) {
this.colorSettings = colorSettings;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
@Override
public String toString() {
return "AmbilightConfigDTO{" + "isExpert = '" + isExpert + '\'' + ",menuSetting = '" + menuSetting + '\''
+ ",styleName = '" + styleName + '\'' + "}";
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AmbilightLoungeDTO} class defines the Data Transfer Object
* for the Philips TV API /ambilight/lounge endpoint to power on or off the ambilight lounge mode.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightLoungeDTO {
@JsonProperty("color")
private AmbilightColorDTO color;
public AmbilightLoungeDTO(AmbilightColorDTO color) {
this.color = color;
}
public void setColor(AmbilightColorDTO color) {
this.color = color;
}
public AmbilightColorDTO getColor() {
return color;
}
@Override
public String toString() {
return "AmbilightLoungeDTO{" + "color = '" + color + '\'' + "}";
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AmbilightModeDTO} class defines the Data Transfer Object
* for the Philips TV API /ambilight/mode endpoint to retrieve or set the ambilight mode.
* <p>
* current (string): One of following values:
* <p>
* internal: The internal ambilight algorithm is used to calculate the ambilight colours.
* <p>
* manual: The cached ambilight colours are shown.
* <p>
* expert: The cached ambilight colours are used as input for the internal ambilight algorithm
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightModeDTO {
@JsonProperty("current")
private String current = "";
public AmbilightModeDTO() {
}
public void setCurrent(String current) {
this.current = current;
}
public String getCurrent() {
return current;
}
@Override
public String toString() {
return "AmbilightModeDTO{" + "current = '" + current + '\'' + "}";
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.POWER_ON;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AmbilightPowerDTO} class defines the Data Transfer Object
* for the Philips TV API /ambilight/power endpoint to retrieve or set the current power state.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightPowerDTO {
@JsonProperty("power")
private String power = "";
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
@JsonIgnore
public boolean isPoweredOn() {
return power.equalsIgnoreCase(POWER_ON);
}
}

View File

@ -0,0 +1,124 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.ambilight;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AmbilightTopologyDTO} class defines the Data Transfer Object
* for the Philips TV API /ambilight/topology endpoint to retrieve the ambilight topology information.
* <p>
* Endpoint returns:
* <p>
* layers (integer): The number of layers.
* <p>
* left (integer): The number of pixels on the left.
* <p>
* top (integer): The number of pixels on the top.
* <p>
* right (integer): The number of pixels on the right.
* <p>
* bottom (integer): The number of pixels on the bottom.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AmbilightTopologyDTO {
@JsonProperty("top")
private int top;
@JsonProperty("left")
private int left;
@JsonProperty("bottom")
private int bottom;
@JsonProperty("layers")
private int layers;
@JsonProperty("right")
private int right;
public AmbilightTopologyDTO() {
}
public void setTop(int top) {
this.top = top;
}
public int getTop() {
return top;
}
public void setLeft(int left) {
this.left = left;
}
public int getLeft() {
return left;
}
public void setBottom(int bottom) {
this.bottom = bottom;
}
public int getBottom() {
return bottom;
}
public void setLayers(int layers) {
this.layers = layers;
}
public int getLayers() {
return layers;
}
public void setRight(int right) {
this.right = right;
}
public int getRight() {
return right;
}
@JsonIgnore
public int getPixelSizeForGivenSide(String side) {
int value;
switch (side) {
case "left":
value = left;
break;
case "right":
value = right;
break;
case "top":
value = top;
break;
case "bottom":
value = bottom;
break;
default:
throw new IllegalStateException("Unexpected side: " + side);
}
return value;
}
@Override
public String toString() {
return "AmbilightTopologyDTO{" + "top = '" + top + '\'' + ",left = '" + left + '\'' + ",bottom = '" + bottom
+ '\'' + ",layers = '" + layers + '\'' + ",right = '" + right + '\'' + "}";
}
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link AvailableAppsDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ApplicationsDTO {
@JsonProperty("label")
private String label = "";
@JsonProperty("id")
private String id = "";
@JsonProperty("type")
private String type = "";
@JsonProperty("intent")
private IntentDTO intent;
@JsonProperty("order")
private int order;
public ApplicationsDTO() {
}
public ApplicationsDTO(IntentDTO intent) {
this.intent = intent;
}
public void setLabel(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setIntent(IntentDTO intent) {
this.intent = intent;
}
public IntentDTO getIntent() {
return intent;
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return order;
}
@Override
public String toString() {
return "ApplicationsItem{" + "label = '" + label + '\'' + ",id = '" + id + '\'' + ",type = '" + type + '\''
+ ",intent = '" + intent + '\'' + ",order = '" + order + '\'' + "}";
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AvailableAppsDTO} class defines the Data Transfer Object
* for the Philips TV API /applications endpoint for retrieving all installed apps.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AvailableAppsDTO {
@JsonProperty("version")
private int version;
@JsonProperty("applications")
private @Nullable List<ApplicationsDTO> applications;
public AvailableAppsDTO() {
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
public void setApplications(List<ApplicationsDTO> applications) {
this.applications = applications;
}
public @Nullable List<ApplicationsDTO> getApplications() {
return applications;
}
@Override
public String toString() {
return "AvailableAppsDTO{" + "version = '" + version + '\'' + ",applications = '" + applications + '\'' + "}";
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link LaunchAppDTO} and {@link CurrentAppDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ComponentDTO {
@JsonProperty("className")
private String className = "";
@JsonProperty("packageName")
private String packageName = "";
public void setClassName(String className) {
this.className = className;
}
public String getClassName() {
return className;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getPackageName() {
return packageName;
}
@Override
public String toString() {
return "Component{" + "className = '" + className + '\'' + ",packageName = '" + packageName + '\'' + "}";
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link LaunchAppDTO} class defines the Data Transfer Object
* for the Philips TV API /activities/current endpoint for retrieving the current running TV app.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class CurrentAppDTO {
@JsonProperty("component")
private ComponentDTO component;
public CurrentAppDTO() {
}
public CurrentAppDTO(ComponentDTO component) {
this.component = component;
}
public void setComponent(ComponentDTO component) {
this.component = component;
}
public ComponentDTO getComponent() {
return component;
}
@Override
public String toString() {
return "Intent{" + "component = '" + component + "}";
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link IntentDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ExtrasDTO {
@JsonProperty("query")
private String query = "";
public void setQuery(String query) {
this.query = query;
}
public String getQuery() {
return query;
}
@Override
public String toString() {
return "Extras{" + "query = '" + query + '\'' + "}";
}
}

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link LaunchAppDTO} and {@link LaunchAppDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class IntentDTO {
@JsonProperty("component")
private ComponentDTO component;
@JsonProperty("action")
private String action = "";
@JsonProperty("extras")
private ExtrasDTO extras;
public IntentDTO() {
}
public IntentDTO(ComponentDTO component, ExtrasDTO extras) {
this.component = component;
this.extras = extras;
}
public void setComponent(ComponentDTO component) {
this.component = component;
}
public ComponentDTO getComponent() {
return component;
}
public void setAction(String action) {
this.action = action;
}
public String getAction() {
return action;
}
public void setExtras(ExtrasDTO extras) {
this.extras = extras;
}
public ExtrasDTO getExtras() {
return extras;
}
@Override
public String toString() {
return "Intent{" + "component = '" + component + '\'' + ",action = '" + action + '\'' + ",extras = '" + extras
+ '\'' + "}";
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.application;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link LaunchAppDTO} class defines the Data Transfer Object
* for the Philips TV API /activities/launch endpoint for launching TV apps and launching search for content.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class LaunchAppDTO {
@JsonProperty("intent")
private IntentDTO intent;
public LaunchAppDTO() {
}
public LaunchAppDTO(IntentDTO intent) {
this.intent = intent;
}
public void setIntent(IntentDTO intent) {
this.intent = intent;
}
public IntentDTO getIntent() {
return intent;
}
@Override
public String toString() {
return "LaunchAppDTO{" + "intent = '" + intent + '\'' + "}";
}
}

View File

@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.channel;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link AvailableTvChannelsDTO} class defines the Data Transfer Object
* for the Philips TV API channeldb/tv/channelLists/all endpoint for retrieving all tv channels.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class AvailableTvChannelsDTO {
@JsonProperty("Channel")
private @Nullable List<ChannelDTO> channel;
@JsonProperty("id")
private String id = "";
@JsonProperty("medium")
private String medium = "";
@JsonProperty("version")
private int version;
@JsonProperty("listType")
private String listType = "";
@JsonProperty("operator")
private String operator = "";
@JsonProperty("installCountry")
private String installCountry = "";
public AvailableTvChannelsDTO() {
}
public void setChannel(List<ChannelDTO> channel) {
this.channel = channel;
}
public @Nullable List<ChannelDTO> getChannel() {
return channel;
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setMedium(String medium) {
this.medium = medium;
}
public String getMedium() {
return medium;
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
public void setListType(String listType) {
this.listType = listType;
}
public String getListType() {
return listType;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getOperator() {
return operator;
}
public void setInstallCountry(String installCountry) {
this.installCountry = installCountry;
}
public String getInstallCountry() {
return installCountry;
}
@Override
public String toString() {
return "AvailableTvChannelsDTO{" + "channel = '" + channel + '\'' + ",id = '" + id + '\'' + ",medium = '"
+ medium + '\'' + ",version = '" + version + '\'' + ",listType = '" + listType + '\'' + ",operator = '"
+ operator + '\'' + ",installCountry = '" + installCountry + '\'' + "}";
}
}

View File

@ -0,0 +1,135 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.channel;
import org.eclipse.jdt.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvChannelDTO} and {@link AvailableTvChannelsDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ChannelDTO {
@JsonProperty("serviceType")
private String serviceType = "";
@JsonProperty("logoVersion")
private int logoVersion;
@JsonProperty("ccid")
private String ccid = "";
@JsonProperty("name")
private String name = "";
@JsonProperty("preset")
private String preset = "";
@JsonProperty("tsid")
private int tsid;
@JsonProperty("type")
private String type = "";
@JsonProperty("onid")
private int onid;
@JsonProperty("sid")
private int sid;
public void setServiceType(String serviceType) {
this.serviceType = serviceType;
}
public String getServiceType() {
return serviceType;
}
public void setLogoVersion(int logoVersion) {
this.logoVersion = logoVersion;
}
public int getLogoVersion() {
return logoVersion;
}
public void setCcid(@Nullable String ccid) {
if (!ccid.isEmpty()) {
this.ccid = ccid;
}
}
public String getCcid() {
return ccid;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setPreset(String preset) {
this.preset = preset;
}
public String getPreset() {
return preset;
}
public void setTsid(int tsid) {
this.tsid = tsid;
}
public int getTsid() {
return tsid;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setOnid(int onid) {
this.onid = onid;
}
public int getOnid() {
return onid;
}
public void setSid(int sid) {
this.sid = sid;
}
public int getSid() {
return sid;
}
@Override
public String toString() {
return "ChannelItem{" + "serviceType = '" + serviceType + '\'' + ",logoVersion = '" + logoVersion + '\''
+ ",ccid = '" + ccid + '\'' + ",name = '" + name + '\'' + ",preset = '" + preset + '\'' + ",tsid = '"
+ tsid + '\'' + ",type = '" + type + '\'' + ",onid = '" + onid + '\'' + ",sid = '" + sid + '\'' + "}";
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.channel;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Part of {@link TvChannelDTO}
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class ChannelListDTO {
@JsonProperty("id")
private String id = "";
@JsonProperty("version")
private String version = "";
public String getId() {
return id;
}
public String getVersion() {
return version;
}
public void setId(String id) {
this.id = id;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.channel;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link TvChannelDTO} class defines the Data Transfer Object
* for the Philips TV API /activities/tv endpoint to get and switch tv channels.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class TvChannelDTO {
@JsonProperty("channel")
private ChannelDTO channel;
@JsonProperty("channelList")
private ChannelListDTO channelList;
public TvChannelDTO() {
}
public TvChannelDTO(ChannelDTO channel, ChannelListDTO channelList) {
this.channel = channel;
this.channelList = channelList;
}
public ChannelDTO getChannel() {
return channel;
}
public ChannelListDTO getChannelList() {
return channelList;
}
public void setChannel(ChannelDTO channelDTO) {
this.channel = channelDTO;
}
public void setChannelList(ChannelListDTO channelList) {
this.channelList = channelList;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.keypress;
import org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPress;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link KeyPressDTO} class defines the Data Transfer Object
* for the Philips TV API /input/key endpoint for remote controller emulation.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class KeyPressDTO {
@JsonProperty("key")
private KeyPress key;
public KeyPressDTO() {
}
public KeyPressDTO(KeyPress key) {
this.key = key;
}
public KeyPress getKey() {
return key;
}
public void setKey(KeyPress key) {
this.key = key;
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.power;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.POWER_ON;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.STANDBY;
import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.STANDBYKEEP;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link PowerStateDTO} class defines the Data Transfer Object
* for the Philips TV API /powerstate endpoint to retrieve or set the current power state.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class PowerStateDTO {
@JsonProperty("powerstate")
private String powerState = "";
public PowerStateDTO() {
}
public String getPowerState() {
return powerState;
}
public void setPowerState(String powerState) {
this.powerState = powerState;
}
@JsonIgnore
public boolean isPoweredOn() {
return powerState.equalsIgnoreCase(POWER_ON);
}
@JsonIgnore
public boolean isStandby() {
return (powerState.equalsIgnoreCase(STANDBY) || powerState.equalsIgnoreCase(STANDBYKEEP));
}
@JsonIgnore
public boolean isStandbyKeep() {
return powerState.equalsIgnoreCase(STANDBYKEEP);
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2024 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.androidtv.internal.protocol.philipstv.service.model.volume;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The {@link VolumeDTO} class defines the Data Transfer Object
* for the Philips TV API /audio/volume endpoint.
*
* @author Benjamin Meyer - Initial contribution
* @author Ben Rosenblum - Merged into AndroidTV
*/
public class VolumeDTO {
@JsonProperty("current")
private int currentVolume;
@JsonProperty("muted")
private boolean muted;
public VolumeDTO() {
}
public int getCurrentVolume() {
return currentVolume;
}
public void setCurrentVolume(int currentVolume) {
this.currentVolume = currentVolume;
}
public boolean isMuted() {
return muted;
}
public void setMuted(boolean muted) {
this.muted = muted;
}
}

View File

@ -7,15 +7,17 @@ addon.androidtv.description = This is the add-on for AndroidTV.
thing-type.androidtv.googletv.label = GoogleTV thing-type.androidtv.googletv.label = GoogleTV
thing-type.androidtv.googletv.description = GoogleTV thing-type.androidtv.googletv.description = GoogleTV
thing-type.androidtv.philipstv.label = Philips TV
thing-type.androidtv.philipstv.description = A Philips TV device
thing-type.androidtv.shieldtv.label = ShieldTV thing-type.androidtv.shieldtv.label = ShieldTV
thing-type.androidtv.shieldtv.description = Nvidia ShieldTV thing-type.androidtv.shieldtv.description = Nvidia ShieldTV
# thing types config # thing types config
thing-type.config.androidtv.googletv.delay.label = Delay thing-type.config.androidtv.googletv.delay.label = Message Delay
thing-type.config.androidtv.googletv.delay.description = Delay between messages thing-type.config.androidtv.googletv.delay.description = Delay between messages
thing-type.config.androidtv.googletv.gtvEnabled.label = Enable GoogleTV thing-type.config.androidtv.googletv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.googletv.gtvEnabled.description = Enable the GoogleTV Protocol thing-type.config.androidtv.googletv.googletvPort.description = Port to connect to
thing-type.config.androidtv.googletv.heartbeat.label = Heartbeat Frequency thing-type.config.androidtv.googletv.heartbeat.label = Heartbeat Frequency
thing-type.config.androidtv.googletv.heartbeat.description = Frequency of heartbeats thing-type.config.androidtv.googletv.heartbeat.description = Frequency of heartbeats
thing-type.config.androidtv.googletv.ipAddress.label = Hostname thing-type.config.androidtv.googletv.ipAddress.label = Hostname
@ -24,12 +26,34 @@ thing-type.config.androidtv.googletv.keystoreFileName.label = Keystore File Name
thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs
thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password
thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file
thing-type.config.androidtv.googletv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.googletv.googletvPort.description = Port to connect to
thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay
thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts
thing-type.config.androidtv.philipstv.delay.label = Delay
thing-type.config.androidtv.philipstv.delay.description = Delay between messages
thing-type.config.androidtv.philipstv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.philipstv.googletvPort.description = Port to connect to
thing-type.config.androidtv.philipstv.gtvEnabled.label = Enable GoogleTV
thing-type.config.androidtv.philipstv.gtvEnabled.description = Enable the GoogleTV Protocol
thing-type.config.androidtv.philipstv.heartbeat.label = Heartbeat Frequency
thing-type.config.androidtv.philipstv.heartbeat.description = Frequency of heartbeats
thing-type.config.androidtv.philipstv.ipAddress.label = Hostname
thing-type.config.androidtv.philipstv.ipAddress.description = Hostname or IP address of the device
thing-type.config.androidtv.philipstv.keystoreFileName.label = Keystore File Name
thing-type.config.androidtv.philipstv.keystoreFileName.description = Java keystore containing key and certs
thing-type.config.androidtv.philipstv.keystorePassword.label = Keystore Password
thing-type.config.androidtv.philipstv.keystorePassword.description = Password for the keystore file
thing-type.config.androidtv.philipstv.philipstvPort.label = PhilipsTV Port
thing-type.config.androidtv.philipstv.philipstvPort.description = Port to connect to
thing-type.config.androidtv.philipstv.reconnect.label = Reconnect Delay
thing-type.config.androidtv.philipstv.reconnect.description = Delay between reconnection attempts
thing-type.config.androidtv.philipstv.refreshRate.label = Refresh Rate
thing-type.config.androidtv.philipstv.refreshRate.description = How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing.
thing-type.config.androidtv.philipstv.useUpnpDiscovery.label = Use UPnP Discovery
thing-type.config.androidtv.philipstv.useUpnpDiscovery.description = Enables UPnP Discovery. If disabled, constant HTTPS polling will happen.
thing-type.config.androidtv.shieldtv.delay.label = Delay thing-type.config.androidtv.shieldtv.delay.label = Delay
thing-type.config.androidtv.shieldtv.delay.description = Delay between messages thing-type.config.androidtv.shieldtv.delay.description = Delay between messages
thing-type.config.androidtv.shieldtv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.shieldtv.googletvPort.description = Port to connect to
thing-type.config.androidtv.shieldtv.gtvEnabled.label = Enable GoogleTV thing-type.config.androidtv.shieldtv.gtvEnabled.label = Enable GoogleTV
thing-type.config.androidtv.shieldtv.gtvEnabled.description = Enable the GoogleTV Protocol thing-type.config.androidtv.shieldtv.gtvEnabled.description = Enable the GoogleTV Protocol
thing-type.config.androidtv.shieldtv.heartbeat.label = Heartbeat Frequency thing-type.config.androidtv.shieldtv.heartbeat.label = Heartbeat Frequency
@ -40,21 +64,65 @@ thing-type.config.androidtv.shieldtv.keystoreFileName.label = Keystore File Name
thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs
thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password
thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file
thing-type.config.androidtv.shieldtv.googletvPort.label = GoogleTV Port
thing-type.config.androidtv.shieldtv.googletvPort.description = Port to connect to
thing-type.config.androidtv.shieldtv.shieldtvPort.label = ShieldTV Port
thing-type.config.androidtv.shieldtv.shieldtvPort.description = Port to connect to
thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay
thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts
thing-type.config.androidtv.shieldtv.shieldtvPort.label = ShieldTV Port
thing-type.config.androidtv.shieldtv.shieldtvPort.description = Port to connect to
# channel types # channel types
channel-type.androidtv.ambilightBottomColor.label = Bottom Ambilight
channel-type.androidtv.ambilightBottomColor.description = Sets the Ambilight color for the bottom.
channel-type.androidtv.ambilightColor.label = All Ambilight
channel-type.androidtv.ambilightColor.description = Sets the Ambilight color for all sides.
channel-type.androidtv.ambilightHuePower.label = Ambilight + Hue Power
channel-type.androidtv.ambilightHuePower.description = Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off.
channel-type.androidtv.ambilightLeftColor.label = Left Ambilight
channel-type.androidtv.ambilightLeftColor.description = Sets the Ambilight color for the left side.
channel-type.androidtv.ambilightLoungePower.label = Ambilight Lounge Power
channel-type.androidtv.ambilightLoungePower.description = Ambilight lounge power. Turns ambilight lounge on or off.
channel-type.androidtv.ambilightPower.label = Ambilight Power
channel-type.androidtv.ambilightPower.description = Ambilight power. Turns ambilight on or off.
channel-type.androidtv.ambilightRightColor.label = Right Ambilight
channel-type.androidtv.ambilightRightColor.description = Sets the Ambilight color for the right side.
channel-type.androidtv.ambilightStyle.label = Ambilight Style
channel-type.androidtv.ambilightStyle.description = Current ambilight style. Changing this to a value from the List, switches the ambilight style.
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ STANDARD = FOLLOW_VIDEO STANDARD
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ NATURAL = FOLLOW_VIDEO NATURAL
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ IMMERSIVE = FOLLOW_VIDEO IMMERSIVE
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ VIVID = FOLLOW_VIDEO VIVID
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ GAME = FOLLOW_VIDEO GAME
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ COMFORT = FOLLOW_VIDEO COMFORT
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_VIDEO\ RELAX = FOLLOW_VIDEO RELAX
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_BRIGHTNESS = FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ ENERGY_ADAPTIVE_COLORS = FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ VU_METER = FOLLOW_AUDIO VU_METER
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ SPECTRUM_ANALYZER = FOLLOW_AUDIO SPECTRUM_ANALYZER
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_CLOCKWISE = FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ KNIGHT_RIDER_ALTERNATING = FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ RANDOM_PIXEL_FLASH = FOLLOW_AUDIO RANDOM_PIXEL_FLASH
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ PARTY = FOLLOW_AUDIO PARTY
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_AUDIO\ MODE_RANDOM = FOLLOW_AUDIO MODE_RANDOM
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ MANUAL_HUE = FOLLOW_COLOR MANUAL_HUE
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ HOT_LAVA = FOLLOW_COLOR HOT_LAVA
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ DEEP_WATER = FOLLOW_COLOR DEEP_WATER
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ FRESH_NATURE = FOLLOW_COLOR FRESH_NATURE
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ ISF = FOLLOW_COLOR ISF
channel-type.androidtv.ambilightStyle.state.option.FOLLOW_COLOR\ PTA_LOUNGE = FOLLOW_COLOR PTA_LOUNGE
channel-type.androidtv.ambilightTopColor.label = Top Ambilight
channel-type.androidtv.ambilightTopColor.description = Sets the Ambilight color for the top.
channel-type.androidtv.app.label = App channel-type.androidtv.app.label = App
channel-type.androidtv.app.description = App Control channel-type.androidtv.app.description = App Control
channel-type.androidtv.appicon.label = App Icon
channel-type.androidtv.appicon.description = App Icon
channel-type.androidtv.appname.label = App Name channel-type.androidtv.appname.label = App Name
channel-type.androidtv.appname.description = App Name channel-type.androidtv.appname.description = App Name
channel-type.androidtv.appurl.label = App URL channel-type.androidtv.appurl.label = App URL
channel-type.androidtv.appurl.description = App URL channel-type.androidtv.appurl.description = App URL
channel-type.androidtv.brightness.label = Brightness
channel-type.androidtv.brightness.description = Brightness of the TV picture.
channel-type.androidtv.contrast.label = Contrast
channel-type.androidtv.contrast.description = Contrast of the TV picture.
channel-type.androidtv.debug.label = DEBUG Command channel-type.androidtv.debug.label = DEBUG Command
channel-type.androidtv.debug.description = Binding control (for debugging) channel-type.androidtv.debug.description = Binding control (for debugging)
channel-type.androidtv.keyboard.label = Keyboard channel-type.androidtv.keyboard.label = Keyboard
@ -67,6 +135,17 @@ channel-type.androidtv.pincode.label = Pin Code
channel-type.androidtv.pincode.description = Send Pin Code channel-type.androidtv.pincode.description = Send Pin Code
channel-type.androidtv.player.label = Player channel-type.androidtv.player.label = Player
channel-type.androidtv.player.description = Player Control channel-type.androidtv.player.description = Player Control
channel-type.androidtv.searchContent.label = Search Content
channel-type.androidtv.searchContent.description = Keyword(s) to search for on TV via Google Assistant
channel-type.androidtv.sharpness.label = Sharpness
channel-type.androidtv.sharpness.description = Sharpness of the TV picture.
channel-type.androidtv.tvChannel.label = TV Channel
channel-type.androidtv.tvChannel.description = Name of the currently running TV Channel. Changing this to a value from the List, switches the channel.
# thing types config
thing-type.config.androidtv.googletv.gtvEnabled.label = Enable GoogleTV
thing-type.config.androidtv.googletv.gtvEnabled.description = Enable the GoogleTV Protocol
# custom thing status # custom thing status
@ -86,5 +165,18 @@ offline.interrupted = Interrupted
offline.io-error = I/O Error offline.io-error = I/O Error
offline.runtime-exception = Runtime exception offline.runtime-exception = Runtime exception
offline.user-forced-pin-process = User Forced PIN Process offline.user-forced-pin-process = User Forced PIN Process
offline.error-occured-while-presenting-pairing-code = Error occurred while trying to present a Pairing Code on TV
offline.error-occured-during-retrieval-of-credentials = Error occurred during retrieval of credentials
offline.pairing-is-not-configured-yet = Pairing is not configured yet
offline.error-occurred-while-trying-to-present-a-pairing-code-on-tv = Error occurred while trying to present a Pairing Code on TV
offline.pairing-code-is-available-but-credentials-missing = Pairing Code is available, but credentials missing. Trying to retrieve them
offline.error-occurred-during-retrieval-of-credentials = Error occurred during retrieval of credentials
offline.pairing-was-unsuccessful = Pairing was unsuccessful
offline.error-occurred-during-creation-of-http-client = Error occurred during creation of HTTP client
offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv = Authentication with Philips TV device was successful. Continuing initialization of the tv.
offline.could-not-successfully-finish-pairing-process-with-the-tv = Could not successfully finish pairing process with the TV
offline.tv-is-not-reachable-and-should-therefore-be-off = TV is not reachable and should therefore be off
offline.tv-does-not-accept-commands-at-the-moment = TV does not accept commands at the moment
offline.unknown = Unknown offline.unknown = Unknown
online.standby = Standby
online.online = Online online.online = Online

View File

@ -157,6 +157,106 @@
<default>5</default> <default>5</default>
<advanced>true</advanced> <advanced>true</advanced>
</parameter> </parameter>
<parameter name="delay" type="integer" min="0">
<label>Message Delay</label>
<description>Delay between messages</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="philipstv">
<label>Philips TV</label>
<description>A Philips TV device</description>
<channels>
<channel id="debug" typeId="debug"/>
<channel id="keypress" typeId="keypress"/>
<channel id="keyboard" typeId="keyboard"/>
<channel id="keycode" typeId="keycode"/>
<channel id="pincode" typeId="pincode"/>
<channel id="volume" typeId="system.volume"/>
<channel id="mute" typeId="system.mute"/>
<channel id="power" typeId="system.power"/>
<channel id="brightness" typeId="brightness"/>
<channel id="contrast" typeId="contrast"/>
<channel id="sharpness" typeId="sharpness"/>
<channel id="app" typeId="app"/>
<channel id="appname" typeId="appname"/>
<channel id="appicon" typeId="appicon"/>
<channel id="tvChannel" typeId="tvChannel"/>
<channel id="player" typeId="player"/>
<channel id="searchContent" typeId="searchContent"/>
<channel id="ambilightPower" typeId="ambilightPower"/>
<channel id="ambilightHuePower" typeId="ambilightHuePower"/>
<channel id="ambilightLoungePower" typeId="ambilightLoungePower"/>
<channel id="ambilightStyle" typeId="ambilightStyle"/>
<channel id="ambilightColor" typeId="ambilightColor"/>
<channel id="ambilightLeftColor" typeId="ambilightLeftColor"/>
<channel id="ambilightRightColor" typeId="ambilightRightColor"/>
<channel id="ambilightTopColor" typeId="ambilightTopColor"/>
<channel id="ambilightBottomColor" typeId="ambilightBottomColor"/>
</channels>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>Hostname</label>
<description>Hostname or IP address of the device</description>
</parameter>
<parameter name="googletvPort" type="integer">
<label>GoogleTV Port</label>
<description>Port to connect to</description>
<default>6466</default>
<advanced>true</advanced>
</parameter>
<parameter name="philipstvPort" type="integer">
<label>PhilipsTV Port</label>
<description>Port to connect to</description>
<default>1926</default>
<advanced>true</advanced>
</parameter>
<parameter name="refreshRate" type="integer">
<label>Refresh Rate</label>
<description>
How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing.
</description>
<advanced>true</advanced>
<default>10</default>
</parameter>
<parameter name="useUpnpDiscovery" type="boolean">
<label>Use UPnP Discovery</label>
<description>
Enables UPnP Discovery. If disabled, constant HTTPS polling will happen.
</description>
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="keystoreFileName" type="text">
<label>Keystore File Name</label>
<description>Java keystore containing key and certs</description>
<advanced>true</advanced>
</parameter>
<parameter name="keystorePassword" type="text">
<context>password</context>
<label>Keystore Password</label>
<description>Password for the keystore file</description>
<advanced>true</advanced>
</parameter>
<parameter name="reconnect" type="integer" min="0">
<label>Reconnect Delay</label>
<description>Delay between reconnection attempts</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
<parameter name="heartbeat" type="integer" min="0">
<label>Heartbeat Frequency</label>
<description>Frequency of heartbeats</description>
<default>5</default>
<advanced>true</advanced>
</parameter>
<parameter name="delay" type="integer" min="0"> <parameter name="delay" type="integer" min="0">
<label>Delay</label> <label>Delay</label>
<description>Delay between messages</description> <description>Delay between messages</description>
@ -197,6 +297,12 @@
<description>App URL</description> <description>App URL</description>
</channel-type> </channel-type>
<channel-type id="appicon" advanced="true">
<item-type>Image</item-type>
<label>App Icon</label>
<description>App Icon</description>
</channel-type>
<channel-type id="keypress"> <channel-type id="keypress">
<item-type>String</item-type> <item-type>String</item-type>
<label>Key Press</label> <label>Key Press</label>
@ -227,4 +333,131 @@
<description>Player Control</description> <description>Player Control</description>
</channel-type> </channel-type>
<channel-type id="tvChannel" advanced="true">
<item-type>String</item-type>
<label>TV Channel</label>
<description>Name of the currently running TV Channel. Changing this to a value from the List, switches the
channel.
</description>
</channel-type>
<channel-type id="searchContent" advanced="true">
<item-type>String</item-type>
<label>Search Content</label>
<description>Keyword(s) to search for on TV via Google Assistant</description>
</channel-type>
<channel-type id="ambilightPower">
<item-type>Switch</item-type>
<label>Ambilight Power</label>
<description>Ambilight power. Turns ambilight on or off.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightHuePower">
<item-type>Switch</item-type>
<label>Ambilight + Hue Power</label>
<description>Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightLoungePower">
<item-type>Switch</item-type>
<label>Ambilight Lounge Power</label>
<description>Ambilight lounge power. Turns ambilight lounge on or off.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightStyle" advanced="true">
<item-type>String</item-type>
<label>Ambilight Style</label>
<description>Current ambilight style. Changing this to a value from the List, switches the ambilight style.
</description>
<category>Ambilight</category>
<state>
<options>
<option value="FOLLOW_VIDEO STANDARD">FOLLOW_VIDEO STANDARD</option>
<option value="FOLLOW_VIDEO NATURAL">FOLLOW_VIDEO NATURAL</option>
<option value="FOLLOW_VIDEO IMMERSIVE">FOLLOW_VIDEO IMMERSIVE</option>
<option value="FOLLOW_VIDEO VIVID">FOLLOW_VIDEO VIVID</option>
<option value="FOLLOW_VIDEO GAME">FOLLOW_VIDEO GAME</option>
<option value="FOLLOW_VIDEO COMFORT">FOLLOW_VIDEO COMFORT</option>
<option value="FOLLOW_VIDEO RELAX">FOLLOW_VIDEO RELAX</option>
<option value="FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS">FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS</option>
<option value="FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS">FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS</option>
<option value="FOLLOW_AUDIO VU_METER">FOLLOW_AUDIO VU_METER</option>
<option value="FOLLOW_AUDIO SPECTRUM_ANALYZER">FOLLOW_AUDIO SPECTRUM_ANALYZER</option>
<option value="FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE">FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE</option>
<option value="FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING">FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING</option>
<option value="FOLLOW_AUDIO RANDOM_PIXEL_FLASH">FOLLOW_AUDIO RANDOM_PIXEL_FLASH</option>
<option value="FOLLOW_AUDIO PARTY">FOLLOW_AUDIO PARTY</option>
<option value="FOLLOW_AUDIO MODE_RANDOM">FOLLOW_AUDIO MODE_RANDOM</option>
<option value="FOLLOW_COLOR MANUAL_HUE">FOLLOW_COLOR MANUAL_HUE</option>
<option value="FOLLOW_COLOR HOT_LAVA">FOLLOW_COLOR HOT_LAVA</option>
<option value="FOLLOW_COLOR DEEP_WATER">FOLLOW_COLOR DEEP_WATER</option>
<option value="FOLLOW_COLOR FRESH_NATURE">FOLLOW_COLOR FRESH_NATURE</option>
<option value="FOLLOW_COLOR ISF">FOLLOW_COLOR ISF</option>
<option value="FOLLOW_COLOR PTA_LOUNGE">FOLLOW_COLOR PTA_LOUNGE</option>
</options>
</state>
</channel-type>
<channel-type id="ambilightColor" advanced="true">
<item-type>Color</item-type>
<label>All Ambilight</label>
<description>Sets the Ambilight color for all sides.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightLeftColor" advanced="true">
<item-type>Color</item-type>
<label>Left Ambilight</label>
<description>Sets the Ambilight color for the left side.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightRightColor" advanced="true">
<item-type>Color</item-type>
<label>Right Ambilight</label>
<description>Sets the Ambilight color for the right side.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightTopColor" advanced="true">
<item-type>Color</item-type>
<label>Top Ambilight</label>
<description>Sets the Ambilight color for the top.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="ambilightBottomColor" advanced="true">
<item-type>Color</item-type>
<label>Bottom Ambilight</label>
<description>Sets the Ambilight color for the bottom.</description>
<category>Ambilight</category>
</channel-type>
<channel-type id="brightness" advanced="true">
<item-type>Dimmer</item-type>
<label>Brightness</label>
<description>Brightness of the TV picture.</description>
<category>Tv Picture</category>
</channel-type>
<channel-type id="contrast" advanced="true">
<item-type>Dimmer</item-type>
<label>Contrast</label>
<description>Contrast of the TV picture.</description>
<category>Tv Picture</category>
</channel-type>
<channel-type id="sharpness" advanced="true">
<item-type>Dimmer</item-type>
<label>Sharpness</label>
<description>Sharpness of the TV picture.</description>
<category>Tv Picture</category>
</channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>