mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[hue] Implement CLIP 2 / API v2 (#13570)
* [hue] Implement CLIP 2 / API v2 --------- Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
dd913e5245
commit
bd4a8385e0
@ -139,7 +139,7 @@
|
|||||||
/bundles/org.openhab.binding.homewizard/ @Daniel-42
|
/bundles/org.openhab.binding.homewizard/ @Daniel-42
|
||||||
/bundles/org.openhab.binding.hpprinter/ @cossey
|
/bundles/org.openhab.binding.hpprinter/ @cossey
|
||||||
/bundles/org.openhab.binding.http/ @openhab/add-ons-maintainers
|
/bundles/org.openhab.binding.http/ @openhab/add-ons-maintainers
|
||||||
/bundles/org.openhab.binding.hue/ @cweitkamp
|
/bundles/org.openhab.binding.hue/ @cweitkamp @andrewfg
|
||||||
/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
|
||||||
|
@ -1,79 +1,39 @@
|
|||||||
# Philips Hue Binding
|
# Philips Hue Binding
|
||||||
|
|
||||||
This binding integrates the [Philips Hue Lighting system](https://www.meethue.com).
|
This binding integrates the [Philips Hue Lighting system](https://www.meethue.com).
|
||||||
The integration happens through the Hue Bridge, which acts as an IP gateway to the ZigBee devices.
|
The integration happens through the Hue Bridge, which acts as an IP gateway to the Zigbee devices.
|
||||||
|
|
||||||
![Philips Hue](doc/hue.jpg)
|
![Philips Hue](doc/hue.jpg) ![Philips Hue](doc/hue2.png)
|
||||||
|
|
||||||
## Supported Things
|
## Introduction
|
||||||
|
|
||||||
The Hue Bridge is required as a "bridge" for accessing any other Hue device.
|
The Hue Bridge is required as a "bridge" for accessing any other Hue device.
|
||||||
It supports the Zigbee Light Link protocol as well as the upwards compatible Zigbee 3.0 protocol.
|
It supports the Zigbee Light Link protocol as well as the upwards compatible Zigbee 3.0 protocol.
|
||||||
There are two types of Hue Bridges, generally referred to as v1 (the rounded version) and v2 (the squared version).
|
There are two types of Hue Bridges, generally referred to as v1 (the rounded version) and v2 (the squared version).
|
||||||
Only noticeable difference between the two generation of bridges is the added support for Apple HomeKit in v2.
|
The difference between the two generations of bridges is that the v2 bridge added support for Apple HomeKit and the CLIP v2 API [see next paragraph](#api-versions).
|
||||||
Both bridges are fully supported by this binding.
|
Both bridges are fully supported by this binding.
|
||||||
|
|
||||||
Almost all available Hue devices are supported by this binding.
|
Almost all available Hue devices are supported by this binding.
|
||||||
This includes not only the "Friends of Hue", but also products like the LivingWhites adapter.
|
This includes not only the "Friends of Hue", but also products like the LivingWhites adapter.
|
||||||
Additionally, it is possible to use OSRAM Lightify devices as well as other Zigbee Light Link compatible products, including the IKEA TRÅDFRI lights (when updated).
|
Additionally, it is possible to use OSRAM Lightify devices as well as other Zigbee Light Link compatible products, including the IKEA TRÅDFRI lights (when updated).
|
||||||
Beside bulbs and luminaires the Hue binding also supports some Zigbee sensors. Currently only Hue specific sensors are tested successfully (Hue Motion Sensor and Hue Dimmer Switch).
|
Beside bulbs and luminaires the Hue binding also supports some Zigbee sensors.
|
||||||
|
Currently only Hue specific sensors are tested successfully (Hue Motion Sensor and Hue Dimmer Switch).
|
||||||
Please note that the devices need to be registered with the Hue Bridge before it is possible for this binding to use them.
|
Please note that the devices need to be registered with the Hue Bridge before it is possible for this binding to use them.
|
||||||
|
|
||||||
The Hue binding supports all seven types of lighting devices defined for Zigbee Light Link ([see page 24, table 2](https://www.nxp.com/docs/en/user-guide/JN-UG-3091.pdf).
|
## API Versions
|
||||||
These are:
|
|
||||||
|
|
||||||
| Device type | Zigbee Device ID | Thing type |
|
Bridges are accessed by means of the "CLIP" ('Connected Lighting Interface Protocol') Application Program Interface ('API').
|
||||||
|--------------------------|------------------|------------|
|
There are two versions of CLIP - namely CLIP v1 and CLIP v2, which are referred to as API v1 and API v2 in the links below.
|
||||||
| On/Off Light | 0x0000 | 0000 |
|
Signify has stated that any new features (such as dynamic scenes) will only be available on API v2, and in the long term API v1 will eventually be removed.
|
||||||
| On/Off Plug-in Unit | 0x0010 | 0010 |
|
The API v2 has more features, e.g. it supports Server Sent Events 'SSE' which means that it is much faster to receive status updates in openHAB.
|
||||||
| Dimmable Light | 0x0100 | 0100 |
|
For this reason it is recommended to use API v2 for new openHAB installations.
|
||||||
| Dimmable Plug-in Unit | 0x0110 | 0110 |
|
But unfortunately the API v2 is not supported by older v1 (round) bridges, nor by newer v2 (square-ish) bridges if their firmware is under v1948086000.
|
||||||
| Colour Light | 0x0200 | 0200 |
|
|
||||||
| Extended Colour Light | 0x0210 | 0210 |
|
|
||||||
| Colour Temperature Light | 0x0220 | 0220 |
|
|
||||||
|
|
||||||
All different models of Hue, OSRAM, or other bulbs nicely fit into one of these seven types.
|
|
||||||
This type also determines the capability of a device and with that the possible ways of interacting with it.
|
|
||||||
The following matrix lists the capabilities (channels) for each type:
|
|
||||||
|
|
||||||
| Thing type | On/Off | Brightness | Color | Color Temperature |
|
|
||||||
|-------------|:------:|:----------:|:-----:|:-----------------:|
|
|
||||||
| 0000 | X | | | |
|
|
||||||
| 0010 | X | | | |
|
|
||||||
| 0100 | X | X | | |
|
|
||||||
| 0110 | X | X | | |
|
|
||||||
| 0200 | X | | X | |
|
|
||||||
| 0210 | X | | X | X |
|
|
||||||
| 0220 | X | X | | X |
|
|
||||||
|
|
||||||
Beside bulbs and luminaires the Hue binding supports some Zigbee sensors.
|
|
||||||
Currently only Hue specific sensors are tested successfully (e.g. Hue Motion Sensor, Hue Dimmer Switch, Hue Tap, CLIP Sensor).
|
|
||||||
The Hue Motion Sensor registers a `ZLLLightLevel` sensor (0106), a `ZLLPresence` sensor (0107) and a `ZLLTemperature` sensor (0302) in one device.
|
|
||||||
The Hue CLIP Sensor saves scene states with status or flag for HUE rules.
|
|
||||||
They are presented by the following Zigbee Device ID and _Thing type_:
|
|
||||||
|
|
||||||
| Device type | Zigbee Device ID | Thing type |
|
|
||||||
|-----------------------------|------------------|----------------|
|
|
||||||
| Light Sensor | 0x0106 | 0106 |
|
|
||||||
| Occupancy Sensor | 0x0107 | 0107 |
|
|
||||||
| Temperature Sensor | 0x0302 | 0302 |
|
|
||||||
| Non-Colour Controller | 0x0820 | 0820 |
|
|
||||||
| Non-Colour Scene Controller | 0x0830 | 0830 |
|
|
||||||
| CLIP Generic Status Sensor | 0x0840 | 0840 |
|
|
||||||
| CLIP Generic Flag Sensor | 0x0850 | 0850 |
|
|
||||||
| Geofence Sensor | | geofencesensor |
|
|
||||||
|
|
||||||
The Hue Dimmer Switch has 4 buttons and registers as a Non-Colour Controller switch, while the Hue Tap (also 4 buttons) registers as a Non-Colour Scene Controller in accordance with the ZLL standard.
|
|
||||||
|
|
||||||
Also, Hue Bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor.
|
|
||||||
These sensors save state for rules and calculate what actions to do.
|
|
||||||
CLIP Sensor set or get by JSON through IP.
|
|
||||||
|
|
||||||
Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue Bridge.
|
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
The Hue Bridge is discovered through mDNS in the local network.
|
The Hue Bridge is discovered through mDNS in the local network.
|
||||||
|
Potentially two types of Bridge will be discovered - namely an API v1 Bridge and/or an API v2 Bridge.
|
||||||
|
|
||||||
Auto-discovery is enabled by default.
|
Auto-discovery is enabled by default.
|
||||||
To disable it, you can add the following line to `<openHAB-conf>/services/runtime.cfg`:
|
To disable it, you can add the following line to `<openHAB-conf>/services/runtime.cfg`:
|
||||||
|
|
||||||
@ -82,293 +42,59 @@ discovery.hue:background=false
|
|||||||
```
|
```
|
||||||
|
|
||||||
Once it is added as a Thing, its authentication button (in the middle) needs to be pressed in order to authorize the binding to access it.
|
Once it is added as a Thing, its authentication button (in the middle) needs to be pressed in order to authorize the binding to access it.
|
||||||
Once the binding is authorized, it automatically reads all devices and groups that are set up on the Hue Bridge and puts them into the Inbox.
|
Once the binding is authorized, it automatically reads all devices (and groups) that are set up on the Hue Bridge and puts them into the Inbox.
|
||||||
|
|
||||||
## Thing Configuration
|
## Configuration for API v1 and API v2
|
||||||
|
|
||||||
The Hue Bridge requires the IP address as a configuration value in order for the binding to know where to access it.
|
- [Configuration for API v1](doc/readme_v1.md#philips-hue-binding-api-v1)
|
||||||
In the thing file, this looks e.g. like
|
- [Configuration for API v2](doc/readme_v2.md#philips-hue-binding-api-v2)
|
||||||
|
|
||||||
|
## Migration from API v1 to API v2
|
||||||
|
|
||||||
|
You can create new API v2 things either via the automatic discovery services, via a `.things` file, or manually in the UI.
|
||||||
|
If things are created manually in the UI then you will have to enter all configuration parameters by hand.
|
||||||
|
You can use the [console command](doc/readme_v2.md#console-command-for-finding-resourceids) to discover the `resourceId` of all the things in the bridge.
|
||||||
|
You might also need to edit the names and types of your items, depending on the individual circumstances below.
|
||||||
|
|
||||||
|
### Migration via Automatic Discovery Services
|
||||||
|
|
||||||
|
When new API v2 things are created via the discovery services, then if a matching legacy API v1 thing exists, the new v2 thing will clone some of the the attributes of the existing API v1 thing.
|
||||||
|
And also, if a legacy API v1 thing exists and has items linked to its channels, then the new API v2 thing will replicate the links between those items and the respective new API v2 thing's channels.
|
||||||
|
|
||||||
|
### Migration via a `.things` File
|
||||||
|
|
||||||
|
You need to manually edit your bridge and thing definitions as shown below:
|
||||||
|
|
||||||
|
- Bridge definitions change from `hue:bridge:bridgename` to `hue:bridge-api2:bridgename`.
|
||||||
|
- Bridge configuration parameters change `userName` to `applicationKey`.
|
||||||
|
- Physical thing definitions change from `hue:0100:thingname` or `hue:0210:thingname` etc. to `hue:device:thingname`.
|
||||||
|
- Room or zone thing definitions change from `hue:group:thingname` to `hue:room:thingname` resp. `hue:zone:thingname`.
|
||||||
|
- Thing configuration parameters change from `lightId` or `sensorId` etc. to `resourceId`.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1. In API v1 different things have different types (`0100`, `0220`, `0830`, etc.) but in API v2 all things have the same type `device`.
|
||||||
|
1. In API v1 different things are configured by different parameters (`sensorId`, `lightId`, etc.) but in API v2 all things are configured via the same `resourceId` parameter.
|
||||||
|
1. In API v1 some channel names contain underscore characters (`_`) but in API v2 they have changed to dashes (`-`) e.g `color_temperature` -> `color-temperature`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Bridge hue:bridge:1 [ ipAddress="192.168.0.64" ]
|
// old (API v1) ..
|
||||||
```
|
Bridge hue:bridge:g24 "Philips Hue Hub" @ "Under Stairs" [ipAddress="192.168.1.234", userName="abcdefghijklmnopqrstuvwxyz0123456789ABCD"] {
|
||||||
|
Thing 0210 b01 "Living Room Standard Lamp Left" @ "Living Room" [lightId="1"]
|
||||||
|
}
|
||||||
|
|
||||||
A user to authenticate against the Hue Bridge is automatically generated.
|
// new (API v2) ...
|
||||||
Please note that the generated user name cannot be written automatically to the `.thing` file, and has to be set manually.
|
Bridge hue:bridge-api2:g24 "Philips Hue Hub (api2)" @ "Home" [ipAddress="192.168.1.234", applicationKey="abcdefghijklmnopqrstuvwxyz0123456789ABCD"] {
|
||||||
The generated user name can be found, after pressing the authentication button on the bridge, with the following console command: `hue <bridgeUID> username`.
|
// Device things
|
||||||
The user name can be set using the `userName` configuration value, e.g.:
|
Thing device 11111111-2222-3333-4444-555555555555 "Living Room Standard Lamp Left" @ "Living Room" [resourceId="11111111-2222-3333-4444-555555555555"] // Hue color lamp
|
||||||
|
..
|
||||||
```java
|
// Room things
|
||||||
Bridge hue:bridge:1 [ ipAddress="192.168.0.64", userName="qwertzuiopasdfghjklyxcvbnm1234" ]
|
Thing room 99999999-8888-7777-6666-555555555555 "Back Bedroom (Room)" [resourceId="99999999-8888-7777-6666-555555555555"] // Room
|
||||||
```
|
..
|
||||||
|
// Zone things
|
||||||
| Parameter | Description |
|
Thing zone 99999999-8888-7777-6666-555555555555 "Standard Lamps" [resourceId="99999999-8888-7777-6666-555555555555"] // Zone
|
||||||
|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
..
|
||||||
| ipAddress | Network address of the Hue Bridge. **Mandatory**. |
|
|
||||||
| port | Port of the Hue Bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined. |
|
|
||||||
| protocol | Protocol to connect to the Hue Bridge ("http" or "https"), default value is "https"). |
|
|
||||||
| useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. **Advanced**, default value is `true`. |
|
|
||||||
| userName | Name of a registered Hue Bridge user, that allows to access the API. **Mandatory** |
|
|
||||||
| pollingInterval | Seconds between fetching light values from the Hue Bridge. Optional, the default value is 10 (min="1", step="1"). |
|
|
||||||
| sensorPollingInterval | Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. |
|
|
||||||
|
|
||||||
### Devices
|
|
||||||
|
|
||||||
The devices are identified by the number that the Hue Bridge assigns to them (also shown in the Hue App as an identifier).
|
|
||||||
Thus, all it needs for manual configuration is this single value like
|
|
||||||
|
|
||||||
```java
|
|
||||||
0210 bulb1 "Lamp 1" @ "Office" [ lightId="1" ]
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```java
|
|
||||||
0107 motion-sensor "Motion Sensor" @ "Entrance" [ sensorId="4" ]
|
|
||||||
```
|
|
||||||
|
|
||||||
You can freely choose the thing identifier (such as motion-sensor), its name (such as "Motion Sensor") and the location (such as "Entrance").
|
|
||||||
|
|
||||||
The following device types also have an optional configuration value to specify the fade time in milliseconds for the transition to a new state:
|
|
||||||
|
|
||||||
- Dimmable Light
|
|
||||||
- Dimmable Plug-in Unit
|
|
||||||
- Colour Light
|
|
||||||
- Extended Colour Light
|
|
||||||
- Colour Temperature Light
|
|
||||||
|
|
||||||
| Parameter | Description |
|
|
||||||
|-----------|-------------------------------------------------------------------------------|
|
|
||||||
| lightId | Number of the device provided by the Hue Bridge. **Mandatory** |
|
|
||||||
| fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
|
|
||||||
|
|
||||||
### Groups
|
|
||||||
|
|
||||||
The groups are identified by the number that the Hue Bridge assigns to them.
|
|
||||||
Thus, all it needs for manual configuration is this single value like
|
|
||||||
|
|
||||||
```java
|
|
||||||
group kitchen-bulbs "Kitchen Lamps" @ "Kitchen" [ groupId="1" ]
|
|
||||||
```
|
|
||||||
|
|
||||||
You can freely choose the thing identifier (such as kitchen-bulbs), its name (such as "Kitchen Lamps") and the location (such as "Kitchen").
|
|
||||||
|
|
||||||
The group type also have an optional configuration value to specify the fade time in milliseconds for the transition to a new state.
|
|
||||||
|
|
||||||
| Parameter | Description |
|
|
||||||
|-----------|-------------------------------------------------------------------------------|
|
|
||||||
| groupId | Number of the group provided by the Hue Bridge. **Mandatory** |
|
|
||||||
| fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
|
|
||||||
|
|
||||||
## Channels
|
|
||||||
|
|
||||||
The devices support some of the following channels:
|
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|
|
||||||
|-----------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|
|
|
||||||
| switch | Switch | This channel supports switching the device on and off. | 0000, 0010, group |
|
|
||||||
| color | Color | This channel supports full color control with hue, saturation and brightness values. | 0200, 0210, group |
|
|
||||||
| brightness | Dimmer | This channel supports adjusting the brightness value. Note that this is not available, if the color channel is supported. | 0100, 0110, 0220, group |
|
|
||||||
| color_temperature | Dimmer | This channel supports adjusting the color temperature from cold (0%) to warm (100%). | 0210, 0220, group |
|
|
||||||
| color_temperature_abs | Number:Temperature | This channel supports adjusting the color temperature in Kelvin. **Advanced** | 0210, 0220, group |
|
|
||||||
| alert | String | This channel supports displaying alerts by flashing the bulb either once or multiple times. Valid values are: NONE, SELECT and LSELECT. | 0000, 0100, 0200, 0210, 0220, group |
|
|
||||||
| effect | Switch | This channel supports color looping. | 0200, 0210, 0220 |
|
|
||||||
| dimmer_switch | Number | This channel shows which button was last pressed on the dimmer switch. | 0820 |
|
|
||||||
| illuminance | Number:Illuminance | This channel shows the current illuminance measured by the sensor. | 0106 |
|
|
||||||
| light_level | Number | This channel shows the current light level measured by the sensor. **Advanced** | 0106 |
|
|
||||||
| dark | Switch | This channel indicates whether the light level is below the darkness threshold or not. | 0106 |
|
|
||||||
| daylight | Switch | This channel indicates whether the light level is below the daylight threshold or not. | 0106 |
|
|
||||||
| presence | Switch | This channel indicates whether a motion is detected by the sensor or not. | 0107 |
|
|
||||||
| enabled | Switch | This channel activated or deactivates the sensor | 0107 |
|
|
||||||
| temperature | Number:Temperature | This channel shows the current temperature measured by the sensor. | 0302 |
|
|
||||||
| flag | Switch | This channel save flag state for a CLIP sensor. | 0850 |
|
|
||||||
| status | Number | This channel save status state for a CLIP sensor. | 0840 |
|
|
||||||
| last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
|
|
||||||
| battery_level | Number | This channel shows the battery level. | 0820, 0106, 0107, 0302 |
|
|
||||||
| battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 |
|
|
||||||
| scene | String | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue Bridge. | bridge, group |
|
|
||||||
|
|
||||||
To load a hue scene inside a rule for example, the ID of the scene will be required.
|
|
||||||
You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.
|
|
||||||
|
|
||||||
### Trigger Channels
|
|
||||||
|
|
||||||
The dimmer switch additionally supports a trigger channel.
|
|
||||||
|
|
||||||
| Channel ID | Description | Thing types supporting this channel |
|
|
||||||
|---------------------|----------------------------------|-------------------------------------|
|
|
||||||
| dimmer_switch_event | Event for dimmer switch pressed. | 0820 |
|
|
||||||
| tap_switch_event | Event for tap switch pressed. | 0830 |
|
|
||||||
|
|
||||||
The `dimmer_switch_event` can trigger one of the following events:
|
|
||||||
|
|
||||||
| Button | State | Event |
|
|
||||||
|---------------------|-----------------|-------|
|
|
||||||
| Button 1 (ON) | INITIAL_PRESSED | 1000 |
|
|
||||||
| | HOLD | 1001 |
|
|
||||||
| | SHORT RELEASED | 1002 |
|
|
||||||
| | LONG RELEASED | 1003 |
|
|
||||||
| Button 2 (DIM UP) | INITIAL_PRESSED | 2000 |
|
|
||||||
| | HOLD | 2001 |
|
|
||||||
| | SHORT RELEASED | 2002 |
|
|
||||||
| | LONG RELEASED | 2003 |
|
|
||||||
| Button 3 (DIM DOWN) | INITIAL_PRESSED | 3000 |
|
|
||||||
| | HOLD | 3001 |
|
|
||||||
| | SHORT RELEASED | 3002 |
|
|
||||||
| | LONG RELEASED | 3003 |
|
|
||||||
| Button 4 (OFF) | INITIAL_PRESSED | 4000 |
|
|
||||||
| | HOLD | 4001 |
|
|
||||||
| | SHORT RELEASED | 4002 |
|
|
||||||
| | LONG RELEASED | 4003 |
|
|
||||||
|
|
||||||
The `tap_switch_event` can trigger one of the following events:
|
|
||||||
|
|
||||||
| Button | State | Event |
|
|
||||||
|----------|----------|-------|
|
|
||||||
| Button 1 | Button 1 | 34 |
|
|
||||||
| Button 2 | Button 2 | 16 |
|
|
||||||
| Button 3 | Button 3 | 17 |
|
|
||||||
| Button 4 | Button 4 | 18 |
|
|
||||||
|
|
||||||
## Rule Actions
|
|
||||||
|
|
||||||
This binding includes a rule action, which allows to change a light channel with a specific fading time from within rules.
|
|
||||||
There is a separate instance for each light or light group, which can be retrieved e.g. through
|
|
||||||
|
|
||||||
```php
|
|
||||||
val hueActions = getActions("hue","hue:0210:00178810d0dc:1")
|
|
||||||
```
|
|
||||||
|
|
||||||
where the first parameter always has to be `hue` and the second is the full Thing UID of the light that should be used.
|
|
||||||
Once this action instance is retrieved, you can invoke the `fadingLightCommand(String channel, Command command, DecimalType fadeTime)` method on it:
|
|
||||||
|
|
||||||
```php
|
|
||||||
hueActions.fadingLightCommand("color", new PercentType(100), new DecimalType(1000))
|
|
||||||
```
|
|
||||||
|
|
||||||
| Parameter | Description |
|
|
||||||
|-----------|--------------------------------------------------------------------------------------------------|
|
|
||||||
| channel | The following channels have fade time support: **brightness, color, color_temperature, switch** |
|
|
||||||
| command | All commands supported by the channel can be used |
|
|
||||||
| fadeTime | Fade time in milliseconds to a new light value (min="0", step="100") |
|
|
||||||
|
|
||||||
## Full Example
|
|
||||||
|
|
||||||
In this example **bulb1** is a standard Philips Hue bulb (LCT001) which supports `color` and `color_temperature`.
|
|
||||||
Therefore it is a thing of type **0210**.
|
|
||||||
**bulb2** is an OSRAM tunable white bulb (PAR16 50 TW) supporting `color_temperature` and so the type is **0220**.
|
|
||||||
And there is one Hue Motion Sensor (represented by three devices) and a Hue Dimmer Switch **dimmer-switch** with a Rule to trigger an action when a key has been pressed.
|
|
||||||
|
|
||||||
### demo.things:
|
|
||||||
|
|
||||||
```java
|
|
||||||
Bridge hue:bridge:1 "Hue Bridge" [ ipAddress="192.168.0.64" ] {
|
|
||||||
0210 bulb1 "Lamp 1" @ "Kitchen" [ lightId="1" ]
|
|
||||||
0220 bulb2 "Lamp 2" @ "Kitchen" [ lightId="2" ]
|
|
||||||
group kitchen-bulbs "Kitchen Lamps" @ "Kitchen" [ groupId="1" ]
|
|
||||||
0106 light-level-sensor "Light-Sensor" @ "Entrance" [ sensorId="3" ]
|
|
||||||
0107 motion-sensor "Motion-Sensor" @ "Entrance" [ sensorId="4" ]
|
|
||||||
0302 temperature-sensor "Temp-Sensor" @ "Entrance" [ sensorId="5" ]
|
|
||||||
0820 dimmer-switch "Dimmer-Switch" @ "Entrance" [ sensorId="6" ]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### demo.items:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Bulb1
|
|
||||||
Switch Light1_Toggle { channel="hue:0210:1:bulb1:color" }
|
|
||||||
Dimmer Light1_Dimmer { channel="hue:0210:1:bulb1:color" }
|
|
||||||
Color Light1_Color { channel="hue:0210:1:bulb1:color" }
|
|
||||||
Dimmer Light1_ColorTemp { channel="hue:0210:1:bulb1:color_temperature" }
|
|
||||||
String Light1_Alert { channel="hue:0210:1:bulb1:alert" }
|
|
||||||
Switch Light1_Effect { channel="hue:0210:1:bulb1:effect" }
|
|
||||||
|
|
||||||
// Bulb2
|
|
||||||
Switch Light2_Toggle { channel="hue:0220:1:bulb2:brightness" }
|
|
||||||
Dimmer Light2_Dimmer { channel="hue:0220:1:bulb2:brightness" }
|
|
||||||
Dimmer Light2_ColorTemp { channel="hue:0220:1:bulb2:color_temperature" }
|
|
||||||
|
|
||||||
// Kitchen
|
|
||||||
Switch Kitchen_Switch { channel="hue:group:1:kitchen-bulbs:switch" }
|
|
||||||
Dimmer Kitchen_Dimmer { channel="hue:group:1:kitchen-bulbs:brightness" }
|
|
||||||
Color Kitchen_Color { channel="hue:group:1:kitchen-bulbs:color" }
|
|
||||||
Dimmer Kitchen_ColorTemp { channel="hue:group:1:kitchen-bulbs:color_temperature" }
|
|
||||||
|
|
||||||
// Light Level Sensor
|
|
||||||
Number:Illuminance LightLevelSensorIlluminance { channel="hue:0106:1:light-level-sensor:illuminance" }
|
|
||||||
|
|
||||||
// Motion Sensor
|
|
||||||
Switch MotionSensorPresence { channel="hue:0107:1:motion-sensor:presence" }
|
|
||||||
DateTime MotionSensorLastUpdate { channel="hue:0107:1:motion-sensor:last_updated" }
|
|
||||||
Number MotionSensorBatteryLevel { channel="hue:0107:1:motion-sensor:battery_level" }
|
|
||||||
Switch MotionSensorLowBattery { channel="hue:0107:1:motion-sensor:battery_low" }
|
|
||||||
|
|
||||||
// Temperature Sensor
|
|
||||||
Number:Temperature TemperatureSensorTemperature { channel="hue:0302:1:temperature-sensor:temperature" }
|
|
||||||
|
|
||||||
// Scenes
|
|
||||||
String LightScene { channel="hue:bridge:1:scene"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The bridge ID is in this example **1** but can be different in each system.
|
|
||||||
Also, if you are doing all your configuration through files, you may add the full bridge id to the channel definitions (e.g. `channel="hue:0210:00178810d0dc:bulb1:color`) instead of the short version (e.g. `channel="hue:0210:1:bulb1:color`) to prevent frequent discovery messages in the log file.
|
|
||||||
|
|
||||||
### demo.sitemap:
|
|
||||||
|
|
||||||
```perl
|
|
||||||
sitemap demo label="Main Menu"
|
|
||||||
{
|
|
||||||
Frame {
|
|
||||||
// Bulb1
|
|
||||||
Switch item= Light1_Toggle
|
|
||||||
Slider item= Light1_Dimmer
|
|
||||||
Colorpicker item= Light1_Color
|
|
||||||
Slider item= Light1_ColorTemp
|
|
||||||
Switch item= Light1_Alert mappings=[NONE="None", SELECT="Alert", LSELECT="Long Alert"]
|
|
||||||
Switch item= Light1_Effect
|
|
||||||
|
|
||||||
// Bulb2
|
|
||||||
Switch item= Light2_Toggle
|
|
||||||
Slider item= Light2_Dimmer
|
|
||||||
Slider item= Light2_ColorTemp
|
|
||||||
|
|
||||||
// Kitchen
|
|
||||||
Switch item= Kitchen_Switch
|
|
||||||
Slider item= Kitchen_Dimmer
|
|
||||||
Colorpicker item= Kitchen_Color
|
|
||||||
Slider item= Kitchen_ColorTemp
|
|
||||||
|
|
||||||
// Motion Sensor
|
|
||||||
Switch item=MotionSensorPresence
|
|
||||||
Text item=MotionSensorLastUpdate
|
|
||||||
Text item=MotionSensorBatteryLevel
|
|
||||||
Switch item=MotionSensorLowBattery
|
|
||||||
|
|
||||||
// Light Scenes
|
|
||||||
Default item=LightScene label="Scene []"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Events
|
|
||||||
|
|
||||||
```php
|
|
||||||
rule "example trigger rule"
|
|
||||||
when
|
|
||||||
Channel "hue:0820:1:dimmer-switch:dimmer_switch_event" triggered <EVENT>
|
|
||||||
then
|
|
||||||
...
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The optional `<EVENT>` represents one of the button events that are generated by the Hue Dimmer Switch.
|
|
||||||
If ommited the rule gets triggered by any key action and you can determine the event that triggered it with the `receivedEvent` method.
|
|
||||||
Be aware that the events have a '.0' attached to them, like `2001.0` or `34.0`.
|
|
||||||
So, testing for specific events looks like this:
|
|
||||||
|
|
||||||
```php
|
|
||||||
if (receivedEvent == "1000.0")) {
|
|
||||||
//do stuff
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
BIN
bundles/org.openhab.binding.hue/doc/hue2.png
Normal file
BIN
bundles/org.openhab.binding.hue/doc/hue2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 KiB |
358
bundles/org.openhab.binding.hue/doc/readme_v1.md
Normal file
358
bundles/org.openhab.binding.hue/doc/readme_v1.md
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
# Philips Hue Binding Configuration for API v1
|
||||||
|
|
||||||
|
[Back to Overview](../README.md#philips-hue-binding)
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Almost all available Hue devices are supported by this binding.
|
||||||
|
This includes not only the "Friends of Hue", but also products like the LivingWhites adapter.
|
||||||
|
Additionally, it is possible to use OSRAM Lightify devices as well as other Zigbee Light Link compatible products, including the IKEA TRÅDFRI lights (when updated).
|
||||||
|
Beside bulbs and luminaires the Hue binding also supports some Zigbee sensors. Currently only Hue specific sensors are tested successfully (Hue Motion Sensor and Hue Dimmer Switch).
|
||||||
|
Please note that the devices need to be registered with the Hue Bridge before it is possible for this binding to use them.
|
||||||
|
|
||||||
|
The Hue binding supports all seven types of lighting devices defined for Zigbee Light Link ([see page 24, table 2](https://www.nxp.com/docs/en/user-guide/JN-UG-3091.pdf).
|
||||||
|
These are:
|
||||||
|
|
||||||
|
| Device type | Zigbee Device ID | Thing type |
|
||||||
|
|--------------------------|------------------|------------|
|
||||||
|
| On/Off Light | 0x0000 | 0000 |
|
||||||
|
| On/Off Plug-in Unit | 0x0010 | 0010 |
|
||||||
|
| Dimmable Light | 0x0100 | 0100 |
|
||||||
|
| Dimmable Plug-in Unit | 0x0110 | 0110 |
|
||||||
|
| Colour Light | 0x0200 | 0200 |
|
||||||
|
| Extended Colour Light | 0x0210 | 0210 |
|
||||||
|
| Colour Temperature Light | 0x0220 | 0220 |
|
||||||
|
|
||||||
|
All different models of Hue, OSRAM, or other bulbs nicely fit into one of these seven types.
|
||||||
|
This type also determines the capability of a device and with that the possible ways of interacting with it.
|
||||||
|
The following matrix lists the capabilities (channels) for each type:
|
||||||
|
|
||||||
|
| Thing type | On/Off | Brightness | Color | Color Temperature |
|
||||||
|
|-------------|:------:|:----------:|:-----:|:-----------------:|
|
||||||
|
| 0000 | X | | | |
|
||||||
|
| 0010 | X | | | |
|
||||||
|
| 0100 | X | X | | |
|
||||||
|
| 0110 | X | X | | |
|
||||||
|
| 0200 | X | | X | |
|
||||||
|
| 0210 | X | | X | X |
|
||||||
|
| 0220 | X | X | | X |
|
||||||
|
|
||||||
|
Beside bulbs and luminaires the Hue binding supports some Zigbee sensors.
|
||||||
|
Currently only Hue specific sensors are tested successfully (e.g. Hue Motion Sensor, Hue Dimmer Switch, Hue Tap, CLIP Sensor).
|
||||||
|
The Hue Motion Sensor registers a `ZLLLightLevel` sensor (0106), a `ZLLPresence` sensor (0107) and a `ZLLTemperature` sensor (0302) in one device.
|
||||||
|
The Hue CLIP Sensor saves scene states with status or flag for HUE rules.
|
||||||
|
They are presented by the following Zigbee Device ID and _Thing type_:
|
||||||
|
|
||||||
|
| Device type | Zigbee Device ID | Thing type |
|
||||||
|
|-----------------------------|------------------|----------------|
|
||||||
|
| Light Sensor | 0x0106 | 0106 |
|
||||||
|
| Occupancy Sensor | 0x0107 | 0107 |
|
||||||
|
| Temperature Sensor | 0x0302 | 0302 |
|
||||||
|
| Non-Colour Controller | 0x0820 | 0820 |
|
||||||
|
| Non-Colour Scene Controller | 0x0830 | 0830 |
|
||||||
|
| CLIP Generic Status Sensor | 0x0840 | 0840 |
|
||||||
|
| CLIP Generic Flag Sensor | 0x0850 | 0850 |
|
||||||
|
| Geofence Sensor | | geofencesensor |
|
||||||
|
|
||||||
|
The Hue Dimmer Switch has 4 buttons and registers as a Non-Colour Controller switch, while the Hue Tap (also 4 buttons) registers as a Non-Colour Scene Controller in accordance with the ZLL standard.
|
||||||
|
|
||||||
|
Also, Hue Bridge support CLIP Generic Status Sensor and CLIP Generic Flag Sensor.
|
||||||
|
These sensors save state for rules and calculate what actions to do.
|
||||||
|
CLIP Sensor set or get by JSON through IP.
|
||||||
|
|
||||||
|
Finally, the Hue binding also supports the groups of lights and rooms set up on the Hue Bridge.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
The Hue Bridge requires the IP address as a configuration value in order for the binding to know where to access it.
|
||||||
|
In the thing file, this looks e.g. like
|
||||||
|
|
||||||
|
```java
|
||||||
|
Bridge hue:bridge:1 [ ipAddress="192.168.0.64" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
A user to authenticate against the Hue Bridge is automatically generated.
|
||||||
|
Please note that the generated user name cannot be written automatically to the `.things` file, and has to be set manually.
|
||||||
|
The generated user name can be found, after pressing the authentication button on the bridge, with the following console command: `hue <bridgeUID> username`.
|
||||||
|
The user name can be set using the `userName` configuration value, e.g.:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Bridge hue:bridge:1 [ ipAddress="192.168.0.64", userName="qwertzuiopasdfghjklyxcvbnm1234" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ipAddress | Network address of the Hue Bridge. **Mandatory**. |
|
||||||
|
| port | Port of the Hue Bridge. Optional, default value is 80 or 443, derived from protocol, otherwise user-defined. |
|
||||||
|
| protocol | Protocol to connect to the Hue Bridge ("http" or "https"), default value is "https"). |
|
||||||
|
| useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. **Advanced**, default value is `true`. |
|
||||||
|
| userName | Name of a registered Hue Bridge user, that allows to access the API. **Mandatory** |
|
||||||
|
| pollingInterval | Seconds between fetching light values from the Hue Bridge. Optional, the default value is 10 (min="1", step="1"). |
|
||||||
|
| sensorPollingInterval | Milliseconds between fetching sensor-values from the Hue Bridge. A higher value means more delay for the sensor values, but a too low value can cause congestion on the bridge. Optional, the default value is 500. Default value will be considered if the value is lower than 50. Use 0 to disable the polling for sensors. |
|
||||||
|
|
||||||
|
### Devices
|
||||||
|
|
||||||
|
The devices are identified by the number that the Hue Bridge assigns to them (also shown in the Hue App as an identifier).
|
||||||
|
Thus, all it needs for manual configuration is this single value like
|
||||||
|
|
||||||
|
```java
|
||||||
|
0210 bulb1 "Lamp 1" @ "Office" [ lightId="1" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```java
|
||||||
|
0107 motion-sensor "Motion Sensor" @ "Entrance" [ sensorId="4" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can freely choose the thing identifier (such as motion-sensor), its name (such as "Motion Sensor") and the location (such as "Entrance").
|
||||||
|
|
||||||
|
The following device types also have an optional configuration value to specify the fade time in milliseconds for the transition to a new state:
|
||||||
|
|
||||||
|
- Dimmable Light
|
||||||
|
- Dimmable Plug-in Unit
|
||||||
|
- Colour Light
|
||||||
|
- Extended Colour Light
|
||||||
|
- Colour Temperature Light
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------------------------------------------------------------------------|
|
||||||
|
| lightId | Number of the device provided by the Hue Bridge. **Mandatory** |
|
||||||
|
| fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
|
||||||
|
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
The groups are identified by the number that the Hue Bridge assigns to them.
|
||||||
|
Thus, all it needs for manual configuration is this single value like
|
||||||
|
|
||||||
|
```java
|
||||||
|
group kitchen-bulbs "Kitchen Lamps" @ "Kitchen" [ groupId="1" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can freely choose the thing identifier (such as kitchen-bulbs), its name (such as "Kitchen Lamps") and the location (such as "Kitchen").
|
||||||
|
|
||||||
|
The group type also have an optional configuration value to specify the fade time in milliseconds for the transition to a new state.
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------------------------------------------------------------------------|
|
||||||
|
| groupId | Number of the group provided by the Hue Bridge. **Mandatory** |
|
||||||
|
| fadetime | Fade time in Milliseconds to a new state (min="0", step="100", default="400") |
|
||||||
|
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
The devices support some of the following channels:
|
||||||
|
|
||||||
|
| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|
||||||
|
|-----------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|
|
||||||
|
| switch | Switch | This channel supports switching the device on and off. | 0000, 0010, group |
|
||||||
|
| color | Color | This channel supports full color control with hue, saturation and brightness values. | 0200, 0210, group |
|
||||||
|
| brightness | Dimmer | This channel supports adjusting the brightness value. Note that this is not available, if the color channel is supported. | 0100, 0110, 0220, group |
|
||||||
|
| color_temperature | Dimmer | This channel supports adjusting the color temperature from cold (0%) to warm (100%). | 0210, 0220, group |
|
||||||
|
| color_temperature_abs | Number:Temperature | This channel supports adjusting the color temperature in Kelvin.
|
||||||
|
**Advanced** | 0210, 0220, group |
|
||||||
|
| alert | String | This channel supports displaying alerts by flashing the bulb either once or multiple times. Valid values are: NONE, SELECT and LSELECT. | 0000, 0100, 0200, 0210, 0220, group |
|
||||||
|
| effect | Switch | This channel supports color looping. | 0200, 0210, 0220 |
|
||||||
|
| dimmer_switch | Number | This channel shows which button was last pressed on the dimmer switch. | 0820 |
|
||||||
|
| illuminance | Number:Illuminance | This channel shows the current illuminance measured by the sensor. | 0106 |
|
||||||
|
| light_level | Number | This channel shows the current light level measured by the sensor. **Advanced** | 0106 |
|
||||||
|
| dark | Switch | This channel indicates whether the light level is below the darkness threshold or not. | 0106 |
|
||||||
|
| daylight | Switch | This channel indicates whether the light level is below the daylight threshold or not. | 0106 |
|
||||||
|
| presence | Switch | This channel indicates whether a motion is detected by the sensor or not. | 0107 |
|
||||||
|
| enabled | Switch | This channel activated or deactivates the sensor | 0107 |
|
||||||
|
| temperature | Number:Temperature | This channel shows the current temperature measured by the sensor. | 0302 |
|
||||||
|
| flag | Switch | This channel save flag state for a CLIP sensor. | 0850 |
|
||||||
|
| status | Number | This channel save status state for a CLIP sensor. | 0840 |
|
||||||
|
| last_updated | DateTime | This channel the date and time when the sensor was last updated. | 0820, 0830, 0840, 0850, 0106, 0107, 0302 |
|
||||||
|
| battery_level | Number | This channel shows the battery level. | 0820, 0106, 0107, 0302 |
|
||||||
|
| battery_low | Switch | This channel indicates whether the battery is low or not. | 0820, 0106, 0107, 0302 |
|
||||||
|
| scene | String | This channel activates the scene with the given ID String. The ID String of each scene is assigned by the Hue Bridge. | bridge, group |
|
||||||
|
|
||||||
|
To load a hue scene inside a rule for example, the ID of the scene will be required.
|
||||||
|
You can list all the scene IDs with the following console commands: `hue <bridgeUID> scenes` and `hue <groupThingUID> scenes`.
|
||||||
|
|
||||||
|
### Trigger Channels
|
||||||
|
|
||||||
|
The dimmer switch additionally supports a trigger channel.
|
||||||
|
|
||||||
|
| Channel ID | Description | Thing types supporting this channel |
|
||||||
|
|---------------------|----------------------------------|-------------------------------------|
|
||||||
|
| dimmer_switch_event | Event for dimmer switch pressed. | 0820 |
|
||||||
|
| tap_switch_event | Event for tap switch pressed. | 0830 |
|
||||||
|
|
||||||
|
The `dimmer_switch_event` can trigger one of the following events:
|
||||||
|
|
||||||
|
| Button | State | Event |
|
||||||
|
|---------------------|-----------------|-------|
|
||||||
|
| Button 1 (ON) | INITIAL_PRESSED | 1000 |
|
||||||
|
| | HOLD | 1001 |
|
||||||
|
| | SHORT RELEASED | 1002 |
|
||||||
|
| | LONG RELEASED | 1003 |
|
||||||
|
| Button 2 (DIM UP) | INITIAL_PRESSED | 2000 |
|
||||||
|
| | HOLD | 2001 |
|
||||||
|
| | SHORT RELEASED | 2002 |
|
||||||
|
| | LONG RELEASED | 2003 |
|
||||||
|
| Button 3 (DIM DOWN) | INITIAL_PRESSED | 3000 |
|
||||||
|
| | HOLD | 3001 |
|
||||||
|
| | SHORT RELEASED | 3002 |
|
||||||
|
| | LONG RELEASED | 3003 |
|
||||||
|
| Button 4 (OFF) | INITIAL_PRESSED | 4000 |
|
||||||
|
| | HOLD | 4001 |
|
||||||
|
| | SHORT RELEASED | 4002 |
|
||||||
|
| | LONG RELEASED | 4003 |
|
||||||
|
|
||||||
|
The `tap_switch_event` can trigger one of the following events:
|
||||||
|
|
||||||
|
| Button | State | Event |
|
||||||
|
|----------|----------|-------|
|
||||||
|
| Button 1 | Button 1 | 34 |
|
||||||
|
| Button 2 | Button 2 | 16 |
|
||||||
|
| Button 3 | Button 3 | 17 |
|
||||||
|
| Button 4 | Button 4 | 18 |
|
||||||
|
|
||||||
|
|
||||||
|
## Rule Actions
|
||||||
|
|
||||||
|
This binding includes a rule action, which allows to change a light channel with a specific fading time from within rules.
|
||||||
|
There is a separate instance for each light or light group, which can be retrieved e.g. through
|
||||||
|
|
||||||
|
```php
|
||||||
|
val hueActions = getActions("hue","hue:0210:00178810d0dc:1")
|
||||||
|
```
|
||||||
|
|
||||||
|
where the first parameter always has to be `hue` and the second is the full Thing UID of the light that should be used.
|
||||||
|
Once this action instance is retrieved, you can invoke the `fadingLightCommand(String channel, Command command, DecimalType fadeTime)` method on it:
|
||||||
|
|
||||||
|
```php
|
||||||
|
hueActions.fadingLightCommand("color", new PercentType(100), new DecimalType(1000))
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|--------------------------------------------------------------------------------------------------|
|
||||||
|
| channel | The following channels have fade time support: **brightness, color, color_temperature, switch** |
|
||||||
|
| command | All commands supported by the channel can be used |
|
||||||
|
| fadeTime | Fade time in milliseconds to a new light value (min="0", step="100") |
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
In this example **bulb1** is a standard Philips Hue bulb (LCT001) which supports `color` and `color_temperature`.
|
||||||
|
Therefore it is a thing of type **0210**.
|
||||||
|
**bulb2** is an OSRAM tunable white bulb (PAR16 50 TW) supporting `color_temperature` and so the type is **0220**.
|
||||||
|
And there is one Hue Motion Sensor (represented by three devices) and a Hue Dimmer Switch **dimmer-switch** with a Rule to trigger an action when a key has been pressed.
|
||||||
|
|
||||||
|
### demo.things:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Bridge hue:bridge:1 "Hue Bridge" [ ipAddress="192.168.0.64" ] {
|
||||||
|
0210 bulb1 "Lamp 1" @ "Kitchen" [ lightId="1" ]
|
||||||
|
0220 bulb2 "Lamp 2" @ "Kitchen" [ lightId="2" ]
|
||||||
|
group kitchen-bulbs "Kitchen Lamps" @ "Kitchen" [ groupId="1" ]
|
||||||
|
0106 light-level-sensor "Light-Sensor" @ "Entrance" [ sensorId="3" ]
|
||||||
|
0107 motion-sensor "Motion-Sensor" @ "Entrance" [ sensorId="4" ]
|
||||||
|
0302 temperature-sensor "Temp-Sensor" @ "Entrance" [ sensorId="5" ]
|
||||||
|
0820 dimmer-switch "Dimmer-Switch" @ "Entrance" [ sensorId="6" ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo.items:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Bulb1
|
||||||
|
Switch Light1_Toggle { channel="hue:0210:1:bulb1:color" }
|
||||||
|
Dimmer Light1_Dimmer { channel="hue:0210:1:bulb1:color" }
|
||||||
|
Color Light1_Color { channel="hue:0210:1:bulb1:color" }
|
||||||
|
Dimmer Light1_ColorTemp { channel="hue:0210:1:bulb1:color_temperature" }
|
||||||
|
String Light1_Alert { channel="hue:0210:1:bulb1:alert" }
|
||||||
|
Switch Light1_Effect { channel="hue:0210:1:bulb1:effect" }
|
||||||
|
|
||||||
|
// Bulb2
|
||||||
|
Switch Light2_Toggle { channel="hue:0220:1:bulb2:brightness" }
|
||||||
|
Dimmer Light2_Dimmer { channel="hue:0220:1:bulb2:brightness" }
|
||||||
|
Dimmer Light2_ColorTemp { channel="hue:0220:1:bulb2:color_temperature" }
|
||||||
|
|
||||||
|
// Kitchen
|
||||||
|
Switch Kitchen_Switch { channel="hue:group:1:kitchen-bulbs:switch" }
|
||||||
|
Dimmer Kitchen_Dimmer { channel="hue:group:1:kitchen-bulbs:brightness" }
|
||||||
|
Color Kitchen_Color { channel="hue:group:1:kitchen-bulbs:color" }
|
||||||
|
Dimmer Kitchen_ColorTemp { channel="hue:group:1:kitchen-bulbs:color_temperature" }
|
||||||
|
|
||||||
|
// Light Level Sensor
|
||||||
|
Number:Illuminance LightLevelSensorIlluminance { channel="hue:0106:1:light-level-sensor:illuminance" }
|
||||||
|
|
||||||
|
// Motion Sensor
|
||||||
|
Switch MotionSensorPresence { channel="hue:0107:1:motion-sensor:presence" }
|
||||||
|
DateTime MotionSensorLastUpdate { channel="hue:0107:1:motion-sensor:last_updated" }
|
||||||
|
Number MotionSensorBatteryLevel { channel="hue:0107:1:motion-sensor:battery_level" }
|
||||||
|
Switch MotionSensorLowBattery { channel="hue:0107:1:motion-sensor:battery_low" }
|
||||||
|
|
||||||
|
// Temperature Sensor
|
||||||
|
Number:Temperature TemperatureSensorTemperature { channel="hue:0302:1:temperature-sensor:temperature" }
|
||||||
|
|
||||||
|
// Scenes
|
||||||
|
String LightScene { channel="hue:bridge:1:scene"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The bridge ID is in this example **1** but can be different in each system.
|
||||||
|
Also, if you are doing all your configuration through files, you may add the full bridge id to the channel definitions (e.g. `channel="hue:0210:00178810d0dc:bulb1:color`) instead of the short version (e.g. `channel="hue:0210:1:bulb1:color`) to prevent frequent discovery messages in the log file.
|
||||||
|
|
||||||
|
### demo.sitemap:
|
||||||
|
|
||||||
|
```perl
|
||||||
|
sitemap demo label="Main Menu"
|
||||||
|
{
|
||||||
|
Frame {
|
||||||
|
// Bulb1
|
||||||
|
Switch item= Light1_Toggle
|
||||||
|
Slider item= Light1_Dimmer
|
||||||
|
Colorpicker item= Light1_Color
|
||||||
|
Slider item= Light1_ColorTemp
|
||||||
|
Switch item= Light1_Alert mappings=[NONE="None", SELECT="Alert", LSELECT="Long Alert"]
|
||||||
|
Switch item= Light1_Effect
|
||||||
|
|
||||||
|
// Bulb2
|
||||||
|
Switch item= Light2_Toggle
|
||||||
|
Slider item= Light2_Dimmer
|
||||||
|
Slider item= Light2_ColorTemp
|
||||||
|
|
||||||
|
// Kitchen
|
||||||
|
Switch item= Kitchen_Switch
|
||||||
|
Slider item= Kitchen_Dimmer
|
||||||
|
Colorpicker item= Kitchen_Color
|
||||||
|
Slider item= Kitchen_ColorTemp
|
||||||
|
|
||||||
|
// Motion Sensor
|
||||||
|
Switch item=MotionSensorPresence
|
||||||
|
Text item=MotionSensorLastUpdate
|
||||||
|
Text item=MotionSensorBatteryLevel
|
||||||
|
Switch item=MotionSensorLowBattery
|
||||||
|
|
||||||
|
// Light Scenes
|
||||||
|
Default item=LightScene label="Scene []"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
```php
|
||||||
|
rule "example trigger rule"
|
||||||
|
when
|
||||||
|
Channel "hue:0820:1:dimmer-switch:dimmer_switch_event" triggered <EVENT>
|
||||||
|
then
|
||||||
|
...
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The optional `<EVENT>` represents one of the button events that are generated by the Hue Dimmer Switch.
|
||||||
|
If ommited the rule gets triggered by any key action and you can determine the event that triggered it with the `receivedEvent` method.
|
||||||
|
Be aware that the events have a '.0' attached to them, like `2001.0` or `34.0`.
|
||||||
|
So, testing for specific events looks like this:
|
||||||
|
|
||||||
|
```php
|
||||||
|
if (receivedEvent == "1000.0") {
|
||||||
|
//do stuff
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Overview](../README.md#philips-hue-binding)
|
237
bundles/org.openhab.binding.hue/doc/readme_v2.md
Normal file
237
bundles/org.openhab.binding.hue/doc/readme_v2.md
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
# Philips Hue Binding Configuration for API v2
|
||||||
|
|
||||||
|
[Back to Overview](../README.md#philips-hue-binding)
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
The binding supports `bridge-api2`, `device`, `room`, and `zone` thing types.
|
||||||
|
The `bridge-api2` thing type represents the Hue Bridge which is the server for all other things.
|
||||||
|
The `device` thing type represents a piece of physical equipment in the home.
|
||||||
|
Such `device` things may contain either a *light*, a *button*, or (one or more) *sensors*.
|
||||||
|
Lights can be of any type from a simple on/off light, through dimmable monochrome lights, to full colour dimmable lights.
|
||||||
|
Buttons are devices having one or more push buttons.
|
||||||
|
Sensors can be (for example) light level sensors, temperature sensors, or motion sensors.
|
||||||
|
The `room` and `zone` thing type represents logical groupings of equipment in the home, either within a specific room, or a logical group of equipment.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
### Bridge
|
||||||
|
|
||||||
|
The Hue Bridge requires the IP address as a configuration value in order for the binding to know where to access it.
|
||||||
|
It requires an 'application key' to authenticate against the Hue Bridge.
|
||||||
|
This may be copied from an API v1 installation, or it may be automatically generated (press button to authenticate).
|
||||||
|
Please note that the generated application key cannot be written automatically to the `.things` file, and has to be set manually.
|
||||||
|
The generated application key can be found, after pressing the authentication button on the bridge, with the following console command: `openhab:hue <bridgeUID> applicationkey`.
|
||||||
|
The application key can be set using the `applicationKey` configuration value, e.g.:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Bridge hue:bridge-api2:1 [ ipAddress="192.168.0.64", applicationKey="qwertzuiopasdfghjklyxcvbnm1234" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|--------------------------|----------------------------------------------------------------------------------------------------|
|
||||||
|
| ipAddress | Network address of the Hue Bridge. **Mandatory**. |
|
||||||
|
| applicationKey | A code generated by the bridge that allows to access the API. **Mandatory** |
|
||||||
|
| checkMinutes | Interval in minutes between retrying the HTTP 2 and SSE connections. Default is 60. **Advanced** |
|
||||||
|
| useSelfSignedCertificate | Use self-signed certificate for HTTPS connection to Hue Bridge. Default is `true`. **Advanced** |
|
||||||
|
|
||||||
|
### Devices, Rooms, and Zones
|
||||||
|
|
||||||
|
Apart from the Bridge, there are three other types of thing -- namely `device`, `room`, and `zone`.
|
||||||
|
Device things represent physical hardware devices in the system, whereas `room` and `zone` things represent sets of physical lights, either in a room or a zone.
|
||||||
|
In addition to regular rooms and zones, there is a 'super' `zone` that allows you to control all of the lights in the system.
|
||||||
|
|
||||||
|
All things are identified by a unique Resource Identifier string that the Hue Bridge assigns to them e.g. `d1ae958e-8908-449a-9897-7f10f9b8d4c2`.
|
||||||
|
Thus, all it needs for manual configuration is this single value, like:
|
||||||
|
|
||||||
|
```java
|
||||||
|
device officelamp "Lamp 1" @ "Office" [ resourceId="d1ae958e-8908-449a-9897-7f10f9b8d4c2" ]
|
||||||
|
..
|
||||||
|
zone kitchenLights "Kitchen Down Lights" @ "Kitchen" [ resourceId="7f10f9b8-8908-449a-9897-d4c2d1ae958e" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get a list of all devices in the bridge and their respective Resource Ids by entering the following console command: `openhab:hue <bridgeUID> things`
|
||||||
|
See [console command](#console-command-for-finding-resourceids)
|
||||||
|
|
||||||
|
The configuration of all things (as described above) is the same regardless of whether it is a device containing a light, a button, or (one or more) sensors, or whether it is a room or zone.
|
||||||
|
|
||||||
|
### Channels for Devices
|
||||||
|
|
||||||
|
Device things support some of the following channels:
|
||||||
|
|
||||||
|
| Channel ID | Item Type | Description |
|
||||||
|
|-----------------------|--------------------|---------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| color | Color | Supports full color control with hue, saturation and brightness values, or brightness only, or switching on or off. |
|
||||||
|
| brightness | Dimmer | Supports control of the brightness value, or switching on or off. |
|
||||||
|
| color-temperature | Dimmer | Supports control of the color temperature in percent from cold (0%) to warm (100%). |
|
||||||
|
| color-temperature-abs | Number:Temperature | Supports control of the color temperature via a QuantityType having a temperature unit e.g. Kelvin. (Advanced) |
|
||||||
|
| switch | Switch | Supports switching the device on and off. |
|
||||||
|
| dynamics | Number:Time | Sets the duration of dynamic transitions between light states. (Advanced) |
|
||||||
|
| alert | String | Allows setting an alert on a light e.g. flashing them. (Advanced) |
|
||||||
|
| effect | String | Allows setting an effect on a light e.g. 'candle' effect. (Advanced) |
|
||||||
|
| button-last-event | Number | Informs which button was last pressed in the device. (Trigger Channel) |
|
||||||
|
| rotary-steps | Number | Informs about the number of rotary steps of the last rotary dial movement. (Trigger Channel) |
|
||||||
|
| motion | Switch | Shows if motion has been detected by the sensor. (Read Only) |
|
||||||
|
| motion-enabled | Switch | Supports enabling / disabling the motion sensor. (Advanced) |
|
||||||
|
| light-level | Number:Illuminance | Shows the current light level measured by the sensor. (Read Only) |
|
||||||
|
| light-level-enabled | Switch | Supports enabling / disabling the light level sensor. (Advanced) |
|
||||||
|
| temperature | Number:Temperature | Shows the current temperature measured by the sensor. (Read Only) |
|
||||||
|
| temperature-enabled | Switch | Supports enabling / disabling the temperature sensor. (Advanced) |
|
||||||
|
| battery-level | Number | Shows the battery level. (Read Only) |
|
||||||
|
| battery-low | Switch | Indicates whether the battery is low or not. (Read Only) |
|
||||||
|
| last-updated | DateTime | The date and time when the thing state was last updated. (Read Only) (Advanced) |
|
||||||
|
| color-xy-only | Color | Allows access to the `color-xy` parameter of the light(s) only. Has no impact on `dimming` or `on-off` parameters. |
|
||||||
|
| dimming-only | Dimmer | Allows access to the `dimming` parameter of the light(s) only. Has no impact on `color-xy` or `on-off` parameters. |
|
||||||
|
| on-off-only | Switch | Allows access to the `on-off` parameter of the light(s) only. Has no impact on `color-xy` or `dimming` parameters. |
|
||||||
|
|
||||||
|
The exact list of channels in a given device is determined at run time when the system is started.
|
||||||
|
Each device reports its own live list of capabilities, and the respective list of channels is created accordingly.
|
||||||
|
|
||||||
|
The channels `color-xy-only`, `dimming-only` and `on-off-only` are *advanced* channels - see [below](###advanced-channels-for-devices-,-rooms-and-zones) for more details.
|
||||||
|
|
||||||
|
The `button-last-event` channel is a trigger channel.
|
||||||
|
When the button is pressed the channel receives a number as calculated by the following formula:
|
||||||
|
|
||||||
|
```text
|
||||||
|
value = (button_id * 1000) + event_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
In a single button device, the `button_id` is 1, whereas in a multi- button device the `button_id` can be either 1, 2, 3, or 4 depending on which button was pressed.
|
||||||
|
The `event_id` can have the following values:
|
||||||
|
|
||||||
|
| Event | Value |
|
||||||
|
|----------------------|-------|
|
||||||
|
| INITIAL_PRESS | 0 |
|
||||||
|
| REPEAT | 1 |
|
||||||
|
| SHORT_RELEASE | 2 |
|
||||||
|
| LONG_RELEASE | 3 |
|
||||||
|
| DOUBLE_SHORT_RELEASE | 4 |
|
||||||
|
|
||||||
|
So (for example) the channel value `1002` ((1 * 1000) + 2) means that the second button in the device had a short release event.
|
||||||
|
|
||||||
|
The `rotary-steps` channel is a trigger channel.
|
||||||
|
When the dial is turned, the channel receives a number corresponding to the number of steps of the last movement of a rotary dial.
|
||||||
|
A positive number means the dial was rotated clock-wise, whereas a negative number means it was rotated counter-clockwise.
|
||||||
|
|
||||||
|
### Channels for Rooms and Zones
|
||||||
|
|
||||||
|
Room and Zone things allow you to control the lights in a given zone or room.
|
||||||
|
They support the following channels:
|
||||||
|
|
||||||
|
| Channel ID | Item Type | Description |
|
||||||
|
|---------------------|--------------------|-----------------------------------------------------------------------------------|
|
||||||
|
| brightness | Dimmer | Supports adjusting the brightness or switching the lights on and off. |
|
||||||
|
| switch | Switch | Supports switching the lights on and off. |
|
||||||
|
| scene<sup>1)</sup> | String | Setting the string to a valid scene friendly name activates the respective scene. |
|
||||||
|
| dynamics | Number:Time | The duration of dynamic transitions between light or scene states. |
|
||||||
|
| alert<sup>1)</sup> | String | This channel allows setting an alert on the lights e.g. flashing them. |
|
||||||
|
|
||||||
|
<sup>1)</sup> The scene and alert channels are optional.
|
||||||
|
If the respective room or zone has no scenes or alerts associated with it, the respective channel will not be shown.
|
||||||
|
|
||||||
|
### The `dynamics` Channel
|
||||||
|
|
||||||
|
Some channels support dynamic transitions between light states.
|
||||||
|
A dynamic transition is where, instead of the light state changing immediately to its new target value, it changes gradually to the new value over a period of time.
|
||||||
|
|
||||||
|
If a thing supports dynamic transitions, then it will have a `dynamics` channel.
|
||||||
|
This is a numeric channel where you can set the time delay for the transition in milliseconds.
|
||||||
|
When you set a value for the `dynamics` channel (e.g. 2000 milliseconds) and then quickly issue another command (e.g. brightness 100%), the second command will be executed gradually over the period of milliseconds given by the `dynamics` channel value.
|
||||||
|
When the `dynamics` channel value is changed, it triggers a time window of ten seconds during which the value is active.
|
||||||
|
If the second command is sent within the active time window, it will be executed gradually according to the `dynamics` channel value.
|
||||||
|
However, if the second command is sent after the active time window has expired, then it will be executed immediately.
|
||||||
|
|
||||||
|
### Advanced Channels for Devices, Rooms and Zones
|
||||||
|
|
||||||
|
Some things support additional advanced channels `color-xy-only`, `dimming-only` and/or `on-off-only`.
|
||||||
|
For convenience the normal channels often amalgamate multiple elements of the state of a light, room or zone into one single channel.
|
||||||
|
For example, a full color light has one single `color` channel that can accept HSBType commands for changing the color, PercentType commands for changing the brightness, and OnOffType commands for switching it on or off.
|
||||||
|
By contrast, the purpose of the advanced channels is to individually access specificstate elements of the respective lights, rooms or zones.
|
||||||
|
|
||||||
|
These advanced channels can be used as "presets".
|
||||||
|
For example, you may want to preset the `dimming-only` channel to 20% at night, and to 100% in the day time.
|
||||||
|
Then if somebody turns on the light at night time it will turn on to 20% resp. to 100% in the day time.
|
||||||
|
You can also use the `color-xy-only` channel to preset (say) a cool color in the morning, and a warm color in the evening.
|
||||||
|
NOTE: you can also preset color temperature values in advance via the `color-temperature` and `color-temperature-abs` channels described above.
|
||||||
|
|
||||||
|
## Console Command for finding ResourceIds
|
||||||
|
|
||||||
|
The openHAB console has a command named `openhab:hue` that (among other things) lists the `resourceId` of all device things in the bridge.
|
||||||
|
The console command usage is `openhab:hue <brigeUID> things`.
|
||||||
|
An exampe of such a console command, and its respective output, is shown below.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
openhab> openhab:hue hue:bridge-api2:g24 things
|
||||||
|
Bridge hue:bridge-api2:g24 "Philips Hue Bridge" [ipAddress="192.168.1.234", applicationKey="abcdefghijklmnopqrstuvwxyz0123456789ABCD"] {
|
||||||
|
Thing device 11111111-2222-3333-4444-555555555555 "Standard Lamp L" [resourceId="11111111-2222-3333-4444-555555555555"] // Hue color lamp
|
||||||
|
Thing device 11111111-2222-3333-4444-666666666666 "Kitchen Wallplate Switch" [resourceId="11111111-2222-3333-4444-666666666666"] // Hue wall switch module
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `openhab:hue <brigeUID> things` command produces an output that can be used to directly create a `.things` file, as shown below.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
openhab> openhab:hue hue:bridge-api2:g24 things > myThingsFile.things
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rule Actions
|
||||||
|
|
||||||
|
This binding includes a rule action, which implements dynamic (i.e. gradual) transitions to a new scene or light(s) state.
|
||||||
|
Each thing has a separate action instance, which can be retrieved as follows.
|
||||||
|
|
||||||
|
```php
|
||||||
|
val hueActions = getActions("hue","hue:device:g24:11111111-2222-3333-4444-555555555555")
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the first parameter must always be `hue` and the second must be the full thing UID.
|
||||||
|
Once the action instance has been retrieved, you can invoke its `dynamicCommand(String channelId, Command command, Long durationMs)` method as follows.
|
||||||
|
|
||||||
|
```php
|
||||||
|
hueActions.dynamicCommand("brightness", new PercentType(100), new Long(10000))
|
||||||
|
|
||||||
|
hueActions.dynamicCommand("scene", new StringType("SceneName"), new Long(20000))
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| channelId | The channel ID of the channel to send the command to (one of `brightness`, `color`, `color-temperature`, `color-temp-kelvin`, or `scene`). |
|
||||||
|
| command | The target command state to transition to. |
|
||||||
|
| durationMs | The dynamic transition duration in milliseconds. |
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
### demo.things:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Bridge hue:bridge-api2:g24 "Philips Hue Hub" @ "Home" [ipAddress="192.168.1.234", applicationKey="abcdefghijklmnopqrstuvwxyz0123456789ABCD"] {
|
||||||
|
Thing device 11111111-2222-3333-4444-555555555555 "Living Room Standard Lamp Left" @ "Living Room" [resourceId="11111111-2222-3333-4444-555555555555"]
|
||||||
|
Thing device 11111111-2222-3333-4444-666666666666 "Kitchen Wallplate Switch" @ "Kitchen" [resourceId="11111111-2222-3333-4444-666666666666"]
|
||||||
|
|
||||||
|
Thing zone 11111111-2222-3333-4444-666666666666 "Kitchen Lights" @ "Kitchen" [resourceId="11111111-2222-3333-4444-666666666666"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo.items:
|
||||||
|
|
||||||
|
```java
|
||||||
|
Color Living_Room_Standard_Lamp_Left_Colour "Living Room Standard Lamp Left Colour" {channel="hue:device:g24:11111111-2222-3333-4444-555555555555:color"}
|
||||||
|
Dimmer Living_Room_Standard_Lamp_Left_Brightness "Living Room Standard Lamp Left Brightness [%.0f %%]" {channel="hue:device:g24:11111111-2222-3333-4444-555555555555:brightness"}
|
||||||
|
Switch Living_Room_Standard_Lamp_Left_Switch "Living Room Standard Lamp Left Switch" (g_Lights_On_Count) {channel="hue:device:g24:11111111-2222-3333-4444-555555555555:switch"}
|
||||||
|
|
||||||
|
Number Kitchen_Wallplate_Switch_Last_Event "Kitchen Wallplate Switch Last Event" {channel="hue:device:g24:11111111-2222-3333-4444-666666666666:button-last-event"}
|
||||||
|
Switch Kitchen_Wallplate_Switch_Battery_Low_Alarm "Kitchen Wallplate Switch Battery Low Alarm" {channel="hue:device:g24:11111111-2222-3333-4444-666666666666:battery-low"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo.sitemap:
|
||||||
|
|
||||||
|
```perl
|
||||||
|
sitemap demo label="Hue" {
|
||||||
|
Frame label="Standard Lamp" {
|
||||||
|
Switch item=Living_Room_Standard_Lamp_Left_Switch
|
||||||
|
Slider item=Living_Room_Standard_Lamp_Left_Brightness
|
||||||
|
Colorpicker item=Living_Room_Standard_Lamp_Left_Colour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[Back to Overview](../README.md#philips-hue-binding)
|
@ -12,6 +12,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.hue.internal;
|
package org.openhab.binding.hue.internal;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
@ -53,9 +56,14 @@ public class HueBindingConstants {
|
|||||||
public static final ThingTypeUID THING_TYPE_GEOFENCE_SENSOR = new ThingTypeUID(BINDING_ID, "geofencesensor");
|
public static final ThingTypeUID THING_TYPE_GEOFENCE_SENSOR = new ThingTypeUID(BINDING_ID, "geofencesensor");
|
||||||
public static final ThingTypeUID THING_TYPE_TEMPERATURE_SENSOR = new ThingTypeUID(BINDING_ID, "0302");
|
public static final ThingTypeUID THING_TYPE_TEMPERATURE_SENSOR = new ThingTypeUID(BINDING_ID, "0302");
|
||||||
public static final ThingTypeUID THING_TYPE_LIGHT_LEVEL_SENSOR = new ThingTypeUID(BINDING_ID, "0106");
|
public static final ThingTypeUID THING_TYPE_LIGHT_LEVEL_SENSOR = new ThingTypeUID(BINDING_ID, "0106");
|
||||||
|
|
||||||
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
|
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group");
|
||||||
|
|
||||||
|
public static final Set<ThingTypeUID> V1_THING_TYPE_UIDS = Set.of(THING_TYPE_COLOR_LIGHT,
|
||||||
|
THING_TYPE_COLOR_TEMPERATURE_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_EXTENDED_COLOR_LIGHT,
|
||||||
|
THING_TYPE_ON_OFF_LIGHT, THING_TYPE_ON_OFF_PLUG, THING_TYPE_DIMMABLE_PLUG, THING_TYPE_DIMMER_SWITCH,
|
||||||
|
THING_TYPE_TAP_SWITCH, THING_TYPE_PRESENCE_SENSOR, THING_TYPE_TEMPERATURE_SENSOR,
|
||||||
|
THING_TYPE_LIGHT_LEVEL_SENSOR, THING_TYPE_GROUP);
|
||||||
|
|
||||||
// List all channels
|
// List all channels
|
||||||
public static final String CHANNEL_COLORTEMPERATURE = "color_temperature";
|
public static final String CHANNEL_COLORTEMPERATURE = "color_temperature";
|
||||||
public static final String CHANNEL_COLORTEMPERATURE_ABS = "color_temperature_abs";
|
public static final String CHANNEL_COLORTEMPERATURE_ABS = "color_temperature_abs";
|
||||||
@ -96,11 +104,25 @@ public class HueBindingConstants {
|
|||||||
// Thing configuration properties
|
// Thing configuration properties
|
||||||
public static final String LIGHT_ID = "lightId";
|
public static final String LIGHT_ID = "lightId";
|
||||||
public static final String SENSOR_ID = "sensorId";
|
public static final String SENSOR_ID = "sensorId";
|
||||||
public static final String PRODUCT_NAME = "productName";
|
public static final String PROPERTY_PRODUCT_NAME = "productName";
|
||||||
public static final String UNIQUE_ID = "uniqueId";
|
public static final String UNIQUE_ID = "uniqueId";
|
||||||
public static final String FADETIME = "fadetime";
|
public static final String FADETIME = "fadetime";
|
||||||
public static final String GROUP_ID = "groupId";
|
public static final String GROUP_ID = "groupId";
|
||||||
|
|
||||||
|
// property names for API v2
|
||||||
|
public static final String PROPERTY_RESOURCE_ID = "resourceId";
|
||||||
|
public static final String PROPERTY_RESOURCE_TYPE = "resourceType";
|
||||||
|
public static final String PROPERTY_RESOURCE_NAME = "resourceName";
|
||||||
|
public static final String PROPERTY_RESOURCE_ARCHETYPE = "resourceArchetype";
|
||||||
|
public static final String PROPERTY_PRODUCT_ARCHETYPE = "productArchetype";
|
||||||
|
public static final String PROPERTY_PRODUCT_CERTIFIED = "productCertified";
|
||||||
|
public static final String PROPERTY_LEGACY_THING_UID = "legacyThingUID";
|
||||||
|
public static final String PROPERTY_OWNER = "owner";
|
||||||
|
public static final String PROPERTY_OWNER_TYPE = "ownerType";
|
||||||
|
public static final String PROPERTY_DIMMING_RANGE = "dimmingRange";
|
||||||
|
public static final String PROPERTY_COLOR_TEMP_RANGE = "colorTemperatureRange";
|
||||||
|
public static final String PROPERTY_COLOR_GAMUT = "colorGamut";
|
||||||
|
|
||||||
public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
|
public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
|
||||||
|
|
||||||
public static final String DISCOVERY_LABEL_PATTERN = "Philips Hue (%s)";
|
public static final String DISCOVERY_LABEL_PATTERN = "Philips Hue (%s)";
|
||||||
@ -111,4 +133,60 @@ public class HueBindingConstants {
|
|||||||
|
|
||||||
// Config status messages
|
// Config status messages
|
||||||
public static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration";
|
public static final String IP_ADDRESS_MISSING = "missing-ip-address-configuration";
|
||||||
|
|
||||||
|
// thing types for API v2
|
||||||
|
public static final ThingTypeUID THING_TYPE_BRIDGE_API2 = new ThingTypeUID(BINDING_ID, "bridge-api2");
|
||||||
|
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
|
||||||
|
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
|
||||||
|
public static final ThingTypeUID THING_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
|
||||||
|
|
||||||
|
// channels for API v2
|
||||||
|
public static final String CHANNEL_2_COLOR = CHANNEL_COLOR;
|
||||||
|
public static final String CHANNEL_2_COLOR_TEMP_PERCENT = "color-temperature";
|
||||||
|
public static final String CHANNEL_2_COLOR_TEMP_ABSOLUTE = "color-temperature-abs";
|
||||||
|
public static final String CHANNEL_2_BRIGHTNESS = CHANNEL_BRIGHTNESS;
|
||||||
|
public static final String CHANNEL_2_SWITCH = CHANNEL_SWITCH;
|
||||||
|
public static final String CHANNEL_2_SCENE = CHANNEL_SCENE;
|
||||||
|
public static final String CHANNEL_2_DYNAMICS = "dynamics";
|
||||||
|
public static final String CHANNEL_2_ALERT = CHANNEL_ALERT;
|
||||||
|
public static final String CHANNEL_2_EFFECT = CHANNEL_EFFECT;
|
||||||
|
public static final String CHANNEL_2_BUTTON_LAST_EVENT = "button-last-event";
|
||||||
|
public static final String CHANNEL_2_ROTARY_STEPS = "rotary-steps";
|
||||||
|
public static final String CHANNEL_2_MOTION = "motion";
|
||||||
|
public static final String CHANNEL_2_MOTION_ENABLED = "motion-enabled";
|
||||||
|
public static final String CHANNEL_2_LIGHT_LEVEL = "light-level";
|
||||||
|
public static final String CHANNEL_2_LIGHT_LEVEL_ENABLED = "light-level-enabled";
|
||||||
|
public static final String CHANNEL_2_TEMPERATURE = CHANNEL_TEMPERATURE;
|
||||||
|
public static final String CHANNEL_2_TEMPERATURE_ENABLED = "temperature-enabled";
|
||||||
|
public static final String CHANNEL_2_BATTERY_LEVEL = "battery-level";
|
||||||
|
public static final String CHANNEL_2_BATTERY_LOW = "battery-low";
|
||||||
|
public static final String CHANNEL_2_LAST_UPDATED = "last-updated";
|
||||||
|
public static final String CHANNEL_2_COLOR_XY_ONLY = "color-xy-only";
|
||||||
|
public static final String CHANNEL_2_DIMMING_ONLY = "dimming-only";
|
||||||
|
public static final String CHANNEL_2_ON_OFF_ONLY = "on-off-only";
|
||||||
|
|
||||||
|
// channel IDs that (optionally) support dynamics
|
||||||
|
public static final Set<String> DYNAMIC_CHANNELS = Set.of(CHANNEL_2_BRIGHTNESS, CHANNEL_2_COLOR,
|
||||||
|
CHANNEL_2_COLOR_TEMP_PERCENT, CHANNEL_2_COLOR_TEMP_ABSOLUTE, CHANNEL_2_SCENE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Map of API v1 channel IDs against API v2 channel IDs where, if the v1 channel exists in the system, then we
|
||||||
|
* should try to replicate the channel/item links from the v1 channel into the respective v2 channel.
|
||||||
|
*/
|
||||||
|
public static final Map<String, String> REPLICATE_CHANNEL_ID_MAP = Map.ofEntries(
|
||||||
|
Map.entry(CHANNEL_BRIGHTNESS, CHANNEL_2_BRIGHTNESS), //
|
||||||
|
Map.entry(CHANNEL_COLOR, CHANNEL_2_COLOR), //
|
||||||
|
Map.entry(CHANNEL_SWITCH, CHANNEL_2_SWITCH), //
|
||||||
|
Map.entry(CHANNEL_SCENE, CHANNEL_2_SCENE), //
|
||||||
|
Map.entry(CHANNEL_COLORTEMPERATURE, CHANNEL_2_COLOR_TEMP_PERCENT), //
|
||||||
|
Map.entry(CHANNEL_COLORTEMPERATURE_ABS, CHANNEL_2_COLOR_TEMP_ABSOLUTE), //
|
||||||
|
Map.entry(CHANNEL_DIMMER_SWITCH, CHANNEL_2_BUTTON_LAST_EVENT), //
|
||||||
|
Map.entry(CHANNEL_LIGHT_LEVEL, CHANNEL_2_LIGHT_LEVEL), //
|
||||||
|
Map.entry(CHANNEL_PRESENCE, CHANNEL_2_MOTION), //
|
||||||
|
Map.entry(CHANNEL_TEMPERATURE, CHANNEL_2_TEMPERATURE), //
|
||||||
|
Map.entry(CHANNEL_BATTERY_LEVEL, CHANNEL_2_BATTERY_LEVEL), //
|
||||||
|
Map.entry(CHANNEL_BATTERY_LOW, CHANNEL_2_BATTERY_LOW), //
|
||||||
|
Map.entry(CHANNEL_LAST_UPDATED, CHANNEL_2_LAST_UPDATED));
|
||||||
|
|
||||||
|
public static final String ALL_LIGHTS_KEY = "discovery.group.all-lights.label";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.action;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2ThingHandler;
|
||||||
|
import org.openhab.core.automation.annotation.ActionInput;
|
||||||
|
import org.openhab.core.automation.annotation.RuleAction;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.MetricPrefix;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.thing.binding.ThingActions;
|
||||||
|
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the {@link ThingActions} interface used for sending 'dynamics' commands to Hue API v2 devices,
|
||||||
|
* rooms or zones.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@ThingActionsScope(name = "hue")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DynamicsActions implements ThingActions {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DynamicsActions.class);
|
||||||
|
|
||||||
|
private @Nullable Clip2ThingHandler handler;
|
||||||
|
|
||||||
|
public static void dynamicCommand(ThingActions actions, @Nullable String channelId, @Nullable Command command,
|
||||||
|
@Nullable Long durationMs) {
|
||||||
|
((DynamicsActions) actions).dynamicCommand(channelId, command, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RuleAction(label = "@text/dynamics.action.label", description = "@text/dynamics.action.description")
|
||||||
|
public void dynamicCommand(
|
||||||
|
@ActionInput(name = "channelId", label = "@text/dynamics.channel.label", description = "@text/dynamics.channel.description") @Nullable String channelId,
|
||||||
|
@ActionInput(name = "command", label = "@text/dynamics.command.label", description = "@text/dynamics.command.description") @Nullable Command command,
|
||||||
|
@ActionInput(name = "durationMs", label = "@text/dynamics.duration.label", description = "@text/dynamics.duration.description") @Nullable Long durationMs) {
|
||||||
|
//
|
||||||
|
Clip2ThingHandler handler = this.handler;
|
||||||
|
if (handler == null) {
|
||||||
|
logger.warn("ThingHandler is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (channelId == null) {
|
||||||
|
logger.debug("Channel ID is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (command == null) {
|
||||||
|
logger.debug("Command is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (durationMs == null || durationMs.longValue() <= 0) {
|
||||||
|
logger.debug("Duration is null, zero or negative.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.handleDynamicsCommand(channelId, command,
|
||||||
|
new QuantityType<>(durationMs.longValue(), MetricPrefix.MILLI(Units.SECOND)));
|
||||||
|
logger.debug("Dynamic command '{}' sent to channelId '{}' with duration {}ms.", command, channelId, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||||
|
this.handler = (Clip2ThingHandler) handler;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.config;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for the Clip2BridgeHandler.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Clip2BridgeConfig {
|
||||||
|
public static final String APPLICATION_KEY = "applicationKey";
|
||||||
|
|
||||||
|
public String ipAddress = "";
|
||||||
|
public String applicationKey = "";
|
||||||
|
public int checkMinutes = 60;
|
||||||
|
public boolean useSelfSignedCertificate = true;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.config;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for CLIP V2 things.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Clip2ThingConfig {
|
||||||
|
public String resourceId = "";
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -12,13 +12,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.hue.internal.console;
|
package org.openhab.binding.hue.internal.console;
|
||||||
|
|
||||||
|
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.hue.internal.HueBindingConstants;
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
|
||||||
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
||||||
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
|
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
|
||||||
import org.openhab.core.io.console.Console;
|
import org.openhab.core.io.console.Console;
|
||||||
@ -38,19 +52,36 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
* The {@link HueCommandExtension} is responsible for handling console commands
|
* The {@link HueCommandExtension} is responsible for handling console commands
|
||||||
*
|
*
|
||||||
* @author Laurent Garnier - Initial contribution
|
* @author Laurent Garnier - Initial contribution
|
||||||
|
* @author Andrew Fiddian-Green - Added CLIP 2 console commands
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = ConsoleCommandExtension.class)
|
@Component(service = ConsoleCommandExtension.class)
|
||||||
public class HueCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
|
public class HueCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
|
||||||
|
|
||||||
|
private static final String FMT_BRIDGE = "Bridge %s \"Philips Hue Bridge\" [ipAddress=\"%s\", applicationKey=\"%s\"] {";
|
||||||
|
private static final String FMT_THING = " Thing %s %s \"%s\" [resourceId=\"%s\"] // %s idV1:%s";
|
||||||
|
private static final String FMT_COMMENT = " // %s things";
|
||||||
|
private static final String FMT_APPKEY = " - Application key: %s";
|
||||||
|
private static final String FMT_SCENE = " %s '%s'";
|
||||||
|
|
||||||
private static final String USER_NAME = "username";
|
private static final String USER_NAME = "username";
|
||||||
private static final String SCENES = "scenes";
|
private static final String SCENES = "scenes";
|
||||||
|
private static final String APPLICATION_KEY = "applicationkey";
|
||||||
|
private static final String THINGS = "things";
|
||||||
|
|
||||||
private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(USER_NAME, SCENES), false);
|
private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(USER_NAME, SCENES), false);
|
||||||
|
|
||||||
|
private static final StringsCompleter SUBCMD_COMPLETER_2 = new StringsCompleter(
|
||||||
|
List.of(APPLICATION_KEY, THINGS, SCENES), false);
|
||||||
|
|
||||||
private static final StringsCompleter SCENES_COMPLETER = new StringsCompleter(List.of(SCENES), false);
|
private static final StringsCompleter SCENES_COMPLETER = new StringsCompleter(List.of(SCENES), false);
|
||||||
|
|
||||||
private final ThingRegistry thingRegistry;
|
private final ThingRegistry thingRegistry;
|
||||||
|
|
||||||
|
public static final Set<ResourceType> SUPPORTED_RESOURCES = Set.of(ResourceType.DEVICE, ResourceType.ROOM,
|
||||||
|
ResourceType.ZONE, ResourceType.BRIDGE_HOME);
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
public HueCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
||||||
super("hue", "Interact with the Hue binding.");
|
super("hue", "Interact with the Hue binding.");
|
||||||
@ -64,9 +95,12 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension impleme
|
|||||||
ThingHandler thingHandler = null;
|
ThingHandler thingHandler = null;
|
||||||
HueBridgeHandler bridgeHandler = null;
|
HueBridgeHandler bridgeHandler = null;
|
||||||
HueGroupHandler groupHandler = null;
|
HueGroupHandler groupHandler = null;
|
||||||
|
Clip2BridgeHandler clip2BridgeHandler = null;
|
||||||
if (thing != null) {
|
if (thing != null) {
|
||||||
thingHandler = thing.getHandler();
|
thingHandler = thing.getHandler();
|
||||||
if (thingHandler instanceof HueBridgeHandler) {
|
if (thingHandler instanceof Clip2BridgeHandler) {
|
||||||
|
clip2BridgeHandler = (Clip2BridgeHandler) thingHandler;
|
||||||
|
} else if (thingHandler instanceof HueBridgeHandler) {
|
||||||
bridgeHandler = (HueBridgeHandler) thingHandler;
|
bridgeHandler = (HueBridgeHandler) thingHandler;
|
||||||
} else if (thingHandler instanceof HueGroupHandler) {
|
} else if (thingHandler instanceof HueGroupHandler) {
|
||||||
groupHandler = (HueGroupHandler) thingHandler;
|
groupHandler = (HueGroupHandler) thingHandler;
|
||||||
@ -74,45 +108,130 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension impleme
|
|||||||
}
|
}
|
||||||
if (thing == null) {
|
if (thing == null) {
|
||||||
console.println("Bad thing id '" + args[0] + "'");
|
console.println("Bad thing id '" + args[0] + "'");
|
||||||
printUsage(console);
|
|
||||||
} else if (thingHandler == null) {
|
} else if (thingHandler == null) {
|
||||||
console.println("No handler initialized for the thingUID '" + args[0] + "'");
|
console.println("No handler initialized for the thingUID '" + args[0] + "'");
|
||||||
printUsage(console);
|
} else if (bridgeHandler == null && groupHandler == null && clip2BridgeHandler == null) {
|
||||||
} else if (bridgeHandler == null && groupHandler == null) {
|
|
||||||
console.println("'" + args[0] + "' is neither a Hue BridgeUID nor a Hue groupThingUID");
|
console.println("'" + args[0] + "' is neither a Hue BridgeUID nor a Hue groupThingUID");
|
||||||
printUsage(console);
|
|
||||||
} else {
|
} else {
|
||||||
switch (args[1]) {
|
if (bridgeHandler != null) {
|
||||||
case USER_NAME:
|
switch (args[1]) {
|
||||||
if (bridgeHandler != null) {
|
case USER_NAME:
|
||||||
String userName = bridgeHandler.getUserName();
|
String userName = bridgeHandler.getUserName();
|
||||||
console.println("Your user name is " + (userName != null ? userName : "undefined"));
|
console.println("Your user name is " + (userName != null ? userName : "undefined"));
|
||||||
} else {
|
return;
|
||||||
console.println("'" + args[0] + "' is not a Hue BridgeUID");
|
case SCENES:
|
||||||
printUsage(console);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SCENES:
|
|
||||||
if (bridgeHandler != null) {
|
|
||||||
bridgeHandler.listScenesForConsole().forEach(console::println);
|
bridgeHandler.listScenesForConsole().forEach(console::println);
|
||||||
} else if (groupHandler != null) {
|
return;
|
||||||
|
}
|
||||||
|
} else if (groupHandler != null) {
|
||||||
|
switch (args[1]) {
|
||||||
|
case SCENES:
|
||||||
groupHandler.listScenesForConsole().forEach(console::println);
|
groupHandler.listScenesForConsole().forEach(console::println);
|
||||||
}
|
return;
|
||||||
break;
|
}
|
||||||
default:
|
} else if (clip2BridgeHandler != null) {
|
||||||
printUsage(console);
|
String applicationKey = clip2BridgeHandler.getApplicationKey();
|
||||||
break;
|
String ipAddress = clip2BridgeHandler.getIpAddress();
|
||||||
|
String exception = "";
|
||||||
|
|
||||||
|
switch (args[1]) {
|
||||||
|
case APPLICATION_KEY:
|
||||||
|
console.println(String.format(FMT_APPKEY, applicationKey));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case SCENES:
|
||||||
|
console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
|
||||||
|
try {
|
||||||
|
List<Resource> scenes = clip2BridgeHandler
|
||||||
|
.getResources(new ResourceReference().setType(ResourceType.SCENE))
|
||||||
|
.getResources();
|
||||||
|
if (scenes.isEmpty()) {
|
||||||
|
console.println("no scenes found");
|
||||||
|
} else {
|
||||||
|
scenes.forEach(scene -> console
|
||||||
|
.println(String.format(FMT_SCENE, scene.getId(), scene.getName())));
|
||||||
|
}
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
exception = String.format("%s: '%s'", e.getClass().getName(), e.getMessage());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
console.println("}");
|
||||||
|
if (!exception.isBlank()) {
|
||||||
|
console.println(exception);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
case THINGS:
|
||||||
|
console.println(String.format(FMT_BRIDGE, thing.getUID(), ipAddress, applicationKey));
|
||||||
|
|
||||||
|
for (ResourceType resourceType : SUPPORTED_RESOURCES) {
|
||||||
|
List<Resource> resources;
|
||||||
|
try {
|
||||||
|
resources = clip2BridgeHandler
|
||||||
|
.getResources(new ResourceReference().setType(resourceType)).getResources();
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
exception = String.format("%s: '%s'", e.getClass().getName(), e.getMessage());
|
||||||
|
break;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!resources.isEmpty()) {
|
||||||
|
console.println(String.format(FMT_COMMENT, resourceType.toString()));
|
||||||
|
Map<String, String> lines = new TreeMap<>();
|
||||||
|
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
MetaData metaData = resource.getMetaData();
|
||||||
|
if (Objects.nonNull(metaData)
|
||||||
|
&& (metaData.getArchetype() == Archetype.BRIDGE_V2)) {
|
||||||
|
// do not list the bridge itself
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String resourceId = resource.getId();
|
||||||
|
String idv1 = resource.getIdV1();
|
||||||
|
String thingLabel = resource.getName();
|
||||||
|
String comment = resource.getProductName();
|
||||||
|
String thingType = resourceType.name().toLowerCase();
|
||||||
|
String thingId = resourceId;
|
||||||
|
|
||||||
|
// special zone 'all lights'
|
||||||
|
if (resource.getType() == ResourceType.BRIDGE_HOME) {
|
||||||
|
thingLabel = clip2BridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
|
||||||
|
comment = "Zone";
|
||||||
|
thingType = comment.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Thing> legacyThingOptional = clip2BridgeHandler.getLegacyThing(idv1);
|
||||||
|
if (legacyThingOptional.isPresent()) {
|
||||||
|
Thing legacyThing = legacyThingOptional.get();
|
||||||
|
thingId = legacyThing.getUID().getId();
|
||||||
|
String legacyLabel = legacyThing.getLabel();
|
||||||
|
thingLabel = Objects.nonNull(legacyLabel) ? legacyLabel : thingLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.put(thingLabel, String.format(FMT_THING, thingType, thingId, thingLabel,
|
||||||
|
resourceId, comment, idv1));
|
||||||
|
}
|
||||||
|
lines.entrySet().forEach(entry -> console.println(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.println("}");
|
||||||
|
if (!exception.isBlank()) {
|
||||||
|
console.println(exception);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
printUsage(console);
|
|
||||||
}
|
}
|
||||||
|
printUsage(console);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getUsages() {
|
public List<String> getUsages() {
|
||||||
return Arrays.asList(new String[] { buildCommandUsage("<bridgeUID> " + USER_NAME, "show the user name"),
|
return Arrays.asList(new String[] { buildCommandUsage("<bridgeUID> " + USER_NAME, "show the user name"),
|
||||||
|
buildCommandUsage("<bridgeUID> " + APPLICATION_KEY, "show the API v2 application key"),
|
||||||
buildCommandUsage("<bridgeUID> " + SCENES, "list all the scenes with their id"),
|
buildCommandUsage("<bridgeUID> " + SCENES, "list all the scenes with their id"),
|
||||||
|
buildCommandUsage("<bridgeUID> " + THINGS, "list all the API v2 device/room/zone things with their id"),
|
||||||
buildCommandUsage("<groupThingUID> " + SCENES, "list all the scenes from this group with their id") });
|
buildCommandUsage("<groupThingUID> " + SCENES, "list all the scenes from this group with their id") });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,15 +244,18 @@ public class HueCommandExtension extends AbstractConsoleCommandExtension impleme
|
|||||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||||
if (cursorArgumentIndex <= 0) {
|
if (cursorArgumentIndex <= 0) {
|
||||||
return new StringsCompleter(thingRegistry.getAll().stream()
|
return new StringsCompleter(thingRegistry.getAll().stream()
|
||||||
.filter(t -> HueBindingConstants.THING_TYPE_BRIDGE.equals(t.getThingTypeUID())
|
.filter(t -> THING_TYPE_BRIDGE.equals(t.getThingTypeUID())
|
||||||
|| HueBindingConstants.THING_TYPE_GROUP.equals(t.getThingTypeUID()))
|
|| THING_TYPE_BRIDGE_API2.equals(t.getThingTypeUID())
|
||||||
|
|| THING_TYPE_GROUP.equals(t.getThingTypeUID()))
|
||||||
.map(t -> t.getUID().getAsString()).collect(Collectors.toList()), true)
|
.map(t -> t.getUID().getAsString()).collect(Collectors.toList()), true)
|
||||||
.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||||
} else if (cursorArgumentIndex == 1) {
|
} else if (cursorArgumentIndex == 1) {
|
||||||
Thing thing = getThing(args[0]);
|
Thing thing = getThing(args[0]);
|
||||||
if (thing != null && HueBindingConstants.THING_TYPE_BRIDGE.equals(thing.getThingTypeUID())) {
|
if (thing != null && (THING_TYPE_BRIDGE.equals(thing.getThingTypeUID()))) {
|
||||||
return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||||
} else if (thing != null && HueBindingConstants.THING_TYPE_GROUP.equals(thing.getThingTypeUID())) {
|
} else if (thing != null && (THING_TYPE_BRIDGE_API2.equals(thing.getThingTypeUID()))) {
|
||||||
|
return SUBCMD_COMPLETER_2.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||||
|
} else if (thing != null && THING_TYPE_GROUP.equals(thing.getThingTypeUID())) {
|
||||||
return SCENES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
return SCENES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.discovery;
|
||||||
|
|
||||||
|
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
|
||||||
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discovery service to find resource things on a Hue Bridge that is running CLIP 2.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial Contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Clip2ThingDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Clip2ThingDiscoveryService.class);
|
||||||
|
|
||||||
|
private static final int DISCOVERY_TIMEOUT_SECONDS = 20;
|
||||||
|
private static final int DISCOVERY_INTERVAL_SECONDS = 600;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of resource types and respective thing types that shall be discovered.
|
||||||
|
*/
|
||||||
|
private static final Map<ResourceType, ThingTypeUID> DISCOVERY_TYPES = Map.of( //
|
||||||
|
ResourceType.DEVICE, THING_TYPE_DEVICE, //
|
||||||
|
ResourceType.ROOM, THING_TYPE_ROOM, //
|
||||||
|
ResourceType.ZONE, THING_TYPE_ZONE, //
|
||||||
|
ResourceType.BRIDGE_HOME, THING_TYPE_ZONE);
|
||||||
|
|
||||||
|
private @Nullable Clip2BridgeHandler bridgeHandler;
|
||||||
|
private @Nullable ScheduledFuture<?> discoveryTask;
|
||||||
|
|
||||||
|
public Clip2ThingDiscoveryService() {
|
||||||
|
super(Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM, THING_TYPE_ZONE), DISCOVERY_TIMEOUT_SECONDS, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activate() {
|
||||||
|
Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
|
||||||
|
if (Objects.nonNull(bridgeHandler)) {
|
||||||
|
bridgeHandler.registerDiscoveryService(this);
|
||||||
|
}
|
||||||
|
super.activate(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
super.deactivate();
|
||||||
|
Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
|
||||||
|
if (Objects.nonNull(bridgeHandler)) {
|
||||||
|
bridgeHandler.unregisterDiscoveryService();
|
||||||
|
removeOlderResults(new Date().getTime(), bridgeHandler.getThing().getBridgeUID());
|
||||||
|
this.bridgeHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the bridge is online, then query it to get all resource types within it, which are allowed to be instantiated
|
||||||
|
* as OH things, and announce those respective things by calling the core 'thingDiscovered()' method.
|
||||||
|
*/
|
||||||
|
private synchronized void discoverThings() {
|
||||||
|
Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
|
||||||
|
if (Objects.nonNull(bridgeHandler) && bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||||
|
try {
|
||||||
|
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||||
|
for (Entry<ResourceType, ThingTypeUID> entry : DISCOVERY_TYPES.entrySet()) {
|
||||||
|
for (Resource resource : bridgeHandler.getResources(new ResourceReference().setType(entry.getKey()))
|
||||||
|
.getResources()) {
|
||||||
|
|
||||||
|
MetaData metaData = resource.getMetaData();
|
||||||
|
if (Objects.nonNull(metaData) && (metaData.getArchetype() == Archetype.BRIDGE_V2)) {
|
||||||
|
// the bridge device is handled by a bridge thing handler
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resourceId = resource.getId();
|
||||||
|
String idv1 = resource.getIdV1();
|
||||||
|
String resourceType = resource.getType().toString();
|
||||||
|
String resourceName = resource.getName();
|
||||||
|
String thingId = resourceId;
|
||||||
|
String thingLabel = resourceName;
|
||||||
|
String legacyThingUID = null;
|
||||||
|
|
||||||
|
// special zone 'all lights'
|
||||||
|
if (resource.getType() == ResourceType.BRIDGE_HOME) {
|
||||||
|
thingLabel = bridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Thing> legacyThingOptional = bridgeHandler.getLegacyThing(idv1);
|
||||||
|
if (legacyThingOptional.isPresent()) {
|
||||||
|
Thing legacyThing = legacyThingOptional.get();
|
||||||
|
legacyThingUID = legacyThing.getUID().getAsString();
|
||||||
|
thingId = legacyThing.getUID().getId();
|
||||||
|
String legacyLabel = legacyThing.getLabel();
|
||||||
|
thingLabel = Objects.nonNull(legacyLabel) ? legacyLabel : thingLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscoveryResultBuilder builder = DiscoveryResultBuilder
|
||||||
|
.create(new ThingUID(entry.getValue(), bridgeUID, thingId)) //
|
||||||
|
.withBridge(bridgeUID) //
|
||||||
|
.withLabel(thingLabel) //
|
||||||
|
.withProperty(PROPERTY_RESOURCE_ID, resourceId)
|
||||||
|
.withProperty(PROPERTY_RESOURCE_TYPE, resourceType)
|
||||||
|
.withProperty(PROPERTY_RESOURCE_NAME, resourceName)
|
||||||
|
.withRepresentationProperty(PROPERTY_RESOURCE_ID);
|
||||||
|
|
||||||
|
if (Objects.nonNull(legacyThingUID)) {
|
||||||
|
builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
|
||||||
|
}
|
||||||
|
thingDiscovered(builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
logger.debug("discoverThings() bridge is offline or in a bad state");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopScan();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return bridgeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(ThingHandler handler) {
|
||||||
|
if (handler instanceof Clip2BridgeHandler) {
|
||||||
|
bridgeHandler = (Clip2BridgeHandler) handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startBackgroundDiscovery() {
|
||||||
|
ScheduledFuture<?> discoveryTask = this.discoveryTask;
|
||||||
|
if (Objects.isNull(discoveryTask) || discoveryTask.isCancelled()) {
|
||||||
|
this.discoveryTask = scheduler.scheduleWithFixedDelay(this::discoverThings, 0, DISCOVERY_INTERVAL_SECONDS,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startScan() {
|
||||||
|
scheduler.execute(this::discoverThings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void stopBackgroundDiscovery() {
|
||||||
|
ScheduledFuture<?> discoveryTask = this.discoveryTask;
|
||||||
|
if (Objects.nonNull(discoveryTask)) {
|
||||||
|
discoveryTask.cancel(true);
|
||||||
|
this.discoveryTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,37 +14,41 @@ package org.openhab.binding.hue.internal.discovery;
|
|||||||
|
|
||||||
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Dictionary;
|
import java.util.Dictionary;
|
||||||
import java.util.Map;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.jmdns.ServiceInfo;
|
import javax.jmdns.ServiceInfo;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
import org.openhab.binding.hue.internal.connection.Clip2Bridge;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||||
import org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService;
|
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
import org.osgi.service.component.ComponentContext;
|
import org.osgi.service.component.ComponentContext;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Modified;
|
import org.osgi.service.component.annotations.Modified;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HueBridgeMDNSDiscoveryParticipant} is responsible for discovering new and removed Hue Bridges. It uses the
|
* The {@link HueBridgeMDNSDiscoveryParticipant} is responsible for discovering new and removed Hue Bridges. It uses the
|
||||||
* central {@link MDNSDiscoveryService}.
|
* central MDNSDiscoveryService.
|
||||||
*
|
*
|
||||||
* @author Kai Kreuzer - Initial contribution
|
* @author Kai Kreuzer - Initial contribution
|
||||||
* @author Thomas Höfer - Added representation
|
* @author Thomas Höfer - Added representation
|
||||||
* @author Christoph Weitkamp - Change discovery protocol to mDNS
|
* @author Christoph Weitkamp - Change discovery protocol to mDNS
|
||||||
|
* @author Andrew Fiddian-Green - Added support for CLIP 2 bridge discovery
|
||||||
*/
|
*/
|
||||||
@Component(configurationPid = "discovery.hue")
|
@Component(configurationPid = "discovery.hue")
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@ -55,11 +59,16 @@ public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipa
|
|||||||
private static final String MDNS_PROPERTY_MODEL_ID = "modelid";
|
private static final String MDNS_PROPERTY_MODEL_ID = "modelid";
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(HueBridgeMDNSDiscoveryParticipant.class);
|
private final Logger logger = LoggerFactory.getLogger(HueBridgeMDNSDiscoveryParticipant.class);
|
||||||
|
protected final ThingRegistry thingRegistry;
|
||||||
|
|
||||||
private long removalGracePeriod = 0L;
|
private long removalGracePeriod = 0L;
|
||||||
|
|
||||||
private boolean isAutoDiscoveryEnabled = true;
|
private boolean isAutoDiscoveryEnabled = true;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public HueBridgeMDNSDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) {
|
||||||
|
this.thingRegistry = thingRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
protected void activate(ComponentContext componentContext) {
|
protected void activate(ComponentContext componentContext) {
|
||||||
activateOrModifyService(componentContext);
|
activateOrModifyService(componentContext);
|
||||||
@ -90,7 +99,7 @@ public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||||
return HueBridgeHandler.SUPPORTED_THING_TYPES;
|
return Set.of(THING_TYPE_BRIDGE, THING_TYPE_BRIDGE_API2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -102,29 +111,62 @@ public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipa
|
|||||||
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||||
if (isAutoDiscoveryEnabled) {
|
if (isAutoDiscoveryEnabled) {
|
||||||
ThingUID uid = getThingUID(service);
|
ThingUID uid = getThingUID(service);
|
||||||
if (uid != null) {
|
if (Objects.nonNull(uid)) {
|
||||||
String host = service.getHostAddresses()[0];
|
String host = service.getHostAddresses()[0];
|
||||||
String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
|
String serial = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
|
||||||
String friendlyName = String.format(DISCOVERY_LABEL_PATTERN, host);
|
String label = String.format(DISCOVERY_LABEL_PATTERN, host);
|
||||||
return DiscoveryResultBuilder.create(uid) //
|
String legacyThingUID = null;
|
||||||
.withProperties(Map.of( //
|
|
||||||
HOST, host, //
|
if (new ThingUID(THING_TYPE_BRIDGE_API2, uid.getId()).equals(uid)) {
|
||||||
Thing.PROPERTY_MODEL_ID, service.getPropertyString(MDNS_PROPERTY_MODEL_ID), //
|
Optional<Thing> legacyThingOptional = getLegacyBridge(host);
|
||||||
Thing.PROPERTY_SERIAL_NUMBER, id.toLowerCase())) //
|
if (legacyThingOptional.isPresent()) {
|
||||||
.withLabel(friendlyName) //
|
Thing legacyThing = legacyThingOptional.get();
|
||||||
|
legacyThingUID = legacyThing.getUID().getAsString();
|
||||||
|
String label2 = legacyThing.getLabel();
|
||||||
|
label = Objects.nonNull(label2) ? label2 : label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid) //
|
||||||
|
.withLabel(label) //
|
||||||
|
.withProperty(HOST, host) //
|
||||||
|
.withProperty(Thing.PROPERTY_MODEL_ID, service.getPropertyString(MDNS_PROPERTY_MODEL_ID)) //
|
||||||
|
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, serial.toLowerCase()) //
|
||||||
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
|
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
|
||||||
.withTTL(120L) //
|
.withTTL(120L);
|
||||||
.build();
|
|
||||||
|
if (Objects.nonNull(legacyThingUID)) {
|
||||||
|
builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the legacy Hue bridge (if any) on the given IP address.
|
||||||
|
*
|
||||||
|
* @param ipAddress the IP address.
|
||||||
|
* @return Optional of a legacy bridge thing.
|
||||||
|
*/
|
||||||
|
private Optional<Thing> getLegacyBridge(String ipAddress) {
|
||||||
|
return thingRegistry.getAll().stream().filter(thing -> THING_TYPE_BRIDGE.equals(thing.getThingTypeUID())
|
||||||
|
&& ipAddress.equals(thing.getConfiguration().get(HOST))).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||||
String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
|
String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
|
||||||
if (id != null && !id.isBlank()) {
|
if (id != null && !id.isBlank()) {
|
||||||
return new ThingUID(THING_TYPE_BRIDGE, id.toLowerCase());
|
id = id.toLowerCase();
|
||||||
|
try {
|
||||||
|
return Clip2Bridge.isClip2Supported(service.getHostAddresses()[0])
|
||||||
|
? new ThingUID(THING_TYPE_BRIDGE_API2, id)
|
||||||
|
: new ThingUID(THING_TYPE_BRIDGE, id);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,24 @@ import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
import org.openhab.binding.hue.internal.connection.Clip2Bridge;
|
||||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -58,9 +62,12 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
|
|||||||
private static final int DISCOVERY_TIMEOUT = 10;
|
private static final int DISCOVERY_TIMEOUT = 10;
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
|
private final Logger logger = LoggerFactory.getLogger(HueBridgeNupnpDiscovery.class);
|
||||||
|
private final ThingRegistry thingRegistry;
|
||||||
|
|
||||||
public HueBridgeNupnpDiscovery() {
|
@Activate
|
||||||
super(HueBridgeHandler.SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT, false);
|
public HueBridgeNupnpDiscovery(final @Reference ThingRegistry thingRegistry) {
|
||||||
|
super(Set.of(THING_TYPE_BRIDGE, THING_TYPE_BRIDGE_API2), DISCOVERY_TIMEOUT, false);
|
||||||
|
this.thingRegistry = thingRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,14 +84,31 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
|
|||||||
String host = bridge.getInternalIpAddress();
|
String host = bridge.getInternalIpAddress();
|
||||||
String serialNumber = bridge.getId().toLowerCase();
|
String serialNumber = bridge.getId().toLowerCase();
|
||||||
ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
|
ThingUID uid = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
|
||||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid) //
|
ThingUID legacyUID = null;
|
||||||
.withProperties(Map.of( //
|
String label = String.format(DISCOVERY_LABEL_PATTERN, host);
|
||||||
HOST, host, //
|
|
||||||
Thing.PROPERTY_SERIAL_NUMBER, serialNumber)) //
|
if (isClip2Supported(host)) {
|
||||||
.withLabel(String.format(DISCOVERY_LABEL_PATTERN, host)) //
|
legacyUID = uid;
|
||||||
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
|
uid = new ThingUID(THING_TYPE_BRIDGE_API2, serialNumber);
|
||||||
.build();
|
Optional<Thing> legacyThingOptional = getLegacyBridge(host);
|
||||||
thingDiscovered(result);
|
if (legacyThingOptional.isPresent()) {
|
||||||
|
Thing legacyThing = legacyThingOptional.get();
|
||||||
|
String label2 = legacyThing.getLabel();
|
||||||
|
label = Objects.nonNull(label2) ? label2 : label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid) //
|
||||||
|
.withLabel(label) //
|
||||||
|
.withProperty(HOST, host) //
|
||||||
|
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber) //
|
||||||
|
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER);
|
||||||
|
|
||||||
|
if (Objects.nonNull(legacyUID)) {
|
||||||
|
builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyUID.getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
thingDiscovered(builder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,4 +194,27 @@ public class HueBridgeNupnpDiscovery extends AbstractDiscoveryService {
|
|||||||
protected @Nullable String doGetRequest(String url) throws IOException {
|
protected @Nullable String doGetRequest(String url) throws IOException {
|
||||||
return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);
|
return HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the legacy Hue bridge (if any) on the given IP address.
|
||||||
|
*
|
||||||
|
* @param ipAddress the IP address.
|
||||||
|
* @return Optional of a legacy bridge thing.
|
||||||
|
*/
|
||||||
|
private Optional<Thing> getLegacyBridge(String ipAddress) {
|
||||||
|
return thingRegistry.getAll().stream().filter(thing -> THING_TYPE_BRIDGE.equals(thing.getThingTypeUID())
|
||||||
|
&& ipAddress.equals(thing.getConfiguration().get(HOST))).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap Clip2Bridge.isClip2Supported() inside this method so that integration tests can can override the method, to
|
||||||
|
* avoid making live network calls.
|
||||||
|
*/
|
||||||
|
protected boolean isClip2Supported(String ipAddress) {
|
||||||
|
try {
|
||||||
|
return Clip2Bridge.isClip2Supported(ipAddress);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ public class HueDeviceDiscoveryService extends AbstractDiscoveryService implemen
|
|||||||
|
|
||||||
String name;
|
String name;
|
||||||
if ("0".equals(group.getId())) {
|
if ("0".equals(group.getId())) {
|
||||||
name = "@text/discovery.group.all_lights.label";
|
name = "@text/discovery.group.all-lights.label";
|
||||||
} else if ("Room".equals(group.getType())) {
|
} else if ("Room".equals(group.getType())) {
|
||||||
name = group.getName();
|
name = group.getName();
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO that contains an API Action entry.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ActionEntry {
|
||||||
|
private @NonNullByDefault({}) ResourceReference target;
|
||||||
|
private @NonNullByDefault({}) Resource action;
|
||||||
|
|
||||||
|
public ResourceReference getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for 'alert' of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Alerts {
|
||||||
|
private @Nullable @SerializedName("action_values") List<String> actionValues;
|
||||||
|
private @Nullable String action;
|
||||||
|
|
||||||
|
public @Nullable ActionType getAction() {
|
||||||
|
String action = this.action;
|
||||||
|
return Objects.nonNull(action) ? ActionType.of(action) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActionType> getActionValues() {
|
||||||
|
List<String> actionValues = this.actionValues;
|
||||||
|
if (Objects.nonNull(actionValues)) {
|
||||||
|
return actionValues.stream().map(ActionType::of).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Alerts setAction(ActionType actionType) {
|
||||||
|
this.action = actionType.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 'special' DTO for bridge discovery to read the software version from a bridge.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BridgeConfig {
|
||||||
|
public @Nullable String swversion;
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 button state.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Button {
|
||||||
|
private @NonNullByDefault({}) @SerializedName("last_event") String lastEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the last button event as an enum.
|
||||||
|
* @throws IllegalArgumentException if lastEvent is bad.
|
||||||
|
*/
|
||||||
|
public ButtonEventType getLastEvent() throws IllegalArgumentException {
|
||||||
|
return ButtonEventType.valueOf(lastEvent.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getLastEventState() {
|
||||||
|
return new StringType(getLastEvent().name());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for colour temperature of a light in CLIP 2.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ColorTemperature {
|
||||||
|
private @Nullable Long mirek;
|
||||||
|
private @Nullable @SerializedName("mirek_schema") MirekSchema mirekSchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color temperature as a QuantityType value.
|
||||||
|
*
|
||||||
|
* @return a QuantityType value
|
||||||
|
* @throws DTOPresentButEmptyException to indicate that the DTO is present but empty.
|
||||||
|
*/
|
||||||
|
public @Nullable QuantityType<?> getAbsolute() throws DTOPresentButEmptyException {
|
||||||
|
Long mirek = this.mirek;
|
||||||
|
if (Objects.nonNull(mirek)) {
|
||||||
|
return QuantityType.valueOf(mirek, Units.MIRED).toInvertibleUnit(Units.KELVIN);
|
||||||
|
}
|
||||||
|
throw new DTOPresentButEmptyException("'color_temperature' DTO is present but empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Long getMirek() {
|
||||||
|
return mirek;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable MirekSchema getMirekSchema() {
|
||||||
|
return mirekSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color temperature as a percentage based on the MirekSchema. Note: this method is only to be used on
|
||||||
|
* cached state DTOs which already have a defined mirek schema.
|
||||||
|
*
|
||||||
|
* @return the percentage of the mirekSchema range.
|
||||||
|
* @throws DTOPresentButEmptyException to indicate that the DTO is present but empty.
|
||||||
|
*/
|
||||||
|
public @Nullable Double getPercent() throws DTOPresentButEmptyException {
|
||||||
|
Long mirek = this.mirek;
|
||||||
|
if (Objects.nonNull(mirek)) {
|
||||||
|
MirekSchema mirekSchema = this.mirekSchema;
|
||||||
|
mirekSchema = Objects.nonNull(mirekSchema) ? mirekSchema : MirekSchema.DEFAULT_SCHEMA;
|
||||||
|
double min = mirekSchema.getMirekMinimum();
|
||||||
|
double max = mirekSchema.getMirekMaximum();
|
||||||
|
double percent = 100f * (mirek.doubleValue() - min) / (max - min);
|
||||||
|
return Math.max(0, Math.min(100, percent));
|
||||||
|
}
|
||||||
|
throw new DTOPresentButEmptyException("'mirek_schema' DTO is present but empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorTemperature setMirek(double mirek) {
|
||||||
|
this.mirek = Math.round(mirek);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorTemperature setMirekSchema(@Nullable MirekSchema mirekSchema) {
|
||||||
|
this.mirekSchema = mirekSchema;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
import org.openhab.core.util.ColorUtil.Gamut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for colour X/Y of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ColorXy {
|
||||||
|
private @Nullable PairXy xy;
|
||||||
|
private @Nullable Gamut2 gamut;
|
||||||
|
|
||||||
|
public @Nullable Gamut getGamut() {
|
||||||
|
Gamut2 gamut = this.gamut;
|
||||||
|
return Objects.nonNull(gamut) ? gamut.getGamut() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Gamut2 getGamut2() {
|
||||||
|
return this.gamut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws DTOPresentButEmptyException to indicate that the DTO is present but empty.
|
||||||
|
*/
|
||||||
|
public double[] getXY() throws DTOPresentButEmptyException {
|
||||||
|
PairXy pairXy = this.xy;
|
||||||
|
if (Objects.nonNull(pairXy)) {
|
||||||
|
return pairXy.getXY();
|
||||||
|
}
|
||||||
|
throw new DTOPresentButEmptyException("'color' DTO is present but empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorXy setGamut(@Nullable Gamut gamut) {
|
||||||
|
this.gamut = Objects.nonNull(gamut) ? new Gamut2().setGamut(gamut) : null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorXy setXY(double[] xyValues) {
|
||||||
|
PairXy pairXy = this.xy;
|
||||||
|
pairXy = Objects.nonNull(pairXy) ? pairXy : new PairXy();
|
||||||
|
pairXy.setXY(xyValues);
|
||||||
|
this.xy = pairXy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for dimming brightness of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Dimming {
|
||||||
|
private @Nullable Double brightness;
|
||||||
|
private @Nullable @SerializedName("min_dim_level") Double minimumDimmingLevel;
|
||||||
|
|
||||||
|
public static final double DEFAULT_MINIMUM_DIMMIMG_LEVEL = 0.5f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws DTOPresentButEmptyException to indicate that the DTO is present but empty.
|
||||||
|
*/
|
||||||
|
public double getBrightness() throws DTOPresentButEmptyException {
|
||||||
|
Double brightness = this.brightness;
|
||||||
|
if (Objects.nonNull(brightness)) {
|
||||||
|
return brightness;
|
||||||
|
}
|
||||||
|
throw new DTOPresentButEmptyException("'dimming' DTO is present but empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Double getMinimumDimmingLevel() {
|
||||||
|
return minimumDimmingLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dimming setBrightness(double brightness) {
|
||||||
|
this.brightness = brightness;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dimming setMinimumDimmingLevel(Double minimumDimmingLevel) {
|
||||||
|
this.minimumDimmingLevel = minimumDimmingLevel;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String toPropertyValue() {
|
||||||
|
Double minimumDimmingLevel = this.minimumDimmingLevel;
|
||||||
|
if (Objects.nonNull(minimumDimmingLevel)) {
|
||||||
|
return String.format("%.1f %% .. 100 %%", minimumDimmingLevel.doubleValue());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for dynamics of transitions between light states.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Dynamics {
|
||||||
|
private @Nullable @SuppressWarnings("unused") Long duration;
|
||||||
|
private @Nullable @SuppressWarnings("unused") Double speed;
|
||||||
|
|
||||||
|
public Dynamics setDuration(Duration duration) {
|
||||||
|
this.duration = duration.toMillis();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for 'effects' of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Effects {
|
||||||
|
private @Nullable @SerializedName("effect_values") List<String> effectValues;
|
||||||
|
private @Nullable String effect;
|
||||||
|
private @Nullable @SerializedName("status_values") List<String> statusValues;
|
||||||
|
private @Nullable String status;
|
||||||
|
|
||||||
|
public boolean allows(EffectType effect) {
|
||||||
|
List<String> statusValues = this.statusValues;
|
||||||
|
return Objects.nonNull(statusValues) ? statusValues.contains(effect.name().toLowerCase()) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EffectType getEffect() {
|
||||||
|
String effect = this.effect;
|
||||||
|
return Objects.nonNull(effect) ? EffectType.of(effect) : EffectType.NO_EFFECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EffectType getStatus() {
|
||||||
|
return Objects.nonNull(status) ? EffectType.of(status) : EffectType.NO_EFFECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getStatusValues() {
|
||||||
|
List<String> statusValues = this.statusValues;
|
||||||
|
return Objects.nonNull(statusValues) ? statusValues : List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Effects setEffect(EffectType effectType) {
|
||||||
|
effect = effectType.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Effects setStatusValues(List<String> statusValues) {
|
||||||
|
this.statusValues = statusValues;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 communication errors.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Error {
|
||||||
|
private @NonNullByDefault({}) String description;
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 event stream objects.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Event {
|
||||||
|
public static final Type EVENT_LIST_TYPE = new TypeToken<List<Event>>() {
|
||||||
|
}.getType();
|
||||||
|
|
||||||
|
private @Nullable List<Resource> data = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<Resource> getData() {
|
||||||
|
List<Resource> data = this.data;
|
||||||
|
return Objects.nonNull(data) ? data : List.of();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.util.ColorUtil.Gamut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for colour gamut of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Gamut2 {
|
||||||
|
private @Nullable PairXy red;
|
||||||
|
private @Nullable PairXy green;
|
||||||
|
private @Nullable PairXy blue;
|
||||||
|
|
||||||
|
public @Nullable Gamut getGamut() {
|
||||||
|
PairXy red = this.red;
|
||||||
|
PairXy green = this.green;
|
||||||
|
PairXy blue = this.blue;
|
||||||
|
if (Objects.nonNull(red) && Objects.nonNull(green) && Objects.nonNull(blue)) {
|
||||||
|
return new Gamut(red.getXY(), green.getXY(), blue.getXY());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Gamut2 setGamut(Gamut gamut) {
|
||||||
|
red = new PairXy().setXY(gamut.r());
|
||||||
|
green = new PairXy().setXY(gamut.g());
|
||||||
|
blue = new PairXy().setXY(gamut.b());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String toPropertyValue() {
|
||||||
|
PairXy red = this.red;
|
||||||
|
PairXy green = this.green;
|
||||||
|
PairXy blue = this.blue;
|
||||||
|
if (Objects.nonNull(red) && Objects.nonNull(green) && Objects.nonNull(blue)) {
|
||||||
|
double[] r = red.getXY();
|
||||||
|
double[] g = green.getXY();
|
||||||
|
double[] b = blue.getXY();
|
||||||
|
return String.format("(%.3f,%.3f) (%.3f,%.3f) (%.3f,%.3f)", r[0], r[1], g[0], g[1], b[0], b[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 light level sensor.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class LightLevel {
|
||||||
|
private @SerializedName("light_level") int lightLevel;
|
||||||
|
private @SerializedName("light_level_valid") boolean lightLevelValid;
|
||||||
|
|
||||||
|
public int getLightLevel() {
|
||||||
|
return lightLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLightLevelValid() {
|
||||||
|
return lightLevelValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw sensor light level is '10000 * log10(lux) + 1' so apply the inverse formula to convert to Lux.
|
||||||
|
*
|
||||||
|
* @return a QuantityType with light level in Lux, or UNDEF.
|
||||||
|
*/
|
||||||
|
public State getLightLevelState() {
|
||||||
|
if (lightLevelValid) {
|
||||||
|
double rawLightLevel = lightLevel;
|
||||||
|
if (rawLightLevel > 1f) {
|
||||||
|
return new QuantityType<>(Math.pow(10f, (rawLightLevel - 1f) / 10000f), Units.LUX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State isLightLevelValidState() {
|
||||||
|
return OnOffType.from(lightLevelValid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 product metadata.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MetaData {
|
||||||
|
private @Nullable String archetype;
|
||||||
|
private @Nullable String name;
|
||||||
|
private @Nullable @SerializedName("control_id") Integer controlId;
|
||||||
|
|
||||||
|
public Archetype getArchetype() {
|
||||||
|
return Archetype.of(archetype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getControlId() {
|
||||||
|
Integer controlId = this.controlId;
|
||||||
|
return controlId != null ? controlId.intValue() : 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 mirek schema.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MirekSchema {
|
||||||
|
private static final int MIN = 153;
|
||||||
|
private static final int MAX = 500;
|
||||||
|
|
||||||
|
public static final MirekSchema DEFAULT_SCHEMA = new MirekSchema();
|
||||||
|
|
||||||
|
private @SerializedName("mirek_minimum") int mirekMinimum = MIN;
|
||||||
|
private @SerializedName("mirek_maximum") int mirekMaximum = MAX;
|
||||||
|
|
||||||
|
public int getMirekMaximum() {
|
||||||
|
return mirekMaximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMirekMinimum() {
|
||||||
|
return mirekMinimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toKelvin(int mirek) {
|
||||||
|
QuantityType<?> kelvin = QuantityType.valueOf(mirek, Units.MIRED).toInvertibleUnit(Units.KELVIN);
|
||||||
|
return Objects.nonNull(kelvin) ? String.format("%.0f K", kelvin.doubleValue()) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toPropertyValue() {
|
||||||
|
return String.format("%s .. %s", toKelvin(mirekMinimum), toKelvin(mirekMaximum));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 motion sensor.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Motion {
|
||||||
|
private boolean motion;
|
||||||
|
private @SerializedName("motion_valid") boolean motionValid;
|
||||||
|
|
||||||
|
public boolean isMotion() {
|
||||||
|
return motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMotionValid() {
|
||||||
|
return motionValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getMotionState() {
|
||||||
|
return motionValid ? OnOffType.from(motion) : UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getMotionValidState() {
|
||||||
|
return OnOffType.from(motionValid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for 'on' state of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class OnState {
|
||||||
|
private @Nullable Boolean on;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws DTOPresentButEmptyException to indicate that the DTO is present but empty.
|
||||||
|
*/
|
||||||
|
public boolean isOn() throws DTOPresentButEmptyException {
|
||||||
|
Boolean on = this.on;
|
||||||
|
if (Objects.nonNull(on)) {
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
throw new DTOPresentButEmptyException("'on' DTO is present but empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOn(boolean on) {
|
||||||
|
this.on = on;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO that contains an x and y pair of doubles.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
public class PairXy {
|
||||||
|
private double x;
|
||||||
|
private double y;
|
||||||
|
|
||||||
|
public double[] getXY() {
|
||||||
|
return new double[] { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
public PairXy setXY(double[] xy) {
|
||||||
|
x = xy.length > 0 ? xy[0] : 0f;
|
||||||
|
y = xy.length > 1 ? xy[1] : 0f;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 power state.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Power {
|
||||||
|
private @NonNullByDefault({}) @SerializedName("battery_state") String batteryState;
|
||||||
|
private @SerializedName("battery_level") int batteryLevel;
|
||||||
|
|
||||||
|
public BatteryStateType getBatteryState() {
|
||||||
|
try {
|
||||||
|
return BatteryStateType.valueOf(batteryState.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return BatteryStateType.CRITICAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatteryLevel() {
|
||||||
|
return batteryLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getBatteryLowState() {
|
||||||
|
return OnOffType.from(getBatteryState() != BatteryStateType.NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getBatteryLevelState() {
|
||||||
|
return new DecimalType(getBatteryLevel());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 product data.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ProductData {
|
||||||
|
private @NonNullByDefault({}) @SerializedName("model_id") String modelId;
|
||||||
|
private @NonNullByDefault({}) @SerializedName("manufacturer_name") String manufacturerName;
|
||||||
|
private @NonNullByDefault({}) @SerializedName("product_name") String productName;
|
||||||
|
private @NonNullByDefault({}) @SerializedName("product_archetype") String productArchetype;
|
||||||
|
private @NonNullByDefault({}) Boolean certified;
|
||||||
|
private @NonNullByDefault({}) @SerializedName("software_version") String softwareVersion;
|
||||||
|
private @Nullable @SerializedName("hardware_platform_type") String hardwarePlatformType;
|
||||||
|
|
||||||
|
public String getModelId() {
|
||||||
|
return modelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getManufacturerName() {
|
||||||
|
return manufacturerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Archetype getProductArchetype() {
|
||||||
|
return Archetype.of(productArchetype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCertified() {
|
||||||
|
return certified != null ? certified : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftwareVersion() {
|
||||||
|
return softwareVersion != null ? softwareVersion : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getHardwarePlatformType() {
|
||||||
|
return hardwarePlatformType;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for scene recall.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Recall {
|
||||||
|
private @Nullable @SuppressWarnings("unused") String action;
|
||||||
|
private @Nullable @SuppressWarnings("unused") String status;
|
||||||
|
private @Nullable @SuppressWarnings("unused") Long duration;
|
||||||
|
|
||||||
|
public Recall setAction(RecallAction action) {
|
||||||
|
this.action = action.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Recall setDuration(Duration duration) {
|
||||||
|
this.duration = duration.toMillis();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 tap switch rotary dial state.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RelativeRotary {
|
||||||
|
private @Nullable @SerializedName("last_event") RotationEvent lastEvent;
|
||||||
|
|
||||||
|
public State getActionState() {
|
||||||
|
RotationEvent lastEvent = getLastEvent();
|
||||||
|
return Objects.nonNull(lastEvent) ? lastEvent.getActionState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable RotationEvent getLastEvent() {
|
||||||
|
return lastEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getStepsState() {
|
||||||
|
RotationEvent lastEvent = getLastEvent();
|
||||||
|
return Objects.nonNull(lastEvent) ? lastEvent.getStepsState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,659 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RecallAction;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.DTOPresentButEmptyException;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.HSBType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.openhab.core.util.ColorUtil;
|
||||||
|
import org.openhab.core.util.ColorUtil.Gamut;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete Resource information DTO for CLIP 2.
|
||||||
|
*
|
||||||
|
* Note: all fields are @Nullable because some cases do not (must not) use them.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Resource {
|
||||||
|
|
||||||
|
public static final double PERCENT_DELTA = 30f;
|
||||||
|
public static final MathContext PERCENT_MATH_CONTEXT = new MathContext(4, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SSE event mechanism sends resources in a sparse (skeleton) format that only includes state fields whose
|
||||||
|
* values have changed. A sparse resource does not contain the full state of the resource. And the absence of any
|
||||||
|
* field from such a resource does not indicate that the field value is UNDEF, but rather that the value is the same
|
||||||
|
* as what it was previously set to by the last non-sparse resource.
|
||||||
|
*/
|
||||||
|
private transient boolean hasSparseData;
|
||||||
|
|
||||||
|
private @Nullable String type;
|
||||||
|
private @Nullable String id;
|
||||||
|
private @Nullable @SerializedName("bridge_id") String bridgeId;
|
||||||
|
private @Nullable @SerializedName("id_v1") String idV1;
|
||||||
|
private @Nullable ResourceReference owner;
|
||||||
|
private @Nullable MetaData metadata;
|
||||||
|
private @Nullable @SerializedName("product_data") ProductData productData;
|
||||||
|
private @Nullable List<ResourceReference> services;
|
||||||
|
private @Nullable OnState on;
|
||||||
|
private @Nullable Dimming dimming;
|
||||||
|
private @Nullable @SerializedName("color_temperature") ColorTemperature colorTemperature;
|
||||||
|
private @Nullable ColorXy color;
|
||||||
|
private @Nullable Alerts alert;
|
||||||
|
private @Nullable Effects effects;
|
||||||
|
private @Nullable @SerializedName("timed_effects") TimedEffects timedEffects;
|
||||||
|
private @Nullable ResourceReference group;
|
||||||
|
private @Nullable List<ActionEntry> actions;
|
||||||
|
private @Nullable Recall recall;
|
||||||
|
private @Nullable Boolean enabled;
|
||||||
|
private @Nullable LightLevel light;
|
||||||
|
private @Nullable Button button;
|
||||||
|
private @Nullable Temperature temperature;
|
||||||
|
private @Nullable Motion motion;
|
||||||
|
private @Nullable @SerializedName("power_state") Power powerState;
|
||||||
|
private @Nullable @SerializedName("relative_rotary") RelativeRotary relativeRotary;
|
||||||
|
private @Nullable List<ResourceReference> children;
|
||||||
|
private @Nullable JsonElement status;
|
||||||
|
private @Nullable @SuppressWarnings("unused") Dynamics dynamics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param resourceType
|
||||||
|
*/
|
||||||
|
public Resource(@Nullable ResourceType resourceType) {
|
||||||
|
if (Objects.nonNull(resourceType)) {
|
||||||
|
setType(resourceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable List<ActionEntry> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Alerts getAlerts() {
|
||||||
|
return alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getAlertState() {
|
||||||
|
Alerts alerts = this.alert;
|
||||||
|
if (Objects.nonNull(alerts)) {
|
||||||
|
if (!alerts.getActionValues().isEmpty()) {
|
||||||
|
ActionType alertType = alerts.getAction();
|
||||||
|
if (Objects.nonNull(alertType)) {
|
||||||
|
return new StringType(alertType.name());
|
||||||
|
}
|
||||||
|
return new StringType(ActionType.NO_ACTION.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getArchetype() {
|
||||||
|
MetaData metaData = getMetaData();
|
||||||
|
if (Objects.nonNull(metaData)) {
|
||||||
|
return metaData.getArchetype().toString();
|
||||||
|
}
|
||||||
|
return getType().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getBatteryLevelState() {
|
||||||
|
Power powerState = this.powerState;
|
||||||
|
return Objects.nonNull(powerState) ? powerState.getBatteryLevelState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getBatteryLowState() {
|
||||||
|
Power powerState = this.powerState;
|
||||||
|
return Objects.nonNull(powerState) ? powerState.getBatteryLowState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getBridgeId() {
|
||||||
|
String bridgeId = this.bridgeId;
|
||||||
|
return Objects.isNull(bridgeId) || bridgeId.isBlank() ? null : bridgeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the brightness as a PercentType. If off the brightness is 0, otherwise use dimming value.
|
||||||
|
*
|
||||||
|
* @return a PercentType with the dimming state, or UNDEF, or NULL
|
||||||
|
*/
|
||||||
|
public State getBrightnessState() {
|
||||||
|
Dimming dimming = this.dimming;
|
||||||
|
if (Objects.nonNull(dimming)) {
|
||||||
|
try {
|
||||||
|
// if off the brightness is 0, otherwise it is dimming value
|
||||||
|
OnState on = this.on;
|
||||||
|
double brightness = Objects.nonNull(on) && !on.isOn() ? 0f
|
||||||
|
: Math.max(0f, Math.min(100f, dimming.getBrightness()));
|
||||||
|
return new PercentType(new BigDecimal(brightness, PERCENT_MATH_CONTEXT));
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Button getButton() {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the state corresponding to a button's last event value multiplied by the controlId found for it in the given
|
||||||
|
* controlIds map. States are decimal values formatted like '1002' where the first digit is the button's controlId
|
||||||
|
* and the last digit is the ordinal value of the button's last event.
|
||||||
|
*
|
||||||
|
* @param controlIds the map of control ids to be referenced.
|
||||||
|
* @return the state.
|
||||||
|
*/
|
||||||
|
public State getButtonEventState(Map<String, Integer> controlIds) {
|
||||||
|
Button button = this.button;
|
||||||
|
if (Objects.nonNull(button)) {
|
||||||
|
try {
|
||||||
|
return new DecimalType(
|
||||||
|
(controlIds.getOrDefault(getId(), 0).intValue() * 1000) + button.getLastEvent().ordinal());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getButtonLastEventState() {
|
||||||
|
Button button = this.button;
|
||||||
|
return Objects.nonNull(button) ? button.getLastEventState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceReference> getChildren() {
|
||||||
|
List<ResourceReference> children = this.children;
|
||||||
|
return Objects.nonNull(children) ? children : List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the color as an HSBType. This returns an HSB that is based on an amalgamation of the color xy, dimming, and
|
||||||
|
* on/off JSON elements. It takes its 'H' & 'S' parts from the 'ColorXy' JSON element, and its 'B' part from the
|
||||||
|
* on/off resp. dimming JSON elements. If off the B part is 0, otherwise it is the dimming element value. Note: this
|
||||||
|
* method is only to be used on cached state DTOs which already have a defined color gamut.
|
||||||
|
*
|
||||||
|
* @return an HSBType containing the current color and brightness level, or UNDEF or NULL.
|
||||||
|
*/
|
||||||
|
public State getColorState() {
|
||||||
|
ColorXy color = this.color;
|
||||||
|
if (Objects.nonNull(color)) {
|
||||||
|
try {
|
||||||
|
Gamut gamut = color.getGamut();
|
||||||
|
gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
|
||||||
|
HSBType hsb = ColorUtil.xyToHsb(color.getXY(), gamut);
|
||||||
|
OnState on = this.on;
|
||||||
|
Dimming dimming = this.dimming;
|
||||||
|
double brightness = Objects.nonNull(on) && !on.isOn() ? 0
|
||||||
|
: Objects.nonNull(dimming) ? Math.max(0, Math.min(100, dimming.getBrightness())) : 50;
|
||||||
|
return new HSBType(hsb.getHue(), hsb.getSaturation(),
|
||||||
|
new PercentType(new BigDecimal(brightness, PERCENT_MATH_CONTEXT)));
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ColorTemperature getColorTemperature() {
|
||||||
|
return colorTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getColorTemperatureAbsoluteState() {
|
||||||
|
ColorTemperature colorTemp = colorTemperature;
|
||||||
|
if (Objects.nonNull(colorTemp)) {
|
||||||
|
try {
|
||||||
|
QuantityType<?> colorTemperature = colorTemp.getAbsolute();
|
||||||
|
if (Objects.nonNull(colorTemperature)) {
|
||||||
|
return colorTemperature;
|
||||||
|
}
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the colour temperature in percent. Note: this method is only to be used on cached state DTOs which already
|
||||||
|
* have a defined mirek schema.
|
||||||
|
*
|
||||||
|
* @return a PercentType with the colour temperature percentage.
|
||||||
|
*/
|
||||||
|
public State getColorTemperaturePercentState() {
|
||||||
|
ColorTemperature colorTemperature = this.colorTemperature;
|
||||||
|
if (Objects.nonNull(colorTemperature)) {
|
||||||
|
try {
|
||||||
|
Double percent = colorTemperature.getPercent();
|
||||||
|
if (Objects.nonNull(percent)) {
|
||||||
|
return new PercentType(new BigDecimal(percent, PERCENT_MATH_CONTEXT));
|
||||||
|
}
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ColorXy getColorXy() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an HSB where the HS part is derived from the color xy JSON element (only), so the B part is 100%
|
||||||
|
*
|
||||||
|
* @return an HSBType.
|
||||||
|
*/
|
||||||
|
public State getColorXyState() {
|
||||||
|
ColorXy color = this.color;
|
||||||
|
if (Objects.nonNull(color)) {
|
||||||
|
try {
|
||||||
|
Gamut gamut = color.getGamut();
|
||||||
|
gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
|
||||||
|
HSBType hsb = ColorUtil.xyToHsb(color.getXY(), gamut);
|
||||||
|
return new HSBType(hsb.getHue(), hsb.getSaturation(), PercentType.HUNDRED);
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getControlId() {
|
||||||
|
MetaData metadata = this.metadata;
|
||||||
|
return Objects.nonNull(metadata) ? metadata.getControlId() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Dimming getDimming() {
|
||||||
|
return dimming;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a PercentType which is derived from the dimming JSON element (only).
|
||||||
|
*
|
||||||
|
* @return a PercentType.
|
||||||
|
*/
|
||||||
|
public State getDimmingState() {
|
||||||
|
Dimming dimming = this.dimming;
|
||||||
|
if (Objects.nonNull(dimming)) {
|
||||||
|
try {
|
||||||
|
double dimmingValue = Math.max(0f, Math.min(100f, dimming.getBrightness()));
|
||||||
|
return new PercentType(new BigDecimal(dimmingValue, PERCENT_MATH_CONTEXT));
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Effects getEffects() {
|
||||||
|
return effects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getEffectState() {
|
||||||
|
Effects effects = this.effects;
|
||||||
|
return Objects.nonNull(effects) ? new StringType(effects.getStatus().name()) : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getEnabledState() {
|
||||||
|
Boolean enabled = this.enabled;
|
||||||
|
return Objects.nonNull(enabled) ? OnOffType.from(enabled.booleanValue()) : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Gamut getGamut() {
|
||||||
|
ColorXy color = this.color;
|
||||||
|
return Objects.nonNull(color) ? color.getGamut() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ResourceReference getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
String id = this.id;
|
||||||
|
return Objects.nonNull(id) ? id : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdV1() {
|
||||||
|
String idV1 = this.idV1;
|
||||||
|
return Objects.nonNull(idV1) ? idV1 : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable LightLevel getLightLevel() {
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getLightLevelState() {
|
||||||
|
LightLevel light = this.light;
|
||||||
|
return Objects.nonNull(light) ? light.getLightLevelState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable MetaData getMetaData() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Double getMinimumDimmingLevel() {
|
||||||
|
Dimming dimming = this.dimming;
|
||||||
|
return Objects.nonNull(dimming) ? dimming.getMinimumDimmingLevel() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable MirekSchema getMirekSchema() {
|
||||||
|
ColorTemperature colorTemp = this.colorTemperature;
|
||||||
|
return Objects.nonNull(colorTemp) ? colorTemp.getMirekSchema() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Motion getMotion() {
|
||||||
|
return motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getMotionState() {
|
||||||
|
Motion motion = this.motion;
|
||||||
|
return Objects.nonNull(motion) ? motion.getMotionState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getMotionValidState() {
|
||||||
|
Motion motion = this.motion;
|
||||||
|
return Objects.nonNull(motion) ? motion.getMotionValidState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
MetaData metaData = getMetaData();
|
||||||
|
if (Objects.nonNull(metaData)) {
|
||||||
|
String name = metaData.getName();
|
||||||
|
if (Objects.nonNull(name)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getType().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the state of the On/Off element (only).
|
||||||
|
*/
|
||||||
|
public State getOnOffState() {
|
||||||
|
try {
|
||||||
|
OnState on = this.on;
|
||||||
|
return Objects.nonNull(on) ? OnOffType.from(on.isOn()) : UnDefType.NULL;
|
||||||
|
} catch (DTOPresentButEmptyException e) {
|
||||||
|
return UnDefType.UNDEF; // indicates the DTO is present but its inner fields are missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable OnState getOnState() {
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ResourceReference getOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Power getPowerState() {
|
||||||
|
return powerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ProductData getProductData() {
|
||||||
|
return productData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
ProductData productData = getProductData();
|
||||||
|
if (Objects.nonNull(productData)) {
|
||||||
|
return productData.getProductName();
|
||||||
|
}
|
||||||
|
return getType().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Recall getRecall() {
|
||||||
|
return recall;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable RelativeRotary getRelativeRotary() {
|
||||||
|
return relativeRotary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getRelativeRotaryActionState() {
|
||||||
|
RelativeRotary relativeRotary = this.relativeRotary;
|
||||||
|
return Objects.nonNull(relativeRotary) ? relativeRotary.getActionState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getRotaryStepsState() {
|
||||||
|
RelativeRotary relativeRotary = this.relativeRotary;
|
||||||
|
return Objects.nonNull(relativeRotary) ? relativeRotary.getStepsState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the scene resource contains a 'status.active' element. If such an element is present, returns a Boolean
|
||||||
|
* Optional whose value depends on the value of that element, or an empty Optional if it is not.
|
||||||
|
*
|
||||||
|
* @return true, false, or empty.
|
||||||
|
*/
|
||||||
|
public Optional<Boolean> getSceneActive() {
|
||||||
|
if (ResourceType.SCENE == getType()) {
|
||||||
|
JsonElement status = this.status;
|
||||||
|
if (Objects.nonNull(status) && status.isJsonObject()) {
|
||||||
|
JsonElement active = ((JsonObject) status).get("active");
|
||||||
|
if (Objects.nonNull(active) && active.isJsonPrimitive()) {
|
||||||
|
return Optional.of(!"inactive".equalsIgnoreCase(active.getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the getSceneActive() optional result is empty return 'UnDefType.NULL'. Otherwise if the optional result is
|
||||||
|
* present and 'true' (i.e. the scene is active) return the scene name. Or finally (the optional result is present
|
||||||
|
* and 'false') return 'UnDefType.UNDEF'.
|
||||||
|
*
|
||||||
|
* @return either 'UnDefType.NULL', a StringType containing the (active) scene name, or 'UnDefType.UNDEF'.
|
||||||
|
*/
|
||||||
|
public State getSceneState() {
|
||||||
|
Optional<Boolean> active = getSceneActive();
|
||||||
|
return active.isEmpty() ? UnDefType.NULL : active.get() ? new StringType(getName()) : UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ResourceReference> getServiceReferences() {
|
||||||
|
List<ResourceReference> services = this.services;
|
||||||
|
return Objects.nonNull(services) ? services : List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject getStatus() {
|
||||||
|
JsonElement status = this.status;
|
||||||
|
if (Objects.nonNull(status) && status.isJsonObject()) {
|
||||||
|
return status.getAsJsonObject();
|
||||||
|
}
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Temperature getTemperature() {
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getTemperatureState() {
|
||||||
|
Temperature temperature = this.temperature;
|
||||||
|
return Objects.nonNull(temperature) ? temperature.getTemperatureState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getTemperatureValidState() {
|
||||||
|
Temperature temperature = this.temperature;
|
||||||
|
return Objects.nonNull(temperature) ? temperature.getTemperatureValidState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Effects getTimedEffects() {
|
||||||
|
return timedEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceType getType() {
|
||||||
|
return ResourceType.of(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getZigbeeState() {
|
||||||
|
ZigbeeStatus zigbeeStatus = getZigbeeStatus();
|
||||||
|
return Objects.nonNull(zigbeeStatus) ? new StringType(zigbeeStatus.toString()) : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ZigbeeStatus getZigbeeStatus() {
|
||||||
|
JsonElement status = this.status;
|
||||||
|
if (Objects.nonNull(status) && status.isJsonPrimitive()) {
|
||||||
|
return ZigbeeStatus.of(status.getAsString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFullState() {
|
||||||
|
return !hasSparseData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark that the resource has sparse data.
|
||||||
|
*
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public Resource markAsSparse() {
|
||||||
|
hasSparseData = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setAlerts(Alerts alert) {
|
||||||
|
this.alert = alert;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setColorTemperature(ColorTemperature colorTemperature) {
|
||||||
|
this.colorTemperature = colorTemperature;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setColorXy(ColorXy color) {
|
||||||
|
this.color = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setDimming(Dimming dimming) {
|
||||||
|
this.dimming = dimming;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setDynamicsDuration(Duration duration) {
|
||||||
|
dynamics = new Dynamics().setDuration(duration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setEffects(Effects effect) {
|
||||||
|
this.effects = effect;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setEnabled(Command command) {
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
this.enabled = ((OnOffType) command) == OnOffType.ON;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setMetadata(MetaData metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setMirekSchema(@Nullable MirekSchema schema) {
|
||||||
|
ColorTemperature colorTemperature = this.colorTemperature;
|
||||||
|
if (Objects.nonNull(colorTemperature)) {
|
||||||
|
colorTemperature.setMirekSchema(schema);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the on/off JSON element (only).
|
||||||
|
*
|
||||||
|
* @param command an OnOffTypee command value.
|
||||||
|
* @return this resource instance.
|
||||||
|
*/
|
||||||
|
public Resource setOnOff(Command command) {
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
OnOffType onOff = (OnOffType) command;
|
||||||
|
OnState on = this.on;
|
||||||
|
on = Objects.nonNull(on) ? on : new OnState();
|
||||||
|
on.setOn(OnOffType.ON.equals(onOff));
|
||||||
|
this.on = on;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnState(OnState on) {
|
||||||
|
this.on = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setRecallAction(RecallAction recallAction) {
|
||||||
|
Recall recall = this.recall;
|
||||||
|
this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setAction(recallAction);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setRecallDuration(Duration recallDuration) {
|
||||||
|
Recall recall = this.recall;
|
||||||
|
this.recall = ((Objects.nonNull(recall) ? recall : new Recall())).setDuration(recallDuration);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource setType(ResourceType resourceType) {
|
||||||
|
this.type = resourceType.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String id = this.id;
|
||||||
|
return String.format("id:%s, type:%s", Objects.nonNull(id) ? id : "?" + " ".repeat(35),
|
||||||
|
getType().name().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO that contains an API reference element.
|
||||||
|
*
|
||||||
|
* The V2 API is set up in such a way that all resources of the same type are grouped together under the
|
||||||
|
* /resource/<resourcetype> endpoint, but all those resources commonly reference each other, which is done in a
|
||||||
|
* standardized way by indicating the resource type (rtype) and resource id (rid).
|
||||||
|
*
|
||||||
|
* A typical usage is in a single physical device that hosts multiple services. An existing example is the Philips Hue
|
||||||
|
* Motion sensor which has a motion, light_level, and temperature service, but theoretically any combination can be
|
||||||
|
* supported such as an integrated device with two independently controllable light points and a motion sensor.
|
||||||
|
*
|
||||||
|
* This means that the information of the device itself can be found under the /device resource endpoint, but it then
|
||||||
|
* contains a services array which references for example the light and motion resources, for which the details can be
|
||||||
|
* found under the /light and /motion resource endpoints respectively. Other services the device might have, such as a
|
||||||
|
* Zigbee radio (zigbee_connectivy) or battery (device_power) are modeled in the same way.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ResourceReference {
|
||||||
|
private @Nullable String rid;
|
||||||
|
private @NonNullByDefault({}) String rtype;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
String rid = this.rid;
|
||||||
|
return (obj instanceof ResourceReference) && (rid != null) && rid.equals(((ResourceReference) obj).rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getId() {
|
||||||
|
return rid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceType getType() {
|
||||||
|
return ResourceType.of(rtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceReference setId(String id) {
|
||||||
|
rid = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceReference setType(ResourceType resourceType) {
|
||||||
|
rtype = resourceType.name().toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String id = rid;
|
||||||
|
return String.format("id:%s, type:%s", id != null ? id : "*" + " ".repeat(35), getType().name().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 to retrieve a list of generic resources from the bridge.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Resources {
|
||||||
|
private List<Error> errors = new ArrayList<>();
|
||||||
|
private List<Resource> data = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<String> getErrors() {
|
||||||
|
return errors.stream().map(Error::getDescription).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Resource> getResources() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for rotation element of a tap dial switch.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Rotation {
|
||||||
|
private @Nullable String direction;
|
||||||
|
private @Nullable Integer duration;
|
||||||
|
private @Nullable Integer steps;
|
||||||
|
|
||||||
|
public @Nullable DirectionType getDirection() {
|
||||||
|
String direction = this.direction;
|
||||||
|
return Objects.nonNull(direction) ? DirectionType.valueOf(direction.toUpperCase()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
Integer duration = this.duration;
|
||||||
|
return Objects.nonNull(duration) ? duration.intValue() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSteps() {
|
||||||
|
Integer steps = this.steps;
|
||||||
|
return Objects.nonNull(steps) ? steps.intValue() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the state corresponding to a relative rotary dial's last steps value. Clockwise rotations are positive, and
|
||||||
|
* counter clockwise rotations negative.
|
||||||
|
*
|
||||||
|
* @return the state or UNDEF.
|
||||||
|
*/
|
||||||
|
public State getStepsState() {
|
||||||
|
DirectionType direction = getDirection();
|
||||||
|
Integer steps = this.steps;
|
||||||
|
if (Objects.nonNull(direction) && Objects.nonNull(steps)) {
|
||||||
|
return new DecimalType(DirectionType.CLOCK_WISE.equals(direction) ? steps.intValue() : -steps.intValue());
|
||||||
|
}
|
||||||
|
return UnDefType.NULL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for rotation event of a dial switch.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RotationEvent {
|
||||||
|
private @Nullable String action;
|
||||||
|
private @Nullable Rotation rotation;
|
||||||
|
|
||||||
|
public @Nullable RotationEventType getAction() {
|
||||||
|
String action = this.action;
|
||||||
|
return Objects.nonNull(action) ? RotationEventType.valueOf(action.toUpperCase()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getActionState() {
|
||||||
|
RotationEventType action = getAction();
|
||||||
|
return Objects.nonNull(action) ? new StringType(action.name()) : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Rotation getRotation() {
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getStepsState() {
|
||||||
|
Rotation rotation = this.rotation;
|
||||||
|
return Objects.nonNull(rotation) ? rotation.getStepsState() : UnDefType.NULL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for CLIP 2 temperature sensor.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Temperature {
|
||||||
|
private float temperature;
|
||||||
|
private @SerializedName("temperature_valid") boolean temperatureValid;
|
||||||
|
|
||||||
|
public float getTemperature() {
|
||||||
|
return temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTemperatureValid() {
|
||||||
|
return temperatureValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getTemperatureState() {
|
||||||
|
return temperatureValid ? new QuantityType<>(temperature, SIUnits.CELSIUS) : UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getTemperatureValidState() {
|
||||||
|
return OnOffType.from(temperatureValid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for 'timed_effects' of a light.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TimedEffects extends Effects {
|
||||||
|
private @Nullable Long duration;
|
||||||
|
|
||||||
|
public @Nullable Duration getDuration() {
|
||||||
|
Long duration = this.duration;
|
||||||
|
return Objects.nonNull(duration) ? Duration.ofMillis(duration) : Duration.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimedEffects setDuration(Duration duration) {
|
||||||
|
this.duration = duration.toMillis();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for 'alert' actions.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum ActionType {
|
||||||
|
BREATHE,
|
||||||
|
NO_ACTION;
|
||||||
|
|
||||||
|
public static ActionType of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_ACTION;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for product archetypes.
|
||||||
|
*
|
||||||
|
* @see <a href="https://developers.meethue.com/develop/hue-api-v2/api-reference/#resource_light_get">API v2
|
||||||
|
* documentation</a>
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum Archetype {
|
||||||
|
// device archetypes
|
||||||
|
BRIDGE_V2,
|
||||||
|
UNKNOWN_ARCHETYPE,
|
||||||
|
CLASSIC_BULB,
|
||||||
|
SULTAN_BULB,
|
||||||
|
FLOOD_BULB,
|
||||||
|
SPOT_BULB,
|
||||||
|
CANDLE_BULB,
|
||||||
|
LUSTER_BULB,
|
||||||
|
PENDANT_ROUND,
|
||||||
|
PENDANT_LONG,
|
||||||
|
CEILING_ROUND,
|
||||||
|
CEILING_SQUARE,
|
||||||
|
FLOOR_SHADE,
|
||||||
|
FLOOR_LANTERN,
|
||||||
|
TABLE_SHADE,
|
||||||
|
RECESSED_CEILING,
|
||||||
|
RECESSED_FLOOR,
|
||||||
|
SINGLE_SPOT,
|
||||||
|
DOUBLE_SPOT,
|
||||||
|
TABLE_WASH,
|
||||||
|
WALL_LANTERN,
|
||||||
|
WALL_SHADE,
|
||||||
|
FLEXIBLE_LAMP,
|
||||||
|
GROUND_SPOT,
|
||||||
|
WALL_SPOT,
|
||||||
|
PLUG,
|
||||||
|
HUE_GO,
|
||||||
|
HUE_LIGHTSTRIP,
|
||||||
|
HUE_IRIS,
|
||||||
|
HUE_BLOOM,
|
||||||
|
BOLLARD,
|
||||||
|
WALL_WASHER,
|
||||||
|
HUE_PLAY,
|
||||||
|
VINTAGE_BULB,
|
||||||
|
CHRISTMAS_TREE,
|
||||||
|
HUE_CENTRIS,
|
||||||
|
HUE_LIGHTSTRIP_TV,
|
||||||
|
HUE_TUBE,
|
||||||
|
HUE_SIGNE,
|
||||||
|
STRING_LIGHT,
|
||||||
|
// room archetypes
|
||||||
|
LIVING_ROOM,
|
||||||
|
KITCHEN,
|
||||||
|
DINING,
|
||||||
|
BEDROOM,
|
||||||
|
KIDS_BEDROOM,
|
||||||
|
BATHROOM,
|
||||||
|
NURSERY,
|
||||||
|
RECREATION,
|
||||||
|
OFFICE,
|
||||||
|
GYM,
|
||||||
|
HALLWAY,
|
||||||
|
TOILET,
|
||||||
|
FRONT_DOOR,
|
||||||
|
GARAGE,
|
||||||
|
TERRACE,
|
||||||
|
GARDEN,
|
||||||
|
DRIVEWAY,
|
||||||
|
CARPORT,
|
||||||
|
HOME,
|
||||||
|
DOWNSTAIRS,
|
||||||
|
UPSTAIRS,
|
||||||
|
TOP_FLOOR,
|
||||||
|
ATTIC,
|
||||||
|
GUEST_ROOM,
|
||||||
|
STAIRCASE,
|
||||||
|
LOUNGE,
|
||||||
|
MAN_CAVE,
|
||||||
|
COMPUTER,
|
||||||
|
STUDIO,
|
||||||
|
MUSIC,
|
||||||
|
TV,
|
||||||
|
READING,
|
||||||
|
CLOSET,
|
||||||
|
STORAGE,
|
||||||
|
LAUNDRY_ROOM,
|
||||||
|
BALCONY,
|
||||||
|
PORCH,
|
||||||
|
BARBECUE,
|
||||||
|
POOL,
|
||||||
|
OTHER;
|
||||||
|
|
||||||
|
public static Archetype of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN_ARCHETYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String s = this.name().replace("_", " ");
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for types battery state.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum BatteryStateType {
|
||||||
|
NORMAL,
|
||||||
|
LOW,
|
||||||
|
CRITICAL;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for types button press.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum ButtonEventType {
|
||||||
|
INITIAL_PRESS,
|
||||||
|
REPEAT,
|
||||||
|
SHORT_RELEASE,
|
||||||
|
LONG_RELEASE,
|
||||||
|
DOUBLE_SHORT_RELEASE;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for tap dial rotation directions.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum DirectionType {
|
||||||
|
CLOCK_WISE,
|
||||||
|
COUNTER_CLOCK_WISE;
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for 'effect' types.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum EffectType {
|
||||||
|
// fixed Effects
|
||||||
|
SPARKLE,
|
||||||
|
FIRE,
|
||||||
|
CANDLE,
|
||||||
|
// timed Effects
|
||||||
|
SUNRISE,
|
||||||
|
// applies to both
|
||||||
|
NO_EFFECT;
|
||||||
|
|
||||||
|
private static final Set<EffectType> FIXED = Set.of(SPARKLE, FIRE, CANDLE);
|
||||||
|
private static final Set<EffectType> TIMED = Set.of(SUNRISE);
|
||||||
|
|
||||||
|
public static EffectType of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_EFFECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFixed() {
|
||||||
|
return FIXED.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTimed() {
|
||||||
|
return TIMED.contains(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for scene recall actions.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum RecallAction {
|
||||||
|
ACTIVE,
|
||||||
|
DYNAMIC_PALETTE,
|
||||||
|
STATIC;
|
||||||
|
|
||||||
|
public static RecallAction of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for resource types.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum ResourceType {
|
||||||
|
AUTH_V1,
|
||||||
|
BEHAVIOR_INSTANCE,
|
||||||
|
BEHAVIOR_SCRIPT,
|
||||||
|
BRIDGE,
|
||||||
|
BRIDGE_HOME,
|
||||||
|
BUTTON,
|
||||||
|
DEVICE,
|
||||||
|
DEVICE_POWER,
|
||||||
|
ENTERTAINMENT,
|
||||||
|
ENTERTAINMENT_CONFIGURATION,
|
||||||
|
GEOFENCE,
|
||||||
|
GEOFENCE_CLIENT,
|
||||||
|
GEOLOCATION,
|
||||||
|
GROUPED_LIGHT,
|
||||||
|
HOMEKIT,
|
||||||
|
LIGHT,
|
||||||
|
LIGHT_LEVEL,
|
||||||
|
MOTION,
|
||||||
|
PUBLIC_IMAGE,
|
||||||
|
ROOM,
|
||||||
|
RELATIVE_ROTARY,
|
||||||
|
SCENE,
|
||||||
|
TEMPERATURE,
|
||||||
|
ZGP_CONNECTIVITY,
|
||||||
|
ZIGBEE_CONNECTIVITY,
|
||||||
|
ZONE,
|
||||||
|
UPDATE,
|
||||||
|
ADD,
|
||||||
|
DELETE,
|
||||||
|
ERROR;
|
||||||
|
|
||||||
|
public static final Set<ResourceType> SSE_TYPES = EnumSet.of(UPDATE, ADD, DELETE, ERROR);
|
||||||
|
|
||||||
|
public static ResourceType of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String s = this.name().replace("_", " ");
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for types of rotary dial events.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum RotationEventType {
|
||||||
|
START,
|
||||||
|
REPEAT;
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.enums;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for possible Zigbee states.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum ZigbeeStatus {
|
||||||
|
CONNECTED,
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTIVITY_ISSUE,
|
||||||
|
UNIDIRECTIONAL_INCOMING;
|
||||||
|
|
||||||
|
public static ZigbeeStatus of(@Nullable String value) {
|
||||||
|
if (value != null) {
|
||||||
|
try {
|
||||||
|
return valueOf(value.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String s = this.name().replace("_", " ");
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,312 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.dto.clip2.helper;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.measure.Unit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Alerts;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ColorTemperature;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ColorXy;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Dimming;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Effects;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.OnState;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.EffectType;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.HSBType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.util.ColorUtil;
|
||||||
|
import org.openhab.core.util.ColorUtil.Gamut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced setter methods for fields in the Resource class for special cases where setting the new value in the target
|
||||||
|
* resource depends on logic using the values of other fields in a another source Resource.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Setters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Alert field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any).
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be a StringType.
|
||||||
|
* @param source another resource containing the allowed alert action values.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setAlert(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
if ((command instanceof StringType) && Objects.nonNull(source)) {
|
||||||
|
Alerts otherAlert = source.getAlerts();
|
||||||
|
if (Objects.nonNull(otherAlert)) {
|
||||||
|
ActionType actionType = ActionType.of(((StringType) command).toString());
|
||||||
|
if (otherAlert.getActionValues().contains(actionType)) {
|
||||||
|
target.setAlerts(new Alerts().setAction(actionType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Color Temperature field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any).
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be a QuantityType<Temperature> (but it can also handle DecimalType).
|
||||||
|
* @param source another resource containing the MirekSchema.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setColorTemperatureAbsolute(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
QuantityType<?> mirek;
|
||||||
|
if (command instanceof QuantityType<?>) {
|
||||||
|
QuantityType<?> quantity = (QuantityType<?>) command;
|
||||||
|
Unit<?> unit = quantity.getUnit();
|
||||||
|
if (Units.KELVIN.equals(unit)) {
|
||||||
|
mirek = quantity.toInvertibleUnit(Units.MIRED);
|
||||||
|
} else if (Units.MIRED.equals(unit)) {
|
||||||
|
mirek = quantity;
|
||||||
|
} else {
|
||||||
|
QuantityType<?> kelvin = quantity.toInvertibleUnit(Units.KELVIN);
|
||||||
|
mirek = Objects.nonNull(kelvin) ? kelvin.toInvertibleUnit(Units.MIRED) : null;
|
||||||
|
}
|
||||||
|
} else if (command instanceof DecimalType) {
|
||||||
|
mirek = QuantityType.valueOf(((DecimalType) command).doubleValue(), Units.KELVIN)
|
||||||
|
.toInvertibleUnit(Units.MIRED);
|
||||||
|
} else {
|
||||||
|
mirek = null;
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(mirek)) {
|
||||||
|
MirekSchema schema = target.getMirekSchema();
|
||||||
|
schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
|
||||||
|
schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
|
||||||
|
ColorTemperature colorTemperature = target.getColorTemperature();
|
||||||
|
colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
|
||||||
|
double min = schema.getMirekMinimum();
|
||||||
|
double max = schema.getMirekMaximum();
|
||||||
|
double val = Math.max(min, Math.min(max, mirek.doubleValue()));
|
||||||
|
target.setColorTemperature(colorTemperature.setMirek(val));
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Color Temperature field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any).
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be a PercentType.
|
||||||
|
* @param source another resource containing the MirekSchema.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setColorTemperaturePercent(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
MirekSchema schema = target.getMirekSchema();
|
||||||
|
schema = Objects.nonNull(schema) ? schema : Objects.nonNull(source) ? source.getMirekSchema() : null;
|
||||||
|
schema = Objects.nonNull(schema) ? schema : MirekSchema.DEFAULT_SCHEMA;
|
||||||
|
ColorTemperature colorTemperature = target.getColorTemperature();
|
||||||
|
colorTemperature = Objects.nonNull(colorTemperature) ? colorTemperature : new ColorTemperature();
|
||||||
|
double min = schema.getMirekMinimum();
|
||||||
|
double max = schema.getMirekMaximum();
|
||||||
|
double val = min + ((max - min) * ((PercentType) command).doubleValue() / 100f);
|
||||||
|
target.setColorTemperature(colorTemperature.setMirek(val));
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Color Xy field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any). Use the HS parts of the HSB value to set the value of the 'ColorXy' JSON element, and ignore the 'B'
|
||||||
|
* part.
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be an HSBType with the new color XY value.
|
||||||
|
* @param source another resource containing the color Gamut.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setColorXy(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
if (command instanceof HSBType) {
|
||||||
|
Gamut gamut = target.getGamut();
|
||||||
|
gamut = Objects.nonNull(gamut) ? gamut : Objects.nonNull(source) ? source.getGamut() : null;
|
||||||
|
gamut = Objects.nonNull(gamut) ? gamut : ColorUtil.DEFAULT_GAMUT;
|
||||||
|
HSBType hsb = (HSBType) command;
|
||||||
|
ColorXy color = target.getColorXy();
|
||||||
|
target.setColorXy((Objects.nonNull(color) ? color : new ColorXy()).setXY(ColorUtil.hsbToXY(hsb, gamut)));
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Dimming field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any).
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be a PercentType with the new dimming parameter.
|
||||||
|
* @param source another resource containing the minimum dimming level.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setDimming(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
if (command instanceof PercentType) {
|
||||||
|
Double min = target.getMinimumDimmingLevel();
|
||||||
|
min = Objects.nonNull(min) ? min : Objects.nonNull(source) ? source.getMinimumDimmingLevel() : null;
|
||||||
|
min = Objects.nonNull(min) ? min : Dimming.DEFAULT_MINIMUM_DIMMIMG_LEVEL;
|
||||||
|
PercentType brightness = (PercentType) command;
|
||||||
|
if (brightness.doubleValue() < min.doubleValue()) {
|
||||||
|
brightness = new PercentType(new BigDecimal(min, Resource.PERCENT_MATH_CONTEXT));
|
||||||
|
}
|
||||||
|
Dimming dimming = target.getDimming();
|
||||||
|
dimming = Objects.nonNull(dimming) ? dimming : new Dimming();
|
||||||
|
dimming.setBrightness(brightness.doubleValue());
|
||||||
|
target.setDimming(dimming);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for Effect field:
|
||||||
|
* Use the given command value to set the target resource DTO value based on the attributes of the source resource
|
||||||
|
* (if any).
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param command the new state command should be a StringType.
|
||||||
|
* @param source another resource containing the allowed effect action values.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setEffect(Resource target, Command command, @Nullable Resource source) {
|
||||||
|
if ((command instanceof StringType) && Objects.nonNull(source)) {
|
||||||
|
Effects otherEffects = source.getEffects();
|
||||||
|
if (Objects.nonNull(otherEffects)) {
|
||||||
|
EffectType effectType = EffectType.of(((StringType) command).toString());
|
||||||
|
if (otherEffects.allows(effectType)) {
|
||||||
|
target.setEffects(new Effects().setEffect(effectType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter to copy persisted fields from the source Resource into the target Resource. If the field in the target is
|
||||||
|
* null and the same field in the source is not null, then the value from the source is copied to the target. This
|
||||||
|
* method allows 'hasSparseData' resources to expand themselves to include necessary fields taken over from a
|
||||||
|
* previously cached full data resource.
|
||||||
|
*
|
||||||
|
* @param target the target resource.
|
||||||
|
* @param source another resource containing the values to be taken over.
|
||||||
|
*
|
||||||
|
* @return the target resource.
|
||||||
|
*/
|
||||||
|
public static Resource setResource(Resource target, Resource source) {
|
||||||
|
// on
|
||||||
|
OnState targetOnOff = target.getOnState();
|
||||||
|
OnState sourceOnOff = source.getOnState();
|
||||||
|
if (Objects.isNull(targetOnOff) && Objects.nonNull(sourceOnOff)) {
|
||||||
|
target.setOnState(sourceOnOff);
|
||||||
|
}
|
||||||
|
// dimming
|
||||||
|
Dimming targetDimming = target.getDimming();
|
||||||
|
Dimming sourceDimming = source.getDimming();
|
||||||
|
if (Objects.isNull(targetDimming) && Objects.nonNull(sourceDimming)) {
|
||||||
|
target.setDimming(sourceDimming);
|
||||||
|
targetDimming = target.getDimming();
|
||||||
|
}
|
||||||
|
// minimum dimming level
|
||||||
|
Double targetMinDimmingLevel = Objects.nonNull(targetDimming) ? targetDimming.getMinimumDimmingLevel() : null;
|
||||||
|
Double sourceMinDimmingLevel = Objects.nonNull(sourceDimming) ? sourceDimming.getMinimumDimmingLevel() : null;
|
||||||
|
if (Objects.isNull(targetMinDimmingLevel) && Objects.nonNull(sourceMinDimmingLevel)) {
|
||||||
|
targetDimming = Objects.nonNull(targetDimming) ? targetDimming : new Dimming();
|
||||||
|
targetDimming.setMinimumDimmingLevel(sourceMinDimmingLevel);
|
||||||
|
}
|
||||||
|
// color
|
||||||
|
ColorXy targetColor = target.getColorXy();
|
||||||
|
ColorXy sourceColor = source.getColorXy();
|
||||||
|
if (Objects.isNull(targetColor) && Objects.nonNull(sourceColor)) {
|
||||||
|
target.setColorXy(sourceColor);
|
||||||
|
targetColor = target.getColorXy();
|
||||||
|
}
|
||||||
|
// color gamut
|
||||||
|
Gamut targetGamut = Objects.nonNull(targetColor) ? targetColor.getGamut() : null;
|
||||||
|
Gamut sourceGamut = Objects.nonNull(sourceColor) ? sourceColor.getGamut() : null;
|
||||||
|
if (Objects.isNull(targetGamut) && Objects.nonNull(sourceGamut)) {
|
||||||
|
targetColor = Objects.nonNull(targetColor) ? targetColor : new ColorXy();
|
||||||
|
targetColor.setGamut(sourceGamut);
|
||||||
|
}
|
||||||
|
// color temperature
|
||||||
|
ColorTemperature targetColorTemp = target.getColorTemperature();
|
||||||
|
ColorTemperature sourceColorTemp = source.getColorTemperature();
|
||||||
|
if (Objects.isNull(targetColorTemp) && Objects.nonNull(sourceColorTemp)) {
|
||||||
|
target.setColorTemperature(sourceColorTemp);
|
||||||
|
targetColorTemp = target.getColorTemperature();
|
||||||
|
}
|
||||||
|
// mirek schema
|
||||||
|
MirekSchema targetMirekSchema = Objects.nonNull(targetColorTemp) ? targetColorTemp.getMirekSchema() : null;
|
||||||
|
MirekSchema sourceMirekSchema = Objects.nonNull(sourceColorTemp) ? sourceColorTemp.getMirekSchema() : null;
|
||||||
|
if (Objects.isNull(targetMirekSchema) && Objects.nonNull(sourceMirekSchema)) {
|
||||||
|
targetColorTemp = Objects.nonNull(targetColorTemp) ? targetColorTemp : new ColorTemperature();
|
||||||
|
targetColorTemp.setMirekSchema(sourceMirekSchema);
|
||||||
|
}
|
||||||
|
// metadata
|
||||||
|
MetaData targetMetaData = target.getMetaData();
|
||||||
|
MetaData sourceMetaData = source.getMetaData();
|
||||||
|
if (Objects.isNull(targetMetaData) && Objects.nonNull(sourceMetaData)) {
|
||||||
|
target.setMetadata(sourceMetaData);
|
||||||
|
}
|
||||||
|
// alerts
|
||||||
|
Alerts targetAlerts = target.getAlerts();
|
||||||
|
Alerts sourceAlerts = source.getAlerts();
|
||||||
|
if (Objects.isNull(targetAlerts) && Objects.nonNull(sourceAlerts)) {
|
||||||
|
target.setAlerts(sourceAlerts);
|
||||||
|
}
|
||||||
|
// effects
|
||||||
|
Effects targetEffects = target.getEffects();
|
||||||
|
Effects sourceEffects = source.getEffects();
|
||||||
|
if (Objects.isNull(targetEffects) && Objects.nonNull(sourceEffects)) {
|
||||||
|
targetEffects = sourceEffects;
|
||||||
|
target.setEffects(sourceEffects);
|
||||||
|
targetEffects = target.getEffects();
|
||||||
|
}
|
||||||
|
// effects values
|
||||||
|
List<String> targetStatusValues = Objects.nonNull(targetEffects) ? targetEffects.getStatusValues() : null;
|
||||||
|
List<String> sourceStatusValues = Objects.nonNull(sourceEffects) ? sourceEffects.getStatusValues() : null;
|
||||||
|
if (Objects.isNull(targetStatusValues) && Objects.nonNull(sourceStatusValues)) {
|
||||||
|
targetEffects = Objects.nonNull(targetEffects) ? targetEffects : new Effects();
|
||||||
|
targetEffects.setStatusValues(sourceStatusValues);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.hue.internal.exceptions;
|
package org.openhab.binding.hue.internal.exceptions;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when the API returns an unknown error.
|
* Thrown when the API returns an unknown error.
|
||||||
@ -29,4 +30,8 @@ public class ApiException extends Exception {
|
|||||||
public ApiException(String message) {
|
public ApiException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ApiException(String message, @Nullable Throwable e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.exceptions;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when one of the connection or handler classes has not loaded all its assets.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class AssetNotLoadedException extends Exception {
|
||||||
|
private static final long serialVersionUID = -1;
|
||||||
|
|
||||||
|
public AssetNotLoadedException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetNotLoadedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.exceptions;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a DTO is present but empty. In some circumstances the API v2 returns an empty DTO ("dtoName":{}) rather
|
||||||
|
* than null ("dtoName":null). This indicates that the DTO is in principle supported by the containing resource, but
|
||||||
|
* currently the DTO contains no actual state fields.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DTOPresentButEmptyException extends Exception {
|
||||||
|
private static final long serialVersionUID = -1;
|
||||||
|
|
||||||
|
public DTOPresentButEmptyException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DTOPresentButEmptyException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.exceptions;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an HTTP call to the CLIP 2 bridge returns with an 'unauthorized' status code.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HttpUnauthorizedException extends ApiException {
|
||||||
|
private static final long serialVersionUID = -1;
|
||||||
|
|
||||||
|
public HttpUnauthorizedException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpUnauthorizedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,10 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.openhab.binding.hue.internal.HueBindingConstants;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2StateDescriptionProvider;
|
||||||
|
import org.openhab.binding.hue.internal.handler.Clip2ThingHandler;
|
||||||
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
import org.openhab.binding.hue.internal.handler.HueBridgeHandler;
|
||||||
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
|
import org.openhab.binding.hue.internal.handler.HueGroupHandler;
|
||||||
import org.openhab.binding.hue.internal.handler.HueLightHandler;
|
import org.openhab.binding.hue.internal.handler.HueLightHandler;
|
||||||
@ -38,17 +41,19 @@ import org.openhab.core.i18n.TranslationProvider;
|
|||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link HueThingHandlerFactory} is a factory for {@link HueBridgeHandler}s.
|
* The factory for all varieties of Hue thing handlers.
|
||||||
*
|
*
|
||||||
* @author Dennis Nobel - Initial contribution of hue binding
|
* @author Dennis Nobel - Initial contribution of hue binding
|
||||||
* @author Kai Kreuzer - added supportsThingType method
|
* @author Kai Kreuzer - added supportsThingType method
|
||||||
@ -56,13 +61,15 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
* @author Samuel Leisering - Added support for sensor API
|
* @author Samuel Leisering - Added support for sensor API
|
||||||
* @author Christoph Weitkamp - Added support for sensor API
|
* @author Christoph Weitkamp - Added support for sensor API
|
||||||
* @author Laurent Garnier - Added support for groups
|
* @author Laurent Garnier - Added support for groups
|
||||||
|
* @author Andrew Fiddian-Green - Added support for CLIP 2 things
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
|
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.hue")
|
||||||
public class HueThingHandlerFactory extends BaseThingHandlerFactory {
|
public class HueThingHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
|
||||||
.of(HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(),
|
.of(Clip2BridgeHandler.SUPPORTED_THING_TYPES.stream(), Clip2ThingHandler.SUPPORTED_THING_TYPES.stream(),
|
||||||
|
HueBridgeHandler.SUPPORTED_THING_TYPES.stream(), HueLightHandler.SUPPORTED_THING_TYPES.stream(),
|
||||||
DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(),
|
DimmerSwitchHandler.SUPPORTED_THING_TYPES.stream(), TapSwitchHandler.SUPPORTED_THING_TYPES.stream(),
|
||||||
PresenceHandler.SUPPORTED_THING_TYPES.stream(),
|
PresenceHandler.SUPPORTED_THING_TYPES.stream(),
|
||||||
GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
|
GeofencePresenceHandler.SUPPORTED_THING_TYPES.stream(),
|
||||||
@ -70,25 +77,39 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
|
ClipHandler.SUPPORTED_THING_TYPES.stream(), HueGroupHandler.SUPPORTED_THING_TYPES.stream())
|
||||||
.flatMap(i -> i).collect(Collectors.toUnmodifiableSet());
|
.flatMap(i -> i).collect(Collectors.toUnmodifiableSet());
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClientFactory httpClientFactory;
|
||||||
private final HueStateDescriptionProvider stateDescriptionProvider;
|
private final HueStateDescriptionProvider stateDescriptionProvider;
|
||||||
|
private final Clip2StateDescriptionProvider clip2StateDescriptionProvider;
|
||||||
private final TranslationProvider i18nProvider;
|
private final TranslationProvider i18nProvider;
|
||||||
private final LocaleProvider localeProvider;
|
private final LocaleProvider localeProvider;
|
||||||
|
private final ThingRegistry thingRegistry;
|
||||||
|
private final ItemChannelLinkRegistry itemChannelLinkRegistry;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public HueThingHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
public HueThingHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||||
final @Reference HueStateDescriptionProvider stateDescriptionProvider,
|
final @Reference HueStateDescriptionProvider stateDescriptionProvider,
|
||||||
final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider) {
|
final @Reference Clip2StateDescriptionProvider clip2StateDescriptionProvider,
|
||||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider,
|
||||||
|
final @Reference ThingRegistry thingRegistry,
|
||||||
|
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry) {
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||||
|
this.clip2StateDescriptionProvider = clip2StateDescriptionProvider;
|
||||||
this.i18nProvider = i18nProvider;
|
this.i18nProvider = i18nProvider;
|
||||||
this.localeProvider = localeProvider;
|
this.localeProvider = localeProvider;
|
||||||
|
this.thingRegistry = thingRegistry;
|
||||||
|
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||||
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||||
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
if (HueBindingConstants.THING_TYPE_BRIDGE_API2.equals(thingTypeUID)) {
|
||||||
|
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
||||||
|
} else if (Clip2ThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
|
ThingUID clip2ThingUID = getClip2ThingUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||||
|
return super.createThing(thingTypeUID, configuration, clip2ThingUID, bridgeUID);
|
||||||
|
} else if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
||||||
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
ThingUID hueLightUID = getLightUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
ThingUID hueLightUID = getLightUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||||
@ -115,6 +136,12 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ThingUID getClip2ThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||||
|
Configuration configuration, @Nullable ThingUID bridgeUID) {
|
||||||
|
return thingUID != null ? thingUID
|
||||||
|
: getThingUID(thingTypeUID, configuration.get(PROPERTY_RESOURCE_ID).toString(), bridgeUID);
|
||||||
|
}
|
||||||
|
|
||||||
private ThingUID getLightUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
|
private ThingUID getLightUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration,
|
||||||
@Nullable ThingUID bridgeUID) {
|
@Nullable ThingUID bridgeUID) {
|
||||||
if (thingUID != null) {
|
if (thingUID != null) {
|
||||||
@ -152,26 +179,32 @@ public class HueThingHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
return new HueBridgeHandler((Bridge) thing, httpClient, stateDescriptionProvider, i18nProvider,
|
if (HueBindingConstants.THING_TYPE_BRIDGE_API2.equals(thingTypeUID)) {
|
||||||
localeProvider);
|
return new Clip2BridgeHandler((Bridge) thing, httpClientFactory, thingRegistry, localeProvider,
|
||||||
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
i18nProvider);
|
||||||
|
} else if (Clip2ThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
|
return new Clip2ThingHandler(thing, clip2StateDescriptionProvider, thingRegistry, itemChannelLinkRegistry);
|
||||||
|
} else if (HueBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
|
return new HueBridgeHandler((Bridge) thing, httpClientFactory.getCommonHttpClient(),
|
||||||
|
stateDescriptionProvider, i18nProvider, localeProvider);
|
||||||
|
} else if (HueLightHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new HueLightHandler(thing, stateDescriptionProvider);
|
return new HueLightHandler(thing, stateDescriptionProvider);
|
||||||
} else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (DimmerSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new DimmerSwitchHandler(thing);
|
return new DimmerSwitchHandler(thing);
|
||||||
} else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (TapSwitchHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new TapSwitchHandler(thing);
|
return new TapSwitchHandler(thing);
|
||||||
} else if (PresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (PresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new PresenceHandler(thing);
|
return new PresenceHandler(thing);
|
||||||
} else if (GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (GeofencePresenceHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new GeofencePresenceHandler(thing);
|
return new GeofencePresenceHandler(thing);
|
||||||
} else if (TemperatureHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (TemperatureHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new TemperatureHandler(thing);
|
return new TemperatureHandler(thing);
|
||||||
} else if (LightLevelHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (LightLevelHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new LightLevelHandler(thing);
|
return new LightLevelHandler(thing);
|
||||||
} else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (ClipHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new ClipHandler(thing);
|
return new ClipHandler(thing);
|
||||||
} else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
} else if (HueGroupHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||||
return new HueGroupHandler(thing, stateDescriptionProvider);
|
return new HueGroupHandler(thing, stateDescriptionProvider);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -0,0 +1,776 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.hue.internal.config.Clip2BridgeConfig;
|
||||||
|
import org.openhab.binding.hue.internal.connection.Clip2Bridge;
|
||||||
|
import org.openhab.binding.hue.internal.connection.HueTlsTrustManagerProvider;
|
||||||
|
import org.openhab.binding.hue.internal.discovery.Clip2ThingDiscoveryService;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ProductData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.ApiException;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
|
||||||
|
import org.openhab.binding.hue.internal.exceptions.HttpUnauthorizedException;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.i18n.LocaleProvider;
|
||||||
|
import org.openhab.core.i18n.TranslationProvider;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.io.net.http.TlsTrustManagerProvider;
|
||||||
|
import org.openhab.core.thing.Bridge;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
import org.openhab.core.thing.binding.builder.BridgeBuilder;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.osgi.framework.Bundle;
|
||||||
|
import org.osgi.framework.FrameworkUtil;
|
||||||
|
import org.osgi.framework.ServiceRegistration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge handler for a CLIP 2 bridge. It communicates with the bridge via CLIP 2 end points, and reads and writes API
|
||||||
|
* V2 resource objects. It also subscribes to the server's SSE event stream, and receives SSE events from it.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution.
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Clip2BridgeHandler extends BaseBridgeHandler {
|
||||||
|
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE_API2);
|
||||||
|
|
||||||
|
private static final int FAST_SCHEDULE_MILLI_SECONDS = 500;
|
||||||
|
private static final int APPLICATION_KEY_MAX_TRIES = 600; // i.e. 300 seconds, 5 minutes
|
||||||
|
private static final int RECONNECT_DELAY_SECONDS = 10;
|
||||||
|
private static final int RECONNECT_MAX_TRIES = 5;
|
||||||
|
|
||||||
|
private static final ResourceReference DEVICE = new ResourceReference().setType(ResourceType.DEVICE);
|
||||||
|
private static final ResourceReference ROOM = new ResourceReference().setType(ResourceType.ROOM);
|
||||||
|
private static final ResourceReference ZONE = new ResourceReference().setType(ResourceType.ZONE);
|
||||||
|
private static final ResourceReference BRIDGE = new ResourceReference().setType(ResourceType.BRIDGE);
|
||||||
|
private static final ResourceReference BRIDGE_HOME = new ResourceReference().setType(ResourceType.BRIDGE_HOME);
|
||||||
|
private static final ResourceReference SCENE = new ResourceReference().setType(ResourceType.SCENE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of resource references that need to be mass down loaded.
|
||||||
|
* NOTE: the SCENE resources must be mass down loaded first!
|
||||||
|
*/
|
||||||
|
private static final List<ResourceReference> MASS_DOWNLOAD_RESOURCE_REFERENCES = List.of(SCENE, DEVICE, ROOM, ZONE);
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Clip2BridgeHandler.class);
|
||||||
|
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
private final ThingRegistry thingRegistry;
|
||||||
|
private final Bundle bundle;
|
||||||
|
private final LocaleProvider localeProvider;
|
||||||
|
private final TranslationProvider translationProvider;
|
||||||
|
|
||||||
|
private @Nullable Clip2Bridge clip2Bridge;
|
||||||
|
private @Nullable ServiceRegistration<?> trustManagerRegistration;
|
||||||
|
private @Nullable Clip2ThingDiscoveryService discoveryService;
|
||||||
|
|
||||||
|
private @Nullable Future<?> checkConnectionTask;
|
||||||
|
private @Nullable Future<?> updateOnlineStateTask;
|
||||||
|
private @Nullable ScheduledFuture<?> scheduledUpdateTask;
|
||||||
|
private Map<Integer, Future<?>> resourcesEventTasks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private boolean assetsLoaded;
|
||||||
|
private int applKeyRetriesRemaining;
|
||||||
|
private int connectRetriesRemaining;
|
||||||
|
|
||||||
|
public Clip2BridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory, ThingRegistry thingRegistry,
|
||||||
|
LocaleProvider localeProvider, TranslationProvider translationProvider) {
|
||||||
|
super(bridge);
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
this.thingRegistry = thingRegistry;
|
||||||
|
this.bundle = FrameworkUtil.getBundle(getClass());
|
||||||
|
this.localeProvider = localeProvider;
|
||||||
|
this.translationProvider = translationProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the given task.
|
||||||
|
*
|
||||||
|
* @param cancelTask the task to be cancelled (may be null)
|
||||||
|
* @param mayInterrupt allows cancel() to interrupt the thread.
|
||||||
|
*/
|
||||||
|
private void cancelTask(@Nullable Future<?> cancelTask, boolean mayInterrupt) {
|
||||||
|
if (Objects.nonNull(cancelTask)) {
|
||||||
|
cancelTask.cancel(mayInterrupt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if assets are loaded.
|
||||||
|
*
|
||||||
|
* @throws AssetNotLoadedException if assets not loaded.
|
||||||
|
*/
|
||||||
|
private void checkAssetsLoaded() throws AssetNotLoadedException {
|
||||||
|
if (!assetsLoaded) {
|
||||||
|
throw new AssetNotLoadedException("Assets not loaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to connect and set the online status accordingly. If the connection attempt throws an
|
||||||
|
* HttpUnAuthorizedException then try to register the existing application key, or create a new one, with the hub.
|
||||||
|
* If the connection attempt throws an ApiException then set the thing status to offline. This method is called on a
|
||||||
|
* scheduler thread, which reschedules itself repeatedly until the thing is shutdown.
|
||||||
|
*/
|
||||||
|
private synchronized void checkConnection() {
|
||||||
|
logger.debug("checkConnection()");
|
||||||
|
|
||||||
|
// check connection to the hub
|
||||||
|
ThingStatusDetail thingStatus;
|
||||||
|
try {
|
||||||
|
checkAssetsLoaded();
|
||||||
|
getClip2Bridge().testConnectionState();
|
||||||
|
thingStatus = ThingStatusDetail.NONE;
|
||||||
|
} catch (HttpUnauthorizedException e) {
|
||||||
|
logger.debug("checkConnection() {}", e.getMessage(), e);
|
||||||
|
thingStatus = ThingStatusDetail.CONFIGURATION_ERROR;
|
||||||
|
} catch (ApiException e) {
|
||||||
|
logger.debug("checkConnection() {}", e.getMessage(), e);
|
||||||
|
thingStatus = ThingStatusDetail.COMMUNICATION_ERROR;
|
||||||
|
} catch (AssetNotLoadedException e) {
|
||||||
|
logger.debug("checkConnection() {}", e.getMessage(), e);
|
||||||
|
thingStatus = ThingStatusDetail.HANDLER_INITIALIZING_ERROR;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the thing status
|
||||||
|
boolean retryApplicationKey = false;
|
||||||
|
boolean retryConnection = false;
|
||||||
|
switch (thingStatus) {
|
||||||
|
case CONFIGURATION_ERROR:
|
||||||
|
if (applKeyRetriesRemaining > 0) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.press-pairing-button");
|
||||||
|
try {
|
||||||
|
registerApplicationKey();
|
||||||
|
retryApplicationKey = true;
|
||||||
|
} catch (HttpUnauthorizedException e) {
|
||||||
|
retryApplicationKey = true;
|
||||||
|
} catch (ApiException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.communication-error");
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.read-only");
|
||||||
|
} catch (AssetNotLoadedException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.assets-not-loaded");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.not-authorized");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMUNICATION_ERROR:
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.communication-error");
|
||||||
|
retryConnection = connectRetriesRemaining > 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HANDLER_INITIALIZING_ERROR:
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.assets-not-loaded");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
updateSelf(); // go online
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int milliSeconds;
|
||||||
|
if (retryApplicationKey) {
|
||||||
|
// short delay used during attempts to create or validate an application key
|
||||||
|
milliSeconds = FAST_SCHEDULE_MILLI_SECONDS;
|
||||||
|
applKeyRetriesRemaining--;
|
||||||
|
} else {
|
||||||
|
// default delay, set via configuration parameter, used as heart-beat 'just-in-case'
|
||||||
|
Clip2BridgeConfig config = getConfigAs(Clip2BridgeConfig.class);
|
||||||
|
milliSeconds = config.checkMinutes * 60000;
|
||||||
|
if (retryConnection) {
|
||||||
|
// exponential back off delay used during attempts to reconnect
|
||||||
|
int backOffDelay = 60000 * (int) Math.pow(2, RECONNECT_MAX_TRIES - connectRetriesRemaining);
|
||||||
|
milliSeconds = Math.min(milliSeconds, backOffDelay);
|
||||||
|
connectRetriesRemaining--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method schedules itself to be called again in a loop..
|
||||||
|
cancelTask(checkConnectionTask, false);
|
||||||
|
checkConnectionTask = scheduler.schedule(() -> checkConnection(), milliSeconds, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a child thing has been added, and the bridge is online, update the child's data.
|
||||||
|
*/
|
||||||
|
public void childInitialized() {
|
||||||
|
if (thing.getStatus() == ThingStatus.ONLINE) {
|
||||||
|
updateThingsScheduled(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (assetsLoaded) {
|
||||||
|
disposeAssets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose the bridge handler's assets. Called from dispose() on a thread, so that dispose() itself can complete
|
||||||
|
* faster.
|
||||||
|
*/
|
||||||
|
private void disposeAssets() {
|
||||||
|
logger.debug("disposeAssets() {}", this);
|
||||||
|
synchronized (this) {
|
||||||
|
assetsLoaded = false;
|
||||||
|
cancelTask(checkConnectionTask, true);
|
||||||
|
cancelTask(updateOnlineStateTask, true);
|
||||||
|
cancelTask(scheduledUpdateTask, true);
|
||||||
|
checkConnectionTask = null;
|
||||||
|
updateOnlineStateTask = null;
|
||||||
|
scheduledUpdateTask = null;
|
||||||
|
synchronized (resourcesEventTasks) {
|
||||||
|
resourcesEventTasks.values().forEach(task -> cancelTask(task, true));
|
||||||
|
resourcesEventTasks.clear();
|
||||||
|
}
|
||||||
|
ServiceRegistration<?> registration = trustManagerRegistration;
|
||||||
|
if (Objects.nonNull(registration)) {
|
||||||
|
registration.unregister();
|
||||||
|
trustManagerRegistration = null;
|
||||||
|
}
|
||||||
|
Clip2Bridge bridge = clip2Bridge;
|
||||||
|
if (Objects.nonNull(bridge)) {
|
||||||
|
bridge.close();
|
||||||
|
clip2Bridge = null;
|
||||||
|
}
|
||||||
|
Clip2ThingDiscoveryService disco = discoveryService;
|
||||||
|
if (Objects.nonNull(disco)) {
|
||||||
|
disco.abortScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the application key for the console app.
|
||||||
|
*
|
||||||
|
* @return the application key.
|
||||||
|
*/
|
||||||
|
public String getApplicationKey() {
|
||||||
|
Clip2BridgeConfig config = getConfigAs(Clip2BridgeConfig.class);
|
||||||
|
return config.applicationKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Clip2Bridge connection and throw an exception if it is null.
|
||||||
|
*
|
||||||
|
* @return the Clip2Bridge.
|
||||||
|
* @throws AssetNotLoadedException if the Clip2Bridge is null.
|
||||||
|
*/
|
||||||
|
private Clip2Bridge getClip2Bridge() throws AssetNotLoadedException {
|
||||||
|
Clip2Bridge clip2Bridge = this.clip2Bridge;
|
||||||
|
if (Objects.nonNull(clip2Bridge)) {
|
||||||
|
return clip2Bridge;
|
||||||
|
}
|
||||||
|
throw new AssetNotLoadedException("Clip2Bridge is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the IP address for the console app.
|
||||||
|
*
|
||||||
|
* @return the IP address.
|
||||||
|
*/
|
||||||
|
public String getIpAddress() {
|
||||||
|
Clip2BridgeConfig config = getConfigAs(Clip2BridgeConfig.class);
|
||||||
|
return config.ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the v1 legacy Hue bridge (if any) which has the same IP address as this.
|
||||||
|
*
|
||||||
|
* @return Optional result containing the legacy bridge (if any found).
|
||||||
|
*/
|
||||||
|
public Optional<Thing> getLegacyBridge() {
|
||||||
|
String ipAddress = getIpAddress();
|
||||||
|
return Objects.nonNull(ipAddress)
|
||||||
|
? thingRegistry.getAll().stream()
|
||||||
|
.filter(thing -> thing.getThingTypeUID().equals(THING_TYPE_BRIDGE)
|
||||||
|
&& ipAddress.equals(thing.getConfiguration().get("ipAddress")))
|
||||||
|
.findFirst()
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the v1 legacy Hue thing (if any) which has a Bridge having the same IP address as this, and an ID that
|
||||||
|
* matches the given parameter.
|
||||||
|
*
|
||||||
|
* @param targetIdV1 the idV1 attribute to match.
|
||||||
|
* @return Optional result containing the legacy thing (if found).
|
||||||
|
*/
|
||||||
|
public Optional<Thing> getLegacyThing(String targetIdV1) {
|
||||||
|
Optional<Thing> legacyBridge = getLegacyBridge();
|
||||||
|
if (legacyBridge.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String config;
|
||||||
|
if (targetIdV1.startsWith("/lights/")) {
|
||||||
|
config = LIGHT_ID;
|
||||||
|
} else if (targetIdV1.startsWith("/sensors/")) {
|
||||||
|
config = SENSOR_ID;
|
||||||
|
} else if (targetIdV1.startsWith("/groups/")) {
|
||||||
|
config = GROUP_ID;
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThingUID legacyBridgeUID = legacyBridge.get().getUID();
|
||||||
|
return thingRegistry.getAll().stream() //
|
||||||
|
.filter(thing -> legacyBridgeUID.equals(thing.getBridgeUID())
|
||||||
|
&& V1_THING_TYPE_UIDS.contains(thing.getThingTypeUID())) //
|
||||||
|
.filter(thing -> {
|
||||||
|
Object id = thing.getConfiguration().get(config);
|
||||||
|
return (id instanceof String) && targetIdV1.endsWith("/" + (String) id);
|
||||||
|
}).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a localized text.
|
||||||
|
*
|
||||||
|
* @param key the i18n text key.
|
||||||
|
* @param arguments for parameterized translation.
|
||||||
|
* @return the localized text.
|
||||||
|
*/
|
||||||
|
public String getLocalizedText(String key, @Nullable Object @Nullable... arguments) {
|
||||||
|
String result = translationProvider.getText(bundle, key, key, localeProvider.getLocale(), arguments);
|
||||||
|
return Objects.nonNull(result) ? result : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an HTTP GET for a resources reference object from the server.
|
||||||
|
*
|
||||||
|
* @param reference containing the resourceType and (optionally) the resourceId of the resource to get. If the
|
||||||
|
* resourceId is null then all resources of the given type are returned.
|
||||||
|
* @return the resource, or null if something fails.
|
||||||
|
* @throws ApiException if a communication error occurred.
|
||||||
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public Resources getResources(ResourceReference reference)
|
||||||
|
throws ApiException, AssetNotLoadedException, InterruptedException {
|
||||||
|
logger.debug("getResources() {}", reference);
|
||||||
|
checkAssetsLoaded();
|
||||||
|
return getClip2Bridge().getResources(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the scheduler.
|
||||||
|
*
|
||||||
|
* @return the scheduler.
|
||||||
|
*/
|
||||||
|
public ScheduledExecutorService getScheduler() {
|
||||||
|
return scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
|
return Set.of(Clip2ThingDiscoveryService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
if (RefreshType.REFRESH.equals(command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.warn("Bridge thing '{}' has no channels, only REFRESH command supported.", thing.getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
updateThingFromLegacy();
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
applKeyRetriesRemaining = APPLICATION_KEY_MAX_TRIES;
|
||||||
|
connectRetriesRemaining = RECONNECT_MAX_TRIES;
|
||||||
|
initializeAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the bridge handler's assets.
|
||||||
|
*/
|
||||||
|
private void initializeAssets() {
|
||||||
|
logger.debug("initializeAssets() {}", this);
|
||||||
|
synchronized (this) {
|
||||||
|
Clip2BridgeConfig config = getConfigAs(Clip2BridgeConfig.class);
|
||||||
|
|
||||||
|
String ipAddress = config.ipAddress;
|
||||||
|
if (ipAddress.isBlank()) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.conf-error-no-ip-address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Clip2Bridge.isClip2Supported(ipAddress)) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.clip2-not-supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.trace("initializeAssets() communication error on '{}'", ipAddress, e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HueTlsTrustManagerProvider trustManagerProvider = new HueTlsTrustManagerProvider(ipAddress + ":443",
|
||||||
|
config.useSelfSignedCertificate);
|
||||||
|
|
||||||
|
if (Objects.isNull(trustManagerProvider.getPEMTrustManager())) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.certificate-load");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trustManagerRegistration = FrameworkUtil.getBundle(getClass()).getBundleContext()
|
||||||
|
.registerService(TlsTrustManagerProvider.class.getName(), trustManagerProvider, null);
|
||||||
|
|
||||||
|
String applicationKey = config.applicationKey;
|
||||||
|
applicationKey = Objects.nonNull(applicationKey) ? applicationKey : "";
|
||||||
|
clip2Bridge = new Clip2Bridge(httpClientFactory, this, ipAddress, applicationKey);
|
||||||
|
|
||||||
|
assetsLoaded = true;
|
||||||
|
}
|
||||||
|
cancelTask(checkConnectionTask, false);
|
||||||
|
checkConnectionTask = scheduler.submit(() -> checkConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the connection goes offline. Schedule a reconnection.
|
||||||
|
*/
|
||||||
|
public void onConnectionOffline() {
|
||||||
|
if (assetsLoaded) {
|
||||||
|
try {
|
||||||
|
getClip2Bridge().setExternalRestartScheduled();
|
||||||
|
cancelTask(checkConnectionTask, false);
|
||||||
|
checkConnectionTask = scheduler.schedule(() -> checkConnection(), RECONNECT_DELAY_SECONDS,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
} catch (AssetNotLoadedException e) {
|
||||||
|
// should never occur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the connection goes online. Schedule a general state update.
|
||||||
|
*/
|
||||||
|
public void onConnectionOnline() {
|
||||||
|
cancelTask(updateOnlineStateTask, false);
|
||||||
|
updateOnlineStateTask = scheduler.schedule(() -> updateOnlineState(), 0, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an SSE event message comes in with a valid list of resources. For each resource received, inform all
|
||||||
|
* child thing handlers with the respective resource.
|
||||||
|
*
|
||||||
|
* @param resources a list of incoming resource objects.
|
||||||
|
*/
|
||||||
|
public void onResourcesEvent(List<Resource> resources) {
|
||||||
|
if (assetsLoaded) {
|
||||||
|
synchronized (resourcesEventTasks) {
|
||||||
|
int index = resourcesEventTasks.size();
|
||||||
|
resourcesEventTasks.put(index, scheduler.submit(() -> {
|
||||||
|
onResourcesEventTask(resources);
|
||||||
|
resourcesEventTasks.remove(index);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onResourcesEventTask(List<Resource> resources) {
|
||||||
|
logger.debug("onResourcesEventTask() resource count {}", resources.size());
|
||||||
|
getThing().getThings().forEach(thing -> {
|
||||||
|
ThingHandler handler = thing.getHandler();
|
||||||
|
if (handler instanceof Clip2ThingHandler) {
|
||||||
|
resources.forEach(resource -> {
|
||||||
|
((Clip2ThingHandler) handler).onResource(resource);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an HTTP PUT to send a Resource object to the server.
|
||||||
|
*
|
||||||
|
* @param resource the resource to put.
|
||||||
|
* @throws ApiException if a communication error occurred.
|
||||||
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void putResource(Resource resource) throws ApiException, AssetNotLoadedException, InterruptedException {
|
||||||
|
logger.debug("putResource() {}", resource);
|
||||||
|
checkAssetsLoaded();
|
||||||
|
getClip2Bridge().putResource(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the application key with the hub. If the current application key is empty it will create a new one.
|
||||||
|
*
|
||||||
|
* @throws HttpUnauthorizedException if the communication was OK but the registration failed anyway.
|
||||||
|
* @throws ApiException if a communication error occurred.
|
||||||
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
|
* @throws IllegalStateException if the configuration cannot be changed e.g. read only.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void registerApplicationKey() throws HttpUnauthorizedException, ApiException, AssetNotLoadedException,
|
||||||
|
IllegalStateException, InterruptedException {
|
||||||
|
logger.debug("registerApplicationKey()");
|
||||||
|
Clip2BridgeConfig config = getConfigAs(Clip2BridgeConfig.class);
|
||||||
|
String newApplicationKey = getClip2Bridge().registerApplicationKey(config.applicationKey);
|
||||||
|
Configuration configuration = editConfiguration();
|
||||||
|
configuration.put(Clip2BridgeConfig.APPLICATION_KEY, newApplicationKey);
|
||||||
|
updateConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the discovery service.
|
||||||
|
*
|
||||||
|
* @param discoveryService new discoveryService.
|
||||||
|
*/
|
||||||
|
public void registerDiscoveryService(Clip2ThingDiscoveryService discoveryService) {
|
||||||
|
this.discoveryService = discoveryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the discovery service.
|
||||||
|
*/
|
||||||
|
public void unregisterDiscoveryService() {
|
||||||
|
discoveryService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the bridge's online state and update its dependent things. Called when the connection goes online.
|
||||||
|
*/
|
||||||
|
private void updateOnlineState() {
|
||||||
|
if (assetsLoaded && (thing.getStatus() != ThingStatus.ONLINE)) {
|
||||||
|
logger.debug("updateOnlineState()");
|
||||||
|
connectRetriesRemaining = RECONNECT_MAX_TRIES;
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
updateThingsScheduled(500);
|
||||||
|
Clip2ThingDiscoveryService discoveryService = this.discoveryService;
|
||||||
|
if (Objects.nonNull(discoveryService)) {
|
||||||
|
discoveryService.startScan(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the bridge thing properties.
|
||||||
|
*
|
||||||
|
* @throws ApiException if a communication error occurred.
|
||||||
|
* @throws AssetNotLoadedException if one of the assets is not loaded.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void updateProperties() throws ApiException, AssetNotLoadedException, InterruptedException {
|
||||||
|
logger.debug("updateProperties()");
|
||||||
|
Map<String, String> properties = new HashMap<>(thing.getProperties());
|
||||||
|
|
||||||
|
for (Resource device : getClip2Bridge().getResources(BRIDGE).getResources()) {
|
||||||
|
// set the serial number
|
||||||
|
String bridgeId = device.getBridgeId();
|
||||||
|
if (Objects.nonNull(bridgeId)) {
|
||||||
|
properties.put(Thing.PROPERTY_SERIAL_NUMBER, bridgeId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Resource device : getClip2Bridge().getResources(DEVICE).getResources()) {
|
||||||
|
MetaData metaData = device.getMetaData();
|
||||||
|
if (Objects.nonNull(metaData) && metaData.getArchetype() == Archetype.BRIDGE_V2) {
|
||||||
|
// set resource properties
|
||||||
|
properties.put(PROPERTY_RESOURCE_ID, device.getId());
|
||||||
|
properties.put(PROPERTY_RESOURCE_TYPE, device.getType().toString());
|
||||||
|
|
||||||
|
// set metadata properties
|
||||||
|
String metaDataName = metaData.getName();
|
||||||
|
if (Objects.nonNull(metaDataName)) {
|
||||||
|
properties.put(PROPERTY_RESOURCE_NAME, metaDataName);
|
||||||
|
}
|
||||||
|
properties.put(PROPERTY_RESOURCE_ARCHETYPE, metaData.getArchetype().toString());
|
||||||
|
|
||||||
|
// set product data properties
|
||||||
|
ProductData productData = device.getProductData();
|
||||||
|
if (Objects.nonNull(productData)) {
|
||||||
|
// set generic thing properties
|
||||||
|
properties.put(Thing.PROPERTY_MODEL_ID, productData.getModelId());
|
||||||
|
properties.put(Thing.PROPERTY_VENDOR, productData.getManufacturerName());
|
||||||
|
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, productData.getSoftwareVersion());
|
||||||
|
String hardwarePlatformType = productData.getHardwarePlatformType();
|
||||||
|
if (Objects.nonNull(hardwarePlatformType)) {
|
||||||
|
properties.put(Thing.PROPERTY_HARDWARE_VERSION, hardwarePlatformType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set hue specific properties
|
||||||
|
properties.put(PROPERTY_PRODUCT_NAME, productData.getProductName());
|
||||||
|
properties.put(PROPERTY_PRODUCT_ARCHETYPE, productData.getProductArchetype().toString());
|
||||||
|
properties.put(PROPERTY_PRODUCT_CERTIFIED, productData.getCertified().toString());
|
||||||
|
}
|
||||||
|
break; // we only needed the BRIDGE_V2 resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thing.setProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the thing's own state. Called sporadically in case any SSE events may have been lost.
|
||||||
|
*/
|
||||||
|
private void updateSelf() {
|
||||||
|
logger.debug("updateSelf()");
|
||||||
|
try {
|
||||||
|
checkAssetsLoaded();
|
||||||
|
updateProperties();
|
||||||
|
getClip2Bridge().open();
|
||||||
|
} catch (ApiException e) {
|
||||||
|
logger.trace("updateSelf() {}", e.getMessage(), e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/offline.api2.comm-error.exception [\"" + e.getMessage() + "\"]");
|
||||||
|
onConnectionOffline();
|
||||||
|
} catch (AssetNotLoadedException e) {
|
||||||
|
logger.trace("updateSelf() {}", e.getMessage(), e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"@text/offline.api2.conf-error.assets-not-loaded");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a PROPERTY_LEGACY_THING_UID value was set by the discovery process, and if so, clone the legacy thing's
|
||||||
|
* settings into this thing.
|
||||||
|
*/
|
||||||
|
private void updateThingFromLegacy() {
|
||||||
|
if (isInitialized()) {
|
||||||
|
logger.warn("Cannot update bridge thing '{}' from legacy since handler already initialized.",
|
||||||
|
thing.getUID());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, String> properties = thing.getProperties();
|
||||||
|
String legacyThingUID = properties.get(PROPERTY_LEGACY_THING_UID);
|
||||||
|
if (Objects.nonNull(legacyThingUID)) {
|
||||||
|
Thing legacyThing = thingRegistry.get(new ThingUID(legacyThingUID));
|
||||||
|
if (Objects.nonNull(legacyThing)) {
|
||||||
|
BridgeBuilder editBuilder = editThing();
|
||||||
|
|
||||||
|
String location = legacyThing.getLocation();
|
||||||
|
if (Objects.nonNull(location) && !location.isBlank()) {
|
||||||
|
editBuilder = editBuilder.withLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object userName = legacyThing.getConfiguration().get(USER_NAME);
|
||||||
|
if (userName instanceof String) {
|
||||||
|
Configuration configuration = thing.getConfiguration();
|
||||||
|
configuration.put(Clip2BridgeConfig.APPLICATION_KEY, userName);
|
||||||
|
editBuilder = editBuilder.withConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> newProperties = new HashMap<>(properties);
|
||||||
|
newProperties.remove(PROPERTY_LEGACY_THING_UID);
|
||||||
|
|
||||||
|
updateThing(editBuilder.withProperties(newProperties).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the mass download of all relevant resource types, and inform all child thing handlers.
|
||||||
|
*/
|
||||||
|
private void updateThingsNow() {
|
||||||
|
logger.debug("updateThingsNow()");
|
||||||
|
try {
|
||||||
|
Clip2Bridge bridge = getClip2Bridge();
|
||||||
|
for (ResourceReference reference : MASS_DOWNLOAD_RESOURCE_REFERENCES) {
|
||||||
|
ResourceType resourceType = reference.getType();
|
||||||
|
List<Resource> resourceList = bridge.getResources(reference).getResources();
|
||||||
|
if (resourceType == ResourceType.ZONE) {
|
||||||
|
// add special 'All Lights' zone to the zone resource list
|
||||||
|
resourceList.addAll(bridge.getResources(BRIDGE_HOME).getResources());
|
||||||
|
}
|
||||||
|
getThing().getThings().forEach(thing -> {
|
||||||
|
ThingHandler handler = thing.getHandler();
|
||||||
|
if (handler instanceof Clip2ThingHandler) {
|
||||||
|
((Clip2ThingHandler) handler).onResourcesList(resourceType, resourceList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (ApiException | AssetNotLoadedException e) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("updateThingsNow() unexpected exception", e);
|
||||||
|
} else {
|
||||||
|
logger.warn("Unexpected exception '{}' while updating things.", e.getMessage());
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a task to call updateThings(). It prevents floods of GET calls when multiple child things are added at
|
||||||
|
* the same time.
|
||||||
|
*
|
||||||
|
* @param delayMilliSeconds the delay before running the next task.
|
||||||
|
*/
|
||||||
|
private void updateThingsScheduled(int delayMilliSeconds) {
|
||||||
|
ScheduledFuture<?> task = this.scheduledUpdateTask;
|
||||||
|
if (Objects.isNull(task) || task.getDelay(TimeUnit.MILLISECONDS) < 100) {
|
||||||
|
cancelTask(scheduledUpdateTask, false);
|
||||||
|
scheduledUpdateTask = scheduler.schedule(() -> updateThingsNow(), delayMilliSeconds, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.events.EventPublisher;
|
||||||
|
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||||
|
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||||
|
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||||
|
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Clip2StateDescriptionProvider} provides dynamic state descriptions of scene channels whose list of options
|
||||||
|
* is determined at runtime.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(service = { DynamicStateDescriptionProvider.class, Clip2StateDescriptionProvider.class })
|
||||||
|
public class Clip2StateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public Clip2StateDescriptionProvider(final @Reference EventPublisher eventPublisher,
|
||||||
|
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
|
||||||
|
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
|
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||||
|
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -180,7 +180,7 @@ public class HueLightHandler extends BaseThingHandler implements HueLightActions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties.put(PROPERTY_VENDOR, fullLight.getManufacturerName());
|
properties.put(PROPERTY_VENDOR, fullLight.getManufacturerName());
|
||||||
properties.put(PRODUCT_NAME, fullLight.getProductName());
|
properties.put(PROPERTY_PRODUCT_NAME, fullLight.getProductName());
|
||||||
String uniqueID = fullLight.getUniqueID();
|
String uniqueID = fullLight.getUniqueID();
|
||||||
if (uniqueID != null) {
|
if (uniqueID != null) {
|
||||||
properties.put(UNIQUE_ID, uniqueID);
|
properties.put(UNIQUE_ID, uniqueID);
|
||||||
|
@ -118,7 +118,7 @@ public abstract class HueSensorHandler extends BaseThingHandler implements Senso
|
|||||||
properties.put(PROPERTY_MODEL_ID, modelId);
|
properties.put(PROPERTY_MODEL_ID, modelId);
|
||||||
}
|
}
|
||||||
properties.put(PROPERTY_VENDOR, fullSensor.getManufacturerName());
|
properties.put(PROPERTY_VENDOR, fullSensor.getManufacturerName());
|
||||||
properties.put(PRODUCT_NAME, fullSensor.getProductName());
|
properties.put(PROPERTY_PRODUCT_NAME, fullSensor.getProductName());
|
||||||
String uniqueID = fullSensor.getUniqueID();
|
String uniqueID = fullSensor.getUniqueID();
|
||||||
if (uniqueID != null) {
|
if (uniqueID != null) {
|
||||||
properties.put(UNIQUE_ID, uniqueID);
|
properties.put(UNIQUE_ID, uniqueID);
|
||||||
|
@ -33,15 +33,54 @@ thing-type.hue.0840.label = CLIP Generic Status Sensor
|
|||||||
thing-type.hue.0840.description = A generic sensor object for IP sensor use.
|
thing-type.hue.0840.description = A generic sensor object for IP sensor use.
|
||||||
thing-type.hue.0850.label = CLIP Generic Flag Sensor
|
thing-type.hue.0850.label = CLIP Generic Flag Sensor
|
||||||
thing-type.hue.0850.description = A generic sensor object for IP sensor use.
|
thing-type.hue.0850.description = A generic sensor object for IP sensor use.
|
||||||
|
thing-type.hue.bridge-api2.label = Hue API v2 Bridge
|
||||||
|
thing-type.hue.bridge-api2.description = The Hue Bridge represents a Philips Hue Bridge supporting API v2.
|
||||||
thing-type.hue.bridge.label = Hue Bridge
|
thing-type.hue.bridge.label = Hue Bridge
|
||||||
thing-type.hue.bridge.description = The Hue Bridge represents the Philips Hue Bridge.
|
thing-type.hue.bridge.description = The Hue Bridge represents the Philips Hue Bridge.
|
||||||
|
thing-type.hue.device.label = Hue Device
|
||||||
|
thing-type.hue.device.description = A Hue API v2 device with channels depending on its actual capabilities.
|
||||||
|
thing-type.hue.device.channel.alert.description = Activate the alert for the light.
|
||||||
|
thing-type.hue.device.channel.color-xy-only.description = Set the color xy parameter of the light without changing other state parameters.
|
||||||
|
thing-type.hue.device.channel.dimming-only.description = Set the dimming parameter of the light without changing other state parameters.
|
||||||
|
thing-type.hue.device.channel.effect.description = Activate the effect for the light.
|
||||||
|
thing-type.hue.device.channel.light-level.description = Current light level.
|
||||||
|
thing-type.hue.device.channel.light-level-enabled.description = Light level sensor enabled.
|
||||||
|
thing-type.hue.device.channel.motion-enabled.description = Motion sensor enabled.
|
||||||
|
thing-type.hue.device.channel.on-off-only.description = Set the on/off parameter of the light without changing other state parameters.
|
||||||
|
thing-type.hue.device.channel.temperature.label = Temperature
|
||||||
|
thing-type.hue.device.channel.temperature.description = Temperature at the sensor location.
|
||||||
|
thing-type.hue.device.channel.temperature-enabled.description = Temperature sensor enabled.
|
||||||
thing-type.hue.geofencesensor.label = Geofence Sensor
|
thing-type.hue.geofencesensor.label = Geofence Sensor
|
||||||
thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection.
|
thing-type.hue.geofencesensor.description = A sensor providing geofence based presence detection.
|
||||||
thing-type.hue.group.label = Hue Group
|
thing-type.hue.group.label = Hue Group
|
||||||
thing-type.hue.group.description = A group of lights or a room that could be switched on and off.
|
thing-type.hue.group.description = A group of lights or a room that could be switched on and off.
|
||||||
|
thing-type.hue.room.label = Hue Room Light Group
|
||||||
|
thing-type.hue.room.description = A group of Hue API v2 lights that are located in a single room.
|
||||||
|
thing-type.hue.room.channel.alert.description = Activate the alert for the group of lights in the room.
|
||||||
|
thing-type.hue.room.channel.brightness.description = Controls the brightness and switches on/off the group of lights in the room.
|
||||||
|
thing-type.hue.room.channel.dimming-only.description = Set the dimming parameter of the group of lights in the room without changing other state parameters.
|
||||||
|
thing-type.hue.room.channel.on-off-only.description = Set the on/off parameter of the group of lights in the room without changing other state parameters.
|
||||||
|
thing-type.hue.room.channel.scene.description = Activate the scene for the group of lights in the room.
|
||||||
|
thing-type.hue.room.channel.switch.description = Switch on/off the group of lights in the room.
|
||||||
|
thing-type.hue.zone.label = Hue Zone Light Group
|
||||||
|
thing-type.hue.zone.description = A group of Hue API v2 lights that are located in a zone.
|
||||||
|
thing-type.hue.zone.channel.alert.description = Activate the alert for the group of lights in the zone.
|
||||||
|
thing-type.hue.zone.channel.brightness.description = Controls the brightness and switches on/off the group of lights in the zone.
|
||||||
|
thing-type.hue.zone.channel.dimming-only.description = Set the dimming parameter of the group of lights in the zone without changing other state parameters.
|
||||||
|
thing-type.hue.zone.channel.on-off-only.description = Set the on/off parameter of the group of lights in the zone without changing other state parameters.
|
||||||
|
thing-type.hue.zone.channel.scene.description = Activate the scene for the group of lights in the zone.
|
||||||
|
thing-type.hue.zone.channel.switch.description = Switch on/off the group of lights in the zone.
|
||||||
|
|
||||||
# thing types config
|
# thing types config
|
||||||
|
|
||||||
|
thing-type.config.hue.bridge-api2.applicationKey.label = Application Key
|
||||||
|
thing-type.config.hue.bridge-api2.applicationKey.description = A registered Hue Bridge application key that allows access to the API.
|
||||||
|
thing-type.config.hue.bridge-api2.checkMinutes.label = Connection Check Interval
|
||||||
|
thing-type.config.hue.bridge-api2.checkMinutes.description = Minutes between retrying the HTTP 2 and SSE connections. Default is 60.
|
||||||
|
thing-type.config.hue.bridge-api2.ipAddress.label = Network Address
|
||||||
|
thing-type.config.hue.bridge-api2.ipAddress.description = Network address of the Hue Bridge.
|
||||||
|
thing-type.config.hue.bridge-api2.useSelfSignedCertificate.label = Use Self-Signed Certificate
|
||||||
|
thing-type.config.hue.bridge-api2.useSelfSignedCertificate.description = Use self-signed certificate for HTTPS connection to Hue Bridge.
|
||||||
thing-type.config.hue.bridge.ipAddress.label = Network Address
|
thing-type.config.hue.bridge.ipAddress.label = Network Address
|
||||||
thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue Bridge.
|
thing-type.config.hue.bridge.ipAddress.description = Network address of the Hue Bridge.
|
||||||
thing-type.config.hue.bridge.pollingInterval.label = Polling Interval
|
thing-type.config.hue.bridge.pollingInterval.label = Polling Interval
|
||||||
@ -58,6 +97,8 @@ thing-type.config.hue.bridge.useSelfSignedCertificate.label = Use Self-Signed Ce
|
|||||||
thing-type.config.hue.bridge.useSelfSignedCertificate.description = Use self-signed certificate for HTTPS connection to Hue Bridge.
|
thing-type.config.hue.bridge.useSelfSignedCertificate.description = Use self-signed certificate for HTTPS connection to Hue Bridge.
|
||||||
thing-type.config.hue.bridge.userName.label = Username
|
thing-type.config.hue.bridge.userName.label = Username
|
||||||
thing-type.config.hue.bridge.userName.description = Name of a registered Hue Bridge user, that allows to access the API.
|
thing-type.config.hue.bridge.userName.description = Name of a registered Hue Bridge user, that allows to access the API.
|
||||||
|
thing-type.config.hue.device.resourceId.label = Resource ID
|
||||||
|
thing-type.config.hue.device.resourceId.description = Unique Resource ID of the device in the Hue bridge
|
||||||
thing-type.config.hue.group.groupId.label = Group ID
|
thing-type.config.hue.group.groupId.label = Group ID
|
||||||
thing-type.config.hue.group.groupId.description = The group identifier identifies one certain Hue group or room.
|
thing-type.config.hue.group.groupId.description = The group identifier identifies one certain Hue group or room.
|
||||||
thing-type.config.hue.lightlevelsensor.tholddark.label = Threshold Dark
|
thing-type.config.hue.lightlevelsensor.tholddark.label = Threshold Dark
|
||||||
@ -68,14 +109,24 @@ thing-type.config.hue.presencesensor.sensitivity.label = Sensitivity
|
|||||||
thing-type.config.hue.presencesensor.sensitivity.description = The current sensitivity of the presence sensor. Cannot exceed maximum sensitivity.
|
thing-type.config.hue.presencesensor.sensitivity.description = The current sensitivity of the presence sensor. Cannot exceed maximum sensitivity.
|
||||||
thing-type.config.hue.presencesensor.sensitivitymax.label = Maximum Sensitivity
|
thing-type.config.hue.presencesensor.sensitivitymax.label = Maximum Sensitivity
|
||||||
thing-type.config.hue.presencesensor.sensitivitymax.description = The maximum sensitivity of the presence sensor.
|
thing-type.config.hue.presencesensor.sensitivitymax.description = The maximum sensitivity of the presence sensor.
|
||||||
|
thing-type.config.hue.room.resourceId.label = Resource ID
|
||||||
|
thing-type.config.hue.room.resourceId.description = Unique Resource ID of the room in the Hue bridge
|
||||||
|
thing-type.config.hue.zone.resourceId.label = Resource ID
|
||||||
|
thing-type.config.hue.zone.resourceId.description = Unique Resource ID of the zone in the Hue bridge
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
channel-type.hue.advanced-brightness.label = Dimming Only
|
||||||
|
channel-type.hue.advanced-color.label = Color XY Only
|
||||||
|
channel-type.hue.advanced-power.label = On/Off Only
|
||||||
|
channel-type.hue.alert-v2.label = Alert
|
||||||
channel-type.hue.alert.label = Alert
|
channel-type.hue.alert.label = Alert
|
||||||
channel-type.hue.alert.description = The alert channel allows a temporary change to the bulb’s state.
|
channel-type.hue.alert.description = The alert channel allows a temporary change to the bulb’s state.
|
||||||
channel-type.hue.alert.state.option.NONE = None
|
channel-type.hue.alert.state.option.NONE = None
|
||||||
channel-type.hue.alert.state.option.SELECT = Alert
|
channel-type.hue.alert.state.option.SELECT = Alert
|
||||||
channel-type.hue.alert.state.option.LSELECT = Long Alert
|
channel-type.hue.alert.state.option.LSELECT = Long Alert
|
||||||
|
channel-type.hue.button-last-event.label = Button Last Event
|
||||||
|
channel-type.hue.button-last-event.description = Numeric code (e.g. 1003) representing the last push button event.
|
||||||
channel-type.hue.dark.label = Dark
|
channel-type.hue.dark.label = Dark
|
||||||
channel-type.hue.dark.description = Light level is below the darkness threshold.
|
channel-type.hue.dark.description = Light level is below the darkness threshold.
|
||||||
channel-type.hue.daylight.label = Daylight
|
channel-type.hue.daylight.label = Daylight
|
||||||
@ -100,19 +151,29 @@ channel-type.hue.dimmer_switch.state.option.4002 = Off (Short Released)
|
|||||||
channel-type.hue.dimmer_switch.state.option.4003 = Off (Long Released)
|
channel-type.hue.dimmer_switch.state.option.4003 = Off (Long Released)
|
||||||
channel-type.hue.dimmer_switch_event.label = Dimmer Switch Event
|
channel-type.hue.dimmer_switch_event.label = Dimmer Switch Event
|
||||||
channel-type.hue.dimmer_switch_event.description = Triggers when a button is pressed on the dimmer switch.
|
channel-type.hue.dimmer_switch_event.description = Triggers when a button is pressed on the dimmer switch.
|
||||||
|
channel-type.hue.dynamics.label = Dynamics
|
||||||
|
channel-type.hue.dynamics.description = The duration (ms) of dynamic transitions between light or scene states.
|
||||||
|
channel-type.hue.effect-v2.label = Effect
|
||||||
channel-type.hue.effect.label = Color Loop
|
channel-type.hue.effect.label = Color Loop
|
||||||
channel-type.hue.effect.description = The effect channel allows putting the bulb in a color looping mode.
|
channel-type.hue.effect.description = The effect channel allows putting the bulb in a color looping mode.
|
||||||
channel-type.hue.flag.label = Flag
|
channel-type.hue.flag.label = Flag
|
||||||
channel-type.hue.flag.description = Flag of CLIP sensor.
|
channel-type.hue.flag.description = Flag of CLIP sensor.
|
||||||
channel-type.hue.illuminance.label = Illuminance
|
channel-type.hue.illuminance.label = Illuminance
|
||||||
channel-type.hue.illuminance.description = Current illuminance.
|
channel-type.hue.illuminance.description = Current illuminance.
|
||||||
|
channel-type.hue.last-updated-v2.label = Last Updated
|
||||||
|
channel-type.hue.last-updated-v2.description = The date and time when the thing was last updated.
|
||||||
|
channel-type.hue.last-updated-v2.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
|
||||||
channel-type.hue.last_updated.label = Last Updated
|
channel-type.hue.last_updated.label = Last Updated
|
||||||
channel-type.hue.last_updated.description = The date and time when the sensor was last updated.
|
channel-type.hue.last_updated.description = The date and time when the sensor was last updated.
|
||||||
channel-type.hue.last_updated.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
|
channel-type.hue.last_updated.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
|
||||||
channel-type.hue.light_level.label = Light Level
|
channel-type.hue.light_level.label = Light Level
|
||||||
channel-type.hue.light_level.description = Current light level.
|
channel-type.hue.light_level.description = Current light level.
|
||||||
|
channel-type.hue.rotary-steps.label = Rotary Steps
|
||||||
|
channel-type.hue.rotary-steps.description = The last 'steps' value (e.g. +/-30) of the rotary dial.
|
||||||
|
channel-type.hue.scene-v2.label = Scene
|
||||||
channel-type.hue.scene.label = Scene
|
channel-type.hue.scene.label = Scene
|
||||||
channel-type.hue.scene.description = The scene channel allows recalling a scene to all lights that belong to the scene.
|
channel-type.hue.scene.description = The scene channel allows recalling a scene to all lights that belong to the scene.
|
||||||
|
channel-type.hue.sensor-enabled.label = Sensor Enabled
|
||||||
channel-type.hue.status.label = Status
|
channel-type.hue.status.label = Status
|
||||||
channel-type.hue.status.description = Status of CLIP sensor.
|
channel-type.hue.status.description = Status of CLIP sensor.
|
||||||
channel-type.hue.tap_switch.label = Tap Switch State
|
channel-type.hue.tap_switch.label = Tap Switch State
|
||||||
@ -168,6 +229,22 @@ offline.light-removed = Hue Bridge reports light as removed.
|
|||||||
offline.sensor-removed = Hue Bridge reports sensor as removed.
|
offline.sensor-removed = Hue Bridge reports sensor as removed.
|
||||||
offline.group-removed = Hue Bridge reports group as removed.
|
offline.group-removed = Hue Bridge reports group as removed.
|
||||||
|
|
||||||
|
# api v2 offline configuration error descriptions
|
||||||
|
|
||||||
|
offline.api2.comm-error.zigbee-connectivity-issue = Zigbee connectivity issue.
|
||||||
|
offline.api2.comm-error.exception = An unexpected exception '{}' occurred.
|
||||||
|
offline.api2.conf-error.certificate-load = Certificate loading failed. Please check your configuration settings (network address, type of certificate).
|
||||||
|
offline.api2.conf-error.assets-not-loaded = Bridge/Thing handler assets not loaded.
|
||||||
|
offline.api2.conf-error.press-pairing-button = Not authenticated. Press pairing button on the Hue Bridge or set a valid application key in configuration.
|
||||||
|
offline.api2.conf-error.read-only = Configuration update failed. Please update the configuration manually.
|
||||||
|
offline.api2.conf-error.clip2-not-supported = The Hue Bridge does not support API v2.
|
||||||
|
offline.api2.conf-error.resource-id-bad = Configuration resourceId is bad.
|
||||||
|
offline.api2.conf-error.not-authorized = The application key is not authorized.
|
||||||
|
|
||||||
|
# scene channel description
|
||||||
|
|
||||||
|
scene.channel.activate = Activate the scene ''{0}''
|
||||||
|
|
||||||
# lightactions
|
# lightactions
|
||||||
|
|
||||||
actionInputChannelLabel = Channel
|
actionInputChannelLabel = Channel
|
||||||
@ -181,4 +258,15 @@ actionDesc = Send a light command with a custom fade time.
|
|||||||
|
|
||||||
# discovery results
|
# discovery results
|
||||||
|
|
||||||
discovery.group.all_lights.label = All lights
|
discovery.group.all-lights.label = All lights
|
||||||
|
|
||||||
|
# api v2 dynamic actions
|
||||||
|
|
||||||
|
dynamics.action.label = send a dynamic command
|
||||||
|
dynamics.action.description = Sends a dynamic command to a device, room or zone.
|
||||||
|
dynamics.channel.label = Target Channel
|
||||||
|
dynamics.channel.description = The channel ID of the channel to send the command to.
|
||||||
|
dynamics.command.label = Target Command
|
||||||
|
dynamics.command.description = The target command state for the light(s) to transition to.
|
||||||
|
dynamics.duration.label = Duration
|
||||||
|
dynamics.duration.description = The dynamic transition duration in ms.
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="hue" 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">
|
||||||
|
|
||||||
|
<!-- API v2 device thing -->
|
||||||
|
<thing-type id="device">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bridge-api2"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Hue Device</label>
|
||||||
|
<description>A Hue API v2 device with channels depending on its actual capabilities.</description>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="color" typeId="system.color"/>
|
||||||
|
<channel id="color-temperature" typeId="system.color-temperature"/>
|
||||||
|
<channel id="brightness" typeId="system.brightness"/>
|
||||||
|
<channel id="switch" typeId="system.power"/>
|
||||||
|
<channel id="alert" typeId="alert-v2">
|
||||||
|
<description>Activate the alert for the light.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="effect" typeId="effect-v2">
|
||||||
|
<description>Activate the effect for the light.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="button-last-event" typeId="button-last-event"/>
|
||||||
|
<channel id="rotary-steps" typeId="rotary-steps"/>
|
||||||
|
<channel id="motion" typeId="system.motion"/>
|
||||||
|
<channel id="motion-enabled" typeId="sensor-enabled">
|
||||||
|
<description>Motion sensor enabled.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="light-level" typeId="illuminance">
|
||||||
|
<description>Current light level.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="light-level-enabled" typeId="sensor-enabled">
|
||||||
|
<description>Light level sensor enabled.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="temperature" typeId="system.indoor-temperature">
|
||||||
|
<label>Temperature</label>
|
||||||
|
<description>Temperature at the sensor location.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="temperature-enabled" typeId="sensor-enabled">
|
||||||
|
<description>Temperature sensor enabled.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="battery-level" typeId="system.battery-level"/>
|
||||||
|
<channel id="battery-low" typeId="system.low-battery"/>
|
||||||
|
<channel id="last-updated" typeId="last-updated-v2"/>
|
||||||
|
<channel id="dynamics" typeId="dynamics"/>
|
||||||
|
<channel id="color-temperature-abs" typeId="system.color-temperature-abs"/>
|
||||||
|
<channel id="color-xy-only" typeId="advanced-color">
|
||||||
|
<description>Set the color xy parameter of the light without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="dimming-only" typeId="advanced-brightness">
|
||||||
|
<description>Set the dimming parameter of the light without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="on-off-only" typeId="advanced-power">
|
||||||
|
<description>Set the on/off parameter of the light without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<representation-property>resourceId</representation-property>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="resourceId" type="text" required="true">
|
||||||
|
<label>Resource ID</label>
|
||||||
|
<description>Unique Resource ID of the device in the Hue bridge</description>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<!-- API v2 room thing -->
|
||||||
|
<thing-type id="room">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bridge-api2"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Hue Room Light Group</label>
|
||||||
|
<description>A group of Hue API v2 lights that are located in a single room.</description>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="brightness" typeId="system.brightness">
|
||||||
|
<description>Controls the brightness and switches on/off the group of lights in the room.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="switch" typeId="system.power">
|
||||||
|
<description>Switch on/off the group of lights in the room.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="scene" typeId="scene-v2">
|
||||||
|
<description>Activate the scene for the group of lights in the room.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="alert" typeId="alert-v2">
|
||||||
|
<description>Activate the alert for the group of lights in the room.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="dynamics" typeId="dynamics"/>
|
||||||
|
<channel id="dimming-only" typeId="advanced-brightness">
|
||||||
|
<description>Set the dimming parameter of the group of lights in the room without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="on-off-only" typeId="advanced-power">
|
||||||
|
<description>Set the on/off parameter of the group of lights in the room without changing other state
|
||||||
|
parameters.</description>
|
||||||
|
</channel>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<representation-property>resourceId</representation-property>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="resourceId" type="text" required="true">
|
||||||
|
<label>Resource ID</label>
|
||||||
|
<description>Unique Resource ID of the room in the Hue bridge</description>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<!-- API v2 zone thing -->
|
||||||
|
<thing-type id="zone">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="bridge-api2"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>Hue Zone Light Group</label>
|
||||||
|
<description>A group of Hue API v2 lights that are located in a zone.</description>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="brightness" typeId="system.brightness">
|
||||||
|
<description>Controls the brightness and switches on/off the group of lights in the zone.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="switch" typeId="system.power">
|
||||||
|
<description>Switch on/off the group of lights in the zone.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="scene" typeId="scene-v2">
|
||||||
|
<description>Activate the scene for the group of lights in the zone.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="alert" typeId="alert-v2">
|
||||||
|
<description>Activate the alert for the group of lights in the zone.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="dynamics" typeId="dynamics"/>
|
||||||
|
<channel id="dimming-only" typeId="advanced-brightness">
|
||||||
|
<description>Set the dimming parameter of the group of lights in the zone without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="on-off-only" typeId="advanced-power">
|
||||||
|
<description>Set the on/off parameter of the group of lights in the zone without changing other state parameters.</description>
|
||||||
|
</channel>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<representation-property>resourceId</representation-property>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="resourceId" type="text" required="true">
|
||||||
|
<label>Resource ID</label>
|
||||||
|
<description>Unique Resource ID of the zone in the Hue bridge</description>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -62,4 +62,37 @@
|
|||||||
</config-description>
|
</config-description>
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
|
||||||
|
<!-- API v2 Bridge -->
|
||||||
|
<bridge-type id="bridge-api2">
|
||||||
|
<label>Hue API v2 Bridge</label>
|
||||||
|
<description>The Hue Bridge represents a Philips Hue Bridge supporting API v2.</description>
|
||||||
|
|
||||||
|
<representation-property>serialNumber</representation-property>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="ipAddress" type="text" required="true">
|
||||||
|
<context>network-address</context>
|
||||||
|
<label>Network Address</label>
|
||||||
|
<description>Network address of the Hue Bridge.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="applicationKey" type="text">
|
||||||
|
<context>password</context>
|
||||||
|
<label>Application Key</label>
|
||||||
|
<description>A registered Hue Bridge application key that allows access to the API.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="checkMinutes" type="integer" min="10" step="1" unit="min">
|
||||||
|
<label>Connection Check Interval</label>
|
||||||
|
<description>Minutes between retrying the HTTP 2 and SSE connections. Default is 60.</description>
|
||||||
|
<default>60</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="useSelfSignedCertificate" type="boolean">
|
||||||
|
<label>Use Self-Signed Certificate</label>
|
||||||
|
<description>Use self-signed certificate for HTTPS connection to Hue Bridge.</description>
|
||||||
|
<default>true</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</bridge-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="light_level" advanced="true">
|
<channel-type id="light_level">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Light Level</label>
|
<label>Light Level</label>
|
||||||
<description>Current light level.</description>
|
<description>Current light level.</description>
|
||||||
@ -180,4 +180,80 @@
|
|||||||
<label>Scene</label>
|
<label>Scene</label>
|
||||||
<description>The scene channel allows recalling a scene to all lights that belong to the scene.</description>
|
<description>The scene channel allows recalling a scene to all lights that belong to the scene.</description>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<!-- API v2 Channel Types -->
|
||||||
|
<channel-type id="button-last-event">
|
||||||
|
<kind>trigger</kind>
|
||||||
|
<label>Button Last Event</label>
|
||||||
|
<description>Numeric code (e.g. 1003) representing the last push button event.</description>
|
||||||
|
<event/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="rotary-steps">
|
||||||
|
<kind>trigger</kind>
|
||||||
|
<label>Rotary Steps</label>
|
||||||
|
<description>The last 'steps' value (e.g. +/-30) of the rotary dial.</description>
|
||||||
|
<event/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="sensor-enabled" advanced="true">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Sensor Enabled</label>
|
||||||
|
<category>Lock</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="scene-v2" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Scene</label>
|
||||||
|
<category>MediaControl</category>
|
||||||
|
<state pattern="%s"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="last-updated-v2" advanced="true">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Last Updated</label>
|
||||||
|
<description>The date and time when the thing was last updated.</description>
|
||||||
|
<category>Time</category>
|
||||||
|
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="alert-v2" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Alert</label>
|
||||||
|
<state pattern="%s"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="effect-v2" advanced="true">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Effect</label>
|
||||||
|
<state pattern="%s"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="dynamics" advanced="true">
|
||||||
|
<item-type>Number:Time</item-type>
|
||||||
|
<label>Dynamics</label>
|
||||||
|
<description>The duration (ms) of dynamic transitions between light or scene states.</description>
|
||||||
|
<category>Time</category>
|
||||||
|
<state min="0" step="100" pattern="%d ms"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="advanced-color" advanced="true">
|
||||||
|
<item-type>Color</item-type>
|
||||||
|
<label>Color XY Only</label>
|
||||||
|
<category>ColorLight</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="advanced-brightness" advanced="true">
|
||||||
|
<item-type>Dimmer</item-type>
|
||||||
|
<label>Dimming Only</label>
|
||||||
|
<category>Light</category>
|
||||||
|
<state pattern="%.1f %%"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="advanced-power" advanced="true">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>On/Off Only</label>
|
||||||
|
<category>Switch</category>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -0,0 +1,600 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.hue.internal.clip2;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ActionEntry;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Alerts;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Button;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Dimming;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Event;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.LightLevel;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MetaData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.MirekSchema;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Motion;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Power;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ProductData;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.RelativeRotary;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resource;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.ResourceReference;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Resources;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Rotation;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.RotationEvent;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.Temperature;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ActionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.Archetype;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.BatteryStateType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ButtonEventType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.DirectionType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ResourceType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.RotationEventType;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.enums.ZigbeeStatus;
|
||||||
|
import org.openhab.binding.hue.internal.dto.clip2.helper.Setters;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.HSBType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.openhab.core.util.ColorUtil;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit test for CLIP 2 DTOs.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
class Clip2DtoTest {
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
private static final Double MINIMUM_DIMMING_LEVEL = Double.valueOf(12.34f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the test JSON payload string from a file
|
||||||
|
*/
|
||||||
|
private String load(String fileName) {
|
||||||
|
try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
|
||||||
|
BufferedReader reader = new BufferedReader(file)) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line).append("\n");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testButton() {
|
||||||
|
String json = load(ResourceType.BUTTON.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(43, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.BUTTON, item.getType());
|
||||||
|
Button button = item.getButton();
|
||||||
|
assertNotNull(button);
|
||||||
|
assertEquals(ButtonEventType.SHORT_RELEASE, button.getLastEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDevice() {
|
||||||
|
String json = load(ResourceType.DEVICE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(34, list.size());
|
||||||
|
boolean itemFound = false;
|
||||||
|
for (Resource item : list) {
|
||||||
|
assertEquals(ResourceType.DEVICE, item.getType());
|
||||||
|
ProductData productData = item.getProductData();
|
||||||
|
assertNotNull(productData);
|
||||||
|
if (productData.getProductArchetype() == Archetype.BRIDGE_V2) {
|
||||||
|
itemFound = true;
|
||||||
|
assertEquals("BSB002", productData.getModelId());
|
||||||
|
assertEquals("Signify Netherlands B.V.", productData.getManufacturerName());
|
||||||
|
assertEquals("Philips hue", productData.getProductName());
|
||||||
|
assertNull(productData.getHardwarePlatformType());
|
||||||
|
assertTrue(productData.getCertified());
|
||||||
|
assertEquals("1.53.1953188020", productData.getSoftwareVersion());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(itemFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDevicePower() {
|
||||||
|
String json = load(ResourceType.DEVICE_POWER.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(16, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.DEVICE_POWER, item.getType());
|
||||||
|
Power power = item.getPowerState();
|
||||||
|
assertNotNull(power);
|
||||||
|
assertEquals(60, power.getBatteryLevel());
|
||||||
|
assertEquals(BatteryStateType.NORMAL, power.getBatteryState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGroupedLight() {
|
||||||
|
String json = load(ResourceType.GROUPED_LIGHT.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(15, list.size());
|
||||||
|
int itemsFound = 0;
|
||||||
|
for (Resource item : list) {
|
||||||
|
assertEquals(ResourceType.GROUPED_LIGHT, item.getType());
|
||||||
|
Alerts alert;
|
||||||
|
switch (item.getId()) {
|
||||||
|
case "db4fd630-3798-40de-b642-c1ef464bf770":
|
||||||
|
itemsFound++;
|
||||||
|
assertEquals(OnOffType.OFF, item.getOnOffState());
|
||||||
|
assertEquals(PercentType.ZERO, item.getBrightnessState());
|
||||||
|
alert = item.getAlerts();
|
||||||
|
assertNotNull(alert);
|
||||||
|
for (ActionType actionValue : alert.getActionValues()) {
|
||||||
|
assertEquals(ActionType.BREATHE, actionValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "9228d710-3c54-4ae4-8c88-bfe57d8fd220":
|
||||||
|
itemsFound++;
|
||||||
|
assertEquals(OnOffType.ON, item.getOnOffState());
|
||||||
|
assertEquals(PercentType.HUNDRED, item.getBrightnessState());
|
||||||
|
alert = item.getAlerts();
|
||||||
|
assertNotNull(alert);
|
||||||
|
for (ActionType actionValue : alert.getActionValues()) {
|
||||||
|
assertEquals(ActionType.BREATHE, actionValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(2, itemsFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLight() {
|
||||||
|
String json = load(ResourceType.LIGHT.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(17, list.size());
|
||||||
|
int itemFoundCount = 0;
|
||||||
|
for (Resource item : list) {
|
||||||
|
assertEquals(ResourceType.LIGHT, item.getType());
|
||||||
|
MetaData metaData = item.getMetaData();
|
||||||
|
assertNotNull(metaData);
|
||||||
|
String name = metaData.getName();
|
||||||
|
assertNotNull(name);
|
||||||
|
State state;
|
||||||
|
if (name.contains("Bay Window Lamp")) {
|
||||||
|
itemFoundCount++;
|
||||||
|
assertEquals(ResourceType.LIGHT, item.getType());
|
||||||
|
assertEquals(OnOffType.OFF, item.getOnOffState());
|
||||||
|
state = item.getBrightnessState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
item.setOnOff(OnOffType.ON);
|
||||||
|
state = item.getBrightnessState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(93.0, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
assertEquals(UnDefType.UNDEF, item.getColorTemperaturePercentState());
|
||||||
|
state = item.getColorState();
|
||||||
|
assertTrue(state instanceof HSBType);
|
||||||
|
double[] xy = ColorUtil.hsbToXY((HSBType) state);
|
||||||
|
assertEquals(0.6367, xy[0], 0.01); // note: rounding errors !!
|
||||||
|
assertEquals(0.3503, xy[1], 0.01); // note: rounding errors !!
|
||||||
|
assertEquals(item.getBrightnessState(), ((HSBType) state).getBrightness());
|
||||||
|
Alerts alert = item.getAlerts();
|
||||||
|
assertNotNull(alert);
|
||||||
|
for (ActionType actionValue : alert.getActionValues()) {
|
||||||
|
assertEquals(ActionType.BREATHE, actionValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name.contains("Table Lamp A")) {
|
||||||
|
itemFoundCount++;
|
||||||
|
assertEquals(ResourceType.LIGHT, item.getType());
|
||||||
|
assertEquals(OnOffType.OFF, item.getOnOffState());
|
||||||
|
state = item.getBrightnessState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(0, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
item.setOnOff(OnOffType.ON);
|
||||||
|
state = item.getBrightnessState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(56.7, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
MirekSchema mirekSchema = item.getMirekSchema();
|
||||||
|
assertNotNull(mirekSchema);
|
||||||
|
assertEquals(153, mirekSchema.getMirekMinimum());
|
||||||
|
assertEquals(454, mirekSchema.getMirekMaximum());
|
||||||
|
|
||||||
|
// test color temperature percent value on light's own scale
|
||||||
|
state = item.getColorTemperaturePercentState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(96.3, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
state = item.getColorTemperatureAbsoluteState();
|
||||||
|
assertTrue(state instanceof QuantityType<?>);
|
||||||
|
assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
|
||||||
|
|
||||||
|
// test color temperature percent value on the default (full) scale
|
||||||
|
MirekSchema temp = item.getMirekSchema();
|
||||||
|
item.setMirekSchema(MirekSchema.DEFAULT_SCHEMA);
|
||||||
|
state = item.getColorTemperaturePercentState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(83.6, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
state = item.getColorTemperatureAbsoluteState();
|
||||||
|
assertTrue(state instanceof QuantityType<?>);
|
||||||
|
assertEquals(2257.3, ((QuantityType<?>) state).doubleValue(), 0.1);
|
||||||
|
item.setMirekSchema(temp);
|
||||||
|
|
||||||
|
// change colour temperature percent to zero
|
||||||
|
Setters.setColorTemperaturePercent(item, PercentType.ZERO, null);
|
||||||
|
assertEquals(PercentType.ZERO, item.getColorTemperaturePercentState());
|
||||||
|
state = item.getColorTemperatureAbsoluteState();
|
||||||
|
assertTrue(state instanceof QuantityType<?>);
|
||||||
|
assertEquals(6535.9, ((QuantityType<?>) state).doubleValue(), 0.1);
|
||||||
|
|
||||||
|
// change colour temperature percent to 100
|
||||||
|
Setters.setColorTemperaturePercent(item, PercentType.HUNDRED, null);
|
||||||
|
assertEquals(PercentType.HUNDRED, item.getColorTemperaturePercentState());
|
||||||
|
state = item.getColorTemperatureAbsoluteState();
|
||||||
|
assertTrue(state instanceof QuantityType<?>);
|
||||||
|
assertEquals(2202.6, ((QuantityType<?>) state).doubleValue(), 0.1);
|
||||||
|
|
||||||
|
// change colour temperature kelvin to 4000 K
|
||||||
|
Setters.setColorTemperatureAbsolute(item, QuantityType.valueOf("4000 K"), null);
|
||||||
|
state = item.getColorTemperaturePercentState();
|
||||||
|
assertTrue(state instanceof PercentType);
|
||||||
|
assertEquals(32.2, ((PercentType) state).doubleValue(), 0.1);
|
||||||
|
assertEquals(QuantityType.valueOf("4000 K"), item.getColorTemperatureAbsoluteState());
|
||||||
|
|
||||||
|
assertEquals(UnDefType.NULL, item.getColorState());
|
||||||
|
Alerts alert = item.getAlerts();
|
||||||
|
assertNotNull(alert);
|
||||||
|
for (ActionType actionValue : alert.getActionValues()) {
|
||||||
|
assertEquals(ActionType.BREATHE, actionValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(2, itemFoundCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLightLevel() {
|
||||||
|
String json = load(ResourceType.LIGHT_LEVEL.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.LIGHT_LEVEL, item.getType());
|
||||||
|
Boolean enabled = item.getEnabled();
|
||||||
|
assertNotNull(enabled);
|
||||||
|
assertTrue(enabled);
|
||||||
|
LightLevel lightLevel = item.getLightLevel();
|
||||||
|
assertNotNull(lightLevel);
|
||||||
|
assertEquals(12725, lightLevel.getLightLevel());
|
||||||
|
assertTrue(lightLevel.isLightLevelValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRelativeRotary() {
|
||||||
|
String json = load(ResourceType.RELATIVE_ROTARY.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.RELATIVE_ROTARY, item.getType());
|
||||||
|
RelativeRotary relativeRotary = item.getRelativeRotary();
|
||||||
|
assertNotNull(relativeRotary);
|
||||||
|
RotationEvent rotationEvent = relativeRotary.getLastEvent();
|
||||||
|
assertNotNull(rotationEvent);
|
||||||
|
assertEquals(RotationEventType.REPEAT, rotationEvent.getAction());
|
||||||
|
Rotation rotation = rotationEvent.getRotation();
|
||||||
|
assertNotNull(rotation);
|
||||||
|
assertEquals(DirectionType.CLOCK_WISE, rotation.getDirection());
|
||||||
|
assertEquals(400, rotation.getDuration());
|
||||||
|
assertEquals(30, rotation.getSteps());
|
||||||
|
assertEquals(new DecimalType(30), relativeRotary.getStepsState());
|
||||||
|
assertEquals(new StringType(ButtonEventType.REPEAT.name()), relativeRotary.getActionState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResourceMerging() {
|
||||||
|
// create resource one
|
||||||
|
Resource one = new Resource(ResourceType.LIGHT).setId("AARDVARK");
|
||||||
|
assertNotNull(one);
|
||||||
|
// preset the minimum dimming level
|
||||||
|
try {
|
||||||
|
Dimming dimming = new Dimming().setMinimumDimmingLevel(MINIMUM_DIMMING_LEVEL);
|
||||||
|
Field dimming2 = one.getClass().getDeclaredField("dimming");
|
||||||
|
dimming2.setAccessible(true);
|
||||||
|
dimming2.set(one, dimming);
|
||||||
|
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
Setters.setColorXy(one, HSBType.RED, null);
|
||||||
|
Setters.setDimming(one, PercentType.HUNDRED, null);
|
||||||
|
assertTrue(one.getColorState() instanceof HSBType);
|
||||||
|
assertEquals(PercentType.HUNDRED, one.getBrightnessState());
|
||||||
|
assertTrue(HSBType.RED.closeTo((HSBType) one.getColorState(), 0.01));
|
||||||
|
|
||||||
|
// switching off should change HSB and Brightness
|
||||||
|
one.setOnOff(OnOffType.OFF);
|
||||||
|
assertEquals(0, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
|
||||||
|
assertEquals(PercentType.ZERO, one.getBrightnessState());
|
||||||
|
one.setOnOff(OnOffType.ON);
|
||||||
|
|
||||||
|
// setting brightness to zero should change it to the minimum dimming level
|
||||||
|
Setters.setDimming(one, PercentType.ZERO, null);
|
||||||
|
assertEquals(MINIMUM_DIMMING_LEVEL, ((HSBType) one.getColorState()).getBrightness().doubleValue(), 0.01);
|
||||||
|
assertEquals(MINIMUM_DIMMING_LEVEL, ((PercentType) one.getBrightnessState()).doubleValue(), 0.01);
|
||||||
|
one.setOnOff(OnOffType.ON);
|
||||||
|
|
||||||
|
// null its Dimming field
|
||||||
|
try {
|
||||||
|
Field dimming = one.getClass().getDeclaredField("dimming");
|
||||||
|
dimming.setAccessible(true);
|
||||||
|
dimming.set(one, null);
|
||||||
|
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm that brightness is no longer valid, and therefore that color has also changed
|
||||||
|
assertEquals(UnDefType.NULL, one.getBrightnessState());
|
||||||
|
assertTrue(one.getColorState() instanceof HSBType);
|
||||||
|
assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, new PercentType(50)))
|
||||||
|
.closeTo((HSBType) one.getColorState(), 0.01));
|
||||||
|
|
||||||
|
PercentType testBrightness = new PercentType(42);
|
||||||
|
|
||||||
|
// create resource two
|
||||||
|
Resource two = new Resource(ResourceType.DEVICE).setId("ALLIGATOR");
|
||||||
|
assertNotNull(two);
|
||||||
|
Setters.setDimming(two, testBrightness, null);
|
||||||
|
assertEquals(UnDefType.NULL, two.getColorState());
|
||||||
|
assertEquals(testBrightness, two.getBrightnessState());
|
||||||
|
|
||||||
|
// merge two => one
|
||||||
|
Setters.setResource(one, two);
|
||||||
|
|
||||||
|
// confirm that brightness and color are both once more valid
|
||||||
|
assertEquals("AARDVARK", one.getId());
|
||||||
|
assertEquals(ResourceType.LIGHT, one.getType());
|
||||||
|
assertEquals(testBrightness, one.getBrightnessState());
|
||||||
|
assertTrue(one.getColorState() instanceof HSBType);
|
||||||
|
assertTrue((new HSBType(DecimalType.ZERO, PercentType.HUNDRED, testBrightness))
|
||||||
|
.closeTo((HSBType) one.getColorState(), 0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRoomGroup() {
|
||||||
|
String json = load(ResourceType.ROOM.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(6, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.ROOM, item.getType());
|
||||||
|
List<ResourceReference> children = item.getChildren();
|
||||||
|
assertEquals(2, children.size());
|
||||||
|
ResourceReference child = children.get(0);
|
||||||
|
assertNotNull(child);
|
||||||
|
assertEquals("0d47bd3d-d82b-4a21-893c-299bff18e22a", child.getId());
|
||||||
|
assertEquals(ResourceType.DEVICE, child.getType());
|
||||||
|
List<ResourceReference> services = item.getServiceReferences();
|
||||||
|
assertEquals(1, services.size());
|
||||||
|
ResourceReference service = services.get(0);
|
||||||
|
assertNotNull(service);
|
||||||
|
assertEquals("08947162-67be-4ed5-bfce-f42dade42416", service.getId());
|
||||||
|
assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testScene() {
|
||||||
|
String json = load(ResourceType.SCENE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(123, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
List<ActionEntry> actions = item.getActions();
|
||||||
|
assertNotNull(actions);
|
||||||
|
assertEquals(3, actions.size());
|
||||||
|
ActionEntry actionEntry = actions.get(0);
|
||||||
|
assertNotNull(actionEntry);
|
||||||
|
Resource action = actionEntry.getAction();
|
||||||
|
assertNotNull(action);
|
||||||
|
assertEquals(OnOffType.ON, action.getOnOffState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSensor2Motion() {
|
||||||
|
String json = load(ResourceType.MOTION.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.MOTION, item.getType());
|
||||||
|
Boolean enabled = item.getEnabled();
|
||||||
|
assertNotNull(enabled);
|
||||||
|
assertTrue(enabled);
|
||||||
|
Motion motion = item.getMotion();
|
||||||
|
assertNotNull(motion);
|
||||||
|
assertTrue(motion.isMotion());
|
||||||
|
assertTrue(motion.isMotionValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetGetPureColors() {
|
||||||
|
Resource resource = new Resource(ResourceType.LIGHT);
|
||||||
|
assertNotNull(resource);
|
||||||
|
|
||||||
|
HSBType cyan = new HSBType("180,100,100");
|
||||||
|
HSBType yellow = new HSBType("60,100,100");
|
||||||
|
HSBType magenta = new HSBType("300,100,100");
|
||||||
|
|
||||||
|
for (HSBType color : Set.of(HSBType.WHITE, HSBType.RED, HSBType.GREEN, HSBType.BLUE, cyan, yellow, magenta)) {
|
||||||
|
Setters.setColorXy(resource, color, null);
|
||||||
|
State state = resource.getColorState();
|
||||||
|
assertTrue(state instanceof HSBType);
|
||||||
|
assertTrue(color.closeTo((HSBType) state, 0.01));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSseLightOrGroupEvent() {
|
||||||
|
String json = load("event");
|
||||||
|
List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
|
||||||
|
assertNotNull(eventList);
|
||||||
|
assertEquals(3, eventList.size());
|
||||||
|
Event event = eventList.get(0);
|
||||||
|
List<Resource> resources = event.getData();
|
||||||
|
assertEquals(9, resources.size());
|
||||||
|
for (Resource r : resources) {
|
||||||
|
ResourceType type = r.getType();
|
||||||
|
assertTrue(ResourceType.LIGHT == type || ResourceType.GROUPED_LIGHT == type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSseSceneEvent() {
|
||||||
|
String json = load("event");
|
||||||
|
List<Event> eventList = GSON.fromJson(json, Event.EVENT_LIST_TYPE);
|
||||||
|
assertNotNull(eventList);
|
||||||
|
assertEquals(3, eventList.size());
|
||||||
|
Event event = eventList.get(2);
|
||||||
|
List<Resource> resources = event.getData();
|
||||||
|
assertEquals(6, resources.size());
|
||||||
|
Resource resource = resources.get(1);
|
||||||
|
assertEquals(ResourceType.SCENE, resource.getType());
|
||||||
|
JsonObject status = resource.getStatus();
|
||||||
|
assertNotNull(status);
|
||||||
|
JsonElement active = status.get("active");
|
||||||
|
assertNotNull(active);
|
||||||
|
assertTrue(active.isJsonPrimitive());
|
||||||
|
assertEquals("inactive", active.getAsString());
|
||||||
|
Optional<Boolean> isActive = resource.getSceneActive();
|
||||||
|
assertTrue(isActive.isPresent());
|
||||||
|
assertEquals(Boolean.FALSE, isActive.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTemperature() {
|
||||||
|
String json = load(ResourceType.TEMPERATURE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(1, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.TEMPERATURE, item.getType());
|
||||||
|
Temperature temperature = item.getTemperature();
|
||||||
|
assertNotNull(temperature);
|
||||||
|
assertEquals(17.2, temperature.getTemperature(), 0.1);
|
||||||
|
assertTrue(temperature.isTemperatureValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidJson() {
|
||||||
|
for (ResourceType res : ResourceType.values()) {
|
||||||
|
if (!ResourceType.SSE_TYPES.contains(res)) {
|
||||||
|
try {
|
||||||
|
String file = res.name().toLowerCase();
|
||||||
|
String json = load(file);
|
||||||
|
JsonElement jsonElement = JsonParser.parseString(json);
|
||||||
|
assertTrue(jsonElement.isJsonObject());
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
fail(res.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testZigbeeStatus() {
|
||||||
|
String json = load(ResourceType.ZIGBEE_CONNECTIVITY.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(35, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.ZIGBEE_CONNECTIVITY, item.getType());
|
||||||
|
ZigbeeStatus zigbeeStatus = item.getZigbeeStatus();
|
||||||
|
assertNotNull(zigbeeStatus);
|
||||||
|
assertEquals("Connected", zigbeeStatus.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testZoneGroup() {
|
||||||
|
String json = load(ResourceType.ZONE.name().toLowerCase());
|
||||||
|
Resources resources = GSON.fromJson(json, Resources.class);
|
||||||
|
assertNotNull(resources);
|
||||||
|
List<Resource> list = resources.getResources();
|
||||||
|
assertNotNull(list);
|
||||||
|
assertEquals(7, list.size());
|
||||||
|
Resource item = list.get(0);
|
||||||
|
assertEquals(ResourceType.ZONE, item.getType());
|
||||||
|
List<ResourceReference> children = item.getChildren();
|
||||||
|
assertEquals(1, children.size());
|
||||||
|
ResourceReference child = children.get(0);
|
||||||
|
assertNotNull(child);
|
||||||
|
assertEquals("bcad47a0-3f1f-498c-a8aa-3cf389965219", child.getId());
|
||||||
|
assertEquals(ResourceType.LIGHT, child.getType());
|
||||||
|
List<ResourceReference> services = item.getServiceReferences();
|
||||||
|
assertEquals(1, services.size());
|
||||||
|
ResourceReference service = services.get(0);
|
||||||
|
assertNotNull(service);
|
||||||
|
assertEquals("db4fd630-3798-40de-b642-c1ef464bf770", service.getId());
|
||||||
|
assertEquals(ResourceType.GROUPED_LIGHT, service.getType());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"configuration": {
|
||||||
|
"what": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"recall": {
|
||||||
|
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"recall": {
|
||||||
|
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
||||||
|
"rtype": "scene"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"when_constrained": {
|
||||||
|
"type": "nighttime"
|
||||||
|
},
|
||||||
|
"where": [
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependees": [
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "11ac9c82-d031-43a6-a8d5-b6efdee72fe6",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "critical",
|
||||||
|
"target": {
|
||||||
|
"rid": "8b65c749-3ad8-435e-a7ed-94e3cc99e9d7",
|
||||||
|
"rtype": "scene"
|
||||||
|
},
|
||||||
|
"type": "ResourceDependee"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enabled": true,
|
||||||
|
"id": "8d0ffbee-e24e-4d3e-b91a-5adc9ef5d49c",
|
||||||
|
"last_error": "",
|
||||||
|
"metadata": {
|
||||||
|
"name": "Coming home"
|
||||||
|
},
|
||||||
|
"script_id": "fd60fcd1-4809-4813-b510-4a18856a595c",
|
||||||
|
"status": "running",
|
||||||
|
"type": "behavior_instance"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,205 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "basic_goto_sleep_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Get ready for nice sleep.",
|
||||||
|
"id": "7e571ac6-f363-42e1-809a-4cbf6523ed72",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Basic go to sleep routine"
|
||||||
|
},
|
||||||
|
"state_schema": null,
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "basic_wake_up_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Get your body in the mood to wake up by fading on the lights in the morning.",
|
||||||
|
"id": "ff8957e3-2eb9-4699-a0c8-ad2cb3ede704",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Basic wake up routine"
|
||||||
|
},
|
||||||
|
"state_schema": null,
|
||||||
|
"supported_features": [
|
||||||
|
"style_sunrise",
|
||||||
|
"intensity"
|
||||||
|
],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "coming_home_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Automatically turn your lights to choosen light states, when you arrive at home.",
|
||||||
|
"id": "fd60fcd1-4809-4813-b510-4a18856a595c",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Coming home"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "leaving_home_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Automatically turn off your lights when you leave",
|
||||||
|
"id": "0194752a-2d53-4f92-8209-dfdc52745af3",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Leaving home"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "schedule_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Schedule turning on and off lights",
|
||||||
|
"id": "7238c707-8693-4f19-9095-ccdc1444d228",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Schedule"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "timer_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Countdown Timer",
|
||||||
|
"id": "e73bc72d-96b1-46f8-aa57-729861f80c78",
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "Timers"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
"$ref": "timer_state.json#"
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "pm_config.json#"
|
||||||
|
},
|
||||||
|
"description": "PM Automation",
|
||||||
|
"id": "db06cabc-c752-4904-9e8f-4ebe98feaa1a",
|
||||||
|
"max_number_instances": 1,
|
||||||
|
"metadata": {
|
||||||
|
"category": "automation",
|
||||||
|
"name": "PM"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
"$ref": "pm_state.json#"
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "pm_trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "lights_state_after_streaming_config.json#"
|
||||||
|
},
|
||||||
|
"description": "State of lights in the entertainment group after streaming ends",
|
||||||
|
"id": "7719b841-6b3d-448d-a0e7-601ae9edb6a2",
|
||||||
|
"metadata": {
|
||||||
|
"category": "entertainment",
|
||||||
|
"name": "Light state after streaming"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "natural_light_config.json#"
|
||||||
|
},
|
||||||
|
"description": "Natural light during the day",
|
||||||
|
"id": "a4260b49-0c69-4926-a29c-417f4a38a352",
|
||||||
|
"metadata": {
|
||||||
|
"category": "",
|
||||||
|
"name": "Natural Light"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
"$ref": "natural_light_state.json#"
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
"$ref": "natural_light_trigger.json#"
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration_schema": {
|
||||||
|
"$ref": "config.json#"
|
||||||
|
},
|
||||||
|
"description": "Tap Switch script",
|
||||||
|
"id": "f306f634-acdb-4dd6-bdf5-48dd626d667e",
|
||||||
|
"metadata": {
|
||||||
|
"category": "accessory",
|
||||||
|
"name": "Tap Switch"
|
||||||
|
},
|
||||||
|
"state_schema": {
|
||||||
|
"$ref": "state.json#"
|
||||||
|
},
|
||||||
|
"supported_features": [],
|
||||||
|
"trigger_schema": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "behavior_script",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "703765c0-f78a-4aac-9458-f50c0b41e1d8",
|
||||||
|
"id_v1": "",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f4c5c816-925b-4e22-a112-2b44a23f5613",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"bridge_id": "001788fffe2157c7",
|
||||||
|
"time_zone": {
|
||||||
|
"time_zone": "Europe/London"
|
||||||
|
},
|
||||||
|
"type": "bridge"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "f467cdcc-405f-40ab-8db9-4664aa1c3d63",
|
||||||
|
"id_v1": "/groups/0",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "dcbc740d-1e4f-48aa-ad02-3e17f1f4eebb",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "c6da8ba8-123e-4d6c-ba58-576f9ac0d98b",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "8cec1e2f-bcc9-45c9-a0aa-bc9c30c68b64",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "1758470a-71b3-4d71-b992-cbb3c64d2d03",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "9228d710-3c54-4ae4-8c88-bfe57d8fd220",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "bridge_home"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
551
bundles/org.openhab.binding.hue/src/test/resources/button.json
Normal file
551
bundles/org.openhab.binding.hue/src/test/resources/button.json
Normal file
@ -0,0 +1,551 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "d1ae958e-8908-449a-9897-7f10f9b8d4c2",
|
||||||
|
"id_v1": "/sensors/110",
|
||||||
|
"owner": {
|
||||||
|
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a83354b7-bae5-4618-8c8d-079c83e0ca2b",
|
||||||
|
"id_v1": "/sensors/236",
|
||||||
|
"owner": {
|
||||||
|
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e20d90ae-b2d8-40ea-9905-66bcde9fd863",
|
||||||
|
"id_v1": "/sensors/63",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4e61a948-22aa-4332-9b97-76245dd9fb87",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2f78855c-717a-4b77-8513-dd68ca5337b8",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7c495cbd-4dd1-4f22-950c-4dd6017b81ec",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a15c9be9-14b9-4d98-8d9f-989d6cb54496",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e1e6b2f9-3ace-41ae-b16c-e68f82b5a949",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "717c978e-383d-4900-ab2a-2685983f6174",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0456ba22-2f1d-4375-8e33-31465420cd96",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "92c00fa4-ae65-462c-9130-a28f5bc52bd9",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "85c18ad0-297d-4fb9-a33a-37f2da70752d",
|
||||||
|
"id_v1": "/sensors/118",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "79af0ff1-a4ed-476d-a381-eabd4d3ee6bd",
|
||||||
|
"id_v1": "/sensors/135",
|
||||||
|
"owner": {
|
||||||
|
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8a16c730-094a-4aa1-abd8-9cb758468d62",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3fed0654-68ec-43e1-b3db-f1a3eba076b9",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "df6e2ba8-94f4-43ce-9bec-72c252616062",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a46f615d-6760-479c-ba6d-2837a18902e8",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c34bb678-b910-4a06-8019-3e0ce922f17f",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "286b1b18-ff43-40be-a2ff-b1edc24ecb7c",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2be1abd3-00a4-4985-b2c9-9bdd4cbe1e4b",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dc9f1653-11a8-4794-b706-464cf53e9722",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ac4ebfeb-2045-4698-96de-0b6b5421a1d8",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8a5159f1-93de-4c07-9d32-1fe555fde285",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "14debc09-496e-4429-8c22-4975c135bbdf",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9672e2a9-a3a9-4aec-b31e-f2338114b22b",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f385a946-c638-4631-b6a6-c490cf809606",
|
||||||
|
"id_v1": "/sensors/245",
|
||||||
|
"owner": {
|
||||||
|
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ba204102-de9f-479e-818a-02ed880caac2",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0032a38b-cf67-478f-934a-520a156a2baf",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4ce58cbd-5d0f-45ec-920d-ad601fd9cc07",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "813a331a-f26a-4ab2-948c-9b4c398939d0",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91ba8839-2bac-4175-9f8c-ed192842d549",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f95addfc-2f7c-453f-924d-ba496e07e5f9",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6615f1f1-f3f1-4a05-b8f7-581097458e34",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b0d5a0af-31fd-4189-9150-c551ff9033d7",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9d3820db-4a20-4925-80f6-c74582ffb871",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a9e1a40b-a13d-4966-b0b1-2b0b5dfe1986",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ba063d7-df16-48f5-bb9b-f45849ec1bd3",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4fc9dcd2-6f71-46c4-b3d0-5d7b496be6f9",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "824ea347-28d1-4e45-b886-1555a160190b",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "801ede68-21b0-4e6e-98be-eb1a60557ac6",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 2
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1218d0c3-9a9c-4984-84a5-c12ad085ef2d",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 3
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb2c07bc-1d09-4096-a044-dec29b40619a",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 4
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6bae5b99-349c-4045-8c8f-d4a60562b1d3",
|
||||||
|
"id_v1": "/sensors/124",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"control_id": 1
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1194
bundles/org.openhab.binding.hue/src/test/resources/device.json
Normal file
1194
bundles/org.openhab.binding.hue/src/test/resources/device.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,213 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "695b7480-42bc-44cf-bd63-1911f98fee34",
|
||||||
|
"id_v1": "/sensors/110",
|
||||||
|
"owner": {
|
||||||
|
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 60
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "65833fc9-1d08-4d03-b5ae-a03e7eca559c",
|
||||||
|
"id_v1": "/sensors/236",
|
||||||
|
"owner": {
|
||||||
|
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 69
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0fb39897-aa8b-49b4-8215-4e27651eb2f1",
|
||||||
|
"id_v1": "/sensors/63",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 20
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "025bf7ea-5ba4-457f-9fa9-50a9a7886b0f",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 73
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "42fe582f-b3f9-4ee9-905b-38357d07d3a5",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 57
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "15c0d166-8059-45fc-9f32-97e69e714a27",
|
||||||
|
"id_v1": "/sensors/118",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 100
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "995a0ef5-a3a0-41a7-aa25-e3c85539c540",
|
||||||
|
"id_v1": "/sensors/135",
|
||||||
|
"owner": {
|
||||||
|
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 100
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c656ed42-7ab1-43fe-a910-039bdd5681bf",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 77
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "45cf7847-bf65-4e68-831a-772d3c067d46",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 49
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ac49e3d-9437-4b6c-9c19-7570f4381d19",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 100
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ec182bc-d727-478a-8cd5-45d9bcf178ee",
|
||||||
|
"id_v1": "/sensors/245",
|
||||||
|
"owner": {
|
||||||
|
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 30
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e4de67d2-130b-42bc-891c-6c61905028bd",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 71
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e77d2083-8728-4382-a631-6a2706bb1a3d",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 71
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e692545e-c085-401e-81c5-e865845e8d87",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 63
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c5dafdfc-b096-4090-ba24-912440168c87",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 70
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "373b4f7c-4e3b-400b-bf34-831af5bdd0e8",
|
||||||
|
"id_v1": "/sensors/124",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"power_state": {
|
||||||
|
"battery_state": "normal",
|
||||||
|
"battery_level": 100
|
||||||
|
},
|
||||||
|
"type": "device_power"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "4658e884-e2a2-425d-ad00-55353afcea4e",
|
||||||
|
"id_v1": "/lights/3",
|
||||||
|
"owner": {
|
||||||
|
"rid": "7bf2175d-0bb8-48dc-a7b4-775d3af3dfc9",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"renderer": true,
|
||||||
|
"proxy": true,
|
||||||
|
"segments": {
|
||||||
|
"configurable": false,
|
||||||
|
"max_segments": 1,
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"length": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "entertainment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "819489fb-2485-4e34-8805-aa19f85c6e5f",
|
||||||
|
"id_v1": "/lights/2",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e0aafa22-59ab-4467-b603-632d92d2c15b",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"renderer": true,
|
||||||
|
"proxy": true,
|
||||||
|
"segments": {
|
||||||
|
"configurable": false,
|
||||||
|
"max_segments": 1,
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"length": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "entertainment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f391a6b1-8266-4039-b19c-3cce3572236f",
|
||||||
|
"id_v1": "",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f4c5c816-925b-4e22-a112-2b44a23f5613",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"renderer": false,
|
||||||
|
"proxy": true,
|
||||||
|
"type": "entertainment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a22e43a7-b804-47dd-b50e-9a8f49efc6df",
|
||||||
|
"id_v1": "/lights/1",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e41beec5-0340-4700-9094-8244f9a8ed0d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"renderer": true,
|
||||||
|
"proxy": true,
|
||||||
|
"segments": {
|
||||||
|
"configurable": false,
|
||||||
|
"max_segments": 1,
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"start": 0,
|
||||||
|
"length": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "entertainment"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"configuration_type": "screen",
|
||||||
|
"id": "7535ee8b-bc2f-4edf-8542-da5d6ab557e9",
|
||||||
|
"id_v1": "/groups/14",
|
||||||
|
"locations": {
|
||||||
|
"service_locations": [
|
||||||
|
{
|
||||||
|
"positions": [
|
||||||
|
{
|
||||||
|
"x": 0.75,
|
||||||
|
"y": -0.52,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service": {
|
||||||
|
"rid": "a22e43a7-b804-47dd-b50e-9a8f49efc6df",
|
||||||
|
"rtype": "entertainment"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": 0.75,
|
||||||
|
"y": -0.51999,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"positions": [
|
||||||
|
{
|
||||||
|
"x": -0.76,
|
||||||
|
"y": -0.53,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service": {
|
||||||
|
"rid": "819489fb-2485-4e34-8805-aa19f85c6e5f",
|
||||||
|
"rtype": "entertainment"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": -0.75999,
|
||||||
|
"y": -0.52999,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Entertainment area 1"
|
||||||
|
},
|
||||||
|
"stream_proxy": {
|
||||||
|
"mode": "auto",
|
||||||
|
"node": {
|
||||||
|
"rtype": "entertainment",
|
||||||
|
"rid": "f391a6b1-8266-4039-b19c-3cce3572236f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"light_services": [
|
||||||
|
{
|
||||||
|
"rtype": "light",
|
||||||
|
"rid": "cb77dbcb-f67b-4e9a-b287-e35a046b4288"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rtype": "light",
|
||||||
|
"rid": "7f077235-0130-4a0a-95f9-107cf83639a3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"channel_id": 0,
|
||||||
|
"position": {
|
||||||
|
"x": 0.75,
|
||||||
|
"y": -0.51999,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"service": {
|
||||||
|
"rtype": "entertainment",
|
||||||
|
"rid": "a22e43a7-b804-47dd-b50e-9a8f49efc6df"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": 1,
|
||||||
|
"position": {
|
||||||
|
"x": -0.75999,
|
||||||
|
"y": -0.52999,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"service": {
|
||||||
|
"rtype": "entertainment",
|
||||||
|
"rid": "819489fb-2485-4e34-8805-aa19f85c6e5f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "entertainment_configuration",
|
||||||
|
"name": "Entertainment area 1",
|
||||||
|
"status": "inactive"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
211
bundles/org.openhab.binding.hue/src/test/resources/event.json
Normal file
211
bundles/org.openhab.binding.hue/src/test/resources/event.json
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"creationtime": "2022-10-18T12:02:45Z",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "09837085-7c06-45e1-92de-a2fa76dbbccb",
|
||||||
|
"id_v1": "/lights/18",
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "d8da96f0-0637-40bc-a89d-65ac47bceb0a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ba38af49-4206-47fb-b05a-b54887c12b4c",
|
||||||
|
"id_v1": "/lights/10",
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "adeb3425-6a6b-49a0-8262-129126de7941",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0
|
||||||
|
},
|
||||||
|
"id": "9228d710-3c54-4ae4-8c88-bfe57d8fd220",
|
||||||
|
"id_v1": "/groups/0",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f467cdcc-405f-40ab-8db9-4664aa1c3d63",
|
||||||
|
"rtype": "bridge_home"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6e2fee8d-c25f-4468-8ac8-5ed75c6f3cf1",
|
||||||
|
"id_v1": "/groups/9",
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "cff8919b-7466-4199-a8c5-a5204cd4fcf1",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"id": "6e2fee8d-c25f-4468-8ac8-5ed75c6f3cf1",
|
||||||
|
"id_v1": "/groups/9",
|
||||||
|
"owner": {
|
||||||
|
"rid": "cff8919b-7466-4199-a8c5-a5204cd4fcf1",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1bfb7090-e2b3-4417-8834-86e1ebb0a50c",
|
||||||
|
"id_v1": "/groups/10",
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "57a20e61-7686-48ca-9ad2-6dbf1a2a0364",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"id": "1bfb7090-e2b3-4417-8834-86e1ebb0a50c",
|
||||||
|
"id_v1": "/groups/10",
|
||||||
|
"owner": {
|
||||||
|
"rid": "57a20e61-7686-48ca-9ad2-6dbf1a2a0364",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3913b23e-4ba8-4ebd-965d-e15c7b3213ce",
|
||||||
|
"id_v1": "/groups/13",
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "1758470a-71b3-4d71-b992-cbb3c64d2d03",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"id": "3913b23e-4ba8-4ebd-965d-e15c7b3213ce",
|
||||||
|
"id_v1": "/groups/13",
|
||||||
|
"owner": {
|
||||||
|
"rid": "1758470a-71b3-4d71-b992-cbb3c64d2d03",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "dba95b8a-e763-4667-a667-a10cd8ba5b3e",
|
||||||
|
"type": "update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creationtime": "2022-10-18T12:02:45Z",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"button": {
|
||||||
|
"last_event": "short_release"
|
||||||
|
},
|
||||||
|
"id": "eb2c07bc-1d09-4096-a044-dec29b40619a",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"type": "button"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "5686a018-0779-4f65-b7d1-2943b5a6b066",
|
||||||
|
"type": "update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creationtime": "2023-03-28T13:30:51Z",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "224d33d8-ceb7-4a8d-a3c9-3c71c9996a59",
|
||||||
|
"id_v1": "/lights/1",
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "6485b533-3d67-429c-89ff-e7ba813513f6",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc344d9a-4c63-4e38-8082-d23b48b2152c",
|
||||||
|
"id_v1": "/scenes/zqEj6fQ8nSN8AUO",
|
||||||
|
"status": {
|
||||||
|
"active": "inactive"
|
||||||
|
},
|
||||||
|
"type": "scene"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c766b174-536e-4a2d-bc62-e3365c5229ae",
|
||||||
|
"id_v1": "/groups/0",
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "bd9983ea-ce23-447e-95a8-cfd02bd037d7",
|
||||||
|
"rtype": "bridge_home"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 84.65
|
||||||
|
},
|
||||||
|
"id": "c766b174-536e-4a2d-bc62-e3365c5229ae",
|
||||||
|
"id_v1": "/groups/0",
|
||||||
|
"owner": {
|
||||||
|
"rid": "bd9983ea-ce23-447e-95a8-cfd02bd037d7",
|
||||||
|
"rtype": "bridge_home"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ba365fdb-f055-410c-9021-561b725cdc22",
|
||||||
|
"id_v1": "/groups/1",
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "dc30bab1-66e9-4b8e-9d9d-8725a770c515",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 84.65
|
||||||
|
},
|
||||||
|
"id": "ba365fdb-f055-410c-9021-561b725cdc22",
|
||||||
|
"id_v1": "/groups/1",
|
||||||
|
"owner": {
|
||||||
|
"rid": "dc30bab1-66e9-4b8e-9d9d-8725a770c515",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "441f1e29-f02f-4936-bbfa-c6c08ff1ea12",
|
||||||
|
"type": "update"
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": []
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "f0291861-4351-8bcd-9cda-1a140dd2bd42",
|
||||||
|
"name": "samsung SM-G960F",
|
||||||
|
"type": "geofence_client"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "fe29550d-92b9-4e36-a832-42d041466300",
|
||||||
|
"is_configured": true,
|
||||||
|
"type": "geolocation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,526 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "db4fd630-3798-40de-b642-c1ef464bf770",
|
||||||
|
"id_v1": "/groups/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e3372839-3464-4be0-94a2-949433bc065c",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": null,
|
||||||
|
"color_temperature": null,
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "08947162-67be-4ed5-bfce-f42dade42416",
|
||||||
|
"id_v1": "/groups/3",
|
||||||
|
"owner": {
|
||||||
|
"rid": "dcbc740d-1e4f-48aa-ad02-3e17f1f4eebb",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0aec7082-c40e-4435-ab01-7b387468f7f9",
|
||||||
|
"id_v1": "/groups/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a98c217f-ce07-4bc5-990f-24df6eaa043b",
|
||||||
|
"id_v1": "/groups/7",
|
||||||
|
"owner": {
|
||||||
|
"rid": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7ef7456a-8c4c-4b85-b433-b8c1bb99249b",
|
||||||
|
"id_v1": "/groups/15",
|
||||||
|
"owner": {
|
||||||
|
"rid": "42bbbee8-f76d-431d-a87f-a5ca71bb3613",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "900f4b11-7ed1-46ae-bcf4-978d0028aac9",
|
||||||
|
"id_v1": "/groups/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "93d0b50b-1500-4351-a3fa-52d18dd4c8fc",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ef9470ca-7e96-4fe0-84f2-a10783df9af0",
|
||||||
|
"id_v1": "/groups/2",
|
||||||
|
"owner": {
|
||||||
|
"rid": "c6da8ba8-123e-4d6c-ba58-576f9ac0d98b",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9228d710-3c54-4ae4-8c88-bfe57d8fd220",
|
||||||
|
"id_v1": "/groups/0",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f467cdcc-405f-40ab-8db9-4664aa1c3d63",
|
||||||
|
"rtype": "bridge_home"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "03b1effe-6521-4579-8734-f0d11ce28ed0",
|
||||||
|
"id_v1": "/groups/5",
|
||||||
|
"owner": {
|
||||||
|
"rid": "8cec1e2f-bcc9-45c9-a0aa-bc9c30c68b64",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dd653c2b-9622-45d5-aa57-d0bf9391592b",
|
||||||
|
"id_v1": "/groups/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "dda859a6-f358-48f5-8d34-e13b04bf6e62",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3913b23e-4ba8-4ebd-965d-e15c7b3213ce",
|
||||||
|
"id_v1": "/groups/13",
|
||||||
|
"owner": {
|
||||||
|
"rid": "1758470a-71b3-4d71-b992-cbb3c64d2d03",
|
||||||
|
"rtype": "room"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6e2fee8d-c25f-4468-8ac8-5ed75c6f3cf1",
|
||||||
|
"id_v1": "/groups/9",
|
||||||
|
"owner": {
|
||||||
|
"rid": "cff8919b-7466-4199-a8c5-a5204cd4fcf1",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f8cac182-2608-40e3-81e4-f6ac02eba55a",
|
||||||
|
"id_v1": "/groups/1",
|
||||||
|
"owner": {
|
||||||
|
"rid": "bdc282b3-750d-45dd-b6c4-12a2927d8951",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1bfb7090-e2b3-4417-8834-86e1ebb0a50c",
|
||||||
|
"id_v1": "/groups/10",
|
||||||
|
"owner": {
|
||||||
|
"rid": "57a20e61-7686-48ca-9ad2-6dbf1a2a0364",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4283d61a-15c4-48e2-a7ca-03257093e256",
|
||||||
|
"id_v1": "/groups/11",
|
||||||
|
"owner": {
|
||||||
|
"rid": "2dfe3207-f44f-4dd3-beb6-ec132abb885f",
|
||||||
|
"rtype": "private_group"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 0.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"type": "grouped_light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "886121d4-6809-4adc-961d-53c25ce05e7d",
|
||||||
|
"status": "paired",
|
||||||
|
"status_values": [
|
||||||
|
"pairing",
|
||||||
|
"paired",
|
||||||
|
"unpaired"
|
||||||
|
],
|
||||||
|
"type": "homekit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
852
bundles/org.openhab.binding.hue/src/test/resources/light.json
Normal file
852
bundles/org.openhab.binding.hue/src/test/resources/light.json
Normal file
@ -0,0 +1,852 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "c42f8220-f232-4400-910c-943547513827",
|
||||||
|
"id_v1": "/lights/5",
|
||||||
|
"owner": {
|
||||||
|
"rid": "01de467b-29a0-48fc-b711-fd9c079bd429",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Wall Lamp",
|
||||||
|
"archetype": "wall_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": true
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0,
|
||||||
|
"min_dim_level": 2.0
|
||||||
|
},
|
||||||
|
"dimming_delta": null,
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": null,
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "29e37aaf-abf6-4e5d-8018-37ba8048cfec",
|
||||||
|
"id_v1": "/lights/16",
|
||||||
|
"owner": {
|
||||||
|
"rid": "18212397-8c4d-4373-8f59-c047b80994ac",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Worktop (L)",
|
||||||
|
"archetype": "wall_washer"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 50.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cd4a1b1d-aac0-4006-a5d3-5dea7a4e4043",
|
||||||
|
"id_v1": "/lights/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0b4c9bdb-3f46-485b-8337-e5649c03b9e2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Aquarium Light",
|
||||||
|
"archetype": "recessed_floor"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": []
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ba38af49-4206-47fb-b05a-b54887c12b4c",
|
||||||
|
"id_v1": "/lights/10",
|
||||||
|
"owner": {
|
||||||
|
"rid": "adeb3425-6a6b-49a0-8262-129126de7941",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Table Lamp A",
|
||||||
|
"archetype": "table_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 56.69,
|
||||||
|
"min_dim_level": 1.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7b4038f8-4311-46e6-bdcf-c560eaa65a12",
|
||||||
|
"id_v1": "/lights/15",
|
||||||
|
"owner": {
|
||||||
|
"rid": "2cf59d54-8624-445f-9a01-aac19682b954",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Cabinet lights",
|
||||||
|
"archetype": "wall_washer"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 72.83
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "09837085-7c06-45e1-92de-a2fa76dbbccb",
|
||||||
|
"id_v1": "/lights/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "d8da96f0-0637-40bc-a89d-65ac47bceb0a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Table Lamp E",
|
||||||
|
"archetype": "table_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 56.69,
|
||||||
|
"min_dim_level": 0.20000000298023225
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"effects": {
|
||||||
|
"status_values": [
|
||||||
|
"no_effect",
|
||||||
|
"candle"
|
||||||
|
],
|
||||||
|
"status": "no_effect",
|
||||||
|
"effect_values": [
|
||||||
|
"no_effect",
|
||||||
|
"candle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bcad47a0-3f1f-498c-a8aa-3cf389965219",
|
||||||
|
"id_v1": "/lights/3",
|
||||||
|
"owner": {
|
||||||
|
"rid": "7bf2175d-0bb8-48dc-a7b4-775d3af3dfc9",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Bay Window Lamp",
|
||||||
|
"archetype": "pendant_round"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 93.0,
|
||||||
|
"min_dim_level": 2.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": null,
|
||||||
|
"mirek_valid": false,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"xy": {
|
||||||
|
"x": 0.6367,
|
||||||
|
"y": 0.3503
|
||||||
|
},
|
||||||
|
"gamut": {
|
||||||
|
"red": {
|
||||||
|
"x": 0.675,
|
||||||
|
"y": 0.322
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"x": 0.409,
|
||||||
|
"y": 0.518
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"x": 0.167,
|
||||||
|
"y": 0.04
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gamut_type": "B"
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none",
|
||||||
|
"dynamic_palette"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3bec6abf-3831-4852-8eb9-d2a227d219f2",
|
||||||
|
"id_v1": "/lights/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "37c25501-53e4-4e01-b1bb-2f5ee6e7e258",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Downlight 1",
|
||||||
|
"archetype": "recessed_ceiling"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 55.12,
|
||||||
|
"min_dim_level": 1.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d2ce3e24-a5d0-499b-9a5a-f53e3ce349df",
|
||||||
|
"id_v1": "/lights/21",
|
||||||
|
"owner": {
|
||||||
|
"rid": "78c2c794-7bdd-4a95-a7e8-4ee7b9af28bd",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Polar Bear Light",
|
||||||
|
"archetype": "table_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": []
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e18446bf-36d1-4cda-aafe-1940bbb8f23b",
|
||||||
|
"id_v1": "/lights/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "346a2c5a-b736-497e-aed2-0dd7a7daff52",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Downlight 3",
|
||||||
|
"archetype": "recessed_ceiling"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 55.12,
|
||||||
|
"min_dim_level": 1.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "632baee1-623d-4a31-b1f1-f87e94ac81b7",
|
||||||
|
"id_v1": "/lights/17",
|
||||||
|
"owner": {
|
||||||
|
"rid": "9680e1fa-3b1d-4979-94ba-1a0a0e7a47b8",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Worktop (R)",
|
||||||
|
"archetype": "wall_washer"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 50.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d3bf7a57-b3e6-45f5-833d-00fb5491b67b",
|
||||||
|
"id_v1": "/lights/9",
|
||||||
|
"owner": {
|
||||||
|
"rid": "055f6ba3-0354-4765-b23e-287b505f2cd2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Downlight 4",
|
||||||
|
"archetype": "recessed_ceiling"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 54.72,
|
||||||
|
"min_dim_level": 1.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3366722b-48b1-4682-a648-607915873c40",
|
||||||
|
"id_v1": "/lights/7",
|
||||||
|
"owner": {
|
||||||
|
"rid": "266759fc-aaac-4eea-af5d-f226a146c119",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Downlight 2",
|
||||||
|
"archetype": "recessed_ceiling"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 54.72,
|
||||||
|
"min_dim_level": 1.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c43e95e6-b268-4e9c-8fc6-cb092e5100d0",
|
||||||
|
"id_v1": "/lights/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0d47bd3d-d82b-4a21-893c-299bff18e22a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Table Lamp",
|
||||||
|
"archetype": "table_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0,
|
||||||
|
"min_dim_level": 2.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7f077235-0130-4a0a-95f9-107cf83639a3",
|
||||||
|
"id_v1": "/lights/2",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e0aafa22-59ab-4467-b603-632d92d2c15b",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Standard Lamp R",
|
||||||
|
"archetype": "floor_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 56.69,
|
||||||
|
"min_dim_level": 2.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"xy": {
|
||||||
|
"x": 0.5017,
|
||||||
|
"y": 0.4152
|
||||||
|
},
|
||||||
|
"gamut": {
|
||||||
|
"red": {
|
||||||
|
"x": 0.675,
|
||||||
|
"y": 0.322
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"x": 0.409,
|
||||||
|
"y": 0.518
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"x": 0.167,
|
||||||
|
"y": 0.04
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gamut_type": "B"
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none",
|
||||||
|
"dynamic_palette"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c8ec2639-cc81-4316-af05-59d705f4babe",
|
||||||
|
"id_v1": "/lights/19",
|
||||||
|
"owner": {
|
||||||
|
"rid": "7da8bcc7-6e69-46c4-a2e1-e0c2de8e2270",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Wall Lamp",
|
||||||
|
"archetype": "wall_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 100.0,
|
||||||
|
"min_dim_level": 0.20000000298023225
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 454
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"effects": {
|
||||||
|
"status_values": [
|
||||||
|
"no_effect",
|
||||||
|
"candle"
|
||||||
|
],
|
||||||
|
"status": "no_effect",
|
||||||
|
"effect_values": [
|
||||||
|
"no_effect",
|
||||||
|
"candle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cb77dbcb-f67b-4e9a-b287-e35a046b4288",
|
||||||
|
"id_v1": "/lights/1",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e41beec5-0340-4700-9094-8244f9a8ed0d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "Standard Lamp L",
|
||||||
|
"archetype": "floor_shade"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"dimming": {
|
||||||
|
"brightness": 56.69,
|
||||||
|
"min_dim_level": 2.0
|
||||||
|
},
|
||||||
|
"dimming_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color_temperature": {
|
||||||
|
"mirek": 443,
|
||||||
|
"mirek_valid": true,
|
||||||
|
"mirek_schema": {
|
||||||
|
"mirek_minimum": 153,
|
||||||
|
"mirek_maximum": 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color_temperature_delta": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"xy": {
|
||||||
|
"x": 0.5017,
|
||||||
|
"y": 0.4152
|
||||||
|
},
|
||||||
|
"gamut": {
|
||||||
|
"red": {
|
||||||
|
"x": 0.675,
|
||||||
|
"y": 0.322
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"x": 0.409,
|
||||||
|
"y": 0.518
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"x": 0.167,
|
||||||
|
"y": 0.04
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gamut_type": "B"
|
||||||
|
},
|
||||||
|
"dynamics": {
|
||||||
|
"status": "none",
|
||||||
|
"status_values": [
|
||||||
|
"none",
|
||||||
|
"dynamic_palette"
|
||||||
|
],
|
||||||
|
"speed": 0.0,
|
||||||
|
"speed_valid": false
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"action_values": [
|
||||||
|
"breathe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"signaling": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"mode": "normal",
|
||||||
|
"type": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "18597697-61dc-4090-9bc3-c7f5704f020e",
|
||||||
|
"id_v1": "/sensors/32",
|
||||||
|
"owner": {
|
||||||
|
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"enabled": true,
|
||||||
|
"light": {
|
||||||
|
"light_level": 12725,
|
||||||
|
"light_level_valid": true
|
||||||
|
},
|
||||||
|
"type": "light_level"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "97244487-dd25-4f4c-b829-8eb5ffb82c29",
|
||||||
|
"id_v1": "/sensors/30",
|
||||||
|
"owner": {
|
||||||
|
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"enabled": true,
|
||||||
|
"motion": {
|
||||||
|
"motion": true,
|
||||||
|
"motion_valid": true
|
||||||
|
},
|
||||||
|
"type": "motion"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "12345678-7bd4d062-a33f-40e3-a7c0-91f64c39ac1a",
|
||||||
|
"id_v1": "/sensors/2",
|
||||||
|
"owner": {
|
||||||
|
"rid": "497051e4-29c3-419b-babd-d916cffcf3a1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"relative_rotary": {
|
||||||
|
"last_event": {
|
||||||
|
"action": "repeat",
|
||||||
|
"rotation": {
|
||||||
|
"direction": "clock_wise",
|
||||||
|
"duration": 400,
|
||||||
|
"steps": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "relative_rotary"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
175
bundles/org.openhab.binding.hue/src/test/resources/room.json
Normal file
175
bundles/org.openhab.binding.hue/src/test/resources/room.json
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "dcbc740d-1e4f-48aa-ad02-3e17f1f4eebb",
|
||||||
|
"id_v1": "/groups/3",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "0d47bd3d-d82b-4a21-893c-299bff18e22a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "7da8bcc7-6e69-46c4-a2e1-e0c2de8e2270",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "08947162-67be-4ed5-bfce-f42dade42416",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Back Bedroom",
|
||||||
|
"archetype": "bedroom"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b8d28681-eba1-4156-85e2-96c9c5179fba",
|
||||||
|
"id_v1": "/groups/12",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "18212397-8c4d-4373-8f59-c047b80994ac",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "7bf2175d-0bb8-48dc-a7b4-775d3af3dfc9",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "9680e1fa-3b1d-4979-94ba-1a0a0e7a47b8",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "0aec7082-c40e-4435-ab01-7b387468f7f9",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Kitchen",
|
||||||
|
"archetype": "kitchen"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8b529073-36dd-409b-8006-80df304048ea",
|
||||||
|
"id_v1": "/groups/7",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "2cf59d54-8624-445f-9a01-aac19682b954",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "37c25501-53e4-4e01-b1bb-2f5ee6e7e258",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "346a2c5a-b736-497e-aed2-0dd7a7daff52",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "055f6ba3-0354-4765-b23e-287b505f2cd2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "266759fc-aaac-4eea-af5d-f226a146c119",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "a98c217f-ce07-4bc5-990f-24df6eaa043b",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Dining Room",
|
||||||
|
"archetype": "dining"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c6da8ba8-123e-4d6c-ba58-576f9ac0d98b",
|
||||||
|
"id_v1": "/groups/2",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "0b4c9bdb-3f46-485b-8337-e5649c03b9e2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "78c2c794-7bdd-4a95-a7e8-4ee7b9af28bd",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "e0aafa22-59ab-4467-b603-632d92d2c15b",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "e41beec5-0340-4700-9094-8244f9a8ed0d",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "ef9470ca-7e96-4fe0-84f2-a10783df9af0",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Living Room",
|
||||||
|
"archetype": "living_room"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8cec1e2f-bcc9-45c9-a0aa-bc9c30c68b64",
|
||||||
|
"id_v1": "/groups/5",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "01de467b-29a0-48fc-b711-fd9c079bd429",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "03b1effe-6521-4579-8734-f0d11ce28ed0",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Top Staircase",
|
||||||
|
"archetype": "staircase"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1758470a-71b3-4d71-b992-cbb3c64d2d03",
|
||||||
|
"id_v1": "/groups/13",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "adeb3425-6a6b-49a0-8262-129126de7941",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "d8da96f0-0637-40bc-a89d-65ac47bceb0a",
|
||||||
|
"rtype": "device"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "3913b23e-4ba8-4ebd-965d-e15c7b3213ce",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Front Bedroom",
|
||||||
|
"archetype": "bedroom"
|
||||||
|
},
|
||||||
|
"type": "room"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10355
bundles/org.openhab.binding.hue/src/test/resources/scene.json
Normal file
10355
bundles/org.openhab.binding.hue/src/test/resources/scene.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "f5f8010d-8356-4604-88dc-4f73912cbb6a",
|
||||||
|
"id_v1": "/sensors/33",
|
||||||
|
"owner": {
|
||||||
|
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"enabled": true,
|
||||||
|
"temperature": {
|
||||||
|
"temperature": 17.26,
|
||||||
|
"temperature_valid": true
|
||||||
|
},
|
||||||
|
"type": "temperature"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": []
|
||||||
|
}
|
@ -0,0 +1,390 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "3a9c03de-9305-443c-b075-39901e56f424",
|
||||||
|
"id_v1": "/sensors/110",
|
||||||
|
"owner": {
|
||||||
|
"rid": "112853f9-c4c4-4d65-ba96-b4c2ab26d94d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:0b:00:0a:4c",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13105daf-e295-4c52-bdba-46d03e431782",
|
||||||
|
"id_v1": "/sensors/236",
|
||||||
|
"owner": {
|
||||||
|
"rid": "cfecbbd0-e918-42a2-b714-2bad33061d95",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:02:42:ff",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f4b23d32-0812-49fb-b134-0090dcfba2ba",
|
||||||
|
"id_v1": "/sensors/63",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0e22f8de-eff5-440a-a9ed-06d547d125d7",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:01:27:b3",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "14439da7-d362-41c8-b716-04a3839e3be0",
|
||||||
|
"id_v1": "/lights/5",
|
||||||
|
"owner": {
|
||||||
|
"rid": "01de467b-29a0-48fc-b711-fd9c079bd429",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connectivity_issue",
|
||||||
|
"mac_address": "00:17:88:01:03:5f:79:06",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9415083c-14fe-4902-a194-376170caf3e3",
|
||||||
|
"id_v1": "/sensors/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b56191ea-8d18-4257-b5d2-cfc3fc86a1ee",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:e5:67:4f",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "86837f1d-0bd9-41e6-a236-060eafc33d2f",
|
||||||
|
"id_v1": "/lights/16",
|
||||||
|
"owner": {
|
||||||
|
"rid": "18212397-8c4d-4373-8f59-c047b80994ac",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "d0:cf:5e:ff:fe:dc:da:16",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "050e2508-7bdb-428c-a070-cdd002b12822",
|
||||||
|
"id_v1": "/sensors/24",
|
||||||
|
"owner": {
|
||||||
|
"rid": "b5fe0539-171c-4733-bf0b-244635a309be",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:35:f5:79",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50fe5b5f-fa28-4d05-97b2-b795bbbc6c21",
|
||||||
|
"id_v1": "/lights/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0b4c9bdb-3f46-485b-8337-e5649c03b9e2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:fc:fd:d5",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4a40ae7d-3d12-44bf-adbe-bb23f417e41f",
|
||||||
|
"id_v1": "/lights/10",
|
||||||
|
"owner": {
|
||||||
|
"rid": "adeb3425-6a6b-49a0-8262-129126de7941",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:02:36:f0:2b",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57b2f3bf-1fcf-442f-bcc9-ff93d0a266e1",
|
||||||
|
"id_v1": "/lights/15",
|
||||||
|
"owner": {
|
||||||
|
"rid": "2cf59d54-8624-445f-9a01-aac19682b954",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "90:fd:9f:ff:fe:03:07:80",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3eb848e9-fd04-48a6-a4fb-305af3b97ecf",
|
||||||
|
"id_v1": "/sensors/118",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a0509519-3ecb-47d0-9183-25db1e4ea2b2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:0b:00:0a:51",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "35a857b1-a376-4f2c-80c8-7542d053fec8",
|
||||||
|
"id_v1": "/sensors/135",
|
||||||
|
"owner": {
|
||||||
|
"rid": "8c5b05ba-b4f4-47b2-8ba0-fc44363192bc",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:03:41:12",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "37a1c077-0e0f-435d-a3d9-69110fc3791d",
|
||||||
|
"id_v1": "/sensors/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "68ff07d0-6543-4967-9889-e0bc0bc16c31",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:e7:c0:fd",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e5e5e388-35ac-4b4a-8e5c-ebeb10d18aee",
|
||||||
|
"id_v1": "/lights/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "d8da96f0-0637-40bc-a89d-65ac47bceb0a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:1f:62:53",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "141895c2-a4f7-4028-83c9-1e356358fb84",
|
||||||
|
"id_v1": "/sensors/18",
|
||||||
|
"owner": {
|
||||||
|
"rid": "96bec26d-d7c6-4c00-98cd-6f96733296a0",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:e9:98:f1",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fa249694-315a-43c3-88ca-b3fe8ac9d683",
|
||||||
|
"id_v1": "/lights/3",
|
||||||
|
"owner": {
|
||||||
|
"rid": "7bf2175d-0bb8-48dc-a7b4-775d3af3dfc9",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:10:3b:f2:68",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "047a7502-fbb9-49b4-8324-8329502de717",
|
||||||
|
"id_v1": "/sensors/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "81d9a9d5-228c-45df-828e-0d224929b3d1",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:e5:67:42",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "78c09361-22b0-4054-87ee-43754cfa5607",
|
||||||
|
"id_v1": "/lights/6",
|
||||||
|
"owner": {
|
||||||
|
"rid": "37c25501-53e4-4e01-b1bb-2f5ee6e7e258",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:c3:17:68",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2f7ae216-0fae-4f91-9fea-418b99149374",
|
||||||
|
"id_v1": "/lights/21",
|
||||||
|
"owner": {
|
||||||
|
"rid": "78c2c794-7bdd-4a95-a7e8-4ee7b9af28bd",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:fb:48:1e",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ca08a39-c00e-4042-97bd-6dfbdb0dd61a",
|
||||||
|
"id_v1": "/lights/8",
|
||||||
|
"owner": {
|
||||||
|
"rid": "346a2c5a-b736-497e-aed2-0dd7a7daff52",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:47:b9:ab",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4b1eb54c-2756-4f20-b8e3-1375cd47da81",
|
||||||
|
"id_v1": "/lights/17",
|
||||||
|
"owner": {
|
||||||
|
"rid": "9680e1fa-3b1d-4979-94ba-1a0a0e7a47b8",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "90:fd:9f:ff:fe:76:99:59",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f975c0b9-0f57-4f97-ab23-826e370b0a7d",
|
||||||
|
"id_v1": "/lights/9",
|
||||||
|
"owner": {
|
||||||
|
"rid": "055f6ba3-0354-4765-b23e-287b505f2cd2",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:47:ce:dc",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3f9e9e41-5e2b-4cc6-8b36-8876f5d049e8",
|
||||||
|
"id_v1": "/sensors/245",
|
||||||
|
"owner": {
|
||||||
|
"rid": "56b560bc-a127-4634-8d80-9946104a4028",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:08:01:99:25",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "49d03c43-75ef-415b-88d0-264e6c2ea748",
|
||||||
|
"id_v1": "/sensors/12",
|
||||||
|
"owner": {
|
||||||
|
"rid": "a1155885-4bbe-469f-83bb-f964f8e13e82",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:f1:b2:55",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e2633d2e-fcbc-4a7e-852e-f46d20ca8f21",
|
||||||
|
"id_v1": "/sensors/20",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e130feac-3a5c-452e-a97d-5bca470783b3",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:e9:99:5e",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e533ace8-78a4-46bd-9491-8821cc9960cd",
|
||||||
|
"id_v1": "/lights/7",
|
||||||
|
"owner": {
|
||||||
|
"rid": "266759fc-aaac-4eea-af5d-f226a146c119",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:c3:0a:77",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "272f9a1c-b99f-4014-a82c-9d6fed39f19b",
|
||||||
|
"id_v1": "/sensors/26",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e12b3d9c-0b23-4edd-a967-66f5b5cefa92",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:35:f5:27",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b3e07aed-51c0-45c4-89dc-643e684a1543",
|
||||||
|
"id_v1": "/lights/4",
|
||||||
|
"owner": {
|
||||||
|
"rid": "0d47bd3d-d82b-4a21-893c-299bff18e22a",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:03:0b:af:21",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ade55bf-0e12-4726-be31-8f3645ad519f",
|
||||||
|
"id_v1": "/lights/2",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e0aafa22-59ab-4467-b603-632d92d2c15b",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:10:54:a1:d6",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ea1571fe-9c39-45a8-b20b-63834fea16bd",
|
||||||
|
"id_v1": "",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f4c5c816-925b-4e22-a112-2b44a23f5613",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:01:61:57:c7",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f0040018-1c7a-49cc-b640-ba62675eced2",
|
||||||
|
"id_v1": "/lights/19",
|
||||||
|
"owner": {
|
||||||
|
"rid": "7da8bcc7-6e69-46c4-a2e1-e0c2de8e2270",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:06:1f:57:6c",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "037dd595-b5b7-48e0-be43-e765367aa8f6",
|
||||||
|
"id_v1": "/lights/1",
|
||||||
|
"owner": {
|
||||||
|
"rid": "e41beec5-0340-4700-9094-8244f9a8ed0d",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:10:3b:f2:6a",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3c0c40e5-cf3f-45ae-89f6-b1038cddbb13",
|
||||||
|
"id_v1": "/sensors/14",
|
||||||
|
"owner": {
|
||||||
|
"rid": "431026fb-298c-4726-8ce4-47450fea13c4",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:04:f1:b2:0b",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3a35dfed-10bc-4403-88c8-7963543fb090",
|
||||||
|
"id_v1": "/sensors/124",
|
||||||
|
"owner": {
|
||||||
|
"rid": "f78c5b4b-2f52-4bc3-8097-1ddf97949cc5",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:0b:00:0a:57",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "59d83ae1-4dff-4716-a343-88ddebe78aad",
|
||||||
|
"id_v1": "/sensors/30",
|
||||||
|
"owner": {
|
||||||
|
"rid": "70660557-692d-4d37-8b6b-e3ec63716a72",
|
||||||
|
"rtype": "device"
|
||||||
|
},
|
||||||
|
"status": "connected",
|
||||||
|
"mac_address": "00:17:88:01:0b:cf:5a:5b",
|
||||||
|
"type": "zigbee_connectivity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
172
bundles/org.openhab.binding.hue/src/test/resources/zone.json
Normal file
172
bundles/org.openhab.binding.hue/src/test/resources/zone.json
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"errors": [],
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "e3372839-3464-4be0-94a2-949433bc065c",
|
||||||
|
"id_v1": "/groups/6",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "bcad47a0-3f1f-498c-a8aa-3cf389965219",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "db4fd630-3798-40de-b642-c1ef464bf770",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Bay",
|
||||||
|
"archetype": "kitchen"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "42bbbee8-f76d-431d-a87f-a5ca71bb3613",
|
||||||
|
"id_v1": "/groups/15",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "7f077235-0130-4a0a-95f9-107cf83639a3",
|
||||||
|
"rtype": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "cb77dbcb-f67b-4e9a-b287-e35a046b4288",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "7ef7456a-8c4c-4b85-b433-b8c1bb99249b",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Standard Lamps",
|
||||||
|
"archetype": "lounge"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "93d0b50b-1500-4351-a3fa-52d18dd4c8fc",
|
||||||
|
"id_v1": "/groups/4",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "3bec6abf-3831-4852-8eb9-d2a227d219f2",
|
||||||
|
"rtype": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "e18446bf-36d1-4cda-aafe-1940bbb8f23b",
|
||||||
|
"rtype": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "d3bf7a57-b3e6-45f5-833d-00fb5491b67b",
|
||||||
|
"rtype": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "3366722b-48b1-4682-a648-607915873c40",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "900f4b11-7ed1-46ae-bcf4-978d0028aac9",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Downlights",
|
||||||
|
"archetype": "dining"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dda859a6-f358-48f5-8d34-e13b04bf6e62",
|
||||||
|
"id_v1": "/groups/8",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "7b4038f8-4311-46e6-bdcf-c560eaa65a12",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "dd653c2b-9622-45d5-aa57-d0bf9391592b",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Display Cabinet",
|
||||||
|
"archetype": "dining"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cff8919b-7466-4199-a8c5-a5204cd4fcf1",
|
||||||
|
"id_v1": "/groups/9",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "ba38af49-4206-47fb-b05a-b54887c12b4c",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "6e2fee8d-c25f-4468-8ac8-5ed75c6f3cf1",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Andrew",
|
||||||
|
"archetype": "bedroom"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bdc282b3-750d-45dd-b6c4-12a2927d8951",
|
||||||
|
"id_v1": "/groups/1",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "29e37aaf-abf6-4e5d-8018-37ba8048cfec",
|
||||||
|
"rtype": "light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rid": "632baee1-623d-4a31-b1f1-f87e94ac81b7",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "f8cac182-2608-40e3-81e4-f6ac02eba55a",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Worktops",
|
||||||
|
"archetype": "kitchen"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "57a20e61-7686-48ca-9ad2-6dbf1a2a0364",
|
||||||
|
"id_v1": "/groups/10",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"rid": "09837085-7c06-45e1-92de-a2fa76dbbccb",
|
||||||
|
"rtype": "light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"rid": "1bfb7090-e2b3-4417-8834-86e1ebb0a50c",
|
||||||
|
"rtype": "grouped_light"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"name": "Elisabeth",
|
||||||
|
"archetype": "bedroom"
|
||||||
|
},
|
||||||
|
"type": "zone"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -15,7 +15,8 @@ package org.openhab.binding.hue.internal.discovery;
|
|||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.openhab.binding.hue.internal.HueBindingConstants.THING_TYPE_BRIDGE;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.openhab.binding.hue.internal.HueBindingConstants.*;
|
||||||
import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingTypeUID;
|
import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingTypeUID;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -25,15 +26,16 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.hue.internal.HueBindingConstants;
|
|
||||||
import org.openhab.core.config.discovery.DiscoveryListener;
|
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
import org.openhab.core.config.discovery.inbox.Inbox;
|
import org.openhab.core.config.discovery.inbox.Inbox;
|
||||||
import org.openhab.core.test.java.JavaOSGiTest;
|
import org.openhab.core.test.java.JavaOSGiTest;
|
||||||
import org.openhab.core.test.storage.VolatileStorageService;
|
import org.openhab.core.test.storage.VolatileStorageService;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
|
|
||||||
private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) {
|
private void checkDiscoveryResult(DiscoveryResult result, String expIp, String expSn) {
|
||||||
assertThat(result.getBridgeUID(), nullValue());
|
assertThat(result.getBridgeUID(), nullValue());
|
||||||
assertThat(result.getLabel(), is(String.format(HueBindingConstants.DISCOVERY_LABEL_PATTERN, expIp)));
|
assertThat(result.getLabel(), is(String.format(DISCOVERY_LABEL_PATTERN, expIp)));
|
||||||
assertThat(result.getProperties().get("ipAddress"), is(expIp));
|
assertThat(result.getProperties().get("ipAddress"), is(expIp));
|
||||||
assertThat(result.getProperties().get("serialNumber"), is(expSn));
|
assertThat(result.getProperties().get("serialNumber"), is(expSn));
|
||||||
}
|
}
|
||||||
@ -81,6 +83,10 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
|
|
||||||
// Mock class which only overrides the doGetRequest method in order to make the class testable
|
// Mock class which only overrides the doGetRequest method in order to make the class testable
|
||||||
class ConfigurableBridgeNupnpDiscoveryMock extends HueBridgeNupnpDiscovery {
|
class ConfigurableBridgeNupnpDiscoveryMock extends HueBridgeNupnpDiscovery {
|
||||||
|
public ConfigurableBridgeNupnpDiscoveryMock(ThingRegistry thingRegistry) {
|
||||||
|
super(thingRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String doGetRequest(String url) throws IOException {
|
protected String doGetRequest(String url) throws IOException {
|
||||||
if (url.contains("meethue")) {
|
if (url.contains("meethue")) {
|
||||||
@ -92,6 +98,11 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
}
|
}
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isClip2Supported(@NonNull String ipAddress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@ -109,8 +120,8 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bridgeThingTypeIsSupported() {
|
public void bridgeThingTypeIsSupported() {
|
||||||
assertThat(sut.getSupportedThingTypes().size(), is(1));
|
assertThat(sut.getSupportedThingTypes().size(), is(2));
|
||||||
assertThat(sut.getSupportedThingTypes().iterator().next(), is(THING_TYPE_BRIDGE));
|
assertThat(sut.getSupportedThingTypes().contains(THING_TYPE_BRIDGE), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -121,7 +132,7 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
inbox.remove(oldResult.getThingUID());
|
inbox.remove(oldResult.getThingUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
sut = new ConfigurableBridgeNupnpDiscoveryMock();
|
sut = new ConfigurableBridgeNupnpDiscoveryMock(mock(ThingRegistry.class));
|
||||||
registerService(sut, DiscoveryService.class.getName());
|
registerService(sut, DiscoveryService.class.getName());
|
||||||
discoveryResult = validBridgeDiscoveryResult;
|
discoveryResult = validBridgeDiscoveryResult;
|
||||||
final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
|
final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
|
||||||
@ -170,7 +181,7 @@ public class HueBridgeNupnpDiscoveryOSGITest extends JavaOSGiTest {
|
|||||||
inbox.remove(oldResult.getThingUID());
|
inbox.remove(oldResult.getThingUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
sut = new ConfigurableBridgeNupnpDiscoveryMock();
|
sut = new ConfigurableBridgeNupnpDiscoveryMock(mock(ThingRegistry.class));
|
||||||
registerService(sut, DiscoveryService.class.getName());
|
registerService(sut, DiscoveryService.class.getName());
|
||||||
final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
|
final Map<ThingUID, DiscoveryResult> results = new HashMap<>();
|
||||||
registerDiscoveryListener(new DiscoveryListener() {
|
registerDiscoveryListener(new DiscoveryListener() {
|
||||||
|
Loading…
Reference in New Issue
Block a user