From 7327d56adcaeefce182675a4205703c3542fd52a Mon Sep 17 00:00:00 2001 From: morph166955 <53797132+morph166955@users.noreply.github.com> Date: Fri, 9 Feb 2024 03:05:48 -0600 Subject: [PATCH] [androidtv] Add PhilipsTV protocol Signed-off-by: Ben Rosenblum Signed-off-by: morph166955 <53797132+morph166955@users.noreply.github.com> Signed-off-by: Ciprian Pascu --- bundles/org.openhab.binding.androidtv/NOTICE | 18 + .../org.openhab.binding.androidtv/README.md | 126 +++- bundles/org.openhab.binding.androidtv/pom.xml | 34 + .../src/main/feature/feature.xml | 1 + .../internal/AndroidTVBindingConstants.java | 22 +- ...roidTVDynamicStateDescriptionProvider.java | 66 ++ .../androidtv/internal/AndroidTVHandler.java | 98 ++- .../internal/AndroidTVHandlerFactory.java | 16 +- .../protocol/philipstv/ConnectionManager.java | 129 ++++ .../philipstv/ConnectionManagerUtil.java | 98 +++ .../philipstv/PhilipsTVBindingConstants.java | 105 +++ .../philipstv/PhilipsTVConfiguration.java | 31 + .../philipstv/PhilipsTVConnectionManager.java | 696 ++++++++++++++++++ .../protocol/philipstv/WakeOnLanUtil.java | 136 ++++ .../PhilipsTVDiscoveryParticipant.java | 103 +++ .../philipstv/pairing/PhilipsTVPairing.java | 204 +++++ .../philipstv/pairing/model/AuthDTO.java | 77 ++ .../philipstv/pairing/model/DeviceDTO.java | 100 +++ .../pairing/model/FinishPairingDTO.java | 60 ++ .../philipstv/pairing/model/PairingDTO.java | 65 ++ .../pairing/model/RequestCodeDTO.java | 62 ++ .../philipstv/service/AmbilightService.java | 316 ++++++++ .../philipstv/service/AppService.java | 207 ++++++ .../protocol/philipstv/service/KeyPress.java | 100 +++ .../philipstv/service/KeyPressService.java | 107 +++ .../philipstv/service/PowerService.java | 116 +++ .../service/SearchContentService.java | 90 +++ .../philipstv/service/ServiceUtil.java | 57 ++ .../philipstv/service/TvChannelService.java | 147 ++++ .../philipstv/service/TvPictureService.java | 132 ++++ .../philipstv/service/VolumeService.java | 107 +++ .../service/api/PhilipsTVService.java | 61 ++ .../philipstv/service/model/DataDTO.java | 47 ++ .../philipstv/service/model/NodesDTO.java | 40 + .../service/model/TvSettingsCurrentDTO.java | 51 ++ .../service/model/TvSettingsUpdateDTO.java | 51 ++ .../philipstv/service/model/ValueDTO.java | 81 ++ .../philipstv/service/model/ValuesDTO.java | 47 ++ .../model/ambilight/AmbilightColorDTO.java | 75 ++ .../ambilight/AmbilightColorDeltaDTO.java | 64 ++ .../ambilight/AmbilightColorSettingsDTO.java | 69 ++ .../model/ambilight/AmbilightConfigDTO.java | 93 +++ .../model/ambilight/AmbilightLoungeDTO.java | 46 ++ .../model/ambilight/AmbilightModeDTO.java | 52 ++ .../model/ambilight/AmbilightPowerDTO.java | 45 ++ .../model/ambilight/AmbilightTopologyDTO.java | 124 ++++ .../model/application/ApplicationsDTO.java | 92 +++ .../model/application/AvailableAppsDTO.java | 59 ++ .../model/application/ComponentDTO.java | 52 ++ .../model/application/CurrentAppDTO.java | 48 ++ .../service/model/application/ExtrasDTO.java | 41 ++ .../service/model/application/IntentDTO.java | 72 ++ .../model/application/LaunchAppDTO.java | 49 ++ .../model/channel/AvailableTvChannelsDTO.java | 116 +++ .../service/model/channel/ChannelDTO.java | 135 ++++ .../service/model/channel/ChannelListDTO.java | 47 ++ .../service/model/channel/TvChannelDTO.java | 55 ++ .../service/model/keypress/KeyPressDTO.java | 46 ++ .../service/model/power/PowerStateDTO.java | 59 ++ .../service/model/volume/VolumeDTO.java | 50 ++ .../OH-INF/i18n/androidtv.properties | 110 ++- .../resources/OH-INF/thing/thing-types.xml | 233 ++++++ 62 files changed, 5795 insertions(+), 41 deletions(-) create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java create mode 100644 bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java diff --git a/bundles/org.openhab.binding.androidtv/NOTICE b/bundles/org.openhab.binding.androidtv/NOTICE index 38d625e3492..489fa7b4b68 100644 --- a/bundles/org.openhab.binding.androidtv/NOTICE +++ b/bundles/org.openhab.binding.androidtv/NOTICE @@ -11,3 +11,21 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code 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 + diff --git a/bundles/org.openhab.binding.androidtv/README.md b/bundles/org.openhab.binding.androidtv/README.md index 1fce2128de7..30c6660dc55 100644 --- a/bundles/org.openhab.binding.androidtv/README.md +++ b/bundles/org.openhab.binding.androidtv/README.md @@ -1,8 +1,9 @@ # AndroidTV Binding 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 PhilipsTV App to interact with a 2016+ PhilipsTV for purposes of remote control. ## Supported Things @@ -10,14 +11,15 @@ This binding supports two thing types: - **googletv** - An AndroidTV running Google Video - **shieldtv** - An Nvidia ShieldTV +- **philipstv** - A 2016+ Philips TV ## 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. -Only the ShieldTV device should be configured, the GoogleTV can be ignored. -There is no benefit to configuring two things for 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 or PhilipsTV device should be configured, the GoogleTV can be ignored. +There is no benefit to configuring two things for a ShieldTV or PhilipsTV device. This could cause undesired effects. ## Binding Configuration @@ -30,37 +32,58 @@ This binding requires GoogleTV to be installed on the device (https://play.googl ## 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 | |------------------|---------|---------------------------------------|---------|----------|----------| | ipAddress | text | IP address of the device | N/A | yes | no | -| googletvPort | text | TCP Port for GoogleTV | 6466 | no | no | -| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | no | -| keystore | text | Location of the Java Keystore | N/A | no | no | -| keystorePassword | text | Password of the Java Keystore | N/A | no | no | -| gtvEnabled | boolean | Enable/Disable the GoogleTV protocol | true | no | no | +| googletvPort | text | TCP Port for GoogleTV | 6466 | no | yes | +| shieldtvPort | text | TCP Port for ShieldTV | 8987 | no | yes | +| philipstvPort | text | TCP Port for PhilipsTV | 1926 | no | yes | +| keystoreFileName | text | Location of the Java Keystore | N/A | no | yes | +| 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 Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] +Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ] ``` ## Channels -| Channel | Type | Description | GoogleTV | ShieldTV | -|------------|--------|-----------------------------|----------|----------| -| keyboard | String | Keyboard Data Entry | RW | RW | -| keypress | String | Manual Key Press Entry | RW | RW | -| keycode | String | Direct KEYCODE Entry | RW | RW | -| pincode | String | PIN Code Entry | RW | RW | -| app | String | App Control | RO | RW | -| appname | String | App Name | N/A | RW | -| appurl | String | App URL | N/A | RW | -| player | Player | Player Control | RW | RW | -| power | Switch | Power Control | RW | RW | -| volume | Dimmer | Volume Control | RO | RO | -| mute | Switch | Mute Control | RW | RW | +| Channel | Type | Description | GoogleTV | ShieldTV | PhilipsTV | +|----------------------|--------|--------------------------------------|----------|----------|-----------| +| keyboard | String | Keyboard Data Entry | RW | RW | RW | +| keypress | String | Manual Key Press Entry | RW | RW | RW | +| keycode | String | Direct KEYCODE Entry | RW | RW | RW | +| pincode | String | PIN Code Entry | RW | RW | RW | +| app | String | App Control | RO | RW | RW | +| appname | String | App Name | N/A | RW | RW | +| appurl | String | App URL | N/A | RO | N/A | +| appicon | Image | App Icon | N/A | N/A | RO | +| player | Player | Player Control | RW | RW | RW | +| power | Switch | Power Control | RW | 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 @@ -76,6 +99,32 @@ Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:po Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" } 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_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } 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. -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. @@ -179,6 +228,7 @@ Upon reconnection (either from reconfiguration or a restart of OpenHAB), you sho ```java Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ] Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ] +Thing androidtv:philipstv:bedroom [ ipAddress="192.168.1.4" ] ``` ```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" } 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_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" } String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" } diff --git a/bundles/org.openhab.binding.androidtv/pom.xml b/bundles/org.openhab.binding.androidtv/pom.xml index 662c89ad214..b4379e26134 100644 --- a/bundles/org.openhab.binding.androidtv/pom.xml +++ b/bundles/org.openhab.binding.androidtv/pom.xml @@ -14,6 +14,10 @@ openHAB Add-ons :: Bundles :: AndroidTV Binding + + !net.sf.ehcache.*,!net.spy.* + + org.bouncycastle @@ -33,6 +37,36 @@ 1.75 compile + + org.apache.httpcomponents + httpclient-osgi + 4.5.14 + compile + + + org.apache.httpcomponents + httpcore-osgi + 4.4.16 + compile + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + compile + diff --git a/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml b/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml index 47047748782..1249665cd8c 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml @@ -4,6 +4,7 @@ openhab-runtime-base + openhab-transport-upnp mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java index 776122e5f86..e5ad0e34dbd 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java @@ -31,8 +31,9 @@ public class AndroidTVBindingConstants { // List of all Thing Type UIDs 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 Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV); + public static final ThingTypeUID THING_TYPE_PHILIPSTV = new ThingTypeUID(BINDING_ID, "philipstv"); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV, + THING_TYPE_PHILIPSTV); // List of all Channel ids 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_APPNAME = "appname"; 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_VOLUME = "volume"; public static final String CHANNEL_MUTE = "mute"; 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 public static final String PARAMETER_IP_ADDRESS = "ipAddress"; public static final String PARAMETER_GOOGLETV_PORT = "googletvPort"; public static final String PARAMETER_SHIELDTV_PORT = "shieldtvPort"; + public static final String PARAMETER_PHILIPSTV_PORT = "philipstvPort"; public static final String PARAMETER_GTV_ENABLED = "gtvEnabled"; // List of all static String literals diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java new file mode 100644 index 00000000000..3cf1557891c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java @@ -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> channelOptionsMap = new ConcurrentHashMap<>(); + + public void setStateOptions(ChannelUID channelUID, List options) { + channelOptionsMap.put(channelUID, options); + } + + @Override + public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, + @Nullable Locale locale) { + List 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(); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java index dd2660b3cc1..c29aba647fe 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java @@ -25,14 +25,19 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; 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.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.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.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; import org.openhab.core.types.CommandOption; @@ -55,6 +60,7 @@ public class AndroidTVHandler extends BaseThingHandler { private @Nullable ShieldTVConnectionManager shieldtvConnectionManager; private @Nullable GoogleTVConnectionManager googletvConnectionManager; + private @Nullable PhilipsTVConnectionManager philipstvConnectionManager; private @Nullable ScheduledFuture monitorThingStatusJob; private final Object monitorThingStatusJobLock = new Object(); @@ -68,13 +74,20 @@ public class AndroidTVHandler extends BaseThingHandler { private String currentThingStatus = ""; private boolean currentThingFailed = false; + private DiscoveryServiceRegistry discoveryServiceRegistry; + + private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider; + public AndroidTVHandler(Thing thing, AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider, - AndroidTVTranslationProvider translationProvider, ThingTypeUID thingTypeUID) { + AndroidTVTranslationProvider translationProvider, DiscoveryServiceRegistry discoveryServiceRegistry, + AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider, ThingTypeUID thingTypeUID) { super(thing); this.commandDescriptionProvider = commandDescriptionProvider; this.translationProvider = translationProvider; this.thingTypeUID = thingTypeUID; this.thingID = this.getThing().getUID().getId(); + this.discoveryServiceRegistry = discoveryServiceRegistry; + this.stateDescriptionProvider = stateDescriptionProvider; } public void setThingProperty(String property, String value) { @@ -89,6 +102,22 @@ public class AndroidTVHandler extends BaseThingHandler { 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) { updateState(channel, state); } @@ -122,6 +151,7 @@ public class AndroidTVHandler extends BaseThingHandler { GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (googletvConnectionManager != null) { 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 (failed) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, statusMessage); @@ -186,14 +225,30 @@ public class AndroidTVHandler extends BaseThingHandler { 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, TimeUnit.MILLISECONDS); } public void sendCommandToProtocol(ChannelUID channelUID, Command command) { ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) { 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; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (CHANNEL_DEBUG.equals(channelUID.getId())) { if (command instanceof StringType) { @@ -231,10 +287,18 @@ public class AndroidTVHandler extends BaseThingHandler { ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class); shieldtvConfig.shim = true; 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)) { googletvConnectionManager.handleCommand(channelUID, command); } else if (command.toString().startsWith("SHIELDTV") && (shieldtvConnectionManager != null)) { shieldtvConnectionManager.handleCommand(channelUID, command); + } else if (command.toString().startsWith("PHILIPSTV") && (philipstvConnectionManager != null)) { + philipstvConnectionManager.handleCommand(channelUID, command); } } 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) { googletvConnectionManager.handleCommand(channelUID, command); return; @@ -279,11 +370,16 @@ public class AndroidTVHandler extends BaseThingHandler { GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager; ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager; + PhilipsTVConnectionManager philipstvConnectionManager = this.philipstvConnectionManager; if (shieldtvConnectionManager != null) { shieldtvConnectionManager.dispose(); } + if (philipstvConnectionManager != null) { + philipstvConnectionManager.dispose(); + } + if (googletvConnectionManager != null) { googletvConnectionManager.dispose(); } diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java index 4b2803b3ec6..6abf7346c9f 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java @@ -18,6 +18,7 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.discovery.DiscoveryServiceRegistry; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.Thing; @@ -39,18 +40,24 @@ import org.osgi.service.component.annotations.Reference; @Component(configurationPid = "binding.androidtv", service = ThingHandlerFactory.class) public class AndroidTVHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, - THING_TYPE_SHIELDTV); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV, + THING_TYPE_PHILIPSTV); private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider; private final AndroidTVTranslationProvider translationProvider; + private final DiscoveryServiceRegistry discoveryServiceRegistry; + private final AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider; @Activate public AndroidTVHandlerFactory( 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.translationProvider = new AndroidTVTranslationProvider(i18nProvider, localeProvider); + this.discoveryServiceRegistry = discoveryServiceRegistry; + this.stateDescriptionProvider = stateDescriptionProvider; } @Override @@ -61,6 +68,7 @@ public class AndroidTVHandlerFactory extends BaseThingHandlerFactory { @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, thingTypeUID); + return new AndroidTVHandler(thing, commandDescriptionProvider, translationProvider, discoveryServiceRegistry, + stateDescriptionProvider, thingTypeUID); } } diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java new file mode 100644 index 00000000000..f282666fdf0 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java @@ -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(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java new file mode 100644 index 00000000000..49a31e7186c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java @@ -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 socketFactoryRegistry = RegistryBuilder. 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(); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java new file mode 100644 index 00000000000..d57bb924d0f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java @@ -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"; +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java new file mode 100644 index 00000000000..e29bad84345 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java @@ -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 = ""; +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java new file mode 100644 index 00000000000..30c49311eac --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java @@ -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> 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 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 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 configMap = OBJECT_MAPPER.readValue(configJson, + new TypeReference>() { + }); + 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 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 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 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 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 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 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 values) { + AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider = this.stateDescriptionProvider; + List 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 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 removeOlderResults(DiscoveryService discoveryService, long l, + @Nullable Collection 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); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java new file mode 100644 index 00000000000..c157244e7b3 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java @@ -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 IS_WOL_ENABLED = j -> j.get("wake-on-lan").asText() + .equalsIgnoreCase("Enabled"); + + private static final Predicate IS_NOT_LOOPBACK = ni -> { + try { + return !ni.isLoopback(); + } catch (SocketException e) { + return false; + } + }; + + private WakeOnLanUtil() { + } + + public static Optional getMacFromEnabledInterface(ConnectionManager connectionManager) throws IOException { + String jsonContent = connectionManager.doHttpsGet(GET_NETWORK_DEVICES_PATH); + List jsonNode = OBJECT_MAPPER.readValue(jsonContent, new TypeReference>() { + }); + + 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 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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java new file mode 100644 index 00000000000..632dc505205 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java @@ -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 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 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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java new file mode 100644 index 00000000000..8b22a977f5c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java @@ -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())); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java new file mode 100644 index 00000000000..47f7e4ae1ad --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java new file mode 100644 index 00000000000..a63ffcc6cf2 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java @@ -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 + '\'' + + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java new file mode 100644 index 00000000000..c28ea0989d2 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java new file mode 100644 index 00000000000..bfe1175d837 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java new file mode 100644 index 00000000000..e72a0b47d07 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java @@ -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 scope; + + @JsonProperty("device") + private DeviceDTO device; + + public RequestCodeDTO(List scope, DeviceDTO device) { + this.scope = scope; + this.device = device; + } + + public void setScope(List scope) { + this.scope = scope; + } + + public List 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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java new file mode 100644 index 00000000000..3cf2f5c97e0 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java @@ -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 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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java new file mode 100644 index 00000000000..8e893e29d65 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java @@ -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 of App + private @Nullable Map> 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>> app = availableApps.entrySet() + .stream().filter(e -> e.getValue().getKey().equalsIgnoreCase(packageName)).findFirst(); + if (app.isPresent()) { + handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName)); + Map.Entry> 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> entry : availableApps.entrySet()) { + Map.Entry 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 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> 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(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java new file mode 100644 index 00000000000..96fd973cfce --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java @@ -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 http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html + * + * + * + * @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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java new file mode 100644 index 00000000000..fb61c3844d3 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java new file mode 100644 index 00000000000..fcf9e211f44 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java new file mode 100644 index 00000000000..344d1222d56 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java new file mode 100644 index 00000000000..a2b62f4a45f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java new file mode 100644 index 00000000000..53ae7fb3c39 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java @@ -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 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 getAvailableTvChannelListFromTv() throws IOException { + AvailableTvChannelsDTO availableTvChannelsDTO = OBJECT_MAPPER.readValue( + connectionManager.doHttpsGet(GET_AVAILABLE_TV_CHANNEL_LIST_PATH), AvailableTvChannelsDTO.class); + + ConcurrentMap 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(); + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java new file mode 100644 index 00000000000..214d843a2d4 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java new file mode 100644 index 00000000000..7fa2859415f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java new file mode 100644 index 00000000000..20b900b32d6 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java @@ -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; + } + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java new file mode 100644 index 00000000000..b065f44d94c --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java new file mode 100644 index 00000000000..feb909ae51e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java new file mode 100644 index 00000000000..321252394f2 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java @@ -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 nodes; + + public TvSettingsCurrentDTO() { + } + + public TvSettingsCurrentDTO(List nodes) { + this.nodes = nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public List getNodes() { + return nodes; + } + + @Override + public String toString() { + return "TvSettingsCurrentDTO{" + "nodes = '" + nodes + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java new file mode 100644 index 00000000000..b6210cdf2a2 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java @@ -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 values; + + public TvSettingsUpdateDTO() { + } + + public TvSettingsUpdateDTO(List values) { + this.values = values; + } + + public void setValues(List values) { + this.values = values; + } + + public List getValues() { + return values; + } + + @Override + public String toString() { + return "TvSettingsDTO{" + "values = '" + values + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java new file mode 100644 index 00000000000..e3113fc879e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java new file mode 100644 index 00000000000..e4a2e8d72db --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java new file mode 100644 index 00000000000..f1d9b885441 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java new file mode 100644 index 00000000000..96315011430 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java new file mode 100644 index 00000000000..fdae3bc3eee --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java new file mode 100644 index 00000000000..584a3358e24 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java new file mode 100644 index 00000000000..8f0fbf36429 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java new file mode 100644 index 00000000000..bc4ef0d5f1e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java @@ -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. + *

