diff --git a/.gitignore b/.gitignore index 93f8fcbb6200b..1fa39ea94ddff 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ xtend-gen/ **/.settings/org.eclipse.* bundles/**/src/main/history +bundles/**/.vscode features/**/src/main/history features/**/src/main/feature diff --git a/CODEOWNERS b/CODEOWNERS index ae0d5d1a3c958..768f73de87de8 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -157,6 +157,7 @@ /bundles/org.openhab.binding.hpprinter/ @cossey /bundles/org.openhab.binding.http/ @J-N-K /bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg +/bundles/org.openhab.binding.huesync/ @pgfeller /bundles/org.openhab.binding.hydrawise/ @digitaldan /bundles/org.openhab.binding.hyperion/ @tavalin /bundles/org.openhab.binding.iammeter/ @lewei50 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 3a3e20519aec8..d518bbbfcc63b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -781,6 +781,11 @@ org.openhab.binding.hue ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.huesync + ${project.version} + org.openhab.addons.bundles org.openhab.binding.hydrawise diff --git a/bundles/org.openhab.binding.huesync/NOTICE b/bundles/org.openhab.binding.huesync/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.huesync/README.md b/bundles/org.openhab.binding.huesync/README.md new file mode 100644 index 0000000000000..4eec27c8dbb9d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/README.md @@ -0,0 +1,320 @@ +# HueSync Binding + + +This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-play-hdmi-sync-box-/046677555221) into openHAB. +The integration happens directly through the Hue [HDMI Sync Box API](https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/). + +- [HueSync Binding](#huesync-binding) + - [Discovery](#discovery) + - [Thing Configuration](#thing-configuration) + - [Thing Configuration "huesyncbox"](#thing-configuration-huesyncbox) + - [Channels](#channels) + - [Channels - Groups](#channels---groups) + - [Channels - device-firmware](#channels----device-firmware) + - [Channels - device-hdmi-connection-\[in|out\]](#channels---device-hdmi-connection-inout) + - [device-commands](#device-commands) + - [Item Configuration](#item-configuration) + - [Items - Groups](#items---groups) + - [Items - Remote Control](#items---remote-control) + - [Items - Firmware](#items---firmware) + +
+ +![Play HDMI Sync Box](doc/bridge1.png) +![Play HDMI Sync Box](doc/bridge2.png) + + + +## Discovery + +The binding is using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to discover HDMI Sync devices in the local network. +The LED on the Sync Box must be white or red. +This indicates that the device is connected to the Network. +If the LED is blinking blue, you need to setup the device using the official [Hue Sync App](https://www.philips-hue.com/en-in/explore-hue/propositions/entertainment/hue-sync). + +If the device is not discovered you can check if it is properly configured and discoverable in the network: + +
+ Linux (Ubuntu based distributions) + +```bash +$ avahi-browse --resolve _huesync._tcp ++ wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local += wlp0s20f3 IPv4 HueSyncBox-XXXXXXXXXXX _huesync._tcp local + hostname = [XXXXXXXXXXX.local] + address = [192.168.0.12] + port = [443] + txt = ["name=Sync Box" "devicetype=HSB1" "uniqueid=XXXXXXXXXXX" "path=/api"] +``` + +
+ +## Thing Configuration + +To enable the binding to communicate with the device, a registration is required. +Once the registration process is completed, the acquired token will authorize the binding to communicate with the device. +After initial discovery and thing creation the device will stay offline. +To complete the authentication you need to pressed the registration button on the sync box for 3 seconds. + +| Name | Type | Description | Default | Required | Advanced | +| -------------------- | ------- | --------------------------------- | ------- | -------- | -------- | +| host | text | IP address of the device | N/A | yes | no | +| port | integer | Port of the HDMI Sync Box. | 443 | yes | yes | +| registrationId | text | Application Registration Id | N/A | no | yes | +| apiAccessToken | text | API Access Token | N/A | no | yes | +| statusUpdateInterval | integer | Status Update Interval in seconds | 10 | yes | yes | + +Once the thing is connection, you can add the equipment to the model using the advanced configuration option of the UI. Select the "**Channels**"-Tab of the thing and click "**Add Equipment to Model**": + +![Add Equipment to Model](doc/add_equipment_to_model.png) + +You may use **Expert Mode** for configuration, or use the UI wizard. + +![Expert Mode](doc/expert_mode.png) + +You need to update the device UID "**..**" in the example with the UID of your device. +
+ Expert Mode - Configuration Example +
+Group HueSyncBox "HueSyncBox" <iconify:mdi:tv> ["NetworkAppliance"]
+
+Group HueSyncBox_Inputs "Inputs" <receiver> (HueSyncBox) ["Receiver"]
+
+Group HueSyncBox_Input_1 "Input 1" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
+Group HueSyncBox_Input_2 "Input 2" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
+Group HueSyncBox_Input_3 "Input 3" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
+Group HueSyncBox_Input_4 "Input 4" <iconify:mdi:hdmi-port> (HueSyncBox_Inputs) ["Receiver"]
+Group HueSyncBox_Output "Output" <iconify:mdi:tv> (HueSyncBox) ["Screen"]
+Group HueSyncBox_Firmware "Firmware" <iconify:mdi:information> (HueSyncBox)
+Group HueSyncBox_Execution "Remote Control" <iconify:mdi:remote> (HueSyncBox) ["RemoteControl"]
+
+String HueSyncBox_Device_Mode  "Mode"  <iconify:mdi:multimedia> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#mode" }
+String HueSyncBox_Device_Input "Input" <iconify:mdi:input>      (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-source" }
+Switch HueSyncBox_Device_Hdmi  "HDMI"  <iconify:mdi:hdmi-port>  (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-active" }
+Switch HueSyncBox_Device_Sync  "Sync"  <iconify:mdi:sync>       (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#sync-active" }
+Number:Dimensionless HueSyncBox_Device_Brightness "Brightness " <iconify:mdi:brightness-percent> (HueSyncBox_Execution) { channel="huesync:huesyncbox:HueSyncBox:device-commands#brightness" }
+
+String HueSyncBox_Firmware_Version        "Firmware Version"        <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncbox:HueSyncBox:device-firmware#firmware" }           
+String HueSyncBox_Latest_Firmware_Version "Latest Firmware Version" <iconify:mdi:text> (HueSyncBox_Firmware) { channel="huesync:huesyncbox:HueSyncBox:device-firmware#available-firmware" } 
+
+String HueSyncBox_Device_hdmi_in1_Name      "Name - Input 1"           <iconify:mdi:text>       (HueSyncBox_Input_1)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#name" }              
+String HueSyncBox_Device_hdmi_in1_Type      "Type - Input 1"           <iconify:mdi:devices>    (HueSyncBox_Input_1)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#type" }              
+String HueSyncBox_Device_hdmi_in1_Status    "Status - Input 1"         <iconify:mdi:connection> (HueSyncBox_Input_1)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#status" }
+String HueSyncBox_Device_hdmi_in1_Mode      "Mode - Input 1"           <iconify:mdi:multimedia> (HueSyncBox_Input_1)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#mode" }               
+
+String HueSyncBox_Device_hdmi_in2_Name      "Name - Input 2"           <iconify:mdi:text>       (HueSyncBox_Input_2)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#name" }              
+String HueSyncBox_Device_hdmi_in2_Type      "Type - Input 2"           <iconify:mdi:devices>    (HueSyncBox_Input_2)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#type" }              
+String HueSyncBox_Device_hdmi_in2_Status    "Status - Input 2"         <iconify:mdi:connection> (HueSyncBox_Input_2)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#status" } 
+String HueSyncBox_Device_hdmi_in2_Mode      "Mode - Input 2"           <iconify:mdi:multimedia> (HueSyncBox_Input_2)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#mode" }              
+
+String HueSyncBox_Device_hdmi_in3_Name      "Name - Input 3"           <iconify:mdi:text>       (HueSyncBox_Input_3)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#name" }              
+String HueSyncBox_Device_hdmi_in3_Type      "Type - Input 3"           <iconify:mdi:devices>    (HueSyncBox_Input_3)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#type" }              
+String HueSyncBox_Device_hdmi_in3_Status    "Status - Input 3"         <iconify:mdi:connection> (HueSyncBox_Input_3)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#status" } 
+String HueSyncBox_Device_hdmi_in3_Mode      "Mode - Input 3"           <iconify:mdi:multimedia> (HueSyncBox_Input_3)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#mode" }              
+
+String HueSyncBox_Device_hdmi_in4_Name      "Name - Input 4"           <iconify:mdi:text>       (HueSyncBox_Input_4)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#name" }              
+String HueSyncBox_Device_hdmi_in4_Type      "Type - Input 4"           <iconify:mdi:devices>    (HueSyncBox_Input_4)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#type" }              
+String HueSyncBox_Device_hdmi_in4_Status    "Status - Input 4"         <iconify:mdi:connection> (HueSyncBox_Input_4)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#status" } 
+String HueSyncBox_Device_hdmi_in4_Mode      "Mode - Input 4"           <iconify:mdi:multimedia> (HueSyncBox_Input_4)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#mode" }              
+
+String HueSyncBox_Device_hdmi_out_Name      "Name - Output"            <iconify:mdi:text>       (HueSyncBox_Output)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#name" }               
+String HueSyncBox_Device_hdmi_out_Type      "Type - Output"            <iconify:mdi:tv>         (HueSyncBox_Output)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#type" }               
+String HueSyncBox_Device_hdmi_out_Status    "Status - Output"          <iconify:mdi:connection> (HueSyncBox_Output)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#status" }              
+String HueSyncBox_Device_hdmi_out_Mode      "Mode - Output"            <iconify:mdi:multimedia> (HueSyncBox_Output)   { channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#mode" }
+  
+
+ +This will give you a default model and item setup: + +![Semantic Model](doc/model.png) + +The following chapters provide more details for a tailored setup. + +## Channels + +### Channels - Groups + +### Channels - device-firmware + +Information about the installed device firmware and available updates. + +| Channel | Type | Read/Write | Description | +| ------------------ | ------ | ---------- | --------------------------------- | +| firmware | String | R | Installed firmware version | +| available-firmware | String | R | Latest available firmware version | + +### Channels - device-hdmi-connection-[in\|out] + +Information about a HDMI input connection. + +| Channel | Type | Read/Write | Description | +| ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| type | String | R |
Friendly Type
| +| status | String | R |
Device connection status
| +| name | String | R | Friendly Name | +| mode | String | R |
Mode
| + +#### device-commands + +| Channel | Type | Read/Write | Description | +| ---------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| mode | String | R/W |
Hue Sync operation mode
| +| hdmi-source | Switch | R/W |
Source
| +| sync-active | Switch | R/W |
Synchronization

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| +| brightness | Number:Dimensionless | R/W |
Brightness

| + + + +## Item Configuration + +### Items - Groups + +
+ Groups + +| | | | | | | | +| ----- | -------------------- | ---------------- | --------------------------- | ------------------- | -------------------- | --- | +| Group | HueSyncBox | "HueSyncBox" | \ | | ["NetworkAppliance"] | | +| Group | HueSyncBox_Execution | "Remote Control" | \ | (HueSyncBox) | ["RemoteControl"] | | +| Group | HueSyncBox_Firmware | "Firmware" | \ | (HueSyncBox) | ["Point"] | | +| Group | HueSyncBox_Inputs | "Inputs" | \ | (HueSyncBox) | ["Receiver"] | | +| Group | HueSyncBox_Input_1 | "Input 1" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_2 | "Input 2" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_3 | "Input 3" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Input_4 | "Input 4" | \ | (HueSyncBox_Inputs) | ["Receiver"] | | +| Group | HueSyncBox_Output | "Output" | \ | (HueSyncBox) | ["Screen"] | | +
+ +### Items - Remote Control + +
+ Remote Control + +| | | | | | | | +| -------------------- | ---------------------------- | ------------ | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| String | HueSyncBox_Device_Mode | "Mode" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#mode" } | | +| String | HueSyncBox_Device_Input | "Input" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-source" } |
HDMI Source
  • input1
  • input2
  • input3
  • input4
| +| Switch | HueSyncBox_Device_Sync | "Sync" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#sync-active" } |
HDMI Sync

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

| +| Switch | HueSyncBox_Device_Hdmi | "HDMI" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#hdmi-active" } |
HDMI Active

OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

| +| Number:Dimensionless | HueSyncBox_Device_Brightness | "Brightness" | \ | (HueSyncBox_Execution) | { channel="huesync:huesyncbox:HueSyncBox:device-commands#brightness" } |
0 ... 200

  • 0 = max reduction
  • 100 = no brightness reduction/boost compared to input
  • 200 = max boost

| +
+ +### Items - Firmware + +
+ Firmware + +| | | | | | | | +| ------ | ---------------------------------- | ------------------------- | -------------------- | --------------------- | ------------ | ---------------------------------------------------------------------------------- | +| String | HueSyncBox_Firmware_Version | "Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-firmware#firmware" }` | +| String | HueSyncBox_Latest_Firmware_Version | "Latest Firmware Version" | \ | (HueSyncBox_Firmware) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-firmware#available-firmware" }` | + +
+ +--- + +
+ Input 1 + +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| String | HueSyncBox_Device_hdmi_in1_Name | "Name - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#name" }` | +| String | HueSyncBox_Device_hdmi_in1_Type | "Type - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#type" }` | +| String | HueSyncBox_Device_hdmi_in1_Status | "Status - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#status" }` | +| String | HueSyncBox_Device_hdmi_in1_Mode | "Mode - Input 1" | \ | (HueSyncBox_Input_1) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-1#mode" }` | + +
+ +
+ Input 2 + +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| String | HueSyncBox_Device_hdmi_in2_Name | "Name - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#name" }` | +| String | HueSyncBox_Device_hdmi_in2_Type | "Type - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#type" }` | +| String | HueSyncBox_Device_hdmi_in2_Status | "Status - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#status" }` | +| String | HueSyncBox_Device_hdmi_in2_Mode | "Mode - Input 2" | \ | (HueSyncBox_Input_2) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-2#mode" }` | +
+ +
+ Input 3 + +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| String | HueSyncBox_Device_hdmi_in3_Name | "Name - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#name" }` | +| String | HueSyncBox_Device_hdmi_in3_Type | "Type - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#type" }` | +| String | HueSyncBox_Device_hdmi_in3_Status | "Status - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#status" }` | +| String | HueSyncBox_Device_hdmi_in3_Mode | "Mode - Input 3" | \ | (HueSyncBox_Input_3) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-3#mode" }` | + +
+ +
+ Input 4 + +| | | | | | | | +| ------ | --------------------------------- | ------------------ | -------------------------- | -------------------- | ------------ | ----------------------------------------------------------------------- | +| String | HueSyncBox_Device_hdmi_in4_Name | "Name - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#name" }` | +| String | HueSyncBox_Device_hdmi_in4_Type | "Type - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#type" }` | +| String | HueSyncBox_Device_hdmi_in4_Status | "Status - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#status" }` | +| String | HueSyncBox_Device_hdmi_in4_Mode | "Mode - Input 4" | \ | (HueSyncBox_Input_4) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-in-4#mode" }` | + +
+ +--- + +
+ Output + +| | | | | | | | +| ------ | --------------------------------- | ----------------- | -------------------------- | ------------------- | ------------ | ---------------------------------------------------------------------- | +| String | HueSyncBox_Device_hdmi_out_Name | "Name - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#name" }` | +| String | HueSyncBox_Device_hdmi_out_Type | "Type - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#type" }` | +| String | HueSyncBox_Device_hdmi_out_Status | "Status - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#status" }` | +| String | HueSyncBox_Device_hdmi_out_Mode | "Mode - Output" | \ | (HueSyncBox_Output) | ["Property"] | `{ channel="huesync:huesyncbox:HueSyncBox:device-hdmi-out#mode" }` | + +
+ + + + diff --git a/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png b/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png new file mode 100644 index 0000000000000..b3303bdfe4b37 Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/add_equipment_to_model.png differ diff --git a/bundles/org.openhab.binding.huesync/doc/bridge1.png b/bundles/org.openhab.binding.huesync/doc/bridge1.png new file mode 100644 index 0000000000000..e7795d60d0c7c Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/bridge1.png differ diff --git a/bundles/org.openhab.binding.huesync/doc/bridge2.png b/bundles/org.openhab.binding.huesync/doc/bridge2.png new file mode 100644 index 0000000000000..e4d24ed0e4701 Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/bridge2.png differ diff --git a/bundles/org.openhab.binding.huesync/doc/expert_mode.png b/bundles/org.openhab.binding.huesync/doc/expert_mode.png new file mode 100644 index 0000000000000..c35d0ba478229 Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/expert_mode.png differ diff --git a/bundles/org.openhab.binding.huesync/doc/logo.jpg b/bundles/org.openhab.binding.huesync/doc/logo.jpg new file mode 100644 index 0000000000000..8a19642cd01a8 Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/logo.jpg differ diff --git a/bundles/org.openhab.binding.huesync/doc/model.png b/bundles/org.openhab.binding.huesync/doc/model.png new file mode 100644 index 0000000000000..f054a9b9db550 Binary files /dev/null and b/bundles/org.openhab.binding.huesync/doc/model.png differ diff --git a/bundles/org.openhab.binding.huesync/pom.xml b/bundles/org.openhab.binding.huesync/pom.xml new file mode 100644 index 0000000000000..7ca10a423d798 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.3.0-SNAPSHOT + + + org.openhab.binding.huesync + + openHAB Add-ons :: Bundles :: Hue Sync Box Binding + + diff --git a/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml new file mode 100644 index 0000000000000..3d292e7237ad3 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/feature/feature.xml @@ -0,0 +1,10 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-mdns + mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version} + + diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java new file mode 100644 index 0000000000000..e8ddd638bf4b8 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/HueSyncConstants.java @@ -0,0 +1,112 @@ +/** + * 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.huesync.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link HueSyncConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncConstants { + public static class ENDPOINTS { + public static final String DEVICE = "device"; + public static final String REGISTRATIONS = "registrations"; + public static final String HDMI = "hdmi"; + public static final String EXECUTION = "execution"; + + public static class EXECUTION_ENDPOINTS { + public static final String MODE = "mode"; + public static final String SYNC = "syncActive"; + public static final String HDMI = "hdmiActive"; + public static final String SOURCE = "hdmiSource"; + public static final String BRIGHTNESS = "brightness"; + } + } + + public static class CHANNELS { + public static class DEVICE { + public static class INFORMATION { + public static final String FIRMWARE = "device-firmware#firmware"; + public static final String FIRMWARE_AVAILABLE = "device-firmware#available-firmware"; + } + } + + public static class COMMANDS { + public static final String MODE = "device-commands#mode"; + public static final String SYNC = "device-commands#sync-active"; + public static final String HDMI = "device-commands#hdmi-active"; + public static final String SOURCE = "device-commands#hdmi-source"; + public static final String BRIGHTNESS = "device-commands#brightness"; + } + + public static class HDMI { + public static class IN_1 { + public static final String NAME = "device-hdmi-in-1#name"; + public static final String TYPE = "device-hdmi-in-1#type"; + public static final String STATUS = "device-hdmi-in-1#status"; + public static final String MODE = "device-hdmi-in-1#mode"; + } + + public static class IN_2 { + public static final String NAME = "device-hdmi-in-2#name"; + public static final String TYPE = "device-hdmi-in-2#type"; + public static final String STATUS = "device-hdmi-in-2#status"; + public static final String MODE = "device-hdmi-in-2#mode"; + } + + public static class IN_3 { + public static final String NAME = "device-hdmi-in-3#name"; + public static final String TYPE = "device-hdmi-in-3#type"; + public static final String STATUS = "device-hdmi-in-3#status"; + public static final String MODE = "device-hdmi-in-3#mode"; + } + + public static class IN_4 { + public static final String NAME = "device-hdmi-in-4#name"; + public static final String TYPE = "device-hdmi-in-4#type"; + public static final String STATUS = "device-hdmi-in-4#status"; + public static final String MODE = "device-hdmi-in-4#mode"; + } + + public static class OUT { + public static final String NAME = "device-hdmi-out#name"; + public static final String TYPE = "device-hdmi-out#type"; + public static final String STATUS = "device-hdmi-out#status"; + public static final String MODE = "device-hdmi-out#mode"; + } + } + } + + public static final String APPLICATION_NAME = "openHAB"; + + /** Minimal API Version required. Only apiLevel >= 7 is supported. */ + public static final Integer MINIMAL_API_VERSION = 7; + + public static final String BINDING_ID = "huesync"; + public static final String THING_TYPE_ID = "huesyncbox"; + public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID); + + public static final String PARAMETER_HOST = "host"; + public static final String PARAMETER_PORT = "port"; + + public static final Integer REGISTRATION_INITIAL_DELAY = 3; + public static final Integer REGISTRATION_INTERVAL = 1; + + public static final String REGISTRATION_ID = "registrationId"; + public static final String API_TOKEN = "apiAccessToken"; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java new file mode 100644 index 0000000000000..57a5ce5b3ed63 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDevice.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * HDMI Sync Box Device Information + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +@NonNullByDefault +public class HueSyncDevice { + /** Friendly name of the device */ + public @Nullable String name; + /** Device Type identifier */ + public @Nullable String deviceType; + /** + * Capitalized hex string of the 6 byte / 12 characters device id without + * delimiters. Used as unique id on label, certificate common name, hostname + * etc. + */ + public @Nullable String uniqueId; + /** + * Increased between firmware versions when api changes. Only apiLevel >= 7 is + * supported. + */ + public int apiLevel = 0; + /** + * User readable version of the device firmware, starting with decimal major + * .minor .maintenance format e.g. “1.12.3” + */ + public @Nullable String firmwareVersion; + /** + * Build number of the firmware. Unique for every build with newer builds + * guaranteed a higher number than older. + */ + public int buildNumber = 0; + + public boolean termsAgreed; + + /** uninitialized, disconnected, lan, wan */ + public @Nullable String wifiState; + public @Nullable String ipAddress; + + public @Nullable HueSyncDeviceCapabilitiesInfo capabilities; + + public boolean beta; + public boolean overheating; + public boolean bluetooth; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java new file mode 100644 index 0000000000000..8049f1557dbf4 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceCapabilitiesInfo.java @@ -0,0 +1,32 @@ +/** + * 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.huesync.internal.api.dto.device; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * HDMI Sync Box Device Information Capabilities + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +@NonNullByDefault +public class HueSyncDeviceCapabilitiesInfo { + /** The total number of IR codes configurable */ + public int maxIrCodes; + /** The total number of Presets configurable */ + public int maxPresets; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java new file mode 100644 index 0000000000000..12bb1ff358c6d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailed.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +import java.util.Date; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * HDMI Sync Box Device Information - Extended information (only available + * to registered clients) + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +@NonNullByDefault +public class HueSyncDeviceDetailed extends HueSyncDevice { + public @Nullable HueSyncDeviceDetailedWifiInfo wifi; + public @Nullable HueSyncDeviceDetailedUpdateInfo update; + + /** UTC time when last check for update was performed. */ + public @Nullable Date lastCheckedUpdate; + /** + * Build number that is available to update to. Item is set to null when there + * is no update available. + */ + public int updatableBuildNumber; + /** + * User readable version of the firmware the device can upgrade to. Item is set + * to null when there is no update available. + */ + public @Nullable String updatableFirmwareVersion; + /** + * 1 = regular; + * 0 = off in powersave, passthrough or sync mode; + * 2 = dimmed in powersave or passthrough mode and off in sync mode + */ + public int ledMode = -1; + + /** none, doSoftwareRestart, doFirmwareUpdate */ + public @Nullable String action; + public @Nullable String pushlink; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java new file mode 100644 index 0000000000000..73c99bb466b24 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedUpdateInfo.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.device; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * HDMI Sync Box Device Information - Automatic Firmware update + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +@NonNullByDefault +public class HueSyncDeviceDetailedUpdateInfo { + /** + * Sync Box checks daily for a firmware update. If true, an available update + * will automatically be installed. This will be postponed if Sync Box is + * passing through content to the TV and being used. + */ + public boolean autoUpdateEnabled; + /** + * TC hour when the automatic update will check and execute, values 0 – 23. + * Default is 10. Ideally this value should be set to 3AM according to user’s + * timezone. + */ + public int autoUpdateTime; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java new file mode 100644 index 0000000000000..0c99f86acb5eb --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/device/HueSyncDeviceDetailedWifiInfo.java @@ -0,0 +1,39 @@ +/** + * 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.huesync.internal.api.dto.device; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * HDMI Sync Box Device Information - Wifi connection information + * + * @author Patrik Gfeller - Initial Contribution + * + * @see Hue + * HDMI Sync Box API + */ +@NonNullByDefault +public class HueSyncDeviceDetailedWifiInfo { + /** Wifi SSID */ + public @Nullable String ssid; + /** + * 0 = not connected; + * 1 = weak; + * 2 = fair; + * 3 = good; + * 4 = excellent + */ + public int strength; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java new file mode 100644 index 0000000000000..5ca1063f553b4 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecution.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.execution; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.handler.HueSyncHandler; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.slf4j.Logger; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Root object for execution resource + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncExecution { + private static final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); + + public static final List KNOWN_MODES = Collections + .unmodifiableList(Arrays.asList("powersave", "passthrough", "video", "game", "music")); + + private @Nullable String mode; + + /** + * + * @return powersave, passthrough, video, game, music + */ + @JsonProperty("mode") + public @Nullable String getMode() { + return this.mode; + } + + /** + * + * @apiNote More modes can be added in the future, so clients must gracefully + * handle modes they don’t recognize. If an unknown mode is received, a + * warning will be logged and mode will fallback to "unknown" + * + * @param mode powersave, passthrough, video, game, music + */ + public void setMode(String mode) { + if (!HueSyncExecution.KNOWN_MODES.contains(mode)) { + logger.warn( + "device mode [{}] is not known by this version of the binding ➡️ please open an issue to notify the maintainer(s). Fallback will be used. ", + mode); + } + + this.mode = HueSyncExecution.KNOWN_MODES.contains(mode) ? mode : "unknown"; + } + + /** + * Reports `false` in case of `powersave` or `passthrough` mode, and `true` in case of `video`, `game`, or `music` + * mode. + * When changed from false to true, it will start syncing in last used mode for current source. + * When changed from true to false, will set passthrough mode. + */ + public boolean syncActive; + /** + * Reports `false` in case of `powersave mode`, and true in case of `passthrough`, `video`, `game`, `music` mode. + * When changed from false to true, it will set passthrough mode. When changed from `true` to `false`, will set + * powersave mode. + */ + public boolean hdmiActive; + + /** + * Currently selected hdmi input: `input1`, `input2`, `input3,` `input4` + */ + public @Nullable String hdmiSource; + + public @Nullable String hueTarget; + public @Nullable String lastSyncMode; + public @Nullable String preset; + + /** + * brightness: + * - Get, Put + * - number, uint + * - 0 ... 200 (100 = no brightness reduction/boost compared to input, 0 = max reduction, 200 = max boost) + */ + public int brightness; + + public @Nullable HueSyncExecutionVideo video; + public @Nullable HueSyncExecutionGame game; + public @Nullable HueSyncExecutionMusic music; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java new file mode 100644 index 0000000000000..f806d2efa425c --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionGame.java @@ -0,0 +1,28 @@ +/** + * 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.huesync.internal.api.dto.execution; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncExecutionGame { + public @Nullable String intensity; + + public boolean backgroundLighting; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java new file mode 100644 index 0000000000000..d56a92782d4d5 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionMusic.java @@ -0,0 +1,27 @@ +/** + * 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.huesync.internal.api.dto.execution; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncExecutionMusic { + public @Nullable String intensity; + public @Nullable String palette; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java new file mode 100644 index 0000000000000..46cb01e574a91 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/execution/HueSyncExecutionVideo.java @@ -0,0 +1,28 @@ +/** + * 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.huesync.internal.api.dto.execution; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncExecutionVideo { + public @Nullable String intensity; + + public boolean backgroundLighting; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java new file mode 100644 index 0000000000000..d4457528b4339 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmi.java @@ -0,0 +1,39 @@ +/** + * 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.huesync.internal.api.dto.hdmi; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncHdmi { + public @Nullable HueSyncHdmiConnectionInfo input1; + public @Nullable HueSyncHdmiConnectionInfo input2; + public @Nullable HueSyncHdmiConnectionInfo input3; + public @Nullable HueSyncHdmiConnectionInfo input4; + + public @Nullable HueSyncHdmiConnectionInfo output; + + /** x @ */ + public @Nullable String contentSpecs; + + /** Current content specs supported for video sync (video/game mode) */ + public boolean videoSyncSupported; + /** Current content specs supported for audio sync (music mode) */ + public boolean audioSyncSupported; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java new file mode 100644 index 0000000000000..1dac4c657e80c --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/hdmi/HueSyncHdmiConnectionInfo.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.api.dto.hdmi; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + * + */ +@NonNullByDefault +public class HueSyncHdmiConnectionInfo { + /** Friendly name, not empty */ + public @Nullable String name; + /** + * Friendly type: + * generic, + * video, + * game, + * music, + * xbox, + * playstation, + * nintendoswitch, + * phone, + * desktop, + * laptop, + * appletv, + * roku, + * shield, + * chromecast, + * firetv, + * diskplayer, + * settopbox, + * satellite, + * avreceiver, + * soundbar, + * hdmiswitch + */ + public @Nullable String type; + /** + * unplugged, + * plugged, + * linked, + * unknown + */ + public @Nullable String status; + /** + * video, + * game, + * music + */ + public @Nullable String lastSyncMode; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java new file mode 100644 index 0000000000000..89b2343dab412 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistration.java @@ -0,0 +1,25 @@ +/** + * 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.huesync.internal.api.dto.registration; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncRegistration { + public String registrationId = ""; + public String accessToken = ""; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java new file mode 100644 index 0000000000000..d7651a6872f89 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/api/dto/registration/HueSyncRegistrationRequest.java @@ -0,0 +1,28 @@ +/** + * 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.huesync.internal.api.dto.registration; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncRegistrationRequest { + /** User recognizable name of registered application */ + public @Nullable String appName; + /** User recognizable name of application instance. */ + public @Nullable String instanceName; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java new file mode 100644 index 0000000000000..08c87729b2117 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/config/HueSyncConfiguration.java @@ -0,0 +1,29 @@ +/** + * 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.huesync.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Binding configuration parameters, + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncConfiguration { + public String registrationId = ""; + public String apiAccessToken = ""; + public String host = ""; + public Integer port = 443; + public Integer statusUpdateInterval = 10; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java new file mode 100644 index 0000000000000..e0a9bbba2fa29 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncAuthenticationResult.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.connection; + +import java.net.URI; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.api.Authentication.Result; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncAuthenticationResult implements Result { + private String token; + private URI uri; + + public HueSyncAuthenticationResult(URI uri, String token) { + this.uri = uri; + this.token = token; + } + + public String getToken() { + return this.token; + } + + @Override + public URI getURI() { + return this.uri; + } + + @Override + public void apply(@Nullable Request request) { + if (request != null && !request.getHeaders().contains(HttpHeader.AUTHORIZATION)) { + request.header(HttpHeader.AUTHORIZATION, "Bearer " + this.token); + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java new file mode 100644 index 0000000000000..16a8068d90a15 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncConnection.java @@ -0,0 +1,259 @@ +/** + * 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.huesync.internal.connection; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpResponseException; +import org.eclipse.jetty.client.api.AuthenticationStore; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncConnection { + public static final ObjectMapper ObjectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + /** + * Request format: The Sync Box API can be accessed locally via HTTPS on root level (port 443, + * /api/v1), resource level /api/v1/ and in some cases sub-resource level + * /api/v1//. + */ + private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s"; + private static final String API = "api/v1"; + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class); + + private Integer port; + private String host; + + private ServiceRegistration tlsProviderService; + private HttpClient httpClient; + private URI uri; + + private Optional authentication = Optional.empty(); + + protected String registrationId = ""; + + public HueSyncConnection(HttpClient httpClient, String host, Integer port) + throws CertificateException, IOException, URISyntaxException { + + this.host = host; + this.port = port; + + this.uri = new URI(String.format("https://%s:%s", this.host, this.port)); + + HueSyncTrustManagerProvider trustManagerProvider = new HueSyncTrustManagerProvider(this.host, this.port); + BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + this.tlsProviderService = context.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, + null); + this.httpClient = httpClient; + } + + public void updateAuthentication(String id, String token) { + // TODO: Simplify this check ... + if (id.equals(this.registrationId) && this.authentication.isPresent() + && this.authentication.get().getToken().equals(token)) { + return; + } + + this.removeAuthentication(); + + if (!id.isBlank() && !token.isBlank()) { + this.registrationId = id; + + this.authentication = Optional.of(new HueSyncAuthenticationResult(this.uri, token)); + this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication.get()); + } + } + + // #region protected + protected @Nullable T executeRequest(HttpMethod method, String endpoint, String payload, + @Nullable Class type) { + try { + return this.processedResponse(this.executeRequest(method, endpoint, payload), type); + } catch (ExecutionException e) { + this.handleExecutionException(e); + } catch (InterruptedException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); + } + + return null; + } + + protected @Nullable T executeGetRequest(String endpoint, Class type) { + try { + return this.processedResponse(this.executeGetRequest(endpoint), type); + } catch (ExecutionException e) { + this.handleExecutionException(e); + } catch (InterruptedException | TimeoutException e) { + this.logger.error("{}", e.getMessage()); + } + + return null; + } + + protected boolean isRegistered() { + return this.authentication.isPresent(); + } + + protected void unregisterDevice() { + if (this.isRegistered()) { + try { + String endpoint = ENDPOINTS.REGISTRATIONS + "/" + this.registrationId; + ContentResponse response = this.executeRequest(HttpMethod.DELETE, endpoint); + + if (response.getStatus() == HttpStatus.OK_200) { + this.removeAuthentication(); + } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + this.logger.error("{}", e.getMessage()); + } + } + } + + protected void dispose() { + this.tlsProviderService.unregister(); + } + // #endregion + + // #region private + private @Nullable T processedResponse(Response response, @Nullable Class type) { + int status = response.getStatus(); + + /* + * 400 Invalid State: Registration in progress + * + * 401 Authentication failed: If credentials are missing or invalid, errors out. If + * credentials are missing, continues on to GET only the Configuration state when + * unauthenticated, to allow for device identification. + * + * 404 Invalid URI Path: Accessing URI path which is not supported + * + * 500 Internal: Internal errors like out of memory + */ + switch (status) { + case HttpStatus.OK_200: + return (type != null && (response instanceof ContentResponse)) + ? this.deserialize(((ContentResponse) response).getContentAsString(), type) + : null; + case HttpStatus.BAD_REQUEST_400: + logger.trace("registration in progress: no token received yet"); + break; + case HttpStatus.UNAUTHORIZED_401: + logger.error("credentials missing or invalid"); + + this.removeAuthentication(); + break; + case HttpStatus.NOT_FOUND_404: + logger.error("invalid device URI or API endpoint"); + break; + case HttpStatus.INTERNAL_SERVER_ERROR_500: + logger.error("hue sync box server problem"); + break; + default: + logger.warn("unexpected HTTP status: {}", status); + } + return null; + } + + private @Nullable T deserialize(String json, Class type) { + try { + return ObjectMapper.readValue(json, type); + } catch (JsonProcessingException | NoClassDefFoundError e) { + this.logger.error("{}", e.getMessage()); + + return null; + } + } + + private ContentResponse executeRequest(HttpMethod method, String endpoint) + throws InterruptedException, TimeoutException, ExecutionException { + return this.executeRequest(method, endpoint, ""); + } + + private ContentResponse executeGetRequest(String endpoint) + throws InterruptedException, ExecutionException, TimeoutException { + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); + + return httpClient.GET(uri); + } + + private ContentResponse executeRequest(HttpMethod method, String endpoint, String payload) + throws InterruptedException, TimeoutException, ExecutionException { + + String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint); + + Request request = this.httpClient.newRequest(uri).method(method); + + this.logger.trace("uri: {}", uri); + this.logger.trace("method: {}", method); + this.logger.trace("payload: {}", payload); + + if (!payload.isBlank()) { + request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON_UTF_8.toString()) + .content(new StringContentProvider(payload)); + } + + return request.send(); + } + + private void handleExecutionException(ExecutionException e) { + this.logger.error("{}", e.getMessage()); + + Throwable cause = e.getCause(); + if (cause != null && cause instanceof HttpResponseException) { + processedResponse(((HttpResponseException) cause).getResponse(), null); + } + } + + private void removeAuthentication() { + AuthenticationStore store = this.httpClient.getAuthenticationStore(); + store.clearAuthenticationResults(); + this.httpClient.setAuthenticationStore(store); + + this.registrationId = ""; + this.authentication = Optional.empty(); + } + + // #endregion +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java new file mode 100644 index 0000000000000..9d208e6d16990 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncDeviceConnection.java @@ -0,0 +1,196 @@ +/** + * 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.huesync.internal.connection; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.HueSyncConstants.ENDPOINTS; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistrationRequest; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Handles the connection to a Hue HDMI Sync Box using the official API. + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncDeviceConnection { + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncDeviceConnection.class); + + private HueSyncConnection connection; + + private Map> DeviceCommandExecutors = new HashMap<>(); + + public HueSyncDeviceConnection(HttpClient httpClient, HueSyncConfiguration configuration) + throws CertificateException, IOException, URISyntaxException { + + this.connection = new HueSyncConnection(httpClient, configuration.host, configuration.port); + + registerCommandHandlers(); + } + + // #region private + + private void registerCommandHandlers() { + this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.MODE, + defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.MODE)); + this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, + defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SOURCE)); + this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, + defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.BRIGHTNESS)); + this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC, + defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.SYNC)); + this.DeviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI, + defaultHandler(HueSyncConstants.ENDPOINTS.EXECUTION_ENDPOINTS.HDMI)); + } + + private Consumer defaultHandler(String endpoint) { + return command -> { + execute(endpoint, command); + }; + } + + private void execute(String key, Command command) { + this.logger.info("Command executor: {} - {}", key, command); + + String value = ""; + + if (command instanceof QuantityType) { + value = Integer.toString(((QuantityType) command).intValue()); + } else if (command instanceof OnOffType) { + value = ((OnOffType) command).name().equals("ON") ? "true" : "false"; + } else if (command instanceof StringType) { + value = "\"" + ((StringType) command).toString() + "\""; + } else { + this.logger.error("Type {} not supported by this connection", command.getClass().getCanonicalName()); + return; + } + + if (!this.connection.isRegistered()) { + this.logger.warn("Device is not registered - ignoring command: {}", command); + return; + } + + String json = String.format("{ \"%s\": %s }", key, value); + this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null); + } + + // #endregion + + @SuppressWarnings("null") + public void executeCommand(Channel channel, Command command) { + String uid = channel.getUID().getAsString(); + String commandId = channel.getUID().getId(); + + this.logger.trace("Channel UID: {} - Command: {}", uid, command.toFullString()); + + if (RefreshType.REFRESH.equals(command)) { + return; + } + + if (this.DeviceCommandExecutors.containsKey(commandId)) { + this.DeviceCommandExecutors.get(commandId).accept(command); + } else { + this.logger.error("No executor registered for command {} - please report this as an issue", commandId); + } + } + + public @Nullable HueSyncDevice getDeviceInfo() { + return this.connection.executeGetRequest(ENDPOINTS.DEVICE, HueSyncDevice.class); + } + + public @Nullable HueSyncDeviceDetailed getDetailedDeviceInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.DEVICE, "", HueSyncDeviceDetailed.class) + : null; + } + + public @Nullable HueSyncHdmi getHdmiInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.HDMI, "", HueSyncHdmi.class) + : null; + } + + public @Nullable HueSyncExecution getExecutionInfo() { + return this.connection.isRegistered() + ? this.connection.executeRequest(HttpMethod.GET, ENDPOINTS.EXECUTION, "", HueSyncExecution.class) + : null; + } + + public @Nullable HueSyncRegistration registerDevice(String id) { + if (!id.isBlank()) { + try { + HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest(); + dto.appName = HueSyncConstants.APPLICATION_NAME; + dto.instanceName = id; + + String payload = HueSyncConnection.ObjectMapper.writeValueAsString(dto); + + HueSyncRegistration registration = this.connection.executeRequest(HttpMethod.POST, + ENDPOINTS.REGISTRATIONS, payload, HueSyncRegistration.class); + if (registration != null) { + this.connection.updateAuthentication(id, registration.accessToken); + + return registration; + } + } catch (JsonProcessingException e) { + this.logger.error("{}", e.getMessage()); + } + } + return null; + } + + public boolean isRegistered() { + return this.connection.isRegistered(); + } + + public void unregisterDevice() { + this.connection.unregisterDevice(); + } + + public void dispose() { + this.connection.dispose(); + } + + public void updateConfiguration(HueSyncConfiguration config) { + this.logger.debug("🔧 Connection configuration update for device {}:{} - Registration Id [{}]", config.host, + config.port, config.registrationId); + + this.connection.updateAuthentication(config.registrationId, config.apiAccessToken); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java new file mode 100644 index 0000000000000..8ff98533a2ea8 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/connection/HueSyncTrustManagerProvider.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.connection; + +import java.io.IOException; +import java.security.cert.CertificateException; + +import javax.net.ssl.X509ExtendedTrustManager; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.net.http.PEMTrustManager; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; + +/** + * Provides a {@link PEMTrustManager} to allow secure connections to a Hue HDMI + * Sync Box + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncTrustManagerProvider implements TlsTrustManagerProvider { + private final String host; + private final Integer port; + + private X509ExtendedTrustManager trustManager; + + public HueSyncTrustManagerProvider(String host, Integer port) throws IOException, CertificateException { + this.trustManager = PEMTrustManager.getInstanceFromServer("https://" + host); + this.port = port; + this.host = host; + } + + @Override + public String getHostName() { + return this.host + ":" + this.port; + } + + @Override + public X509ExtendedTrustManager getTrustManager() { + return this.trustManager; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java new file mode 100644 index 0000000000000..16df0a87c6aac --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/discovery/HueSyncDiscoveryParticipant.java @@ -0,0 +1,146 @@ +/** + * 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.huesync.internal.discovery; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; + +/** + * The {@link HueSyncDiscoveryParticipant} is responsible for discovering + * the remote huesync.boxes using mDNS discovery service. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "mdnsdiscovery.huesync") +public class HueSyncDiscoveryParticipant implements MDNSDiscoveryParticipant { + private Logger logger = HueSyncLogFactory.getLogger(HueSyncDiscoveryParticipant.class); + + /** + * + * Match the hostname + identifier of the discovered huesync-box. + * Input is like "HueSyncBox-XXXXXXXXXXXX._huesync._tcp.local." + * + * @see· + * Service·Name·and·Transport·Protocol·Port·Number·Registry + */ + private static final String SERVICE_TYPE = "_huesync._tcp.local."; + + private boolean autoDiscoveryEnabled = true; + + protected final ThingRegistry thingRegistry; + + @Activate + public HueSyncDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + + @Override + public Set getSupportedThingTypeUIDs() { + return Collections.singleton(HueSyncConstants.THING_TYPE_UID); + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo service) { + if (this.autoDiscoveryEnabled) { + ThingUID uid = getThingUID(service); + if (Objects.nonNull(uid)) { + try { + logger.trace("HDMI Sync Box {} discovered at {}:{}", service.getName(), + service.getHostAddresses()[0], service.getPort()); + + Map properties = new HashMap<>(); + + properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]); + properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort()); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(service.getName()) + .withProperties(properties).build(); + return result; + } catch (Exception e) { + logger.error("Unable to query device information for {}: {}", service.getQualifiedName(), + e.getMessage()); + } + } + } + return null; + } + + @Override + public @Nullable ThingUID getThingUID(ServiceInfo service) { + String id = service.getName(); + String[] addresses = service.getHostAddresses(); + + if (addresses.length == 0 || id == null || id.isBlank()) { + logger.debug("Incomplete mDNS device discovery information - {} ignored.", + id == null ? "[name: null]" : id); + return null; + } + + return new ThingUID(HueSyncConstants.THING_TYPE_UID, id); + } + + @Activate + protected void activate(ComponentContext componentContext) { + updateService(componentContext); + } + + @Modified + protected void modified(ComponentContext componentContext) { + updateService(componentContext); + } + + private void updateService(ComponentContext componentContext) { + String autoDiscoveryPropertyValue = (String) componentContext.getProperties() + .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); + + if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) { + boolean value = Boolean.valueOf(autoDiscoveryPropertyValue); + if (value != this.autoDiscoveryEnabled) { + logger.debug("{} update: {} ➡️ {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, + autoDiscoveryPropertyValue, value); + this.autoDiscoveryEnabled = value; + } + + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java new file mode 100644 index 0000000000000..29f300beda4f1 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncApiException.java @@ -0,0 +1,29 @@ +/** + * 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.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncApiException extends HueSyncException { + private static final long serialVersionUID = 0L; + + public HueSyncApiException(String message, Logger logger) { + super(message, logger); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java new file mode 100644 index 0000000000000..e08825d3fec8d --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionException.java @@ -0,0 +1,33 @@ +/** + * 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.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncConnectionException extends HueSyncException { + public HueSyncConnectionExceptionType type; + + private static final long serialVersionUID = 0L; + + public HueSyncConnectionException(String message, HueSyncConnectionExceptionType type, Logger logger) { + super(message, logger); + + this.type = type; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java new file mode 100644 index 0000000000000..53b3cc29e6717 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncConnectionExceptionType.java @@ -0,0 +1,24 @@ +/** + * 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.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public enum HueSyncConnectionExceptionType { + GENERIC +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java new file mode 100644 index 0000000000000..988ca1dced4b0 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncException.java @@ -0,0 +1,39 @@ +/** + * 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.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer; +import org.slf4j.Logger; + +/** + * Base class for all HueSyncExceptions + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public abstract class HueSyncException extends Exception { + private static final long serialVersionUID = 0L; + + public HueSyncException(String message, Logger logger) { + super(message); + + String logMessage = message; + + if (message.startsWith("@text")) { + logMessage = HueSyncLocalizer.getResourceString(message); + } + + logger.error("{}", logMessage); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java new file mode 100644 index 0000000000000..c0f5618735833 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/exceptions/HueSyncTaskException.java @@ -0,0 +1,29 @@ +/** + * 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.huesync.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncTaskException extends HueSyncException { + private static final long serialVersionUID = 0L; + + public HueSyncTaskException(String message, Logger logger) { + super(message, logger); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java new file mode 100644 index 0000000000000..eecaefa35bcba --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/factory/HueSyncHandlerFactory.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.factory; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.handler.HueSyncHandler; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link HueSyncHandlerFactory} is responsible for creating things and + * thing + * handlers. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.huesync", service = ThingHandlerFactory.class) +public class HueSyncHandlerFactory extends BaseThingHandlerFactory { + + private final HttpClientFactory httpClientFactory; + + @Activate + public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception { + this.httpClientFactory = httpClientFactory; + } + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections + .singleton(HueSyncConstants.THING_TYPE_UID); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (HueSyncConstants.THING_TYPE_UID.equals(thingTypeUID)) { + try { + return new HueSyncHandler(thing, this.httpClientFactory); + } catch (Exception e) { + // TODO: Implementation ... + } + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java new file mode 100644 index 0000000000000..ab47ede8e90bf --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/HueSyncHandler.java @@ -0,0 +1,366 @@ +/** + * 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.huesync.internal.handler; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.huesync.internal.HueSyncConstants; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.config.HueSyncConfiguration; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; +import org.openhab.binding.huesync.internal.exceptions.HueSyncApiException; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncRegistrationTask; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTask; +import org.openhab.binding.huesync.internal.handler.tasks.HueSyncUpdateTaskResultDto; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; + +/** + * The {@link HueSyncHandler} is responsible for handling commands, which are sent to one of the + * channels. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncHandler extends BaseThingHandler { + private static final String REGISTER = "Registration"; + private static final String POLL = "Update"; + + private static final String PROPERTY_API_VERSION = "apiVersion"; + + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncHandler.class); + + Map> tasks = new HashMap<>(); + + private Optional deviceInfo = Optional.empty(); + private HueSyncDeviceConnection connection; + + private HttpClient httpClient; + + public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory) + throws CertificateException, IOException, URISyntaxException { + super(thing); + + this.httpClient = httpClientFactory.getCommonHttpClient(); + this.httpClient.setName(this.thing.getUID().getAsString()); + + this.connection = new HueSyncDeviceConnection(this.httpClient, this.getConfigAs(HueSyncConfiguration.class)); + } + + // #region private + + @SuppressWarnings("null") + private Runnable initializeConnection() { + return () -> { + this.deviceInfo = Optional.ofNullable(this.connection.getDeviceInfo()); + this.deviceInfo.ifPresent(info -> { + setProperty(Thing.PROPERTY_SERIAL_NUMBER, info.uniqueId != null ? info.uniqueId : ""); + setProperty(Thing.PROPERTY_MODEL_ID, info.deviceType); + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, info.firmwareVersion); + + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", info.apiLevel)); + + try { + this.checkCompatibility(); + } catch (HueSyncApiException e) { + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } finally { + this.startTasks(); + } + }); + }; + } + + private void stopTask(@Nullable ScheduledFuture task) { + if (task == null || task.isCancelled() || task.isDone()) { + return; + } + + task.cancel(true); + } + + private @Nullable ScheduledFuture executeTask(Runnable task, long initialDelay, long interval) { + return scheduler.scheduleWithFixedDelay(task, initialDelay, interval, TimeUnit.SECONDS); + } + + private void startTasks() { + this.stopTasks(); + + this.connection.updateConfiguration(this.getConfigAs(HueSyncConfiguration.class)); + + Runnable task = null; + String id = this.connection.isRegistered() ? POLL : REGISTER; + + this.logger.debug("startTasks - [{}]", id); + + long initialDelay = 0; + long interval = 0; + + switch (id) { + case POLL: + initialDelay = 0; + interval = this.getConfigAs(HueSyncConfiguration.class).statusUpdateInterval; + + this.updateStatus(ThingStatus.ONLINE); + + task = new HueSyncUpdateTask(this.connection, this.deviceInfo.get(), + deviceStatus -> this.handleUpdate(deviceStatus)); + + break; + case REGISTER: + initialDelay = HueSyncConstants.REGISTRATION_INITIAL_DELAY; + interval = HueSyncConstants.REGISTRATION_INTERVAL; + + this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "@text/thing.config.huesync.box.registration"); + + task = new HueSyncRegistrationTask(this.connection, this.deviceInfo.get(), + registration -> this.handleRegistration(registration)); + + break; + } + + if (task != null) { + logger.trace("Starting task [{}]", id); + this.tasks.put(id, this.executeTask(task, initialDelay, interval)); + } + } + + private void stopTasks() { + logger.trace("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet())); + + this.tasks.values().forEach(task -> this.stopTask(task)); + this.tasks.clear(); + } + + @SuppressWarnings("null") + private void handleUpdate(@Nullable HueSyncUpdateTaskResultDto dto) { + try { + HueSyncUpdateTaskResultDto update = Optional.ofNullable(dto).get(); + + try { + this.updateFirmwareInformation(Optional.ofNullable(update.deviceStatus).get()); + } catch (NoSuchElementException e) { + this.logMissingUpdateInformation("device"); + } + + try { + this.updateHdmiInformation(Optional.ofNullable(update.hdmiStatus).get()); + } catch (NoSuchElementException e) { + this.logMissingUpdateInformation("hdmi"); + } + + try { + this.updateExecutionInformation(Optional.ofNullable(update.execution).get()); + } catch (NoSuchElementException e) { + this.logMissingUpdateInformation("execution"); + } + } catch (NoSuchElementException e) { + this.startTasks(); + } + } + + private void logMissingUpdateInformation(String api) { + this.logger.warn("⚠️ Device information - {} status missing ⚠️", api); + } + + @SuppressWarnings("null") + private void updateHdmiInformation(HueSyncHdmi hdmiStatus) { + // TODO: Resolve warnings ➡️ consider to encapsulate hdmi status obj to avoid complex null + // handling ... + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.NAME, new StringType(hdmiStatus.input1.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.TYPE, new StringType(hdmiStatus.input1.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.STATUS, new StringType(hdmiStatus.input1.status)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_1.MODE, new StringType(hdmiStatus.input1.lastSyncMode)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.NAME, new StringType(hdmiStatus.input2.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.TYPE, new StringType(hdmiStatus.input2.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.STATUS, new StringType(hdmiStatus.input2.status)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_2.MODE, new StringType(hdmiStatus.input2.lastSyncMode)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.NAME, new StringType(hdmiStatus.input3.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.TYPE, new StringType(hdmiStatus.input3.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.STATUS, new StringType(hdmiStatus.input3.status)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_3.MODE, new StringType(hdmiStatus.input3.lastSyncMode)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.NAME, new StringType(hdmiStatus.input4.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.TYPE, new StringType(hdmiStatus.input4.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.STATUS, new StringType(hdmiStatus.input4.status)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.IN_4.MODE, new StringType(hdmiStatus.input4.lastSyncMode)); + + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.NAME, new StringType(hdmiStatus.output.name)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.TYPE, new StringType(hdmiStatus.output.type)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.STATUS, new StringType(hdmiStatus.output.status)); + this.updateState(HueSyncConstants.CHANNELS.HDMI.OUT.MODE, new StringType(hdmiStatus.output.lastSyncMode)); + } + + private void updateFirmwareInformation(HueSyncDeviceDetailed deviceStatus) { + State firmwareState = new StringType(deviceStatus.firmwareVersion); + State firmwareAvailableState = new StringType( + deviceStatus.updatableFirmwareVersion != null ? deviceStatus.updatableFirmwareVersion + : deviceStatus.firmwareVersion); + + setProperty(Thing.PROPERTY_FIRMWARE_VERSION, deviceStatus.firmwareVersion); + setProperty(HueSyncHandler.PROPERTY_API_VERSION, String.format("%d", deviceStatus.apiLevel)); + + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE, firmwareState); + this.updateState(HueSyncConstants.CHANNELS.DEVICE.INFORMATION.FIRMWARE_AVAILABLE, firmwareAvailableState); + } + + private void updateExecutionInformation(HueSyncExecution executionStatus) { + this.updateState(HueSyncConstants.CHANNELS.COMMANDS.MODE, new StringType(executionStatus.getMode())); + this.updateState(HueSyncConstants.CHANNELS.COMMANDS.SYNC, + executionStatus.syncActive ? OnOffType.ON : OnOffType.OFF); + this.updateState(HueSyncConstants.CHANNELS.COMMANDS.HDMI, + executionStatus.hdmiActive ? OnOffType.ON : OnOffType.OFF); + this.updateState(HueSyncConstants.CHANNELS.COMMANDS.SOURCE, new StringType(executionStatus.hdmiSource)); + this.updateState(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS, new DecimalType(executionStatus.brightness)); + } + + private void handleRegistration(HueSyncRegistration registration) { + this.stopTasks(); + + setProperty(HueSyncConstants.REGISTRATION_ID, registration.registrationId); + + Configuration configuration = this.editConfiguration(); + + configuration.put(HueSyncConstants.REGISTRATION_ID, registration.registrationId); + configuration.put(HueSyncConstants.API_TOKEN, registration.accessToken); + + this.updateConfiguration(configuration); + + this.startTasks(); + } + + private void checkCompatibility() throws HueSyncApiException { + try { + HueSyncDevice deviceInformation = this.deviceInfo.orElseThrow(); + + if (deviceInformation.apiLevel < HueSyncConstants.MINIMAL_API_VERSION) { + throw new HueSyncApiException("@text/api.minimal-version", this.logger); + } + } catch (NoSuchElementException e) { + throw new HueSyncApiException("@text/api.communication-problem", logger); + } + } + + private void setProperty(String key, @Nullable String value) { + if (value != null) { + Map properties = this.editProperties(); + + if (properties.containsKey(key)) { + @Nullable + String currentValue = properties.get(key); + if (!(value.equals(currentValue))) { + saveProperty(key, value, properties); + } + } else { + saveProperty(key, value, properties); + } + } + } + + private void saveProperty(String key, String value, Map properties) { + properties.put(key, value); + this.updateProperties(properties); + } + + // #endregion + + // #region Override + // TODO: Life cycle handling for connection should resolve complex null problem (➡️ mode + // constructor) + @Override + public void initialize() { + try { + updateStatus(ThingStatus.UNKNOWN); + + this.stopTasks(); + + scheduler.execute(initializeConnection()); + } catch (Exception e) { + this.logger.error("{}", e.getMessage()); + + this.updateStatus(ThingStatus.OFFLINE); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (thing.getStatus() != ThingStatus.ONLINE) { + this.logger.warn("Device status: {} ➡️ Command {} for chanel {} will be ignored", + thing.getStatus().toString(), command.toFullString(), channelUID.toString()); + return; + } + + Channel channel = thing.getChannel(channelUID); + + if (channel == null) { + logger.error("Channel UID:{} does not exist - please report this as an issue", channelUID); + return; + } + + this.connection.executeCommand(channel, command); + } + + @Override + public void dispose() { + super.dispose(); + + try { + this.stopTasks(); + this.connection.dispose(); + } catch (Exception e) { + this.logger.error("{}", e.getMessage()); + } finally { + this.logger.info("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID()); + } + } + + @Override + public void handleRemoval() { + super.handleRemoval(); + + this.connection.unregisterDevice(); + } + + // #endregion +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java new file mode 100644 index 0000000000000..2fbcf14d069b7 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncRegistrationTask.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.handler.tasks; + +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.api.dto.registration.HueSyncRegistration; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.slf4j.Logger; + +/** + * Task to handle device registration. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncRegistrationTask implements Runnable { + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncRegistrationTask.class); + + private HueSyncDeviceConnection connection; + private HueSyncDevice deviceInfo; + + private Consumer action; + + public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, + Consumer action) { + + this.connection = connection; + this.deviceInfo = deviceInfo; + this.action = action; + } + + @Override + public void run() { + try { + String id = this.deviceInfo.uniqueId; + + if (this.connection.isRegistered() || id == null) { + return; + } + + this.logger.info("Listening for device registration - {} {}:{}", this.deviceInfo.name, + this.deviceInfo.deviceType, id); + + HueSyncRegistration registration = this.connection.registerDevice(id); + + if (registration != null) { + this.logger.info("API token for {} received", this.deviceInfo.name); + + this.action.accept(registration); + } + } catch (Exception e) { + this.logger.debug("{}", e.getMessage()); + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java new file mode 100644 index 0000000000000..16b1d2782dba0 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTask.java @@ -0,0 +1,71 @@ +/** + * 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.huesync.internal.handler.tasks; + +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDevice; +import org.openhab.binding.huesync.internal.connection.HueSyncDeviceConnection; +import org.openhab.binding.huesync.internal.log.HueSyncLogFactory; +import org.slf4j.Logger; + +/** + * Task to handle device information update. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncUpdateTask implements Runnable { + + private final Logger logger = HueSyncLogFactory.getLogger(HueSyncUpdateTask.class); + + private HueSyncDeviceConnection connection; + private HueSyncDevice deviceInfo; + + private Consumer<@Nullable HueSyncUpdateTaskResultDto> action; + + public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo, + Consumer<@Nullable HueSyncUpdateTaskResultDto> action) { + + this.connection = connection; + this.deviceInfo = deviceInfo; + + this.action = action; + } + + @Override + public void run() { + try { + this.logger.trace("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType, + this.deviceInfo.uniqueId); + + if (!this.connection.isRegistered()) { + this.action.accept(null); + } + + HueSyncUpdateTaskResultDto updateInfo = new HueSyncUpdateTaskResultDto(); + + updateInfo.deviceStatus = this.connection.getDetailedDeviceInfo(); + updateInfo.hdmiStatus = this.connection.getHdmiInfo(); + updateInfo.execution = this.connection.getExecutionInfo(); + + this.action.accept(updateInfo); + + } catch (Exception e) { + this.logger.debug("{}", e.getMessage()); + this.action.accept(null); + } + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java new file mode 100644 index 0000000000000..0eff9cc8317a3 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/handler/tasks/HueSyncUpdateTaskResultDto.java @@ -0,0 +1,30 @@ +/** + * 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.huesync.internal.handler.tasks; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.huesync.internal.api.dto.device.HueSyncDeviceDetailed; +import org.openhab.binding.huesync.internal.api.dto.execution.HueSyncExecution; +import org.openhab.binding.huesync.internal.api.dto.hdmi.HueSyncHdmi; + +/** + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncUpdateTaskResultDto { + public @Nullable HueSyncDeviceDetailed deviceStatus; + public @Nullable HueSyncHdmi hdmiStatus; + public @Nullable HueSyncExecution execution; +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java new file mode 100644 index 0000000000000..321324e737acb --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/i18n/HueSyncLocalizer.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.i18n; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +/** + * + * @author Patrik Gfeller - Initial Contribution + */ +@NonNullByDefault +public class HueSyncLocalizer { + private static final Locale locale = Locale.ENGLISH; + private static final BundleContext bundleContext = FrameworkUtil.getBundle(HueSyncLocalizer.class) + .getBundleContext(); + private static final ServiceReference serviceReference = bundleContext + .getServiceReference(TranslationProvider.class); + private static final Bundle bundle = bundleContext.getBundle(); + + public static String getResourceString(String key) { + String lookupKey = key.replace("@text/", ""); + String missingKey = "⚠️ Missing Translation ⚠️: " + key; + + @Nullable + TranslationProvider translationProvider = bundleContext.getService(serviceReference); + + String result = translationProvider == null ? missingKey + : translationProvider.getText(bundle, lookupKey, missingKey, locale); + + return result == null ? missingKey : result; + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java new file mode 100644 index 0000000000000..64c20e3222785 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/java/org/openhab/binding/huesync/internal/log/HueSyncLogFactory.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.huesync.internal.log; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to use [en] resource files to log messages to be consistent + * with exception message displayed on screen. + * + * @author Patrik Gfeller - Initial contribution + */ +@NonNullByDefault +public class HueSyncLogFactory { + + public static Logger getLogger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } +} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..b9afa2d7f4b11 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,23 @@ + + + + binding + Hue HDMI Sync Box Binding + Binding for the Hue HDMI Sync Box. + local + + + + mdns + + + mdnsServiceType + _huesync._tcp.local. + + + + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..2694fe53011dc --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,50 @@ + + + + + + + + + + network-address + + Network address of the HDMI Sync Box. + true + + + + + Port of the HDMI Sync Box. + true + 443 + true + + + + + The id of the API registration. + true + + + password + + To enable the binding to communicate with the device, a registration is required. Once the registration + process is completed, the acquired token will authorize the binding to interact with the device. After initial + discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 + seconds to grant the binding the required permissions. + true + + + + Seconds between fetching values from the Hue Sync Box. + true + true + 10 + Seconds + + + diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties new file mode 100644 index 0000000000000..89425372441f3 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/i18n/huesync.properties @@ -0,0 +1,179 @@ +# add-on + +addon.huesync.name = Hue HDMI Sync Box Binding +addon.huesync.description = Binding for the Hue HDMI Sync Box. + +# thing types + +thing-type.huesync.huesyncbox.label = Hue HDMI Sync Box +thing-type.huesync.huesyncbox.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. + +# thing types config + +thing-type.config.huesync.thing.apiAccessToken.label = API Access Token +thing-type.config.huesync.thing.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. +thing-type.config.huesync.thing.group.connection.label = Connection Settings +thing-type.config.huesync.thing.host.label = Network Address +thing-type.config.huesync.thing.host.description = Network address of the HDMI Sync Box. +thing-type.config.huesync.thing.port.label = Port +thing-type.config.huesync.thing.port.description = Port of the HDMI Sync Box. +thing-type.config.huesync.thing.registrationId.label = Application Registration Id +thing-type.config.huesync.thing.registrationId.description = The id of the API registration. +thing-type.config.huesync.thing.statusUpdateInterval.label = Status Update Interval +thing-type.config.huesync.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. + +# channel group types + +channel-group-type.huesync.device-commands.label = Hue Sync execution API commands +channel-group-type.huesync.device-commands.description = Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These commands allow you to influence how the lights react to your entertainment. +channel-group-type.huesync.device-firmware.label = Firmware +channel-group-type.huesync.device-firmware.description = Information about the installed device firmaware and available updates. +channel-group-type.huesync.device-hdmi-connection-in.label = HDMI Input +channel-group-type.huesync.device-hdmi-connection-in.description = HDMI connection +channel-group-type.huesync.device-hdmi-connection-out.label = HDMI output +channel-group-type.huesync.device-hdmi-connection-out.description = HDMI connection + +# channel types + +channel-type.huesync.connection-last-sync-mode.label = Last sync mode +channel-type.huesync.connection-last-sync-mode.description = Last sync mode used for this channel +channel-type.huesync.connection-name.label = HDMI Name +channel-type.huesync.connection-name.description = Friendly name of the HDMI connection +channel-type.huesync.connection-status.label = HDMI Status +channel-type.huesync.connection-status.description = Status of the HDMI input +channel-type.huesync.connection-status.command.option.unplugged = Unplugged +channel-type.huesync.connection-status.command.option.plugged = Plugged +channel-type.huesync.connection-status.command.option.linked = Linked +channel-type.huesync.connection-status.command.option.unknown = Unknown +channel-type.huesync.connection-type.label = HDMI Type +channel-type.huesync.connection-type.description = Type of the connected HDMI device +channel-type.huesync.connection-type.command.option.generic = Generic +channel-type.huesync.connection-type.command.option.video = Video +channel-type.huesync.connection-type.command.option.game = Game +channel-type.huesync.connection-type.command.option.music = Music +channel-type.huesync.connection-type.command.option.xbox = XBox +channel-type.huesync.connection-type.command.option.playstation = PlayStation +channel-type.huesync.connection-type.command.option.nintendoswitch = Nintendo Switch +channel-type.huesync.connection-type.command.option.phone = Phone +channel-type.huesync.connection-type.command.option.desktop = Desktop +channel-type.huesync.connection-type.command.option.laptop = Laptop +channel-type.huesync.connection-type.command.option.appletv = Apple TV +channel-type.huesync.connection-type.command.option.roku = Roku +channel-type.huesync.connection-type.command.option.shield = Nvidia Shield +channel-type.huesync.connection-type.command.option.chromecast = Chromecast +channel-type.huesync.connection-type.command.option.firetv = Amazon Fire TV +channel-type.huesync.connection-type.command.option.diskplayer = Disk Player +channel-type.huesync.connection-type.command.option.settopbox = Set-top box +channel-type.huesync.connection-type.command.option.satellite = Satellite +channel-type.huesync.connection-type.command.option.avreceiver = AV receiver +channel-type.huesync.connection-type.command.option.soundbar = Soundbar +channel-type.huesync.connection-type.command.option.hdmiswitch = HDMI switch +channel-type.huesync.device-info-firmware-available.label = Latest Firmware Version +channel-type.huesync.device-info-firmware-available.description = Latest available firmware version +channel-type.huesync.device-info-firmware.label = Firmware Version +channel-type.huesync.device-info-firmware.description = Installed firmware version +channel-type.huesync.executioin-hdmi-active.label = HDMI Active +channel-type.huesync.executioin-hdmi-active.description =

OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

+channel-type.huesync.execution-brightness.label = Brightness +channel-type.huesync.execution-brightness.description =

0 ... 200

  • 0 = max reduction
  • 100 = no brightness reduction/boost compared to input
  • 200 = max boost

+channel-type.huesync.execution-hdmi-source.label = HDMI input +channel-type.huesync.execution-hdmi-source.description =

  • input1
  • input2
  • input3
  • input4

+channel-type.huesync.execution-mode.label = Hue Sync operation mode +channel-type.huesync.execution-mode.description =

  • "video":

    Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience.

  • "music":

    Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes.

  • "game":

    Reacts to the action on your screen, intensifying the in-game atmosphere with bursts of light that correspond to explosions, gunfire, and other gameplay events.

  • "passthrough"
  • "powersave"

+channel-type.huesync.execution-sync-active.label = Synchronization Active +channel-type.huesync.execution-sync-active.description =

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

+ +# channel types + +channel-type.huesync.connection-lastSyncMode.label = Last sync mode +channel-type.huesync.connection-lastSyncMode.description = Last sync mode used for this channel +channel-type.huesync.executioin-hdmiActive.label = HDMI Active +channel-type.huesync.executioin-hdmiActive.description =

OFF in case of powersave mode and ON in case of passthrough, video, game or music mode.

When changed from OFF to ON, it will set passthrough mode. When changed from ON to OFF, will set powersave mode.

+channel-type.huesync.execution-hdmiSource.label = HDMI input +channel-type.huesync.execution-hdmiSource.description =

  • input1
  • input2
  • input3
  • input4

+channel-type.huesync.execution-syncActive.label = Synchronization Active +channel-type.huesync.execution-syncActive.description =

OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode.

When changed from OFF to ON, it will start syncing in last used mode for current source. When changed from ON to OFF, will set passthrough mode.

+ +# channel types + +channel-type.huesync.connection-type-in.label = Friendly type +channel-type.huesync.connection-type-out.label = Friendly type + +# channel group types + +channel-group-type.huesync.device-hdmi-connection.label = hdmi +channel-group-type.huesync.device-hdmi-connection.description = HDMI connection + +# channel group types + +channel-group-type.huesync.device.label = Generic device information + +# channel types + +channel-type.huesync.device-info.label = Last Updated +channel-type.huesync.device-info.description = The date and time when the sensor was last updated. +channel-type.huesync.device-info.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS + +# thing types + +thing-type.huesync.huesyncbox.channel.test.label = test +thing-type.huesync.huesyncbox.channel.test.description = test + +# channel group types + +channel-group-type.huesync.deviceGroup.label = Device Information + +# channel group types + +channel-group-type.huesync.test.label = Test Group +channel-group-type.huesync.test.description = Culpa occaecat aliquip tempor ipsum exercitation incididunt culpa sit mollit officia labore commodo. + +# channel types + +channel-type.huesync.mode.label = Sync Mode +channel-type.huesync.mode.description = Select the sync mode +channel-type.huesync.mode.state.option.powersave = Powersave +channel-type.huesync.mode.state.option.video = Video +channel-type.huesync.mode.state.option.music = Music +channel-type.huesync.mode.state.option.game = Game + +# thing types + +thing-type.huesync.box.label = Hue HDMI Sync Box +thing-type.huesync.box.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. + +# thing types config + +thing-type.config.huesync.box.apiAccessToken.label = API Access Token +thing-type.config.huesync.box.apiAccessToken.description = To enable the binding to communicate with the device, a registration is required. Once the registration process is completed, the acquired token will authorize the binding to interact with the device. After initial discovery and thing creation the device will stay offline. Press the registration button on the sync box for 3 seconds to grant the binding the required permissions. +thing-type.config.huesync.box.group.connection.label = Connection Settings +thing-type.config.huesync.box.host.label = Network Address +thing-type.config.huesync.box.host.description = Network address of the HDMI Sync Box. +thing-type.config.huesync.box.port.label = Port +thing-type.config.huesync.box.port.description = Port of the HDMI Sync Box. +thing-type.config.huesync.box.registrationId.label = Application Registration Id +thing-type.config.huesync.box.registrationId.description = The id of the API registration. +thing-type.config.huesync.box.statusUpdateInterval.label = Status Update Interval +thing-type.config.huesync.box.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box. + +# thing types + +thing-type.huesync.huesync.label = Hue HDMI Sync Box +thing-type.huesync.huesync.description = Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful smart light that responds to and reflects the content you watch or listen to. + +# *** exceptions *** + +exception.generic.connection = "Unable to connect to device." + +# api exceptions + +api.minimal-version = Only devices with API level >= 7 are supported +api.communication-problem = Communication problem with the device + +# registration + +thing.config.huesync.box.registration = Device registration pending. Please press the HDMI Sync Box device button for 3 seconds. + +# logger (to keep text in sync with on-screen messages, log messages will always be in locale.english) + +logger.initialization-problem = Unable to initialize handler for {} ({}): {} diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml new file mode 100644 index 0000000000000..46991adf1435f --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/channel-types.xml @@ -0,0 +1,225 @@ + + + + + String + + Installed firmware version + text + + + + + String + + Latest available firmware version + text + + + + + String + + Friendly name of the HDMI connection + text + + + + + String + + Status of the HDMI input + status + + + + + + + + + + + + + String + + Type of the connected HDMI device + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + Last sync mode used for this channel + text + + + + + + + + + + + + String + + + +
    +
  • + "video": +

    + Analyzes the on-screen visuals, translating colors and brightness into corresponding light + effects for an immersive movie-watching experience. +

    +
  • +
  • + "music": +

    + Analyzes the rhythm and beat of your music, creating + dynamic light along to your tunes. +

    +
  • +
  • + "game": +

    + Reacts to the action on your screen, intensifying the in-game atmosphere + with bursts of light that correspond to explosions, gunfire, and other gameplay events.

    +
  • +
  • "passthrough"
  • +
  • "powersave"
  • +
+

+ ]]> +
+ text + + + + + + + + + +
+ + + Switch + + + + OFF in case of powersave or passthrough mode, and ON in case of video, game or music mode. +

+

+ When changed from OFF to ON, it will start syncing in last used mode for current source. + When changed from ON to OFF, will set passthrough mode. +

+ ]]> +
+ switch +
+ + + Switch + + + + OFF in case of powersave mode and ON in case of passthrough, video, game or music mode. +

+

+ When changed from OFF to ON, it will set passthrough mode. + When changed from ON to OFF, will set powersave mode. +

+ ]]> +
+ switch +
+ + + String + + + +
    +
  • input1
  • +
  • input2
  • +
  • input3
  • +
  • input4
  • +
+

+ ]]> +
+ receiver + + + + + + + + +
+ + + + Number:Dimensionless + + + + 0 ... 200 +
    +
  • 0 = max reduction
  • +
  • 100 = no brightness reduction/boost compared to input
  • +
  • 200 = max boost
  • +
+

+ ]]> +
+ slider + +
+ +
diff --git a/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..78be13ac7f107 --- /dev/null +++ b/bundles/org.openhab.binding.huesync/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,83 @@ + + + + + + + Sync your smart lights to your on-screen TV content with the Philips Hue Play HDMI Sync Box. Four HDMI + inputs allow you to connect your media devices to your Hue setup, resulting in a fast, seamless display of colorful + smart light that responds to and reflects the content you watch or listen to. + + + receiver + + + + + + + + + + + + + + + + Philips + + + host + + + + + + + Information about the installed device firmaware and available updates. + text + + + + + + + + HDMI connection + settings + + + + + + + + + + HDMI connection + settings + + + + + + + + + + Execution API commands are used to control the real-time behavior of the Philips Hue Sync box. These + commands allow you to influence how the lights react to your entertainment. + settings + + + + + + + + + diff --git a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties index fe1ad084aef3f..2c7e447d6a8c2 100644 --- a/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties +++ b/bundles/org.openhab.binding.jellyfin/src/main/resources/OH-INF/i18n/jellyfin.properties @@ -16,10 +16,10 @@ thing-type.config.jellyfin.server.clientActiveWithInSeconds.label = Client Activ thing-type.config.jellyfin.server.clientActiveWithInSeconds.description = Amount off seconds allowed since the last client activity to assert it's online (0 disabled) thing-type.config.jellyfin.server.hostname.label = Hostname/IP thing-type.config.jellyfin.server.hostname.description = Hostname or IP address of the server -thing-type.config.jellyfin.server.port.label = Port -thing-type.config.jellyfin.server.port.description = Port of the server thing-type.config.jellyfin.server.path.label = Base Path thing-type.config.jellyfin.server.path.description = Base path of the server +thing-type.config.jellyfin.server.port.label = Port +thing-type.config.jellyfin.server.port.description = Port of the server thing-type.config.jellyfin.server.refreshSeconds.label = Refresh Seconds thing-type.config.jellyfin.server.refreshSeconds.description = Interval to pull devices state from the server thing-type.config.jellyfin.server.ssl.label = SSL diff --git a/bundles/pom.xml b/bundles/pom.xml index 293021091b5fd..b821c23d7feaf 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -192,6 +192,7 @@ org.openhab.binding.hpprinter org.openhab.binding.http org.openhab.binding.hue + org.openhab.binding.huesync org.openhab.binding.hydrawise org.openhab.binding.hyperion org.openhab.binding.iammeter