]> git.basschouten.com Git - openhab-addons.git/commitdiff
[androidtv] Add PhilipsTV protocol
authormorph166955 <53797132+morph166955@users.noreply.github.com>
Fri, 9 Feb 2024 09:05:48 +0000 (03:05 -0600)
committerGitHub <noreply@github.com>
Fri, 9 Feb 2024 09:05:48 +0000 (10:05 +0100)
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
Signed-off-by: morph166955 <53797132+morph166955@users.noreply.github.com>
62 files changed:
bundles/org.openhab.binding.androidtv/NOTICE
bundles/org.openhab.binding.androidtv/README.md
bundles/org.openhab.binding.androidtv/pom.xml
bundles/org.openhab.binding.androidtv/src/main/feature/feature.xml
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVBindingConstants.java
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVDynamicStateDescriptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandler.java
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/AndroidTVHandlerFactory.java
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManager.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/ConnectionManagerUtil.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/PhilipsTVConnectionManager.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/WakeOnLanUtil.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/discovery/PhilipsTVDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/PhilipsTVPairing.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/AuthDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/DeviceDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/FinishPairingDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/PairingDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/pairing/model/RequestCodeDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AmbilightService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/AppService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPress.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/KeyPressService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/PowerService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/SearchContentService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/ServiceUtil.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvChannelService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/TvPictureService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/VolumeService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/api/PhilipsTVService.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/DataDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/NodesDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsCurrentDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/TvSettingsUpdateDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValueDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ValuesDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorDeltaDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightColorSettingsDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightConfigDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightLoungeDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightModeDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightPowerDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/ambilight/AmbilightTopologyDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ApplicationsDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/AvailableAppsDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ComponentDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/CurrentAppDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/ExtrasDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/IntentDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/application/LaunchAppDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/AvailableTvChannelsDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/ChannelListDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/channel/TvChannelDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/keypress/KeyPressDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/power/PowerStateDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/java/org/openhab/binding/androidtv/internal/protocol/philipstv/service/model/volume/VolumeDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/i18n/androidtv.properties
bundles/org.openhab.binding.androidtv/src/main/resources/OH-INF/thing/thing-types.xml

index 38d625e349232ff5ffcc71bd75e4692cdac12768..489fa7b4b682156c9a74b23b6e9620da1df663ab 100644 (file)
@@ -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
+
index 1fce2128de78bdf28035851a51905bcfda396966..30c6660dc55eecdf2cc2084235687e4099df77bf 100644 (file)
@@ -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" }
index 662c89ad21446d7eeae771dc3413305ea259e952..b4379e26134241babcdb7648385e780542a7788c 100644 (file)
 
   <name>openHAB Add-ons :: Bundles :: AndroidTV Binding</name>
 
+  <properties>
+    <bnd.importpackage>!net.sf.ehcache.*,!net.spy.*</bnd.importpackage>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>org.bouncycastle</groupId>
       <version>1.75</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient-osgi</artifactId>
+      <version>4.5.14</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore-osgi</artifactId>
+      <version>4.4.16</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+      <version>${jackson.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+      <version>${jackson.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>${jackson.version}</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 
 </project>
index 4704774878262aff12bd6b193cf839cbe4ae6072..1249665cd8c9255043abf8bcd3a8d0fb2a78b929 100644 (file)
@@ -4,6 +4,7 @@
 
        <feature name="openhab-binding-androidtv" description="AndroidTV Binding" version="${project.version}">
                <feature>openhab-runtime-base</feature>
+               <feature>openhab-transport-upnp</feature>
                <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version}</bundle>
        </feature>
 </features>
index 776122e5f8669d1981d7f85263cc8253dd7ac1d3..e5ad0e34dbd6bfd24855d543ab747eccefa29636 100644 (file)
@@ -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<ThingTypeUID> 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<ThingTypeUID> 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 (file)
index 0000000..3cf1557
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
+import org.openhab.core.types.StateDescription;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.StateOption;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+
+/**
+ * Dynamic provider of state options while leaving other state description fields as original.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ */
+@Component(service = { DynamicStateDescriptionProvider.class,
+        AndroidTVDynamicStateDescriptionProvider.class }, immediate = true)
+@NonNullByDefault
+public class AndroidTVDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
+    private final Map<ChannelUID, List<@NonNull StateOption>> channelOptionsMap = new ConcurrentHashMap<>();
+
+    public void setStateOptions(ChannelUID channelUID, List<StateOption> options) {
+        channelOptionsMap.put(channelUID, options);
+    }
+
+    @Override
+    public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
+            @Nullable Locale locale) {
+        List<StateOption> options = channelOptionsMap.get(channel.getUID());
+
+        if (options == null) {
+            return null;
+        }
+        if (original != null) {
+            return StateDescriptionFragmentBuilder.create(original).withOptions(options).build().toStateDescription();
+        }
+
+        return StateDescriptionFragmentBuilder.create().withOptions(options).build().toStateDescription();
+    }
+
+    @Deactivate
+    public void deactivate() {
+        channelOptionsMap.clear();
+    }
+}
index dd2660b3cc123f54a63bd1e14eeeced90589d841..c29aba647fe4222e577f8c0687de74b6342c97a7 100644 (file)
@@ -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();
         }