+ * current (string): One of following values: + *

+ * internal: The internal ambilight algorithm is used to calculate the ambilight colours. + *

+ * manual: The cached ambilight colours are shown. + *

+ * 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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java new file mode 100644 index 00000000000..21793b7082e --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java new file mode 100644 index 00000000000..4393d9fa1a0 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java @@ -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. + *

+ * Endpoint returns: + *

+ * layers (integer): The number of layers. + *

+ * left (integer): The number of pixels on the left. + *

+ * top (integer): The number of pixels on the top. + *

+ * right (integer): The number of pixels on the right. + *

+ * 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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java new file mode 100644 index 00000000000..b30b6408d98 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java new file mode 100644 index 00000000000..6097790c620 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java @@ -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 applications; + + public AvailableAppsDTO() { + } + + public void setVersion(int version) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public void setApplications(List applications) { + this.applications = applications; + } + + public @Nullable List getApplications() { + return applications; + } + + @Override + public String toString() { + return "AvailableAppsDTO{" + "version = '" + version + '\'' + ",applications = '" + applications + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java new file mode 100644 index 00000000000..bddad4304df --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java new file mode 100644 index 00000000000..e5dae87ae87 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java @@ -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 + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java new file mode 100644 index 00000000000..5019c33193d --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java new file mode 100644 index 00000000000..53ac1fed71f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java @@ -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 + + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java new file mode 100644 index 00000000000..67fda9c421f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java new file mode 100644 index 00000000000..fc10fd820cf --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java @@ -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 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 channel) { + this.channel = channel; + } + + public @Nullable List 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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java new file mode 100644 index 00000000000..0ef476fdab5 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java @@ -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 + '\'' + "}"; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java new file mode 100644 index 00000000000..db6f13e060f --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java new file mode 100644 index 00000000000..b025452e6a5 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java new file mode 100644 index 00000000000..655fb265f74 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java new file mode 100644 index 00000000000..621379fa3b4 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java new file mode 100644 index 00000000000..74dad6504e1 --- /dev/null +++ b/bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties index 3e644d9af3f..516cb24bbe4 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties +++ b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties @@ -7,15 +7,17 @@ addon.androidtv.description = This is the add-on for AndroidTV. thing-type.androidtv.googletv.label = 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.description = Nvidia ShieldTV # 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.gtvEnabled.label = Enable GoogleTV -thing-type.config.androidtv.googletv.gtvEnabled.description = Enable the GoogleTV Protocol +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.heartbeat.label = Heartbeat Frequency thing-type.config.androidtv.googletv.heartbeat.description = Frequency of heartbeats 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.keystorePassword.label = Keystore Password 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.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.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.description = Enable the GoogleTV Protocol 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.keystorePassword.label = Keystore Password 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.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-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.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.description = App Name channel-type.androidtv.appurl.label = 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.description = Binding control (for debugging) 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.player.label = Player 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 @@ -86,5 +165,18 @@ offline.interrupted = Interrupted offline.io-error = I/O Error offline.runtime-exception = Runtime exception 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 +online.standby = Standby online.online = Online diff --git a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml index 8e5c032c0da..9fd3c643df1 100644 --- a/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml @@ -157,6 +157,106 @@ 5 true + + + Delay between messages + 0 + true + + + + + + + + A Philips TV device + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + network-address + + Hostname or IP address of the device + + + + Port to connect to + 6466 + true + + + + Port to connect to + 1926 + true + + + + + How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing. + + true + 10 + + + + + Enables UPnP Discovery. If disabled, constant HTTPS polling will happen. + + true + true + + + + Java keystore containing key and certs + true + + + password + + Password for the keystore file + true + + + + Delay between reconnection attempts + 60 + true + + + + Frequency of heartbeats + 5 + true + Delay between messages @@ -197,6 +297,12 @@ App URL + + Image + + App Icon + + String @@ -227,4 +333,131 @@ Player Control + + String + + Name of the currently running TV Channel. Changing this to a value from the List, switches the + channel. + + + + + String + + Keyword(s) to search for on TV via Google Assistant + + + + Switch + + Ambilight power. Turns ambilight on or off. + Ambilight + + + + Switch + + Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off. + Ambilight + + + + Switch + + Ambilight lounge power. Turns ambilight lounge on or off. + Ambilight + + + + String + + Current ambilight style. Changing this to a value from the List, switches the ambilight style. + + Ambilight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Color + + Sets the Ambilight color for all sides. + Ambilight + + + + Color + + Sets the Ambilight color for the left side. + Ambilight + + + + Color + + Sets the Ambilight color for the right side. + Ambilight + + + + Color + + Sets the Ambilight color for the top. + Ambilight + + + + Color + + Sets the Ambilight color for the bottom. + Ambilight + + + + Dimmer + + Brightness of the TV picture. + Tv Picture + + + + Dimmer + + Contrast of the TV picture. + Tv Picture + + + + Dimmer + + Sharpness of the TV picture. + Tv Picture + +