[huesync] Initial contribution (#16516)

* ☠️ Binding skeleton created for org.openhab.binding.huesync

Signed-off-by: Patrik Gfeller <patrik.gfeller@gmail.com>
Signed-off-by: Patrik Gfeller <patrik.gfeller@proton.me>
This commit is contained in:
Patrik Gfeller 2024-12-03 18:41:54 +01:00 committed by GitHub
parent 36bde0037f
commit 5a27b0e752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 2937 additions and 0 deletions

View File

@ -159,6 +159,7 @@
/bundles/org.openhab.binding.hpprinter/ @cossey /bundles/org.openhab.binding.hpprinter/ @cossey
/bundles/org.openhab.binding.http/ @J-N-K /bundles/org.openhab.binding.http/ @J-N-K
/bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg /bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg
/bundles/org.openhab.binding.huesync/ @pgfeller
/bundles/org.openhab.binding.hydrawise/ @digitaldan /bundles/org.openhab.binding.hydrawise/ @digitaldan
/bundles/org.openhab.binding.hyperion/ @tavalin /bundles/org.openhab.binding.hyperion/ @tavalin
/bundles/org.openhab.binding.iammeter/ @lewei50 /bundles/org.openhab.binding.iammeter/ @lewei50

View File

@ -796,6 +796,11 @@
<artifactId>org.openhab.binding.hue</artifactId> <artifactId>org.openhab.binding.hue</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.huesync</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.addons.bundles</groupId> <groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.hydrawise</artifactId> <artifactId>org.openhab.binding.hydrawise</artifactId>

View File

@ -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

View File

@ -0,0 +1,220 @@
# hueSync Binding
<!-- markdownlint-disable MD033 -->
This binding integrates the [Play HDMI Sync Box](https://www.philips-hue.com/en-us/p/hue-philips-hue-play-hdmi-sync-box/046677579753) 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)
- [Supported Things](#supported-things)
- [Discovery](#discovery)
- [Configuration](#configuration)
- [Thing(s)](#things)
- [Channels](#channels)
- [Firmware Information](#firmware-information)
- [HDMI connections \[in|out\]](#hdmi-connections-inout)
- [Commands](#commands)
- [Example Configuration](#example-configuration)
- [huesyncbox.things](#huesyncboxthings)
- [huesyncbox.items](#huesyncboxitems)
- [example.sitemap](#examplesitemap)
## Supported Things
This binding provides only one thing type: **`box`**.
Each thing will represent a Hue Play HDMI sync box.
## Discovery
The binding supports auto discovery using [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) to find devices in the local network.
Make sure the device is connected to the network (the LED on the Sync Box is white or red).
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:
<p>
<details>
<summary><b>Linux</b> (Ubuntu based distributions)</summary>
```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"]
```
</details>
</p>
mDNS uses link-local multicast addresses; its scope is limited to a single physical or logical LAN ([Layer 3](https://en.wikipedia.org/wiki/OSI_model#Layer_3:_Network_layer")).
If your device is not automatically discovered, create a thing and manually configure the "host" parameter.
To communicate with the sync box, you need to couple the thing with the hardware (registration).
The thing will start this process automatically.
To complete the registration you just press the "coupling" button on the sync box for 3 seconds.:
![Device Registration](doc/device_registration.png)
For special use cases it is possible to configure the id and token manually in the **advanced configuration** settings section.
## Configuration
### Thing(s)
| 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 |
## Channels
### Firmware Information
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 |
### HDMI connections [in\|out]
Information about a HDMI input connection.
| Channel | Type | Read/Write | Description |
| ------- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type | String | R | <details><summary>Friendly Type</summary><ul><li>generic</li><li>video</li><li>game</li><li>music</li><li>xbox</li><li>playstation</li><li>nintendoswitch</li><li>phone</li><li>desktop</li><li>laptop</li><li>appletv</li><li>roku</li><li>shield</li><li>chromecast</li><li>firetv</li><li>diskplayer</li><li>settopbox</li><li>satellite</li><li>avreceiver</li><li>soundbar</li><li>hdmiswitch</li></ul></details> |
| status | String | R | <details><summary>Device connection status</summary><ul><li>unplugged</li><li>plugged</li><li>linked</li><li>unknown</li></ul></details> |
| name | String | R | Friendly Name |
| mode | String | R | <details><summary>Mode</summary><ul><li>video</li><li>game</li><li>music</li><li>powersave</li><li>passthrough</li></ul></details> |
### Commands
| Channel | Type | Read/Write | Description |
| ----------- | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mode | String | R/W | <details><summary>Hue Sync operation mode</summary><ul><li>video</li><li>game</li><li>music</li><li>powersave</li><li>passthrough</li></ul></details> |
| hdmi-source | Switch | R/W | <details><summary>Source</summary><ul><li>input1</li><li>input2</li><li>input3</li><li>input4</li></ul></details> |
| sync-active | Switch | R/W | <details><summary>Synchronization</summary><p><b>OFF</b> in case of powersave or passthrough mode, and <b>ON</b> 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.</p></details> |
| brightness | Number:Dimensionless | R/W | <details><summary>Brightness</summary><p><ul><li>0 = max reduction</li><li>100 = no brightness reduction/boost compared to input</li><li>200 = max boost</li></ul></p></details> |
## Example Configuration
### huesyncbox.things
```java
Thing huesync:box:LivingRoom "Philips Hue HDMI Sync Box, LivingRoom" [
host="192.168.2.115",
httpPollingInterval=60,
apiAccessToken="yourTokenGoesHere=",
registrationId="8",
port=443,
statusUpdateInterval=10
]
```
### huesyncbox.items
Both item and sitemap configuration example use the `iconify` support for the `firmware` as well as `input1` and `input2`.
Those icons loaded if needed from the internet and not suited for a pure offline setup.
The other items use the `classic` icons.
Read the documentation about the offline provider for `iconify` icons, or use the `classic` icons bundled with openHAB.
```java
// Firmware
Group firmware "Firmware Information" <iconify:mdi:info>
String firmware_version "Current Firmware" <iconify:mdi:text> (firmware) {channel="huesync:box:LivingRoom:device-firmware#firmware"}
String latest_firmware_version "Latest Firmware" <iconify:mdi:text> (firmware) {channel="huesync:box:LivingRoom:device-firmware#available-firmware"}
//HDMI Input 1
Group hdmi_in1 "HDMI 1" <iconify:mdi:hdmi-port>
String friendly_name_input1 "Friendly Name" <iconify:mdi:text> (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#name"}
String friendly_type_input1 "Friendly Type" <iconify:mdi:devices> (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#type"}
String hdmi_connection_status_input1 "Connection Status" <iconify:mdi:connection> (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#status"}
String last_sync_mode_input1 "Last Sync Mode " <iconify:mdi:multimedia> (hdmi_in1) {channel="huesync:box:LivingRoom:device-hdmi-in-1#mode"}
//HDMI Input 2
Group hdmi_in2
String friendly_name_input2 "Friendly Name" {channel="huesync:box:LivingRoom:device-hdmi-in-2#name"}
String friendly_type_input2 "Friendly Type" {channel="huesync:box:LivingRoom:device-hdmi-in-2#type"}
String hdmi_connection_status_input2 "Connection Status" {channel="huesync:box:LivingRoom:device-hdmi-in-2#status"}
String last_sync_mode_input2 "Last Sync Mode" {channel="huesync:box:LivingRoom:device-hdmi-in-2#mode"}
//HDMI Input 3
String friendly_name_input3 "Friendly Name" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-3#name"}
String friendly_type_input3 "Friendly Type" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-3#type"}
String hdmi_connection_status_input3 "Connection Status" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-3#status"}
String last_sync_mode_input3 "Last Sync Mode" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-3#mode"}
//HDMI Input 4
String friendly_name_input4 "Friendly Name" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-4#name"}
String friendly_type_input4 "Friendly Type" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-4#type"}
String hdmi_connection_status_input4 "Connection Status" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-4#status"}
String last_sync_mode_input4 "Last Sync Mode" <text> {channel="huesync:box:LivingRoom:device-hdmi-in-4#mode"}
//HDMI output
String friendly_name_output "Friendly Name" <text> {channel="huesync:box:LivingRoom:device-hdmi-out#name"}
String friendly_type_output "Friendly Type" <text> {channel="huesync:box:LivingRoom:device-hdmi-out#type"}
String hdmi_connection_status_output "Connection Status" <text> {channel="huesync:box:LivingRoom:device-hdmi-out#status"}
String last_sync_mode_output "Last Sync Mode" <text> {channel="huesync:box:LivingRoom:device-hdmi-out#mode"}
//Commands
String huesync_mode "Mode" <switch> {channel="huesync:box:LivingRoom:device-commands#mode"}
Switch sync_active "Sync active" <switch> {channel="huesync:box:LivingRoom:device-commands#sync-active"}
Switch hdmi_active "HDMI active" <switch> {channel="huesync:box:LivingRoom:device-commands#hdmi-active"}
String hdmi_source "HDMI Source" <player> {channel="huesync:box:LivingRoom:device-commands#hdmi-source"}
Dimmer huesync_brightness "Brightness" <slider> {channel="huesync:box:LivingRoom:device-commands#brightness"}
```
### example.sitemap
```java
sitemap demo label="Hue Sync Box" {
Frame {
Group item=firmware
}
Frame label="Commands" icon=settings {
Text item=huesync_mode
Text item=hdmi_active
Switch item=sync_active
Text item=hdmi_source
Buttongrid label="HDMI Source" staticIcon=player {
Button row=1 column=1 item=hdmi_source label="Source 1" stateless click=input1
Button row=2 column=1 item=hdmi_source label="Source 2" stateless click=input2
Button row=3 column=1 item=hdmi_source label="Source 3" stateless click=input3
Button row=4 column=1 item=hdmi_source label="Source 4" stateless click=input4
}
Selection item=hdmi_source mappings=[input1="Source 1", input2="Source 2", input3="Source 3", input3="Source 4"]
Slider item=huesync_brightness minValue=0 maxValue=200 step=10
}
Frame label="HDMI Inputs 1 & 2" icon="iconify:mdi:hdmi-port" {
Default item=hdmi_in1
Group item=hdmi_in2 label="HDMI 2" icon="iconify:mdi:hdmi-port" {
Default item=friendly_name_input2 icon="iconify:mdi:text"
Default item=friendly_type_input2 icon="iconify:mdi:devices"
Default item=hdmi_connection_status_input2 icon="iconify:mdi:connection"
Default item=last_sync_mode_input2 icon="iconify:mdi:multimedia"
}
}
Frame label="HDMI 3" icon=player {
Default item=friendly_name_input3
Default item=friendly_type_input3
Default item=hdmi_connection_status_input3
Default item=last_sync_mode_input3
}
Frame label="HDMI 4" icon=player {
Text item=friendly_name_input4
Text item=friendly_type_input4
Text item=hdmi_connection_status_input4
Text item=last_sync_mode_input4
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.huesync</artifactId>
<name>openHAB Add-ons :: Bundles :: Hue Sync Box Binding</name>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.huesync-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-huesync" description="Hue Sync Box Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mdns</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.huesync/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,34 @@
/**
* 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;
/**
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HdmiChannels {
public String name;
public String type;
public String mode;
public String status;
public HdmiChannels(String name, String type, String mode, String status) {
this.name = name;
this.type = type;
this.mode = mode;
this.status = status;
}
}

View File

@ -0,0 +1,89 @@
/**
* 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 COMMANDS {
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 final HdmiChannels IN_1 = new HdmiChannels("device-hdmi-in-1#name", "device-hdmi-in-1#type",
"device-hdmi-in-1#mode", "device-hdmi-in-1#status");
public static final HdmiChannels IN_2 = new HdmiChannels("device-hdmi-in-2#name", "device-hdmi-in-2#type",
"device-hdmi-in-2#mode", "device-hdmi-in-2#status");
public static final HdmiChannels IN_3 = new HdmiChannels("device-hdmi-in-3#name", "device-hdmi-in-3#type",
"device-hdmi-in-3#mode", "device-hdmi-in-3#status");
public static final HdmiChannels IN_4 = new HdmiChannels("device-hdmi-in-4#name", "device-hdmi-in-4#type",
"device-hdmi-in-4#mode", "device-hdmi-in-4#status");
public static final HdmiChannels OUT = new HdmiChannels("device-hdmi-out#name", "device-hdmi-out#type",
"device-hdmi-out#mode", "device-hdmi-out#status");
}
}
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 = "box";
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";
}

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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 <a href=
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@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;
}

View File

@ -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 <a href=
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@NonNullByDefault
public class HueSyncDeviceCapabilitiesInfo {
/** The total number of IR codes configurable */
public int maxIrCodes;
/** The total number of Presets configurable */
public int maxPresets;
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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 <a href=
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@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;
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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 <a href=
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@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 users
* timezone.
*/
public int autoUpdateTime;
}

View File

@ -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 <a href=
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@NonNullByDefault
public class HueSyncDeviceDetailedWifiInfo {
/** Wifi SSID */
public @Nullable String ssid;
/**
* 0 = not connected;
* 1 = weak;
* 2 = fair;
* 3 = good;
* 4 = excellent
*/
public int strength;
}

View File

@ -0,0 +1,102 @@
/**
* 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Root object for execution resource
*
* @author Patrik Gfeller - Initial Contribution
*
*/
@NonNullByDefault
public class HueSyncExecution {
private final Logger logger = LoggerFactory.getLogger(HueSyncExecution.class);
public static final List<String> 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 dont 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
/** <horizontal pixels> x <vertical pixels> @ <framerate fps> <HDR> */
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;
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

@ -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 = "";
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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 final String token;
private final 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);
}
}
}

View File

@ -0,0 +1,248 @@
/**
* 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.exceptions.HueSyncConnectionException;
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 org.slf4j.LoggerFactory;
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 OBJECT_MAPPER = 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/<resource> and in some cases sub-resource level
* /api/v1/<resource>/<sub-resource>.
*/
private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s";
private static final String API = "api/v1";
private final Logger logger = LoggerFactory.getLogger(HueSyncConnection.class);
private final Integer port;
private final String host;
private final ServiceRegistration<?> tlsProviderService;
private final HttpClient httpClient;
private final URI deviceUri;
private Optional<HueSyncAuthenticationResult> 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.deviceUri = 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) {
this.removeAuthentication();
if (!id.isBlank() && !token.isBlank()) {
this.registrationId = id;
this.authentication = Optional.of(new HueSyncAuthenticationResult(this.deviceUri, token));
this.httpClient.getAuthenticationStore().addAuthenticationResult(this.authentication.get());
}
}
// #region protected
protected @Nullable <T> T executeRequest(HttpMethod method, String endpoint, String payload,
@Nullable Class<T> type) {
try {
return this.processedResponse(this.executeRequest(method, endpoint, payload), type);
} catch (ExecutionException e) {
this.handleExecutionException(e);
} catch (InterruptedException | TimeoutException e) {
this.logger.warn("{}", e.getMessage());
}
return null;
}
protected @Nullable <T> T executeGetRequest(String endpoint, Class<T> type) {
try {
return this.processedResponse(this.executeGetRequest(endpoint), type);
} catch (ExecutionException e) {
this.handleExecutionException(e);
} catch (InterruptedException | TimeoutException e) {
this.logger.warn("{}", 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.warn("{}", e.getMessage());
}
}
}
protected void dispose() {
this.tlsProviderService.unregister();
}
// #endregion
// #region private
private @Nullable <T> T processedResponse(Response response, @Nullable Class<T> type) {
int status = response.getStatus();
try {
/*
* 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 -> this.logger.debug("registration in progress: no token received yet");
case HttpStatus.UNAUTHORIZED_401 -> {
this.authentication = Optional.empty();
throw new HueSyncConnectionException("@text/connection.invalid-login");
}
case HttpStatus.NOT_FOUND_404 -> this.logger.warn("invalid device URI or API endpoint");
case HttpStatus.INTERNAL_SERVER_ERROR_500 -> this.logger.warn("hue sync box server problem");
default -> this.logger.warn("unexpected HTTP status: {}", status);
}
} catch (HueSyncConnectionException e) {
this.logger.warn("{}", e.getMessage());
}
return null;
}
private @Nullable <T> T deserialize(String json, Class<T> type) {
try {
return OBJECT_MAPPER.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.warn("{}", 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
}

View File

@ -0,0 +1,197 @@
/**
* 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.Objects;
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.exceptions.HueSyncConnectionException;
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 org.slf4j.LoggerFactory;
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 = LoggerFactory.getLogger(HueSyncDeviceConnection.class);
private final HueSyncConnection connection;
private final Map<String, Consumer<Command>> 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.COMMANDS.MODE));
this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SOURCE,
defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SOURCE));
this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.BRIGHTNESS,
defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.BRIGHTNESS));
this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.SYNC,
defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.SYNC));
this.deviceCommandExecutors.put(HueSyncConstants.CHANNELS.COMMANDS.HDMI,
defaultHandler(HueSyncConstants.ENDPOINTS.COMMANDS.HDMI));
}
private Consumer<Command> defaultHandler(String endpoint) {
return command -> {
execute(endpoint, command);
};
}
private void execute(String key, Command command) {
this.logger.debug("Command executor: {} - {}", key, command);
if (!this.connection.isRegistered()) {
this.logger.warn("Device is not registered - ignoring command: {}", command);
return;
}
String value;
if (command instanceof QuantityType quantityCommand) {
value = Integer.toString(quantityCommand.intValue());
} else if (command instanceof OnOffType) {
value = command == OnOffType.ON ? "true" : "false";
} else if (command instanceof StringType) {
value = '"' + command.toString() + '"';
} else {
this.logger.warn("Type [{}] not supported by this connection", command.getClass().getCanonicalName());
return;
}
String json = String.format("{ \"%s\": %s }", key, value);
this.connection.executeRequest(HttpMethod.PUT, ENDPOINTS.EXECUTION, json, null);
}
// #endregion
public void executeCommand(Channel channel, Command command) {
String uid = channel.getUID().getAsString();
String commandId = channel.getUID().getId();
this.logger.debug("Channel UID: {} - Command: {}", uid, command.toFullString());
if (RefreshType.REFRESH.equals(command)) {
return;
}
if (this.deviceCommandExecutors.containsKey(commandId)) {
Objects.requireNonNull(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) throws HueSyncConnectionException {
if (!id.isBlank()) {
try {
HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();
dto.appName = HueSyncConstants.APPLICATION_NAME;
dto.instanceName = id;
String payload = HueSyncConnection.OBJECT_MAPPER.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.warn("{}", 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);
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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 final 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;
}
}

View File

@ -0,0 +1,143 @@
/**
* 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.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.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;
import org.slf4j.LoggerFactory;
/**
* 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 final Logger logger = LoggerFactory.getLogger(HueSyncDiscoveryParticipant.class);
/**
*
* Match the hostname + identifier of the discovered huesync-box.
* Input is like "HueSyncBox-XXXXXXXXXXXX._huesync._tcp.local."
*
* @see·<a·href=
* "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=huesync">
* Service·Name·and·Transport·Protocol·Port·Number·Registry</a>
*/
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<ThingTypeUID> 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 (uid != null) {
try {
logger.debug("HDMI Sync Box {} discovered at {}:{}", service.getName(),
service.getHostAddresses()[0], service.getPort());
Map<String, Object> properties = new HashMap<>();
properties.put(HueSyncConstants.PARAMETER_HOST, service.getHostAddresses()[0]);
properties.put(HueSyncConstants.PARAMETER_PORT, service.getPort());
return DiscoveryResultBuilder.create(uid).withLabel(service.getName()).withProperties(properties)
.build();
} catch (Exception e) {
logger.debug("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.parseBoolean(autoDiscoveryPropertyValue);
if (value != this.autoDiscoveryEnabled) {
logger.debug("{} update: {} - {}", DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY,
autoDiscoveryPropertyValue, value);
this.autoDiscoveryEnabled = value;
}
}
}
}

View File

@ -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.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncApiException extends HueSyncException {
private static final long serialVersionUID = 0L;
public HueSyncApiException(String message) {
super(message);
}
}

View File

@ -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.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncConnectionException extends HueSyncException {
private static final long serialVersionUID = 0L;
public HueSyncConnectionException(String message) {
super(message);
}
}

View File

@ -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.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.huesync.internal.i18n.HueSyncLocalizer;
/**
* 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) {
super(message.startsWith("@text") ? HueSyncLocalizer.getResourceString(message) : message);
}
}

View File

@ -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.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncTaskException extends HueSyncException {
private static final long serialVersionUID = 0L;
public HueSyncTaskException(String message) {
super(message);
}
}

View File

@ -0,0 +1,79 @@
/**
* 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.io.IOException;
import java.net.URISyntaxException;
import java.security.cert.CertificateException;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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;
private final Logger logger = LoggerFactory.getLogger(HueSyncHandlerFactory.class);
@Activate
public HueSyncHandlerFactory(@Reference final HttpClientFactory httpClientFactory) throws Exception {
this.httpClientFactory = httpClientFactory;
}
private static final Set<ThingTypeUID> 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 (IOException | URISyntaxException | CertificateException e) {
this.logger.warn("It was not possible to create a handler for {}: {}", thingTypeUID.getId(),
e.getMessage());
}
}
return null;
}
}

View File

@ -0,0 +1,349 @@
/**
* 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.HdmiChannels;
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.hdmi.HueSyncHdmiConnectionInfo;
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.HueSyncUpdateTaskResult;
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;
import org.slf4j.LoggerFactory;
/**
* 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 = LoggerFactory.getLogger(HueSyncHandler.class);
Map<String, @Nullable ScheduledFuture<?>> tasks = new HashMap<>();
private Optional<HueSyncDevice> deviceInfo = Optional.empty();
private final HueSyncDeviceConnection connection;
private final HttpClient httpClient;
public HueSyncHandler(Thing thing, HttpClientFactory httpClientFactory)
throws CertificateException, IOException, URISyntaxException {
super(thing);
this.httpClient = httpClientFactory.getCommonHttpClient();
this.connection = new HueSyncDeviceConnection(this.httpClient, this.getConfigAs(HueSyncConfiguration.class));
}
// #region private
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));
}
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));
}
}
if (task != null) {
logger.debug("Starting task [{}]", id);
this.tasks.put(id, this.executeTask(task, initialDelay, interval));
}
}
private void stopTasks() {
logger.debug("Stopping {} task(s): {}", this.tasks.values().size(), String.join(",", this.tasks.keySet()));
this.tasks.values().forEach(task -> this.stopTask(task));
this.tasks.clear();
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
"@text/thing.config.huesync.box.registration");
}
private void handleUpdate(@Nullable HueSyncUpdateTaskResult dto) {
try {
HueSyncUpdateTaskResult update = Optional.ofNullable(dto).get();
try {
this.updateFirmwareInformation(Optional.ofNullable(update.deviceStatus).get());
} catch (NoSuchElementException e) {
this.logMissingUpdateInformation("device");
}
this.updateHdmiInformation(Optional.ofNullable(update.hdmiStatus).get());
this.updateExecutionInformation(Optional.ofNullable(update.execution).get());
} catch (NoSuchElementException e) {
Configuration configuration = this.editConfiguration();
configuration.put(HueSyncConstants.REGISTRATION_ID, "");
configuration.put(HueSyncConstants.API_TOKEN, "");
this.updateConfiguration(configuration);
this.startTasks();
}
}
private void logMissingUpdateInformation(String api) {
this.logger.warn("Device information - {} status missing", api);
}
private void updateHdmiInformation(HueSyncHdmi hdmiStatus) {
updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_1, hdmiStatus.input1);
updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_2, hdmiStatus.input2);
updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_3, hdmiStatus.input3);
updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.IN_4, hdmiStatus.input4);
updateHdmiStatus(HueSyncConstants.CHANNELS.HDMI.OUT, hdmiStatus.output);
}
private void updateHdmiStatus(HdmiChannels channels, @Nullable HueSyncHdmiConnectionInfo hdmiStatusInfo) {
if (hdmiStatusInfo != null) {
this.updateState(channels.name, new StringType(hdmiStatusInfo.name));
this.updateState(channels.type, new StringType(hdmiStatusInfo.type));
this.updateState(channels.mode, new StringType(hdmiStatusInfo.lastSyncMode));
this.updateState(channels.status, new StringType(hdmiStatusInfo.status));
}
}
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");
}
} catch (NoSuchElementException e) {
throw new HueSyncApiException("@text/api.communication-problem");
}
}
private void setProperty(String key, @Nullable String value) {
if (value != null) {
Map<String, String> 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<String, String> properties) {
properties.put(key, value);
this.updateProperties(properties);
}
// #endregion
// #region Override
@Override
public void initialize() {
try {
updateStatus(ThingStatus.UNKNOWN);
this.stopTasks();
scheduler.execute(initializeConnection());
} catch (Exception e) {
this.logger.warn("{}", 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.warn("{}", e.getMessage());
} finally {
this.logger.debug("Thing {} ({}) disposed.", this.thing.getLabel(), this.thing.getUID());
}
}
@Override
public void handleRemoval() {
super.handleRemoval();
this.connection.unregisterDevice();
}
// #endregion
}

View File

@ -0,0 +1,68 @@
/**
* 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.exceptions.HueSyncConnectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Task to handle device registration.
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncRegistrationTask implements Runnable {
private final Logger logger = LoggerFactory.getLogger(HueSyncRegistrationTask.class);
private final HueSyncDeviceConnection connection;
private final HueSyncDevice deviceInfo;
private final Consumer<HueSyncRegistration> action;
public HueSyncRegistrationTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
Consumer<HueSyncRegistration> 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.debug("Listening for device registration - {} {}:{}", this.deviceInfo.name,
this.deviceInfo.deviceType, id);
HueSyncRegistration registration = this.connection.registerDevice(id);
if (registration != null) {
this.logger.debug("API token for {} received", this.deviceInfo.name);
this.action.accept(registration);
}
} catch (HueSyncConnectionException e) {
this.logger.warn("{}", e.getMessage());
}
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Task to handle device information update.
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncUpdateTask implements Runnable {
private final Logger logger = LoggerFactory.getLogger(HueSyncUpdateTask.class);
private final HueSyncDeviceConnection connection;
private final HueSyncDevice deviceInfo;
private final Consumer<@Nullable HueSyncUpdateTaskResult> action;
public HueSyncUpdateTask(HueSyncDeviceConnection connection, HueSyncDevice deviceInfo,
Consumer<@Nullable HueSyncUpdateTaskResult> action) {
this.connection = connection;
this.deviceInfo = deviceInfo;
this.action = action;
}
@Override
public void run() {
try {
this.logger.debug("Status update query for {} {}:{}", this.deviceInfo.name, this.deviceInfo.deviceType,
this.deviceInfo.uniqueId);
if (!this.connection.isRegistered()) {
this.action.accept(null);
}
HueSyncUpdateTaskResult updateInfo = new HueSyncUpdateTaskResult();
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);
}
}
}

View File

@ -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 HueSyncUpdateTaskResult {
public @Nullable HueSyncDeviceDetailed deviceStatus;
public @Nullable HueSyncHdmi hdmiStatus;
public @Nullable HueSyncExecution execution;
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.huesync.internal.i18n;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
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 BUNDLE_CONTEXT = FrameworkUtil.getBundle(HueSyncLocalizer.class)
.getBundleContext();
private static final ServiceReference<TranslationProvider> SERVICE_REFERENCE = BUNDLE_CONTEXT
.getServiceReference(TranslationProvider.class);
private static final Bundle BUNDLE = BUNDLE_CONTEXT.getBundle();
public static String getResourceString(String key) {
String lookupKey = key.replace("@text/", "");
String missingKey = "Missing Translation: " + key;
String result = (BUNDLE_CONTEXT
.getService(SERVICE_REFERENCE) instanceof TranslationProvider translationProvider)
? translationProvider.getText(BUNDLE, lookupKey, missingKey, LOCALE)
: missingKey;
return result == null ? missingKey : result;
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="huesync" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Hue HDMI Sync Box Binding</name>
<description>Binding for the Hue HDMI Sync Box.</description>
<connection>local</connection>
<discovery-methods>
<discovery-method>
<service-type>mdns</service-type>
<discovery-parameters>
<discovery-parameter>
<name>mdnsServiceType</name>
<value>_huesync._tcp.local.</value>
</discovery-parameter>
</discovery-parameters>
</discovery-method>
</discovery-methods>
</addon:addon>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:box:thing">
<parameter-group name="connection">
<label>Connection</label>
</parameter-group>
<parameter name="host" type="text" groupName="connection">
<context>network-address</context>
<label>Address</label>
<description>Network address of the HDMI Sync Box.</description>
<required>true</required>
</parameter>
<parameter name="port" type="integer" min="1" max="65535" groupName="connection">
<context></context>
<label>Port</label>
<description>Port of the HDMI Sync Box.</description>
<advanced>true</advanced>
<default>443</default>
<required>true</required>
</parameter>
<parameter name="registrationId" type="text" groupName="connection">
<context></context>
<label>Registration Id</label>
<description>The id of the API registration.</description>
<advanced>true</advanced>
</parameter>
<parameter name="apiAccessToken" type="text" groupName="connection">
<context>password</context>
<label>Access Token</label>
<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.</description>
<advanced>true</advanced>
</parameter>
<parameter name="statusUpdateInterval" type="integer" min="1" step="1" unit="s" groupName="connection">
<label>Update Interval</label>
<description>Seconds between fetching values from the Hue Sync Box.</description>
<advanced>true</advanced>
<required>true</required>
<default>10</default>
<unitLabel>Seconds</unitLabel>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,110 @@
# 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.box.label = 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.box.thing.apiAccessToken.label = Access Token
thing-type.config.box.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.box.thing.group.connection.label = Connection
thing-type.config.box.thing.host.label = Address
thing-type.config.box.thing.host.description = Network address of the HDMI Sync Box.
thing-type.config.box.thing.port.label = Port
thing-type.config.box.thing.port.description = Port of the HDMI Sync Box.
thing-type.config.box.thing.registrationId.label = Registration Id
thing-type.config.box.thing.registrationId.description = The id of the API registration.
thing-type.config.box.thing.statusUpdateInterval.label = Update Interval
thing-type.config.box.thing.statusUpdateInterval.description = Seconds between fetching values from the Hue Sync Box.
# channel group types
channel-group-type.huesync.device-commands.label = Commands
channel-group-type.huesync.device-commands.description = Commands are used to control the real-time behavior of the 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 firmware 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 Mode
channel-type.huesync.connection-last-sync-mode.description = Last sync mode used for this channel
channel-type.huesync.connection-last-sync-mode.command.option.video = Video
channel-type.huesync.connection-last-sync-mode.command.option.game = Game
channel-type.huesync.connection-last-sync-mode.command.option.music = Music
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
channel-type.huesync.device-info-firmware-available.description = Latest available firmware version
channel-type.huesync.device-info-firmware.label = Firmware
channel-type.huesync.device-info-firmware.description = Installed firmware version
channel-type.huesync.execution-brightness.label = Brightness
channel-type.huesync.execution-brightness.description = <p> 0 ... 200 <ul> <li>0 = max reduction</li> <li>100 = no brightness reduction/boost compared to input</li> <li>200 = max boost</li> </ul> </p>
channel-type.huesync.execution-hdmi-active.label = HDMI Active
channel-type.huesync.execution-hdmi-active.description = <p> <b>OFF</b> in case of <i>powersave</i> mode and <b>ON</b> in case of <i>passthrough</i>, <i>video</i>, <i>game</i> or <i>music</i> mode. </p> <p> When changed from <b>OFF</b> to <b>ON</b>, it will set <i>passthrough</i> mode. When changed from <b>ON</b> to <b>OFF</b>, will set <i>powersave</i> mode. </p>
channel-type.huesync.execution-hdmi-source.label = HDMI Input
channel-type.huesync.execution-hdmi-source.description = <p> <ul> <li>input1</li> <li>input2</li> <li>input3</li> <li>input4</li> </ul> </p>
channel-type.huesync.execution-mode.label = Mode
channel-type.huesync.execution-mode.description = <p> <ul> <li> <b>"Video":</b> <p> Analyzes the on-screen visuals, translating colors and brightness into corresponding light effects for an immersive movie-watching experience. </p> </li> <li> <b>"Music":</b> <p> Analyzes the rhythm and beat of your music, creating dynamic light along to your tunes. </p> </li> <li> <b>"Game":</b> <p> 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.</p> </li> <li><b>"Passthrough"</b></li> <li><b>"Powersave"</b></li> </ul> </p>
channel-type.huesync.execution-mode.command.option.powersave = Powersave
channel-type.huesync.execution-mode.command.option.passthrough = Passthrough
channel-type.huesync.execution-mode.command.option.video = Video
channel-type.huesync.execution-mode.command.option.game = Game
channel-type.huesync.execution-mode.command.option.music = Music
channel-type.huesync.execution-sync-active.label = Synchronization Active
channel-type.huesync.execution-sync-active.description = <p> <b>OFF</b> in case of <i>powersave</i> or <i>passthrough</i> mode, and <b>ON</b> in case of <i>video</i>, <i>game</i> or <i>music</i> mode. </p> <p> When changed from <b>OFF</b> to <b>ON</b>, it will start syncing in last used mode for current source. When changed from <b>ON</b> to <b>OFF</b>, will set <i>passthrough</i> mode. </p>
# *** exceptions ***
exception.generic.connection = "Unable to connect to device."
# api & connection exceptions
api.minimal-version = Only devices with API level >= 7 are supported
api.communication-problem = Communication problem with the device
connection.invalid-login = Invalid or missing credentials
# 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 {} ({}): {}

View File

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="huesync"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="device-info-firmware">
<item-type>String</item-type>
<label>Firmware</label>
<description>Installed firmware version</description>
<category>text</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="device-info-firmware-available">
<item-type>String</item-type>
<label>Latest Firmware</label>
<description>Latest available firmware version</description>
<category>text</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="connection-name">
<item-type>String</item-type>
<label>HDMI Name</label>
<description>Friendly name of the HDMI connection</description>
<category>text</category>
<state readOnly="true"></state>
</channel-type>
<channel-type id="connection-status">
<item-type>String</item-type>
<label>HDMI Status</label>
<description>Status of the HDMI input</description>
<category>status</category>
<state readOnly="true"></state>
<command>
<options>
<option value="unplugged">Unplugged</option>
<option value="plugged">Plugged</option>
<option value="linked">Linked</option>
<option value="unknown">Unknown</option>
</options>
</command>
</channel-type>
<channel-type id="connection-type">
<item-type>String</item-type>
<label>HDMI Type</label>
<description>Type of the connected HDMI device</description>
<category>text</category>
<state readOnly="true"></state>
<command>
<options>
<option value="generic">Generic</option>
<option value="video">Video</option>
<option value="game">Game</option>
<option value="music">Music</option>
<option value="xbox">XBox</option>
<option value="playstation">PlayStation</option>
<option value="nintendoswitch">Nintendo Switch</option>
<option value="phone">Phone</option>
<option value="desktop">Desktop</option>
<option value="laptop">Laptop</option>
<option value="appletv">Apple TV</option>
<option value="roku">Roku</option>
<option value="shield">Nvidia Shield</option>
<option value="chromecast">Chromecast</option>
<option value="firetv">Amazon Fire TV</option>
<option value="diskplayer">Disk Player</option>
<option value="settopbox">Set-top box</option>
<option value="satellite">Satellite</option>
<option value="avreceiver">AV receiver</option>
<option value="soundbar">Soundbar</option>
<option value="hdmiswitch">HDMI switch</option>
</options>
</command>
</channel-type>
<channel-type id="connection-last-sync-mode">
<item-type>String</item-type>
<label>Last Mode</label>
<description>Last sync mode used for this channel</description>
<category>text</category>
<state readOnly="true"></state>
<command>
<options>
<option value="video">Video</option>
<option value="game">Game</option>
<option value="music">Music</option>
</options>
</command>
</channel-type>
<channel-type id="execution-mode">
<item-type>String</item-type>
<label>Mode</label>
<description>
<![CDATA[
<p>
<ul>
<li>
<b>"Video":</b>
<p>
Analyzes the on-screen visuals, translating colors and brightness into corresponding light
effects for an immersive movie-watching experience.
</p>
</li>
<li>
<b>"Music":</b>
<p>
Analyzes the rhythm and beat of your music, creating
dynamic light along to your tunes.
</p>
</li>
<li>
<b>"Game":</b>
<p>
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.</p>
</li>
<li><b>"Passthrough"</b></li>
<li><b>"Powersave"</b></li>
</ul>
</p>
]]>
</description>
<category>text</category>
<command>
<options>
<option value="powersave">Powersave</option>
<option value="passthrough">Passthrough</option>
<option value="video">Video</option>
<option value="game">Game</option>
<option value="music">Music</option>
</options>
</command>
</channel-type>
<channel-type id="execution-sync-active">
<item-type>Switch</item-type>
<label>Synchronization Active</label>
<description>
<![CDATA[
<p>
<b>OFF</b> in case of <i>powersave</i> or <i>passthrough</i> mode, and <b>ON</b> in case of <i>video</i>, <i>game</i> or <i>music</i> mode.
</p>
<p>
When changed from <b>OFF</b> to <b>ON</b>, it will start syncing in last used mode for current source.
When changed from <b>ON</b> to <b>OFF</b>, will set <i>passthrough</i> mode.
</p>
]]>
</description>
<category>switch</category>
</channel-type>
<channel-type id="execution-hdmi-active">
<item-type>Switch</item-type>
<label>HDMI Active</label>
<description>
<![CDATA[
<p>
<b>OFF</b> in case of <i>powersave</i> mode and <b>ON</b> in case of <i>passthrough</i>, <i>video</i>, <i>game</i> or <i>music</i> mode.
</p>
<p>
When changed from <b>OFF</b> to <b>ON</b>, it will set <i>passthrough</i> mode.
When changed from <b>ON</b> to <b>OFF</b>, will set <i>powersave</i> mode.
</p>
]]>
</description>
<category>switch</category>
</channel-type>
<channel-type id="execution-hdmi-source">
<item-type>String</item-type>
<label>HDMI Input</label>
<description>
<![CDATA[
<p>
<ul>
<li>input1</li>
<li>input2</li>
<li>input3</li>
<li>input4</li>
</ul>
</p>
]]>
</description>
<category>receiver</category>
<command>
<options>
<option value="input1"></option>
<option value="input2"></option>
<option value="input3"></option>
<option value="input4"></option>
</options>
</command>
</channel-type>
<!--
brightness:
- Get, Put
- number, uint
- 0 ... 200 (100 = no brightness reduction/boost compared to input, 0 = max reduction, 200 = max boost)
-->
<channel-type id="execution-brightness">
<item-type>Number:Dimensionless</item-type>
<label>Brightness</label>
<description>
<![CDATA[
<p>
0 ... 200
<ul>
<li>0 = max reduction</li>
<li>100 = no brightness reduction/boost compared to input</li>
<li>200 = max boost</li>
</ul>
</p>
]]>
</description>
<category>slider</category>
<state min="0" max="200" step="1" pattern="%d"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="huesync"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="box">
<label>HDMI Sync Box</label>
<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.
</description>
<category>receiver</category>
<channel-groups>
<channel-group id="device-firmware" typeId="device-firmware"></channel-group>
<channel-group id="device-hdmi-in-1" typeId="device-hdmi-connection-in"></channel-group>
<channel-group id="device-hdmi-in-2" typeId="device-hdmi-connection-in"></channel-group>
<channel-group id="device-hdmi-in-3" typeId="device-hdmi-connection-in"></channel-group>
<channel-group id="device-hdmi-in-4" typeId="device-hdmi-connection-in"></channel-group>
<channel-group id="device-hdmi-out" typeId="device-hdmi-connection-out"></channel-group>
<channel-group id="device-commands" typeId="device-commands"></channel-group>
</channel-groups>
<properties>
<property name="vendor">Philips</property>
</properties>
<representation-property>host</representation-property>
<config-description-ref uri="thing-type:box:thing"/>
</thing-type>
<channel-group-type id="device-firmware">
<label>Firmware</label>
<description>Information about the installed device firmware and available updates.</description>
<category>text</category>
<channels>
<channel id="firmware" typeId="device-info-firmware"></channel>
<channel id="available-firmware" typeId="device-info-firmware-available"></channel>
</channels>
</channel-group-type>
<channel-group-type id="device-hdmi-connection-in">
<label>HDMI Input</label>
<description>HDMI connection</description>
<category>settings</category>
<channels>
<channel id="name" typeId="connection-name"></channel>
<channel id="type" typeId="connection-type"></channel>
<channel id="status" typeId="connection-status"></channel>
<channel id="mode" typeId="connection-last-sync-mode"></channel>
</channels>
</channel-group-type>
<channel-group-type id="device-hdmi-connection-out">
<label>HDMI Output</label>
<description>HDMI connection</description>
<category>settings</category>
<channels>
<channel id="name" typeId="connection-name"></channel>
<channel id="type" typeId="connection-type"></channel>
<channel id="status" typeId="connection-status"></channel>
<channel id="mode" typeId="connection-last-sync-mode"></channel>
</channels>
</channel-group-type>
<channel-group-type id="device-commands">
<label>Commands</label>
<description>Commands are used to control the real-time behavior of the hue sync box. These
commands allow you to
influence how the lights react to your entertainment.</description>
<category>settings</category>
<channels>
<channel id="mode" typeId="execution-mode"></channel>
<channel id="sync-active" typeId="execution-sync-active"></channel>
<channel id="hdmi-active" typeId="execution-hdmi-active"></channel>
<channel id="hdmi-source" typeId="execution-hdmi-source"></channel>
<channel id="brightness" typeId="execution-brightness"></channel>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -195,6 +195,7 @@
<module>org.openhab.binding.hpprinter</module> <module>org.openhab.binding.hpprinter</module>
<module>org.openhab.binding.http</module> <module>org.openhab.binding.http</module>
<module>org.openhab.binding.hue</module> <module>org.openhab.binding.hue</module>
<module>org.openhab.binding.huesync</module>
<module>org.openhab.binding.hydrawise</module> <module>org.openhab.binding.hydrawise</module>
<module>org.openhab.binding.hyperion</module> <module>org.openhab.binding.hyperion</module>
<module>org.openhab.binding.iammeter</module> <module>org.openhab.binding.iammeter</module>