index 4b2803b3ec649b000d159175df578ab585c5f155..6abf7346c9f2d77785a3d2c617cb737cd9720de5 100644 (file)
@@ -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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV,
-            THING_TYPE_SHIELDTV);
+    private static final Set<ThingTypeUID> 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 (file)
index 0000000..f282666
--- /dev/null
@@ -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 (file)
index 0000000..49a31e7
--- /dev/null
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv;
+
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.CONNECT_TIMEOUT_MILLISECONDS;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.HTTPS;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.MAX_REQUEST_RETRIES;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.SOCKET_TIMEOUT_MILLISECONDS;
+
+import java.net.NoRouteToHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Optional;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpHostConnectException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ConnectionManagerUtil} is offering methods for connection specific processes.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public final class ConnectionManagerUtil {
+
+    private ConnectionManagerUtil() {
+    }
+
+    public static CloseableHttpClient createSharedHttpClient(HttpHost target, String username, String password)
+            throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+        CredentialsProvider credProvider = new BasicCredentialsProvider();
+        credProvider.setCredentials(new AuthScope(target.getHostName(), target.getPort()),
+                new UsernamePasswordCredentials(username, password));
+
+        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS)
+                .setSocketTimeout(SOCKET_TIMEOUT_MILLISECONDS).build();
+
+        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(getSslConnectionWithoutCertValidation(),
+                NoopHostnameVerifier.INSTANCE);
+
+        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
+                .register(HTTPS, sslsf).build();
+
+        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+
+        HttpRequestRetryHandler requestRetryHandler = (exception, executionCount, context) -> {
+            if (exception instanceof NoRouteToHostException) {
+                return false;
+            }
+            String message = Optional.ofNullable(exception.getMessage()).orElse("");
+            if (!message.isEmpty()) {
+                if ((exception instanceof HttpHostConnectException) && message.contains("Connection refused")) {
+                    return false;
+                }
+            }
+            return executionCount < MAX_REQUEST_RETRIES;
+        };
+
+        return HttpClients.custom().setDefaultRequestConfig(requestConfig).setSSLSocketFactory(sslsf)
+                .setDefaultCredentialsProvider(credProvider).setConnectionManager(connManager)
+                .setRetryHandler(requestRetryHandler).setConnectionManagerShared(true).build();
+    }
+
+    private static SSLContext getSslConnectionWithoutCertValidation()
+            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
+        return new SSLContextBuilder().loadTrustMaterial(null, (certificate, authType) -> true).build();
+    }
+}
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 (file)
index 0000000..d57bb92
--- /dev/null
@@ -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 (file)
index 0000000..e29bad8
--- /dev/null
@@ -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 (file)
index 0000000..30c4931
--- /dev/null
@@ -0,0 +1,696 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv;
+
+import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.NoRouteToHostException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+
+import org.apache.http.HttpHost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.androidtv.internal.AndroidTVDynamicStateDescriptionProvider;
+import org.openhab.binding.androidtv.internal.AndroidTVHandler;
+import org.openhab.binding.androidtv.internal.AndroidTVTranslationProvider;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.PhilipsTVPairing;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AmbilightService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.AppService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.KeyPressService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.PowerService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.SearchContentService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvChannelService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.TvPictureService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.VolumeService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
+import org.openhab.core.OpenHAB;
+import org.openhab.core.config.discovery.DiscoveryListener;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryServiceRegistry;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.StateOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * The {@link PhilipsTVHandler} is responsible for handling commands, which are sent to one of the
+ * channels.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public class PhilipsTVConnectionManager implements DiscoveryListener {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private AndroidTVHandler handler;
+
+    public PhilipsTVConfiguration config;
+
+    private ScheduledExecutorService scheduler;
+
+    private final AndroidTVTranslationProvider translationProvider;
+
+    private DiscoveryServiceRegistry discoveryServiceRegistry;
+
+    private AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider;
+
+    private @Nullable ThingUID upnpThingUID;
+
+    private @Nullable ScheduledFuture<?> refreshScheduler;
+
+    private final Predicate<ScheduledFuture<?>> isRefreshSchedulerRunning = r -> (r != null) && !r.isCancelled();
+
+    private final ReentrantLock lock = new ReentrantLock();
+
+    private boolean isLoggedIn = false;
+
+    private String statusMessage = "";
+
+    private HttpHost target;
+
+    private String username = "";
+    private String password = "";
+    private String macAddress = "";
+
+    private @Nullable ScheduledFuture<?> deviceHealthJob;
+    private boolean isOnline = true;
+    private boolean pendingPowerOn = false;
+
+    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    /* Philips TV services */
+    private Map<String, PhilipsTVService> channelServices = new HashMap<>();
+
+    public PhilipsTVConnectionManager(AndroidTVHandler handler, PhilipsTVConfiguration config) {
+        logger.debug("Create a Philips TV Handler for thing '{}'", handler.getThingUID());
+        this.handler = handler;
+        this.config = config;
+        this.scheduler = handler.getScheduler();
+        this.translationProvider = handler.getTranslationProvider();
+        this.discoveryServiceRegistry = handler.getDiscoveryServiceRegistry();
+        this.stateDescriptionProvider = handler.getStateDescriptionProvider();
+        this.target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS);
+        initialize();
+    }
+
+    private void setStatus(boolean isLoggedIn) {
+        if (isLoggedIn) {
+            setStatus(isLoggedIn, "online.online");
+        } else {
+            setStatus(isLoggedIn, "offline.unknown");
+        }
+    }
+
+    private void setStatus(boolean isLoggedIn, String statusMessage) {
+        String translatedMessage = translationProvider.getText(statusMessage);
+        logger.trace("setStatus to {} {} {}", isLoggedIn, statusMessage, translatedMessage);
+        if ((this.isLoggedIn != isLoggedIn) || (!this.statusMessage.equals(translatedMessage))) {
+            this.isLoggedIn = isLoggedIn;
+            this.statusMessage = translatedMessage;
+            handler.checkThingStatus();
+        }
+    }
+
+    public String getStatusMessage() {
+        return statusMessage;
+    }
+
+    public void setLoggedIn(boolean isLoggedIn) {
+        if (this.isLoggedIn != isLoggedIn) {
+            setStatus(isLoggedIn);
+        }
+    }
+
+    public boolean getLoggedIn() {
+        return isLoggedIn;
+    }
+
+    public void updateStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String thingStatusMessage) {
+        if (thingStatus == ThingStatus.ONLINE) {
+            setLoggedIn(true);
+        } else {
+            logger.trace("Updating status to {} {} {}", thingStatus, thingStatusDetail, thingStatusMessage);
+            setStatus(false, thingStatusMessage);
+        }
+    }
+
+    public String getMacAddress() {
+        return this.macAddress;
+    }
+
+    public void saveConfigs() {
+        String folderName = OpenHAB.getUserDataFolder() + "/androidtv";
+        File folder = new File(folderName);
+
+        if (!folder.exists()) {
+            logger.debug("Creating directory {}", folderName);
+            folder.mkdirs();
+        }
+
+        String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config";
+
+        Map<String, String> configMap = new HashMap<>();
+        configMap.put("username", username);
+        configMap.put("password", password);
+        configMap.put("macAddress", macAddress);
+
+        try {
+            String configJson = OBJECT_MAPPER.writeValueAsString(configMap);
+            logger.debug("Writing configJson \"{}\" to {}", configJson, fileName);
+            Files.write(Paths.get(fileName), configJson.getBytes());
+        } catch (JsonProcessingException e) {
+            logger.warn("JsonProcessingException trying to save configMap: {}", e.getMessage(), e);
+        } catch (IOException ex) {
+            logger.debug("IOException when writing configJson to file {}", ex.getMessage());
+        }
+    }
+
+    private void readConfigs() {
+        String folderName = OpenHAB.getUserDataFolder() + "/androidtv";
+        String fileName = folderName + "/philipstv." + handler.getThing().getUID().getId() + ".config";
+        File file = new File(fileName);
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            final byte[] contents = Files.readAllBytes(Paths.get(fileName));
+            String configJson = new String(contents);
+            logger.debug("Read configJson \"{}\" from {}", configJson, fileName);
+            Map<String, String> configMap = OBJECT_MAPPER.readValue(configJson,
+                    new TypeReference<HashMap<String, String>>() {
+                    });
+            this.username = Optional.ofNullable(configMap.get("username")).orElse("");
+            this.password = Optional.ofNullable(configMap.get("password")).orElse("");
+            this.macAddress = Optional.ofNullable(configMap.get("macAddress")).orElse("");
+            logger.debug("Processed configJson as {} {} {}", this.username, this.password, this.macAddress);
+        } catch (IOException ex) {
+            logger.debug("IOException when reading configJson from file {}", ex.getMessage());
+        }
+    }
+
+    public void setCreds(String username, String password) {
+        this.username = username;
+        this.password = password;
+        saveConfigs();
+    }
+
+    private boolean servicePing() {
+        int timeout = 500;
+
+        SocketAddress socketAddress = new InetSocketAddress(config.ipAddress, config.philipstvPort);
+        try (Socket socket = new Socket()) {
+            socket.connect(socketAddress, timeout);
+            return true;
+        } catch (ConnectException | SocketTimeoutException | NoRouteToHostException ignored) {
+            return false;
+        } catch (IOException ignored) {
+            // IOException is thrown by automatic close() of the socket.
+            // This should actually never return a value as we should return true above already
+            return true;
+        }
+    }
+
+    private void checkHealth() {
+        boolean isOnline = servicePing();
+        logger.debug("{} - Device Health - Online: {} - Logged In: {}", handler.getThingID(), isOnline, isLoggedIn);
+        if (isOnline != this.isOnline) {
+            this.isOnline = isOnline;
+            if (isOnline) {
+                logger.debug("{} - Device is back online.  Attempting reconnection.", handler.getThingID());
+                connect();
+            } else {
+                logger.debug("{} - Device is offline.", handler.getThingID());
+                postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "offline.communication-error-will-try-to-reconnect");
+            }
+        }
+    }
+
+    public void checkPendingPowerOn() {
+        if (pendingPowerOn) {
+            @Nullable
+            PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
+            if (powerService != null) {
+                powerService.handleCommand(CHANNEL_POWER, OnOffType.ON);
+            }
+            pendingPowerOn = false;
+            startDeviceHealthJob(5, TimeUnit.SECONDS);
+        }
+    }
+
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("Received channel: {}, command: {}", channelUID, command);
+        String username = this.username;
+        String password = this.password;
+
+        if (channelUID.getId().equals(CHANNEL_PINCODE)) {
+            if (command instanceof StringType) {
+                HttpHost target = new HttpHost(config.ipAddress, config.philipstvPort, HTTPS);
+                if (command.toString().equals("REQUEST")) {
+                    try {
+                        initPairingCodeRetrieval(target);
+                    } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                "offline.error-occured-while-presenting-pairing-code");
+                    }
+                } else {
+                    boolean hasFailed = initCredentialsRetrieval(target, command.toString());
+                    if (hasFailed) {
+                        postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                                "offline.error-occured-during-retrieval-of-credentials");
+                        return;
+                    }
+                    readConfigs();
+                    username = this.username;
+                    password = this.password;
+
+                    if ((username.isEmpty()) || (password.isEmpty())) {
+                        postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                                "offline.pairing-was-unsuccessful");
+                        return;
+                    }
+
+                }
+            }
+            return;
+        }
+
+        if ((username.isEmpty()) || (password.isEmpty())) {
+            return; // pairing process is not finished
+        }
+
+        boolean isLoggedIn = this.isLoggedIn;
+        Map<String, PhilipsTVService> channelServices = this.channelServices;
+
+        if ((!isLoggedIn) && (!channelUID.getId().equals(CHANNEL_POWER)
+                & !channelUID.getId().equals(CHANNEL_AMBILIGHT_LOUNGE_POWER))) {
+            // Check if tv turned on meanwhile
+            @Nullable
+            PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
+            if (powerService != null) {
+                powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
+            }
+            isLoggedIn = this.isLoggedIn;
+            if (!isLoggedIn) {
+                // still offline
+                logger.warn(
+                        "Cannot execute command {} for channel {}: PowerState of TV was checked and resolved to offline.",
+                        command, channelUID.getId());
+                return;
+            }
+        }
+
+        String channel = channelUID.getId();
+        long startTime = System.currentTimeMillis();
+        // Delegate the other commands to correct channel service
+        @Nullable
+        PhilipsTVService philipsTvService = channelServices.get(channel);
+
+        if (philipsTvService == null) {
+            logger.warn("Unknown channel for Philips TV Binding: {}", channel);
+            return;
+        }
+
+        if ((!isLoggedIn) && (channelUID.getId().equals(CHANNEL_POWER)) && (command.equals(OnOffType.ON))) {
+            startDeviceHealthJob(1, TimeUnit.SECONDS);
+            pendingPowerOn = true;
+        }
+
+        philipsTvService.handleCommand(channel, command);
+        long stopTime = System.currentTimeMillis();
+        long elapsedTime = stopTime - startTime;
+        logger.trace("The command {} took : {} nanoseconds", command.toFullString(), elapsedTime);
+    }
+
+    public void initialize() {
+        logger.debug("Init of handler for Thing: {}", handler.getThingID());
+
+        readConfigs();
+        String username = this.username;
+        String password = this.password;
+
+        if ((username.isEmpty()) || (password.isEmpty())) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                    "offline.pairing-is-not-configured-yet");
+            return;
+        }
+
+        connect();
+        startDeviceHealthJob(5, TimeUnit.SECONDS);
+    }
+
+    private void startDeviceHealthJob(int interval, TimeUnit unit) {
+        ScheduledFuture<?> deviceHealthJob = this.deviceHealthJob;
+        if (deviceHealthJob != null) {
+            deviceHealthJob.cancel(true);
+        }
+        this.deviceHealthJob = scheduler.scheduleWithFixedDelay(this::checkHealth, interval, interval, unit);
+    }
+
+    private void connect() {
+        HttpHost target = this.target;
+        String username = this.username;
+        String password = this.password;
+        String macAddress = this.macAddress;
+        logger.debug("Starting connection to {} {} {}", username, password, macAddress);
+
+        if (!config.useUpnpDiscovery && isSchedulerInitializable()) {
+            logger.debug("connect starting refresh scheduler");
+            startRefreshScheduler();
+        }
+
+        CloseableHttpClient httpClient;
+
+        try {
+            httpClient = ConnectionManagerUtil.createSharedHttpClient(target, username, password);
+        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
+            postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    String.format("offline.error-occurred-during-creation-of-http-client: %s", e.getMessage()));
+            return;
+        }
+
+        ConnectionManager connectionManager = new ConnectionManager(httpClient, target);
+
+        if (macAddress.isEmpty()) {
+            try {
+                Optional<String> wolAddress = WakeOnLanUtil.getMacFromEnabledInterface(connectionManager);
+                if (wolAddress.isPresent()) {
+                    this.macAddress = wolAddress.get();
+                    saveConfigs();
+                } else {
+                    logger.debug("MAC Address could not be determined for Wake-On-LAN support, "
+                            + "because Wake-On-LAN is not enabled on the TV.");
+                }
+            } catch (IOException e) {
+                logger.debug("Error occurred during retrieval of MAC Address: {}", e.getMessage());
+            }
+        }
+
+        startServices(connectionManager);
+
+        discoveryServiceRegistry.addDiscoveryListener(this);
+
+        // Thing is initialized, check power state and available communication of the TV and set ONLINE or OFFLINE
+        postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.online");
+
+        Map<String, PhilipsTVService> channelServices = this.channelServices;
+        @Nullable
+        PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
+        if (powerService != null) {
+            powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
+        }
+    }
+
+    private void startServices(ConnectionManager connectionManager) {
+        Map<String, PhilipsTVService> services = new HashMap<>();
+
+        PhilipsTVService volumeService = new VolumeService(this, connectionManager);
+        services.put(CHANNEL_VOLUME, volumeService);
+        services.put(CHANNEL_MUTE, volumeService);
+
+        PhilipsTVService tvPictureService = new TvPictureService(this, connectionManager);
+        services.put(CHANNEL_BRIGHTNESS, tvPictureService);
+        services.put(CHANNEL_SHARPNESS, tvPictureService);
+        services.put(CHANNEL_CONTRAST, tvPictureService);
+
+        PhilipsTVService keyPressService = new KeyPressService(this, connectionManager);
+        services.put(CHANNEL_KEYPRESS, keyPressService);
+        services.put(CHANNEL_PLAYER, keyPressService);
+
+        PhilipsTVService appService = new AppService(this, connectionManager);
+        services.put(CHANNEL_APP, appService);
+        services.put(CHANNEL_APPNAME, appService);
+        services.put(CHANNEL_APP_ICON, appService);
+
+        PhilipsTVService ambilightService = new AmbilightService(this, connectionManager);
+        services.put(CHANNEL_AMBILIGHT_POWER, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_HUE_POWER, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_LOUNGE_POWER, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_STYLE, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_COLOR, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_LEFT_COLOR, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_RIGHT_COLOR, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_TOP_COLOR, ambilightService);
+        services.put(CHANNEL_AMBILIGHT_BOTTOM_COLOR, ambilightService);
+
+        services.put(CHANNEL_TV_CHANNEL, new TvChannelService(this, connectionManager));
+        services.put(CHANNEL_POWER, new PowerService(this, connectionManager));
+        services.put(CHANNEL_SEARCH_CONTENT, new SearchContentService(this, connectionManager));
+        channelServices = Collections.unmodifiableMap(services);
+    }
+
+    /**
+     * Starts the pairing Process with the TV, which results in a Pairing Code shown on TV.
+     */
+    private void initPairingCodeRetrieval(HttpHost target)
+            throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+        logger.info("Pairing code for tv authentication is missing. "
+                + "Starting initial pairing process. Please provide manually the pairing code shown on the tv at the configuration of the tv thing.");
+        PhilipsTVPairing pairing = new PhilipsTVPairing();
+        pairing.requestPairingPin(target);
+    }
+
+    private boolean initCredentialsRetrieval(HttpHost target, String pincode) {
+        boolean hasFailed = false;
+        logger.info(
+                "Pairing code is available, but username and/or password is missing. Therefore we try to grant authorization and retrieve username and password.");
+        PhilipsTVPairing pairing = new PhilipsTVPairing();
+        try {
+            if (pincode.isEmpty()) {
+                pairing.finishPairingWithTv(config.pairingCode, this, target);
+            } else {
+                pairing.finishPairingWithTv(pincode, this, target);
+            }
+            postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                    "offline.authentication-with-philips-tv-device-was-successful-continuing-initialization-of-the-tv");
+        } catch (Exception e) {
+            postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
+                    "offline.could-not-successfully-finish-pairing-process-with-the-tv");
+            logger.warn("Error during finishing pairing process with the TV: {}", e.getMessage(), e);
+            hasFailed = true;
+        }
+        return hasFailed;
+    }
+
+    // callback methods for channel services
+    public void postUpdateChannel(String channelUID, State state) {
+        handler.updateChannelState(channelUID, state);
+    }
+
+    public synchronized void postUpdateThing(ThingStatus status, ThingStatusDetail statusDetail, String msg) {
+        logger.trace("postUpdateThing {} {} {}", status, statusDetail, msg);
+        if (status == ThingStatus.ONLINE) {
+            if (msg.equalsIgnoreCase(STANDBY_MSG)) {
+                handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF);
+            } else {
+                handler.updateChannelState(CHANNEL_POWER, OnOffType.ON);
+                startDeviceHealthJob(5, TimeUnit.SECONDS);
+                pendingPowerOn = false;
+            }
+            if (isSchedulerInitializable()) { // Init refresh scheduler only, if pairing is completed
+                startRefreshScheduler();
+            }
+        } else if (status == ThingStatus.OFFLINE) {
+            handler.updateChannelState(CHANNEL_POWER, OnOffType.OFF);
+            if (!TV_NOT_LISTENING_MSG.equals(msg)) { // avoid cancelling refresh if TV is temporarily not available
+                ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
+                if (refreshScheduler != null) {
+                    if (config.useUpnpDiscovery && isRefreshSchedulerRunning.test(refreshScheduler)) {
+                        stopRefreshScheduler();
+                    }
+                }
+                // Reset app and channel list (if existing) for new retrieval during next startup
+                Map<String, PhilipsTVService> channelServices = this.channelServices;
+                @Nullable
+                PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME);
+                if (appnameService != null) {
+                    ((AppService) appnameService).clearAvailableAppList();
+                }
+                @Nullable
+                PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL);
+                if (tvchannelService != null) {
+                    ((TvChannelService) tvchannelService).clearAvailableTvChannelList();
+                }
+            }
+        }
+        updateStatus(status, statusDetail, msg);
+    }
+
+    private boolean isSchedulerInitializable() {
+        String username = this.username;
+        String password = this.password;
+        boolean schedulerIsDone = false;
+        ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
+        if (refreshScheduler != null) {
+            schedulerIsDone = refreshScheduler.isDone();
+        }
+        return (!username.isEmpty()) && (!password.isEmpty()) && ((refreshScheduler == null) || schedulerIsDone);
+    }
+
+    private void startRefreshScheduler() {
+        int configuredRefreshRateOrDefault = Optional.ofNullable(config.refreshRate).orElse(10);
+        if (configuredRefreshRateOrDefault > 0) { // If value equals zero, refreshing should not be scheduled
+            ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
+            if (refreshScheduler != null) {
+                logger.debug("Refresh Scheduler already started for Philips TV {}, terminating.", handler.getThingID());
+                if (isRefreshSchedulerRunning.test(refreshScheduler)) {
+                    stopRefreshScheduler();
+                }
+            }
+            logger.debug("Starting Refresh Scheduler for Philips TV {} with refresh rate of {}.", handler.getThingID(),
+                    configuredRefreshRateOrDefault);
+            this.refreshScheduler = scheduler.scheduleWithFixedDelay(this::refreshTvProperties, 10,
+                    configuredRefreshRateOrDefault, TimeUnit.SECONDS);
+        }
+    }
+
+    private void stopRefreshScheduler() {
+        logger.debug("Stopping Refresh Scheduler for Philips TV: {}", handler.getThingID());
+        ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
+        if (refreshScheduler != null) {
+            refreshScheduler.cancel(true);
+        }
+    }
+
+    private void refreshTvProperties() {
+        try {
+            boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
+            if (isLockAcquired) {
+                try {
+                    if (isOnline) {
+                        Map<String, PhilipsTVService> channelServices = this.channelServices;
+                        @Nullable
+                        PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
+                        if (powerService != null) {
+                            powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
+                        }
+                        @Nullable
+                        PhilipsTVService volumeService = channelServices.get(CHANNEL_VOLUME);
+                        if (volumeService != null) {
+                            volumeService.handleCommand(CHANNEL_VOLUME, RefreshType.REFRESH);
+                        }
+                        @Nullable
+                        PhilipsTVService appnameService = channelServices.get(CHANNEL_APPNAME);
+                        if (appnameService != null) {
+                            appnameService.handleCommand(CHANNEL_APPNAME, RefreshType.REFRESH);
+                        }
+                        @Nullable
+                        PhilipsTVService tvchannelService = channelServices.get(CHANNEL_TV_CHANNEL);
+                        if (tvchannelService != null) {
+                            tvchannelService.handleCommand(CHANNEL_TV_CHANNEL, RefreshType.REFRESH);
+                        }
+                    }
+                } finally {
+                    lock.unlock();
+                }
+            }
+        } catch (InterruptedException e) {
+            logger.warn("Exception occurred during refreshing the tv properties: {}", e.getMessage());
+        }
+    }
+
+    public void updateChannelStateDescription(final String channelId, Map<String, String> values) {
+        AndroidTVDynamicStateDescriptionProvider stateDescriptionProvider = this.stateDescriptionProvider;
+        List<StateOption> options = new ArrayList<>();
+        if (!values.isEmpty()) {
+            values.forEach((key, value) -> options.add(new StateOption(key, value)));
+            stateDescriptionProvider.setStateOptions(new ChannelUID(handler.getThingUID(), channelId), options);
+        }
+    }
+
+    @Override
+    public void thingDiscovered(DiscoveryService source, DiscoveryResult result) {
+        logger.debug("thingDiscovered: {}", result);
+
+        if (config.useUpnpDiscovery && config.ipAddress.equals(result.getProperties().get(HOST))) {
+            upnpThingUID = result.getThingUID();
+            logger.debug("thingDiscovered, thingUID={}, discoveredUID={}", handler.getThingUID(), upnpThingUID);
+            Map<String, PhilipsTVService> channelServices = this.channelServices;
+            @Nullable
+            PhilipsTVService powerService = channelServices.get(CHANNEL_POWER);
+            if (powerService != null) {
+                powerService.handleCommand(CHANNEL_POWER, RefreshType.REFRESH);
+            }
+        }
+    }
+
+    @Override
+    public void thingRemoved(DiscoveryService discoveryService, ThingUID thingUID) {
+        logger.debug("thingRemoved: {}", thingUID);
+
+        if (thingUID.equals(upnpThingUID)) {
+            postUpdateThing(ThingStatus.ONLINE, ThingStatusDetail.NONE, "online.standby");
+        }
+    }
+
+    @Override
+    public @Nullable Collection<ThingUID> removeOlderResults(DiscoveryService discoveryService, long l,
+            @Nullable Collection<ThingTypeUID> collection, @Nullable ThingUID thingUID) {
+        return Collections.emptyList();
+    }
+
+    public void dispose() {
+        discoveryServiceRegistry.removeDiscoveryListener(this);
+        ScheduledFuture<?> refreshScheduler = this.refreshScheduler;
+        if (refreshScheduler != null) {
+            if (isRefreshSchedulerRunning.test(refreshScheduler)) {
+                stopRefreshScheduler();
+            }
+        }
+        ScheduledFuture<?> deviceHealthJob = this.deviceHealthJob;
+        if (deviceHealthJob != null) {
+            deviceHealthJob.cancel(true);
+        }
+    }
+}
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 (file)
index 0000000..c157244
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv;
+
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.GET_NETWORK_DEVICES_PATH;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * The {@link WakeOnLanUtil} is offering methods for powering on TVs via Wake-On-LAN.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public final class WakeOnLanUtil {
+
+    private static final int MAX_WOL_RETRIES = 10;
+
+    private static final int WOL_PORT = 9;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(WakeOnLanUtil.class);
+
+    private static final Pattern MAC_PATTERN = Pattern.compile("([:\\-])");
+
+    private static final Predicate<JsonNode> IS_WOL_ENABLED = j -> j.get("wake-on-lan").asText()
+            .equalsIgnoreCase("Enabled");
+
+    private static final Predicate<NetworkInterface> IS_NOT_LOOPBACK = ni -> {
+        try {
+            return !ni.isLoopback();
+        } catch (SocketException e) {
+            return false;
+        }
+    };
+
+    private WakeOnLanUtil() {
+    }
+
+    public static Optional<String> getMacFromEnabledInterface(ConnectionManager connectionManager) throws IOException {
+        String jsonContent = connectionManager.doHttpsGet(GET_NETWORK_DEVICES_PATH);
+        List<JsonNode> jsonNode = OBJECT_MAPPER.readValue(jsonContent, new TypeReference<List<JsonNode>>() {
+        });
+
+        return jsonNode.stream().filter(IS_WOL_ENABLED).map(j -> j.get("mac").asText())
+                .peek(m -> LOGGER.debug("Mac identified as: {}", m)).findFirst();
+    }
+
+    public static void wakeOnLan(String ip, String mac) throws IOException, InterruptedException {
+        for (int i = 0; i < MAX_WOL_RETRIES; i++) {
+            if (isReachable(ip)) {
+                Thread.sleep(2000);
+                return;
+            } else {
+                Thread.sleep(100);
+                sendWakeOnLanPackage(mac);
+            }
+        }
+    }
+
+    private static void sendWakeOnLanPackage(String mac) throws IOException {
+        byte[] macBytes = getMacBytes(mac);
+        byte[] bytes = new byte[6 + (16 * macBytes.length)];
+        for (int i = 0; i < 6; i++) {
+            bytes[i] = (byte) 0xff;
+        }
+        for (int i = 6; i < bytes.length; i += macBytes.length) {
+            System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
+        }
+
+        List<InetAddress> broadcastAddresses = Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
+                .filter(IS_NOT_LOOPBACK).map(NetworkInterface::getInterfaceAddresses).flatMap(Collection::stream)
+                .map(InterfaceAddress::getBroadcast).filter(Objects::nonNull).collect(Collectors.toList());
+
+        for (InetAddress broadcast : broadcastAddresses) {
+            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, broadcast, WOL_PORT);
+            try (DatagramSocket socket = new DatagramSocket()) {
+                LOGGER.debug("WOL sent to Broadcast-IP {} with MAC {}", broadcast, mac);
+                socket.send(packet);
+            }
+        }
+    }
+
+    private static byte[] getMacBytes(String mac) {
+        byte[] bytes = new byte[6];
+        String[] hex = MAC_PATTERN.split(mac);
+        if (hex.length != 6) {
+            throw new IllegalArgumentException("Invalid MAC address.");
+        }
+        try {
+            for (int i = 0; i < 6; i++) {
+                bytes[i] = (byte) Integer.parseInt(hex[i], 16);
+            }
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Invalid hex digit in MAC address.");
+        }
+        return bytes;
+    }
+
+    public static boolean isReachable(String ipAddress) throws IOException {
+        InetAddress inetAddress = InetAddress.getByName(ipAddress);
+        return inetAddress.isReachable(1000);
+    }
+}
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 (file)
index 0000000..632dc50
--- /dev/null
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.discovery;
+
+import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.jupnp.model.meta.DeviceDetails;
+import org.jupnp.model.meta.ModelDetails;
+import org.jupnp.model.meta.RemoteDevice;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PhilipsTVDiscoveryParticipant} is responsible for discovering Philips TV devices through UPnP.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+@Component(immediate = true)
+public class PhilipsTVDiscoveryParticipant implements UpnpDiscoveryParticipant {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+        return Collections.singleton(THING_TYPE_PHILIPSTV);
+    }
+
+    @Override
+    public @Nullable DiscoveryResult createResult(RemoteDevice device) {
+        final ThingUID uid = getThingUID(device);
+        if (uid == null) {
+            return null;
+        }
+
+        final Map<String, Object> properties = new HashMap<>(2);
+        String ipAddress = device.getIdentity().getDescriptorURL().getHost();
+        properties.put(PARAMETER_IP_ADDRESS, ipAddress);
+        properties.put(PARAMETER_PHILIPSTV_PORT, DEFAULT_PORT);
+        logger.debug("Philips TV Found: {}, using default port {}", ipAddress, DEFAULT_PORT);
+        String friendlyName = device.getDetails().getFriendlyName();
+        if (friendlyName.length() > 0 && Character.isDigit(friendlyName.charAt(0))) {
+            friendlyName = "_" + friendlyName; // label must not start with a digit
+        }
+
+        return DiscoveryResultBuilder.create(uid).withThingType(THING_TYPE_PHILIPSTV).withProperties(properties)
+                .withLabel(friendlyName).build();
+    }
+
+    @Override
+    public @Nullable ThingUID getThingUID(RemoteDevice device) {
+        DeviceDetails details = device.getDetails();
+        if (details != null) {
+            ModelDetails modelDetails = details.getModelDetails();
+            if (modelDetails != null) {
+                String modelName = modelDetails.getModelName();
+                String modelDescription = modelDetails.getModelDescription();
+                if (modelName != null && modelDescription != null) {
+                    if (modelName.contains("Philips TV")) {
+                        logger.debug("Device found: {} with desc {}", modelName, modelDescription);
+                        // One Philips TV contains several UPnP devices.
+                        // Create unique Philips TV thing for every Media Renderer
+                        // device and ignore rest of the UPnP devices.
+                        if (modelDescription.contains("Media")) {
+                            // UDN shouldn't contain '-' characters.
+                            String udn = device.getIdentity().getUdn().getIdentifierString().replace("-", "_");
+                            logger.debug("Discovered a Philips TV '{}' model '{}' thing with UDN '{}'",
+                                    device.getDetails().getFriendlyName(), modelName, udn);
+
+                            return new ThingUID(THING_TYPE_PHILIPSTV, udn);
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
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 (file)
index 0000000..8b22a97
--- /dev/null
@@ -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 (file)
index 0000000..47f7e4a
--- /dev/null
@@ -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 (file)
index 0000000..a63ffcc
--- /dev/null
@@ -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 (file)
index 0000000..c28ea09
--- /dev/null
@@ -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 (file)
index 0000000..bfe1175
--- /dev/null
@@ -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 (file)
index 0000000..e72a0b4
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.pairing.model;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link RequestCodeDTO} class defines the Data Transfer Object
+ * for the Philips TV API /pair/request endpoint to request a pairing code.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public class RequestCodeDTO {
+
+    @JsonProperty("scope")
+    private List<String> scope;
+
+    @JsonProperty("device")
+    private DeviceDTO device;
+
+    public RequestCodeDTO(List<String> scope, DeviceDTO device) {
+        this.scope = scope;
+        this.device = device;
+    }
+
+    public void setScope(List<String> scope) {
+        this.scope = scope;
+    }
+
+    public List<String> getScope() {
+        return scope;
+    }
+
+    public void setDevice(DeviceDTO device) {
+        this.device = device;
+    }
+
+    public DeviceDTO getDevice() {
+        return device;
+    }
+
+    @Override
+    public String toString() {
+        return "RequestPinDTO{" + "scope = '" + scope + '\'' + ",device = '" + device + '\'' + "}";
+    }
+}
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 (file)
index 0000000..3cf2f5c
--- /dev/null
@@ -0,0 +1,316 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service;
+
+import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.WakeOnLanUtil;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.DataDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.TvSettingsUpdateDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValueDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ValuesDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorDeltaDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightColorSettingsDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightConfigDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightLoungeDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightModeDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightPowerDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight.AmbilightTopologyDTO;
+import org.openhab.core.library.types.HSBType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Service for handling commands regarding Ambilight settings of the TV
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public class AmbilightService implements PhilipsTVService {
+
+    private static final List<String> AMBILIGHT_COLOR_CHANNELS = Stream
+            .of(CHANNEL_AMBILIGHT_COLOR, CHANNEL_AMBILIGHT_LEFT_COLOR, CHANNEL_AMBILIGHT_RIGHT_COLOR,
+                    CHANNEL_AMBILIGHT_TOP_COLOR, CHANNEL_AMBILIGHT_BOTTOM_COLOR)
+            .collect(Collectors.toList());
+    private static final int AMBILIGHT_HUE_NODE_ID = 2131230774;
+    private static final int AMBILIGHT_BRIGHTNESS_NODE_ID = 2131230769;
+    private static final String AMBILIGHT_MODE_MANUAL = "manual";
+    private static final String AMBILIGHT_STYLE_FOLLOW_VIDEO = "FOLLOW_VIDEO";
+    private static final String AMBILIGHT_STYLE_FOLLOW_COLOR = "FOLLOW_COLOR";
+    private static final String AMBILIGHT_ALGORITHM_MANUAL_HUE = "MANUAL HUE";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final PhilipsTVConnectionManager handler;
+
+    private final boolean isWakeOnLanEnabled;
+
+    private @Nullable AmbilightTopologyDTO ambilightTopology;
+
+    private final ConnectionManager connectionManager;
+
+    public AmbilightService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
+        this.handler = handler;
+        this.connectionManager = connectionManager;
+        this.isWakeOnLanEnabled = handler.getMacAddress().isEmpty() ? false : true;
+    }
+
+    @Override
+    public void handleCommand(String channel, Command command) {
+        try {
+            if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof OnOffType)) {
+                setAmbilightPowerState(command);
+            } else if (CHANNEL_AMBILIGHT_POWER.equals(channel) && (command instanceof RefreshType)) {
+                AmbilightPowerDTO ambilightPowerDTO = getAmbilightPowerState();
+                handler.postUpdateChannel(CHANNEL_AMBILIGHT_POWER,
+                        ambilightPowerDTO.isPoweredOn() ? OnOffType.ON : OnOffType.OFF);
+            } else if (CHANNEL_AMBILIGHT_HUE_POWER.equals(channel) && (command instanceof OnOffType)) {
+                setAmbilightHuePowerState(command);
+            } else if (CHANNEL_AMBILIGHT_LOUNGE_POWER.equals(channel) && (command instanceof OnOffType)) {
+                setAmbilightLoungePowerState(command);
+            } else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof StringType)) {
+                setAmbilightStyle(command.toString());
+            } else if (CHANNEL_AMBILIGHT_STYLE.equals(channel) && (command instanceof RefreshType)) {
+                AmbilightConfigDTO config = getAmbilightConfig();
+                String styleWithAlgorithm = String.format("%s %s", config.getStyleName(), config.getMenuSetting());
+                handler.postUpdateChannel(CHANNEL_AMBILIGHT_STYLE, new StringType(styleWithAlgorithm));
+            } else if (CHANNEL_AMBILIGHT_COLOR.equals(channel) && (command instanceof HSBType)) {
+                setAllAmbilightColors((HSBType) command);
+            } else if ((CHANNEL_AMBILIGHT_LEFT_COLOR.equals(channel) || CHANNEL_AMBILIGHT_RIGHT_COLOR.equals(channel)
+                    || CHANNEL_AMBILIGHT_TOP_COLOR.equals(channel) || CHANNEL_AMBILIGHT_BOTTOM_COLOR.equals(channel))
+                    && (command instanceof HSBType)) {
+                setAmbilightPixel((HSBType) command, channel);
+            } else if (AMBILIGHT_COLOR_CHANNELS.contains(channel) && (command instanceof PercentType)) {
+                setAmbilightBrightness(((PercentType) command).intValue());
+            } else {
+                if (!(command instanceof RefreshType)) {
+                    logger.warn("Unknown command: {} for Channel {}", command, channel);
+                }
+            }
+        } catch (Exception e) {
+            if (isTvOfflineException(e)) {
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
+            } else if (isTvNotListeningException(e)) {
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        TV_NOT_LISTENING_MSG);
+            } else {
+                logger.warn("Error during handling the Ambilight command {} for Channel {}: {}", command, channel,
+                        e.getMessage(), e);
+            }
+        }
+    }
+
+    private AmbilightPowerDTO getAmbilightPowerState() throws IOException {
+        return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_POWERSTATE_PATH),
+                AmbilightPowerDTO.class);
+    }
+
+    private void setAmbilightPowerState(Command command) throws IOException {
+        if (command.equals(OnOffType.OFF)) {
+            AmbilightPowerDTO ambilightPower = new AmbilightPowerDTO();
+            ambilightPower.setPower(POWER_OFF);
+            String powerStateJson = OBJECT_MAPPER.writeValueAsString(ambilightPower);
+            logger.debug("Post Ambilight power state json: {}", powerStateJson);
+            connectionManager.doHttpsPost(AMBILIGHT_POWERSTATE_PATH, powerStateJson);
+        } else { // power on via setting FOLLOW_VIDEO instead through POWERSTATE_PATH which sets FOLLOW_COLOR
+            setAmbilightStyle(String.format("%s %s", AMBILIGHT_STYLE_FOLLOW_VIDEO, "STANDARD"));
+        }
+    }
+
+    private void setAmbilightHuePowerState(Command command) throws IOException {
+        DataDTO data = new DataDTO((command.equals(OnOffType.ON) ? "true" : "false"));
+
+        ValueDTO value = new ValueDTO(data);
+        value.setNodeid(AMBILIGHT_HUE_NODE_ID);
+        value.setAvailable("true");
+        value.setControllable("true");
+
+        ValuesDTO values = new ValuesDTO(value);
+        TvSettingsUpdateDTO ambilightHuePower = new TvSettingsUpdateDTO(Collections.singletonList(values));
+
+        String ambilightHuePowerJson = OBJECT_MAPPER.writeValueAsString(ambilightHuePower);
+        logger.debug("Post Ambilight hue power state json: {}", ambilightHuePowerJson);
+        connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightHuePowerJson);
+    }
+
+    private void setAmbilightLoungePowerState(Command command) throws IOException, InterruptedException {
+        AmbilightColorDTO ambilightColorDTO = new AmbilightColorDTO();
+        if (command.equals(OnOffType.ON)) {
+            if (isWakeOnLanEnabled && !WakeOnLanUtil.isReachable(handler.config.ipAddress)) {
+                WakeOnLanUtil.wakeOnLan(handler.config.ipAddress, handler.getMacAddress());
+            }
+            ambilightColorDTO.setHue(0);
+        } else {
+            ambilightColorDTO.setHue(255);
+        }
+        AmbilightLoungeDTO ambilightLoungeDTO = new AmbilightLoungeDTO(ambilightColorDTO);
+
+        String setAmbilightLoungeJson = OBJECT_MAPPER.writeValueAsString(ambilightLoungeDTO);
+        logger.debug("Setting ambilight lounge power state json: {}", setAmbilightLoungeJson);
+        connectionManager.doHttpsPost(AMBILIGHT_LOUNGE_PATH, setAmbilightLoungeJson);
+    }
+
+    private void setAmbilightStyle(String styleToSet) throws IOException {
+        String[] styleWithAlgorithm = styleToSet.split(" ");
+        if (styleWithAlgorithm.length != 2) {
+            throw new IllegalStateException("Style and/or algorithm is missing.");
+        }
+        String style = styleWithAlgorithm[0];
+        String algorithm = styleWithAlgorithm[1];
+        AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO(
+                new AmbilightColorSettingsDTO(new AmbilightColorDTO(), new AmbilightColorDeltaDTO()));
+        ambilightConfig.setStyleName(style);
+        ambilightConfig.setMenuSetting(algorithm);
+        if (style.equals(AMBILIGHT_STYLE_FOLLOW_COLOR) && algorithm.equals(AMBILIGHT_ALGORITHM_MANUAL_HUE)) {
+            ambilightConfig.setAlgorithm(algorithm);
+            ambilightConfig.setIsExpert(true);
+            AmbilightColorDeltaDTO ambilightColorDeltaDTO = new AmbilightColorDeltaDTO();
+            ambilightColorDeltaDTO.setHue(0);
+            ambilightColorDeltaDTO.setBrightness(0);
+            ambilightColorDeltaDTO.setSaturation(0);
+            AmbilightColorSettingsDTO ambilightColorSettingsDTO = new AmbilightColorSettingsDTO(new AmbilightColorDTO(),
+                    ambilightColorDeltaDTO);
+            ambilightColorSettingsDTO.setSpeed(255);
+            ambilightConfig.setColorSettings(ambilightColorSettingsDTO);
+        }
+        String ambilightConfigJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig);
+        logger.debug("Post config for Ambilight style json: {}", ambilightConfigJson);
+        connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, ambilightConfigJson);
+    }
+
+    private AmbilightConfigDTO getAmbilightConfig() throws IOException {
+        return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_CONFIG_PATH), AmbilightConfigDTO.class);
+    }
+
+    private void setAmbilightMode(String mode) throws IOException {
+        AmbilightModeDTO ambilightMode = new AmbilightModeDTO();
+        ambilightMode.setCurrent(mode);
+        String ambilightModeJson = OBJECT_MAPPER.writeValueAsString(ambilightMode);
+        logger.debug("Post ambilight mode json: {}", ambilightModeJson);
+        connectionManager.doHttpsPost(AMBILIGHT_MODE_PATH, ambilightModeJson);
+    }
+
+    // private AmbilightModeDTO getAmbilightMode() throws IOException {
+    // return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_MODE_PATH), AmbilightModeDTO.class);
+    // }
+
+    private void setAmbilightBrightness(int brightnessToSet) throws IOException {
+        String ambilightBrightnessJson = ServiceUtil.createTvSettingsUpdateJson(AMBILIGHT_BRIGHTNESS_NODE_ID,
+                brightnessToSet / 10);
+        logger.debug("Post Ambilight brightness json: {}", ambilightBrightnessJson);
+        connectionManager.doHttpsPost(UPDATE_SETTINGS_PATH, ambilightBrightnessJson);
+    }
+
+    private void setAmbilightPixel(HSBType hsb, String channel) throws IOException {
+        if (ambilightTopology == null) {
+            ambilightTopology = getAmbilightTopology();
+        }
+        setAmbilightMode(AMBILIGHT_MODE_MANUAL); // activates the usage of cached values
+        String sideToSet = determineAmbilightSide(channel);
+        int pixelSize = ambilightTopology.getPixelSizeForGivenSide(sideToSet);
+
+        ObjectNode rootNode = OBJECT_MAPPER.createObjectNode();
+
+        ObjectNode pixel = OBJECT_MAPPER.createObjectNode();
+        pixel.put("r", hsb.getRed().intValue());
+        pixel.put("g", hsb.getGreen().intValue());
+        pixel.put("b", hsb.getBlue().intValue());
+
+        ObjectNode sidePixels = OBJECT_MAPPER.createObjectNode();
+        // pixel declaration in json start with 0
+        IntStream.range(0, pixelSize).forEach(i -> sidePixels.set(Integer.toString(i), pixel));
+
+        IntStream.rangeClosed(1, ambilightTopology.getLayers()).forEach(i -> {
+            ObjectNode layerX = OBJECT_MAPPER.createObjectNode();
+            layerX.set(sideToSet, sidePixels);
+
+            rootNode.set("layer" + i, layerX);
+        });
+
+        String ambilightPixelJson = OBJECT_MAPPER.writeValueAsString(rootNode);
+        logger.debug("Sending {} Ambilight pixel json: {}", sideToSet, ambilightPixelJson);
+        connectionManager.doHttpsPost(AMBILIGHT_CACHED_PATH, ambilightPixelJson);
+    }
+
+    private AmbilightTopologyDTO getAmbilightTopology() throws IOException {
+        return OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(AMBILIGHT_TOPOLOGY_PATH),
+                AmbilightTopologyDTO.class);
+    }
+
+    private String determineAmbilightSide(String channel) {
+        String sideToSet;
+        switch (channel) {
+            case CHANNEL_AMBILIGHT_LEFT_COLOR:
+                sideToSet = "left";
+                break;
+            case CHANNEL_AMBILIGHT_RIGHT_COLOR:
+                sideToSet = "right";
+                break;
+            case CHANNEL_AMBILIGHT_TOP_COLOR:
+                sideToSet = "top";
+                break;
+            case CHANNEL_AMBILIGHT_BOTTOM_COLOR:
+                sideToSet = "bottom";
+                break;
+            default:
+                throw new IllegalStateException("Unexpected channel for ambilight pixel set: " + channel);
+        }
+        return sideToSet;
+    }
+
+    private void setAllAmbilightColors(HSBType hsb) throws IOException {
+        AmbilightColorDTO ambilightColor = new AmbilightColorDTO(hsb);
+        AmbilightColorDeltaDTO ambilightColorDelta = new AmbilightColorDeltaDTO();
+        ambilightColorDelta.setHue(0);
+        ambilightColorDelta.setSaturation(0);
+        ambilightColorDelta.setBrightness(0);
+
+        AmbilightColorSettingsDTO ambilightColorSettings = new AmbilightColorSettingsDTO(ambilightColor,
+                ambilightColorDelta);
+        ambilightColorSettings.setSpeed(255);
+
+        AmbilightConfigDTO ambilightConfig = new AmbilightConfigDTO(ambilightColorSettings);
+        ambilightConfig.setIsExpert(true);
+        ambilightConfig.setStyleName("FOLLOW_COLOR");
+        ambilightConfig.setAlgorithm("MANUAL_HUE");
+
+        String setAmbilightColorsJson = OBJECT_MAPPER.writeValueAsString(ambilightConfig);
+        logger.debug("Setting ambilight colors json: {}", setAmbilightColorsJson);
+        connectionManager.doHttpsPost(AMBILIGHT_CONFIG_PATH, setAmbilightColorsJson);
+    }
+}
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 (file)
index 0000000..8e893e2
--- /dev/null
@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service;
+
+import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
+
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.http.ParseException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ApplicationsDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.AvailableAppsDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ComponentDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.CurrentAppDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.ExtrasDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.IntentDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application.LaunchAppDTO;
+import org.openhab.core.library.types.RawType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AppService} is responsible for handling key code commands, which emulate a button
+ * press on a remote control.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public class AppService implements PhilipsTVService {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    // Label , Entry<PackageName,ClassName> of App
+    private @Nullable Map<String, AbstractMap.SimpleEntry<String, String>> availableApps;
+
+    private String currentPackageName = "";
+
+    private final PhilipsTVConnectionManager handler;
+
+    private final ConnectionManager connectionManager;
+
+    public AppService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
+        this.handler = handler;
+        this.connectionManager = connectionManager;
+    }
+
+    @Override
+    public void handleCommand(String channel, Command command) {
+        try {
+            synchronized (this) {
+                if (isAvailableAppListEmpty()) {
+                    getAvailableAppListFromTv();
+                    handler.updateChannelStateDescription(CHANNEL_APPNAME, availableApps.keySet().stream()
+                            .collect(Collectors.toMap(Function.identity(), Function.identity())));
+                }
+            }
+            if (command instanceof RefreshType) {
+                // Get current App name
+                String packageName = getCurrentApp();
+                if (currentPackageName.equals(packageName)) {
+                    return;
+                } else {
+                    currentPackageName = packageName;
+                }
+                Optional<Map.Entry<String, AbstractMap.SimpleEntry<String, String>>> app = availableApps.entrySet()
+                        .stream().filter(e -> e.getValue().getKey().equalsIgnoreCase(packageName)).findFirst();
+                if (app.isPresent()) {
+                    handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName));
+                    Map.Entry<String, AbstractMap.SimpleEntry<String, String>> appEntry = app.get();
+                    handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(appEntry.getKey()));
+                    // Get icon for current App
+                    RawType image = getIconForApp(appEntry.getValue().getKey(), appEntry.getValue().getValue());
+                    handler.postUpdateChannel(CHANNEL_APP_ICON, (image != null) ? image : UnDefType.UNDEF);
+                } else { // NA
+                    handler.postUpdateChannel(CHANNEL_APP, new StringType(packageName));
+                    handler.postUpdateChannel(CHANNEL_APPNAME, new StringType(packageName));
+                    handler.postUpdateChannel(CHANNEL_APP_ICON, UnDefType.UNDEF);
+                }
+            } else if (command instanceof StringType) {
+                String appName = "";
+                if (CHANNEL_APPNAME.equals(channel) && availableApps.containsKey(command.toString())) {
+                    launchApp(command.toString());
+                } else if (CHANNEL_APP.equals(channel)) {
+                    launchDNApp(command.toString());
+                } else {
+                    logger.warn("The given App with Name: {} {} couldn't be found in the local App List from the tv.",
+                            command, appName);
+                }
+            } else {
+                logger.warn("Unknown command: {} for Channel {}", command, channel);
+            }
+        } catch (Exception e) {
+            if (isTvOfflineException(e)) {
+                logger.debug("Could not execute command for apps, the TV is offline.");
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
+            } else if (isTvNotListeningException(e)) {
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        TV_NOT_LISTENING_MSG);
+            } else {
+                logger.warn("Error occurred during handling of command for apps: {}", e.getMessage(), e);
+            }
+        }
+    }
+
+    private boolean isAvailableAppListEmpty() {
+        return (availableApps == null) || availableApps.isEmpty();
+    }
+
+    private void launchDNApp(String appName) throws IOException {
+        for (Map.Entry<String, AbstractMap.SimpleEntry<String, String>> entry : availableApps.entrySet()) {
+            Map.Entry<String, String> app = entry.getValue();
+            if (app.getKey().equals(appName)) {
+                logger.debug("Found app by dn: {} {} {}", entry.getKey(), app.getKey(), app.getValue());
+                launchApp(entry.getKey());
+                return;
+            }
+        }
+        logger.warn("The given App with DN: {} couldn't be found in the local App List from the tv.", appName);
+    }
+
+    private void launchApp(String appName) throws IOException {
+        Map.Entry<String, String> app = availableApps.get(appName);
+
+        ComponentDTO componentDTO = new ComponentDTO();
+        componentDTO.setPackageName(app.getKey());
+        componentDTO.setClassName(app.getValue());
+
+        IntentDTO intentDTO = new IntentDTO(componentDTO, new ExtrasDTO());
+        intentDTO.setAction("empty");
+        LaunchAppDTO launchAppDTO = new LaunchAppDTO(intentDTO);
+        String appLaunchJson = OBJECT_MAPPER.writeValueAsString(launchAppDTO);
+
+        logger.debug("App Launch json: {}", appLaunchJson);
+        connectionManager.doHttpsPost(LAUNCH_APP_PATH, appLaunchJson);
+    }
+
+    private String getCurrentApp() throws IOException, ParseException {
+        CurrentAppDTO currentAppDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(GET_CURRENT_APP_PATH),
+                CurrentAppDTO.class);
+        return currentAppDTO.getComponent().getPackageName();
+    }
+
+    private @Nullable RawType getIconForApp(String packageName, String className) throws IOException {
+        String pathForIcon = String.format("%s%s-%s%sicon", SLASH, className, packageName, SLASH);
+        byte[] icon = connectionManager
+                .doHttpsGetForImage(String.format("%s%s", GET_AVAILABLE_APP_LIST_PATH, pathForIcon));
+        if ((icon != null) && (icon.length > 0)) {
+            return new RawType(icon, "image/png");
+        } else {
+            return null;
+        }
+    }
+
+    private void getAvailableAppListFromTv() throws IOException {
+        AvailableAppsDTO availableAppsDTO = OBJECT_MAPPER
+                .readValue(connectionManager.doHttpsGet(GET_AVAILABLE_APP_LIST_PATH), AvailableAppsDTO.class);
+
+        ConcurrentMap<String, AbstractMap.SimpleEntry<String, String>> appsMap = availableAppsDTO.getApplications()
+                .stream()
+                .collect(Collectors.toConcurrentMap(ApplicationsDTO::getLabel,
+                        a -> new AbstractMap.SimpleEntry<>(a.getIntent().getComponent().getPackageName(),
+                                a.getIntent().getComponent().getClassName()),
+                        (a1, a2) -> a1));
+
+        logger.debug("appsMap - Apps added: {}", appsMap.size());
+        if (logger.isTraceEnabled()) {
+            appsMap.keySet().forEach(app -> logger.trace("appsMap - App found: {}", app));
+        }
+
+        this.availableApps = appsMap;
+    }
+
+    public void clearAvailableAppList() {
+        if (availableApps != null) {
+            availableApps.clear();
+        }
+    }
+}
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 (file)
index 0000000..96fd973
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * The {@link KeyPress} presents all available key codes of Philips TV.
+ *
+ * @see <a
+ *      href=
+ *      "http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html">http://jointspace.sourceforge.net/projectdata/documentation/jasonApi/1/doc/API-Method-input-key-POST.html
+ *      </a>
+ *
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public enum KeyPress {
+
+    KEY_STANDBY("Standby"),
+    KEY_BACK("Back"),
+    KEY_FIND("Find"),
+    KEY_RED_COLOR("RedColour"),
+    KEY_GREEN_COLOR("GreenColour"),
+    KEY_YELLOW_COLOR("YellowColour"),
+    KEY_BLUE_COLOR("BlueColour"),
+    KEY_HOME("Home"),
+    KEY_VOLUME_UP("VolumeUp"),
+    KEY_VOLUME_DOWN("VolumeDown"),
+    KEY_MUTE("Mute"),
+    KEY_OPTIONS("Options"),
+    KEY_DOT("Dot"),
+    KEY_0("Digit0"),
+    KEY_1("Digit1"),
+    KEY_2("Digit2"),
+    KEY_3("Digit3"),
+    KEY_4("Digit4"),
+    KEY_5("Digit5"),
+    KEY_6("Digit6"),
+    KEY_7("Digit7"),
+    KEY_8("Digit8"),
+    KEY_9("Digit9"),
+    KEY_INFO("Info"),
+    KEY_CURSOR_UP("CursorUp"),
+    KEY_CURSOR_DOWN("CursorDown"),
+    KEY_CURSOR_LEFT("CursorLeft"),
+    KEY_CURSOR_RIGHT("CursorRight"),
+    KEY_CONFIRM("Confirm"),
+    KEY_NEXT("Next"),
+    KEY_PREVIOUS("Previous"),
+    KEY_ADJUST("Adjust"),
+    KEY_WATCH_TV("WatchTV"),
+    KEY_VIEW_MODE("Viewmode"),
+    KEY_TELETEXT("Teletext"),
+    KEY_SUBTITLE("Subtitle"),
+    KEY_CHANNEL_STEP_UP("ChannelStepUp"),
+    KEY_CHANNEL_STEP_DOWN("ChannelStepDown"),
+    KEY_SOURCE("Source"),
+    KEY_AMBILIGHT_ON_OFF("AmbilightOnOff"),
+    KEY_PLAY("Play"),
+    KEY_PAUSE("Pause"),
+    KEY_FAST_FORWARD("FastForward"),
+    KEY_STOP("Stop"),
+    KEY_REWIND("Rewind"),
+    KEY_RECORD("Record"),
+    KEY_ONLINE("Online");
+
+    private final String value;
+
+    KeyPress(String value) {
+        this.value = value;
+    }
+
+    public static KeyPress getKeyPressForValue(String value) throws IllegalArgumentException {
+        return Arrays.stream(values()).filter(v -> v.value.equalsIgnoreCase(value)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("Key code could not be recognized: " + value));
+    }
+
+    @JsonValue
+    @Override
+    public String toString() {
+        return this.value;
+    }
+}
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 (file)
index 0000000..fb61c38
--- /dev/null
@@ -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 (file)
index 0000000..fcf9e21
--- /dev/null
@@ -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 (file)
index 0000000..344d122
--- /dev/null
@@ -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 (file)
index 0000000..a2b62f4
--- /dev/null
@@ -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 (file)
index 0000000..53ae7fb
--- /dev/null
@@ -0,0 +1,147 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service;
+
+import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager.OBJECT_MAPPER;
+import static org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVBindingConstants.*;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.ConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.PhilipsTVConnectionManager;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.api.PhilipsTVService;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.AvailableTvChannelsDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.ChannelListDTO;
+import org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel.TvChannelDTO;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service for handling commands regarding setting or retrieving the TV channel
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+@NonNullByDefault
+public class TvChannelService implements PhilipsTVService {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    // Name , ccid of TV Channel
+    private @Nullable Map<String, String> availableTvChannels;
+
+    private final PhilipsTVConnectionManager handler;
+
+    private final ConnectionManager connectionManager;
+
+    public TvChannelService(PhilipsTVConnectionManager handler, ConnectionManager connectionManager) {
+        this.handler = handler;
+        this.connectionManager = connectionManager;
+    }
+
+    @Override
+    public void handleCommand(String channel, Command command) {
+        try {
+            synchronized (this) {
+                if (isTvChannelListEmpty()) {
+                    availableTvChannels = getAvailableTvChannelListFromTv();
+                    handler.updateChannelStateDescription(CHANNEL_TV_CHANNEL, availableTvChannels.keySet().stream()
+                            .collect(Collectors.toMap(Function.identity(), Function.identity())));
+                }
+            }
+            if (command instanceof RefreshType) {
+                // Get current tv channel name
+                String tvChannelName = getCurrentTvChannel();
+                handler.postUpdateChannel(CHANNEL_TV_CHANNEL, new StringType(tvChannelName));
+            } else if (command instanceof StringType) {
+                if (availableTvChannels.containsKey(command.toString())) {
+                    switchTvChannel(command);
+                } else {
+                    logger.warn(
+                            "The given TV Channel with Name: {} couldn't be found in the local Channel List from the TV.",
+                            command);
+                }
+            } else {
+                logger.warn("Unknown command: {} for Channel {}", command, channel);
+            }
+        } catch (Exception e) {
+            if (isTvOfflineException(e)) {
+                logger.warn("Could not execute command for TV Channels, the TV is offline.");
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.NONE, TV_OFFLINE_MSG);
+            } else if (isTvNotListeningException(e)) {
+                handler.postUpdateThing(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        TV_NOT_LISTENING_MSG);
+            } else {
+                logger.warn("Error occurred during handling of command for TV Channels: {}", e.getMessage(), e);
+            }
+        }
+    }
+
+    private boolean isTvChannelListEmpty() {
+        return (availableTvChannels == null) || availableTvChannels.isEmpty();
+    }
+
+    private Map<String, String> getAvailableTvChannelListFromTv() throws IOException {
+        AvailableTvChannelsDTO availableTvChannelsDTO = OBJECT_MAPPER.readValue(
+                connectionManager.doHttpsGet(GET_AVAILABLE_TV_CHANNEL_LIST_PATH), AvailableTvChannelsDTO.class);
+
+        ConcurrentMap<String, String> tvChannelsMap = availableTvChannelsDTO.getChannel().stream()
+                .collect(Collectors.toConcurrentMap(ChannelDTO::getName, ChannelDTO::getCcid, (c1, c2) -> c1));
+
+        logger.debug("TV Channels added: {}", tvChannelsMap.size());
+        if (logger.isTraceEnabled()) {
+            tvChannelsMap.keySet().forEach(app -> logger.trace("TV Channel found: {}", app));
+        }
+        return tvChannelsMap;
+    }
+
+    private String getCurrentTvChannel() throws IOException {
+        TvChannelDTO tvChannelDTO = OBJECT_MAPPER.readValue(connectionManager.doHttpsGet(TV_CHANNEL_PATH),
+                TvChannelDTO.class);
+        return Optional.ofNullable(tvChannelDTO.getChannel()).map(ChannelDTO::getName).orElse("NA");
+    }
+
+    private void switchTvChannel(Command command) throws IOException {
+        ChannelDTO channelDTO = new ChannelDTO();
+        channelDTO.setCcid(availableTvChannels.get(command.toString()));
+
+        ChannelListDTO channelListDTO = new ChannelListDTO();
+        channelListDTO.setId("allter");
+        channelListDTO.setVersion("30");
+
+        TvChannelDTO tvChannelDTO = new TvChannelDTO(channelDTO, channelListDTO);
+        String switchTvChannelJson = OBJECT_MAPPER.writeValueAsString(tvChannelDTO);
+        logger.debug("Switch TV Channel json: {}", switchTvChannelJson);
+        connectionManager.doHttpsPost(TV_CHANNEL_PATH, switchTvChannelJson);
+    }
+
+    public void clearAvailableTvChannelList() {
+        if (availableTvChannels != null) {
+            availableTvChannels.clear();
+        }
+    }
+}
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 (file)
index 0000000..214d843
--- /dev/null
@@ -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 (file)
index 0000000..7fa2859
--- /dev/null
@@ -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 (file)
index 0000000..20b900b
--- /dev/null
@@ -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 (file)
index 0000000..b065f44
--- /dev/null
@@ -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 (file)
index 0000000..feb909a
--- /dev/null
@@ -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 (file)
index 0000000..3212523
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link TvSettingsCurrentDTO} class defines the Data Transfer Object
+ * for the POST Request to Philips TV API /menuitems/settings/current endpoint to retrieve current settings of the tv,
+ * e.g. the tv picture brightness.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class TvSettingsCurrentDTO {
+
+    @JsonProperty("nodes")
+    private List<NodesDTO> nodes;
+
+    public TvSettingsCurrentDTO() {
+    }
+
+    public TvSettingsCurrentDTO(List<NodesDTO> nodes) {
+        this.nodes = nodes;
+    }
+
+    public void setNodes(List<NodesDTO> nodes) {
+        this.nodes = nodes;
+    }
+
+    public List<NodesDTO> getNodes() {
+        return nodes;
+    }
+
+    @Override
+    public String toString() {
+        return "TvSettingsCurrentDTO{" + "nodes = '" + nodes + '\'' + "}";
+    }
+}
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 (file)
index 0000000..b6210cd
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link TvSettingsUpdateDTO} class defines the Data Transfer Object
+ * for the Philips TV API /menuitems/settings/update endpoint to update settings of the tv, e.g. turning on/off
+ * ambilight hue power.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class TvSettingsUpdateDTO {
+
+    @JsonProperty
+    private List<ValuesDTO> values;
+
+    public TvSettingsUpdateDTO() {
+    }
+
+    public TvSettingsUpdateDTO(List<ValuesDTO> values) {
+        this.values = values;
+    }
+
+    public void setValues(List<ValuesDTO> values) {
+        this.values = values;
+    }
+
+    public List<ValuesDTO> getValues() {
+        return values;
+    }
+
+    @Override
+    public String toString() {
+        return "TvSettingsDTO{" + "values = '" + values + '\'' + "}";
+    }
+}
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 (file)
index 0000000..e3113fc
--- /dev/null
@@ -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 (file)
index 0000000..e4a2e8d
--- /dev/null
@@ -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 (file)
index 0000000..f1d9b88
--- /dev/null
@@ -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 (file)
index 0000000..9631501
--- /dev/null
@@ -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 (file)
index 0000000..fdae3bc
--- /dev/null
@@ -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 (file)
index 0000000..584a335
--- /dev/null
@@ -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 (file)
index 0000000..8f0fbf3
--- /dev/null
@@ -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 (file)
index 0000000..bc4ef0d
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link AmbilightModeDTO} class defines the Data Transfer Object
+ * for the Philips TV API /ambilight/mode endpoint to retrieve or set the ambilight mode.
+ * <p>
+ * current (string): One of following values:
+ * <p>
+ * internal: The internal ambilight algorithm is used to calculate the ambilight colours.
+ * <p>
+ * manual: The cached ambilight colours are shown.
+ * <p>
+ * expert: The cached ambilight colours are used as input for the internal ambilight algorithm
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class AmbilightModeDTO {
+
+    @JsonProperty("current")
+    private String current = "";
+
+    public AmbilightModeDTO() {
+    }
+
+    public void setCurrent(String current) {
+        this.current = current;
+    }
+
+    public String getCurrent() {
+        return current;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbilightModeDTO{" + "current = '" + current + '\'' + "}";
+    }
+}
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 (file)
index 0000000..21793b7
--- /dev/null
@@ -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 (file)
index 0000000..4393d9f
--- /dev/null
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.ambilight;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link AmbilightTopologyDTO} class defines the Data Transfer Object
+ * for the Philips TV API /ambilight/topology endpoint to retrieve the ambilight topology information.
+ * <p>
+ * Endpoint returns:
+ * <p>
+ * layers (integer): The number of layers.
+ * <p>
+ * left (integer): The number of pixels on the left.
+ * <p>
+ * top (integer): The number of pixels on the top.
+ * <p>
+ * right (integer): The number of pixels on the right.
+ * <p>
+ * bottom (integer): The number of pixels on the bottom.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class AmbilightTopologyDTO {
+
+    @JsonProperty("top")
+    private int top;
+
+    @JsonProperty("left")
+    private int left;
+
+    @JsonProperty("bottom")
+    private int bottom;
+
+    @JsonProperty("layers")
+    private int layers;
+
+    @JsonProperty("right")
+    private int right;
+
+    public AmbilightTopologyDTO() {
+    }
+
+    public void setTop(int top) {
+        this.top = top;
+    }
+
+    public int getTop() {
+        return top;
+    }
+
+    public void setLeft(int left) {
+        this.left = left;
+    }
+
+    public int getLeft() {
+        return left;
+    }
+
+    public void setBottom(int bottom) {
+        this.bottom = bottom;
+    }
+
+    public int getBottom() {
+        return bottom;
+    }
+
+    public void setLayers(int layers) {
+        this.layers = layers;
+    }
+
+    public int getLayers() {
+        return layers;
+    }
+
+    public void setRight(int right) {
+        this.right = right;
+    }
+
+    public int getRight() {
+        return right;
+    }
+
+    @JsonIgnore
+    public int getPixelSizeForGivenSide(String side) {
+        int value;
+        switch (side) {
+            case "left":
+                value = left;
+                break;
+            case "right":
+                value = right;
+                break;
+            case "top":
+                value = top;
+                break;
+            case "bottom":
+                value = bottom;
+                break;
+            default:
+                throw new IllegalStateException("Unexpected side: " + side);
+        }
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbilightTopologyDTO{" + "top = '" + top + '\'' + ",left = '" + left + '\'' + ",bottom = '" + bottom
+                + '\'' + ",layers = '" + layers + '\'' + ",right = '" + right + '\'' + "}";
+    }
+}
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 (file)
index 0000000..b30b640
--- /dev/null
@@ -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 (file)
index 0000000..6097790
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.application;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link AvailableAppsDTO} class defines the Data Transfer Object
+ * for the Philips TV API /applications endpoint for retrieving all installed apps.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class AvailableAppsDTO {
+
+    @JsonProperty("version")
+    private int version;
+
+    @JsonProperty("applications")
+    private @Nullable List<ApplicationsDTO> applications;
+
+    public AvailableAppsDTO() {
+    }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setApplications(List<ApplicationsDTO> applications) {
+        this.applications = applications;
+    }
+
+    public @Nullable List<ApplicationsDTO> getApplications() {
+        return applications;
+    }
+
+    @Override
+    public String toString() {
+        return "AvailableAppsDTO{" + "version = '" + version + '\'' + ",applications = '" + applications + '\'' + "}";
+    }
+}
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 (file)
index 0000000..bddad43
--- /dev/null
@@ -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 (file)
index 0000000..e5dae87
--- /dev/null
@@ -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 (file)
index 0000000..5019c33
--- /dev/null
@@ -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 (file)
index 0000000..53ac1fe
--- /dev/null
@@ -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 (file)
index 0000000..67fda9c
--- /dev/null
@@ -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 (file)
index 0000000..fc10fd8
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.androidtv.internal.protocol.philipstv.service.model.channel;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The {@link AvailableTvChannelsDTO} class defines the Data Transfer Object
+ * for the Philips TV API channeldb/tv/channelLists/all endpoint for retrieving all tv channels.
+ *
+ * @author Benjamin Meyer - Initial contribution
+ * @author Ben Rosenblum - Merged into AndroidTV
+ */
+public class AvailableTvChannelsDTO {
+
+    @JsonProperty("Channel")
+    private @Nullable List<ChannelDTO> channel;
+
+    @JsonProperty("id")
+    private String id = "";
+
+    @JsonProperty("medium")
+    private String medium = "";
+
+    @JsonProperty("version")
+    private int version;
+
+    @JsonProperty("listType")
+    private String listType = "";
+
+    @JsonProperty("operator")
+    private String operator = "";
+
+    @JsonProperty("installCountry")
+    private String installCountry = "";
+
+    public AvailableTvChannelsDTO() {
+    }
+
+    public void setChannel(List<ChannelDTO> channel) {
+        this.channel = channel;
+    }
+
+    public @Nullable List<ChannelDTO> getChannel() {
+        return channel;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setMedium(String medium) {
+        this.medium = medium;
+    }
+
+    public String getMedium() {
+        return medium;
+    }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setListType(String listType) {
+        this.listType = listType;
+    }
+
+    public String getListType() {
+        return listType;
+    }
+
+    public void setOperator(String operator) {
+        this.operator = operator;
+    }
+
+    public String getOperator() {
+        return operator;
+    }
+
+    public void setInstallCountry(String installCountry) {
+        this.installCountry = installCountry;
+    }
+
+    public String getInstallCountry() {
+        return installCountry;
+    }
+
+    @Override
+    public String toString() {
+        return "AvailableTvChannelsDTO{" + "channel = '" + channel + '\'' + ",id = '" + id + '\'' + ",medium = '"
+                + medium + '\'' + ",version = '" + version + '\'' + ",listType = '" + listType + '\'' + ",operator = '"
+                + operator + '\'' + ",installCountry = '" + installCountry + '\'' + "}";
+    }
+}
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 (file)
index 0000000..0ef476f
--- /dev/null
@@ -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 (file)
index 0000000..db6f13e
--- /dev/null
@@ -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 (file)
index 0000000..b025452
--- /dev/null
@@ -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 (file)
index 0000000..655fb26
--- /dev/null
@@ -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 (file)
index 0000000..621379f
--- /dev/null
@@ -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 (file)
index 0000000..74dad65
--- /dev/null
@@ -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;
+    }
+}
index 3e644d9af3fd56246140222b2a774ade27e44c0d..516cb24bbe43934886b53829d834712c782c07f5 100644 (file)
@@ -7,15 +7,17 @@ addon.androidtv.description = This is the add-on for AndroidTV.
 
 thing-type.androidtv.googletv.label = GoogleTV
 thing-type.androidtv.googletv.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
index 8e5c032c0dadd23ed35ce3dff71db30261bad5c6..9fd3c643df189c85915884910edd8c4b1bafe83e 100644 (file)
                                <default>5</default>
                                <advanced>true</advanced>
                        </parameter>
+                       <parameter name="delay" type="integer" min="0">
+                               <label>Message Delay</label>
+                               <description>Delay between messages</description>
+                               <default>0</default>
+                               <advanced>true</advanced>
+                       </parameter>
+               </config-description>
+
+       </thing-type>
+
+       <thing-type id="philipstv">
+               <label>Philips TV</label>
+               <description>A Philips TV device</description>
+
+               <channels>
+                       <channel id="debug" typeId="debug"/>
+                       <channel id="keypress" typeId="keypress"/>
+                       <channel id="keyboard" typeId="keyboard"/>
+                       <channel id="keycode" typeId="keycode"/>
+                       <channel id="pincode" typeId="pincode"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="brightness" typeId="brightness"/>
+                       <channel id="contrast" typeId="contrast"/>
+                       <channel id="sharpness" typeId="sharpness"/>
+                       <channel id="app" typeId="app"/>
+                       <channel id="appname" typeId="appname"/>
+                       <channel id="appicon" typeId="appicon"/>
+                       <channel id="tvChannel" typeId="tvChannel"/>
+                       <channel id="player" typeId="player"/>
+                       <channel id="searchContent" typeId="searchContent"/>
+                       <channel id="ambilightPower" typeId="ambilightPower"/>
+                       <channel id="ambilightHuePower" typeId="ambilightHuePower"/>
+                       <channel id="ambilightLoungePower" typeId="ambilightLoungePower"/>
+                       <channel id="ambilightStyle" typeId="ambilightStyle"/>
+                       <channel id="ambilightColor" typeId="ambilightColor"/>
+                       <channel id="ambilightLeftColor" typeId="ambilightLeftColor"/>
+                       <channel id="ambilightRightColor" typeId="ambilightRightColor"/>
+                       <channel id="ambilightTopColor" typeId="ambilightTopColor"/>
+                       <channel id="ambilightBottomColor" typeId="ambilightBottomColor"/>
+               </channels>
+
+               <config-description>
+                       <parameter name="ipAddress" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Hostname</label>
+                               <description>Hostname or IP address of the device</description>
+                       </parameter>
+                       <parameter name="googletvPort" type="integer">
+                               <label>GoogleTV Port</label>
+                               <description>Port to connect to</description>
+                               <default>6466</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="philipstvPort" type="integer">
+                               <label>PhilipsTV Port</label>
+                               <description>Port to connect to</description>
+                               <default>1926</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="refreshRate" type="integer">
+                               <label>Refresh Rate</label>
+                               <description>
+                                       How often the Philips TV status details get refreshed. Value in seconds. '0' deactives refreshing.
+                               </description>
+                               <advanced>true</advanced>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="useUpnpDiscovery" type="boolean">
+                               <label>Use UPnP Discovery</label>
+                               <description>
+                                       Enables UPnP Discovery. If disabled, constant HTTPS polling will happen.
+                               </description>
+                               <advanced>true</advanced>
+                               <default>true</default>
+                       </parameter>
+                       <parameter name="keystoreFileName" type="text">
+                               <label>Keystore File Name</label>
+                               <description>Java keystore containing key and certs</description>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="keystorePassword" type="text">
+                               <context>password</context>
+                               <label>Keystore Password</label>
+                               <description>Password for the keystore file</description>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="reconnect" type="integer" min="0">
+                               <label>Reconnect Delay</label>
+                               <description>Delay between reconnection attempts</description>
+                               <default>60</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="heartbeat" type="integer" min="0">
+                               <label>Heartbeat Frequency</label>
+                               <description>Frequency of heartbeats</description>
+                               <default>5</default>
+                               <advanced>true</advanced>
+                       </parameter>
                        <parameter name="delay" type="integer" min="0">
                                <label>Delay</label>
                                <description>Delay between messages</description>
                <description>App URL</description>
        </channel-type>
 
+       <channel-type id="appicon" advanced="true">
+               <item-type>Image</item-type>
+               <label>App Icon</label>
+               <description>App Icon</description>
+       </channel-type>
+
        <channel-type id="keypress">
                <item-type>String</item-type>
                <label>Key Press</label>
                <description>Player Control</description>
        </channel-type>
 
+       <channel-type id="tvChannel" advanced="true">
+               <item-type>String</item-type>
+               <label>TV Channel</label>
+               <description>Name of the currently running TV Channel. Changing this to a value from the List, switches the
+                       channel.
+               </description>
+       </channel-type>
+
+       <channel-type id="searchContent" advanced="true">
+               <item-type>String</item-type>
+               <label>Search Content</label>
+               <description>Keyword(s) to search for on TV via Google Assistant</description>
+       </channel-type>
+
+       <channel-type id="ambilightPower">
+               <item-type>Switch</item-type>
+               <label>Ambilight Power</label>
+               <description>Ambilight power. Turns ambilight on or off.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightHuePower">
+               <item-type>Switch</item-type>
+               <label>Ambilight + Hue Power</label>
+               <description>Ambilight + Hue power. Turns ambilight with connected Philips Hue Lamps on or off.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightLoungePower">
+               <item-type>Switch</item-type>
+               <label>Ambilight Lounge Power</label>
+               <description>Ambilight lounge power. Turns ambilight lounge on or off.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightStyle" advanced="true">
+               <item-type>String</item-type>
+               <label>Ambilight Style</label>
+               <description>Current ambilight style. Changing this to a value from the List, switches the ambilight style.
+               </description>
+               <category>Ambilight</category>
+               <state>
+                       <options>
+                               <option value="FOLLOW_VIDEO STANDARD">FOLLOW_VIDEO STANDARD</option>
+                               <option value="FOLLOW_VIDEO NATURAL">FOLLOW_VIDEO NATURAL</option>
+                               <option value="FOLLOW_VIDEO IMMERSIVE">FOLLOW_VIDEO IMMERSIVE</option>
+                               <option value="FOLLOW_VIDEO VIVID">FOLLOW_VIDEO VIVID</option>
+                               <option value="FOLLOW_VIDEO GAME">FOLLOW_VIDEO GAME</option>
+                               <option value="FOLLOW_VIDEO COMFORT">FOLLOW_VIDEO COMFORT</option>
+                               <option value="FOLLOW_VIDEO RELAX">FOLLOW_VIDEO RELAX</option>
+
+                               <option value="FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS">FOLLOW_AUDIO ENERGY_ADAPTIVE_BRIGHTNESS</option>
+                               <option value="FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS">FOLLOW_AUDIO ENERGY_ADAPTIVE_COLORS</option>
+                               <option value="FOLLOW_AUDIO VU_METER">FOLLOW_AUDIO VU_METER</option>
+                               <option value="FOLLOW_AUDIO SPECTRUM_ANALYZER">FOLLOW_AUDIO SPECTRUM_ANALYZER</option>
+                               <option value="FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE">FOLLOW_AUDIO KNIGHT_RIDER_CLOCKWISE</option>
+                               <option value="FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING">FOLLOW_AUDIO KNIGHT_RIDER_ALTERNATING</option>
+                               <option value="FOLLOW_AUDIO RANDOM_PIXEL_FLASH">FOLLOW_AUDIO RANDOM_PIXEL_FLASH</option>
+                               <option value="FOLLOW_AUDIO PARTY">FOLLOW_AUDIO PARTY</option>
+                               <option value="FOLLOW_AUDIO MODE_RANDOM">FOLLOW_AUDIO MODE_RANDOM</option>
+
+                               <option value="FOLLOW_COLOR MANUAL_HUE">FOLLOW_COLOR MANUAL_HUE</option>
+                               <option value="FOLLOW_COLOR HOT_LAVA">FOLLOW_COLOR HOT_LAVA</option>
+                               <option value="FOLLOW_COLOR DEEP_WATER">FOLLOW_COLOR DEEP_WATER</option>
+                               <option value="FOLLOW_COLOR FRESH_NATURE">FOLLOW_COLOR FRESH_NATURE</option>
+                               <option value="FOLLOW_COLOR ISF">FOLLOW_COLOR ISF</option>
+                               <option value="FOLLOW_COLOR PTA_LOUNGE">FOLLOW_COLOR PTA_LOUNGE</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="ambilightColor" advanced="true">
+               <item-type>Color</item-type>
+               <label>All Ambilight</label>
+               <description>Sets the Ambilight color for all sides.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightLeftColor" advanced="true">
+               <item-type>Color</item-type>
+               <label>Left Ambilight</label>
+               <description>Sets the Ambilight color for the left side.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightRightColor" advanced="true">
+               <item-type>Color</item-type>
+               <label>Right Ambilight</label>
+               <description>Sets the Ambilight color for the right side.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightTopColor" advanced="true">
+               <item-type>Color</item-type>
+               <label>Top Ambilight</label>
+               <description>Sets the Ambilight color for the top.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="ambilightBottomColor" advanced="true">
+               <item-type>Color</item-type>
+               <label>Bottom Ambilight</label>
+               <description>Sets the Ambilight color for the bottom.</description>
+               <category>Ambilight</category>
+       </channel-type>
+
+       <channel-type id="brightness" advanced="true">
+               <item-type>Dimmer</item-type>
+               <label>Brightness</label>
+               <description>Brightness of the TV picture.</description>
+               <category>Tv Picture</category>
+       </channel-type>
+
+       <channel-type id="contrast" advanced="true">
+               <item-type>Dimmer</item-type>
+               <label>Contrast</label>
+               <description>Contrast of the TV picture.</description>
+               <category>Tv Picture</category>
+       </channel-type>
+
+       <channel-type id="sharpness" advanced="true">
+               <item-type>Dimmer</item-type>
+               <label>Sharpness</label>
+               <description>Sharpness of the TV picture.</description>
+               <category>Tv Picture</category>
+       </channel-type>
+
 </thing:thing-descriptions>