diff --git a/CODEOWNERS b/CODEOWNERS index 6cb7f0ce5b4..43ae00e8609 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -84,6 +84,7 @@ /bundles/org.openhab.binding.deutschebahn/ @soenkekueper /bundles/org.openhab.binding.digiplex/ @rmichalak /bundles/org.openhab.binding.digitalstrom/ @openhab/add-ons-maintainers +/bundles/org.openhab.binding.dirigera/ @weymann /bundles/org.openhab.binding.dlinksmarthome/ @MikeJMajor /bundles/org.openhab.binding.dmx/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.dolbycp/ @Cybso diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 91fd0d92962..804f43640be 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -406,6 +406,11 @@ org.openhab.binding.digitalstrom ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.dirigera + ${project.version} + org.openhab.addons.bundles org.openhab.binding.dlinksmarthome diff --git a/bundles/org.openhab.binding.dirigera/NOTICE b/bundles/org.openhab.binding.dirigera/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.dirigera/README.md b/bundles/org.openhab.binding.dirigera/README.md new file mode 100644 index 00000000000..65721a73f0b --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/README.md @@ -0,0 +1,734 @@ +# DIRIGERA Binding + +Binding supporting the DIRIGERA Gateway from IKEA. + +## Supported Things + +The DIRIGERA `bridge` is providing the connection to all devices and scenes. + +Refer to below sections which devices are supported and are covered by `things` connected to the DIRIGERA bridge. + +| ThingTypeUID | Description | Section | Products | +|-----------------------|------------------------------------------------------------|-------------------------------------------|-------------------------------------------| +| `gateway` | IKEA Gateway for smart products | [Gateway](#gateway-channels) | DIRIGERA | +| `air-purifier` | Air cleaning device with particle filter | [Air Purifier](#air-purifier) | STARKVIND | +| `air-quality` | Air measure for temperature, humidity and particles | [Sensors](#air-quality-sensor) | VINDSTYRKA | +| `blind` | Window or door blind | [Blinds](#blinds) | PRAKTLYSING ,KADRILJ ,FRYKTUR, TREDANSEN | +| `blind-controller` | Controller to open and close blinds | [Controller](#blind-controller) | TRÅDFRI | +| `switch-light` | Light with switch ON, OFF capability | [Lights](#switch-lights) | TRÅDFRI | +| `dimmable-light` | Light with brightness support | [Lights](#dimmable-lights) | TRÅDFRI | +| `temperature-light` | Light with color temperature support | [Lights](#temperature-lights) | TRÅDFRI, FLOALT | +| `color-light` | Light with color support | [Lights](#color-lights) | TRÅDFRI, ORMANÅS | +| `light-controller` | Controller to handle light attributes | [Controller](#light-controller) | TRÅDFRI, RODRET,STYRBAAR | +| `motion-sensor` | Sensor detecting motion events | [Sensors](#motion-sensor) | TRÅDFRI | +| `motion-light-sensor` | Sensor detecting motion events and measures light level | [Sensors](#motion-light-sensor) | VALLHORN | +| `single-shortcut` | Shortcut controller with one button | [Controller](#single-shortcut-controller) | TRÅDFRI | +| `double-shortcut` | Shortcut controller with two buttons | [Controller](#double-shortcut-controller) | SOMRIG | +| `simple-plug` | Power plug | [Plugs](#simple-plug) | TRÅDFRI, ÅSKVÄDER | +| `power-plug` | Power plug with status light and child lock | [Plugs](#power-plug) | TRETAKT | +| `smart-plug` | Power plug with electricity measurements | [Plugs](#smart-power-plug) | INSPELNING | +| `speaker` | Speaker with player activities | [Speaker](#speaker) | SYMFONISK | +| `sound-controller` | Controller for speakers | [Controller](#sound-controller) | SYMFONISK, TRÅDFRI | +| `contact-sensor` | Sensor tracking if windows or doors are open | [Sensors](#contact-sensor) | PARASOLL | +| `water-sensor` | Sensor to detect water leaks | [Sensors](#water-sensor) | BADRING | +| `repeater` | Repeater to strengthen signal | [Repeater](#repeater) | TRÅDFRI | +| `scene` | Scene from IKEA Home smart app which can be triggered | [Scenes](#scenes) | - | + +## Discovery + +The discovery will automatically detect your DIRIGERA Gateway via mDNS. +If it cannot be found check your router for IP address. +Manual scan isn't supported. + +After successful creation of DIRIGERA Gateway and pairing process connected devices are automatically added to your INBOX. +You can switch off the automatic detection in [Bridge configuration](#bridge-configuration). + +**Before adding the bridge** read [Pairing section](#gateway-pairing). + +Devices connected to this bridge will be detected automatically unless you don't switch it off in [Bridge Configuration](#bridge-configuration) + +## Gateway Bridge + +### Bridge Configuration + +| Name | Type | Description | Explanation | Default | Required | +|-----------------|---------|------------------------------------------------------------|--------------------------------------------------------------------------------------|---------|----------| +| `ipAddress` | text | DIRIGERA IP Address | Use discovery to obtain this value automatically or enter it manually if known | N/A | yes | +| `id` | text | Unique id of this gateway | Detected automatically after successful pairing | N/A | no | +| `discovery` | boolean | Configure if paired devices shall be detected by discovery | Run continuously in the background and detect new, deleted or changed devices | true | no | + +### Gateway Pairing + +First setup requires pairing the DIRIGERA gateway with openHAB. +You need physical access to the gateway to finish pairing so ensure you can reach it quickly. + +Let's start pairing + +1. Add the bridge found in discovery +2. Pairing started automatically after creation! +3. Press the button on the DIRIGERA rear side +4. Your bridge shall switch to ONLINE + +### Gateway Channels + +| Channel | Type | Read/Write | Description | +|-----------------|-----------|------------|----------------------------------------------| +| `pairing` | Switch | RW | Sets DIRIGERA hub into pairing mode | +| `location` | Location | R(W) | Location in lat.,lon. coordinates | +| `sunrise` | DateTime | R | Date and time of next sunrise | +| `sunset` | DateTime | R | Date and time of next sunset | +| `statistics` | String | R | Several statistics about gateway activities | + +Channel `location` can overwrite GPS position with openHAB location, but it's not possible to delete GPS data. +See [Gateway Limitations](#gateway-limitations) for further information. + +### Follow Sun + + + +[Motion Sensors](#motion-sensor) can be active all the time or follow a schedule. +One schedule is follow the sun which needs to be activated in the IKEA Home smart app in _Hub Settings_. + +## Things + +With [DIRIGERA Gateway Bridge](#gateway-bridge) in place things can be connected as mentioned in the [supported things section](#supported-things). +Things contain generic [configuration](), [properties]() and [channels]() according to their capabilities. + +### Generic Thing Configuration + +Each thing is identified by a unique id which is mandatory to configure. +Discovery will automatically identify the id. + +| Name | Type | Description | Default | Required | +|-------------------|---------|-------------------------------------|---------|----------| +| `id` | text | Unique id of this device / scene | N/A | yes | + +### Generic Thing Properties + +Each thing has properties attached for product information. +It contains information of hardware and firmware version, device model and manufacturer. +Device capabilities are listed in `canReceive` and `canSend`. + + + +### Generic Thing Channels + +#### OTA Channels + +Over-the-Air (OTA) updates are common for many devices. +If device is providing these channels is detected during runtime. + +| Channel | Type | Read/Write | Description | Advanced | +|-----------------|-----------|------------|----------------------------------------------|----------| +| `ota-status` | Number | R | Over-the-air overall status | | +| `ota-state` | Number | R | Over-the-air current state | X | +| `ota-progress` | Number | R | Over-the-air current progress | X | + +`ota-status` shows the _overall status_ if your device is _up to date_ or an _update is available_. +`ota-state` and `ota-progress` shows more detailed information which you may want to follow, that's why they are declared as advanced channels. + +**OTA Mappings** + +Mappings for `ota-status` + +- 0 : Up to date +- 1 : Update available + +Mappings for `ota-state` + +- 0 : Ready to check +- 1 : Check in progress +- 2 : Ready to download +- 3 : Download in progress +- 4 : Update in progress +- 5 : Update failed +- 6 : Ready to update +- 7 : Check failed +- 8 : Download failed +- 9 : Update complete +- 10 : Battery check failed + +#### Links and Candidates + +Devices can be connected directly e.g. sensors or controllers with lights, plugs, blinds or speakers. +It's detected during runtime if a device is capable to support links _and_ if devices are available in your system to support this connection. +The channels are declared advanced and can be used for setup procedure. + +| Channel | Type | Read/Write | Description | Advanced | +|-----------------------|-----------------------|------------|--------------------------------------------------|----------| +| `links` | String | RW | Linked controllers and sensors | X | +| `link-candidates` | String | RW | Candidates which can be linked | X | + + + +Several devices can be linked together like + +- [Light Controller](#light-controller) and [Motion Sensors](#motion-sensor) to [Plugs](#power-plugs) and [Lights](#lights) +- [Blind Controller](#blind-controller) to [Blinds](#blinds) +- [Sound Controller](#sound-controller) to [Speakers](#speaker) + +Established links are shown in channel `links`. +The linked devices can be clicked in the UI and the link will be removed. + +Possible candidates to be linked are shown in channel `link-candidates`. +If a candidate is clicked in the UI the link will be established. + +Candidates and links marked with `(!)` are not present in openHAB environment so no handler is created yet. +In this case it's possible not all links are shown in the UI, but the present ones shall work. + +#### Other Channels + +| Channel | Type | Read/Write | Description | +|-----------------------|-------------------|------------|----------------------------------------------| +| `startup` | Number | RW | Startup behavior after power cutoff | +| `custom-name` | String | RW | Name given from IKEA home smart app | + +`startup` defines how the device shall behave after a power cutoff. +If there's a dedicated hardwired light switch which cuts power towards the bulb it makes sense to switch them on every time the switch is pressed. +But it's also possible to recover the last state. + +Mappings for `startup` + +- 0 : Previous +- 1 : On +- 2 : Off +- 3 : Switch + +Option 3 is offered in IKEA Home smart app to control lights with using your normal light switch _slowly and smooth_. +With this the light shall stay online. +I wasn't able to reproduce this behavior. +Maybe somebody has more success. + + +`custom-name` is declared e.g. in your IKEA Home smart app. +This name is reflected in the discovery and if thing is created this name will be the thing label. +If `custom-name` is changed via openHAB API or a rule the label will not change. + +### Unknown Devices + +Filter your traces regarding 'DIRIGERA MODEL Unsupported Device'. +The trace contains a JSON object at the end which is needed to implement a corresponding handler. + +## Air Purifier + +Air cleaning device with particle filter. + +| Channel | Type | Read/Write | Description | +|-----------------------|-------------------|------------|----------------------------------------------| +| `fan-mode` | Number | RW | Fan on, off, speed or automatic behavior | +| `fan-speed` | Dimmer | RW | Manual regulation of fan speed | +| `fan-runtime` | Number:Time | R | Fan runtime in minutes | +| `filter-elapsed` | Number:Time | R | Filter elapsed time in minutes | +| `filter-remain` | Number:Time | R | Time to filter replacement in minutes | +| `filter-lifetime` | Number:Time | R | Filter lifetime in minutes | +| `filter-alarm` | Switch | R | Filter alarm signal | +| `particulate-matter` | Number:Density | R | Category 2.5 particulate matter | +| `disable-status-light`| Switch | RW | Disable status light on plug | +| `child-lock` | Switch | RW | Child lock for button on plug | + +There are several `Number:Time` which are delivered in minutes as default. +Note you can change the unit when connecting an item e.g. to `d` (days) for readability. +So you can check in a rule if your remaining filter time is going below 7 days instead of calculating minutes. + +### Air Purifier Channel Mappings + +Mappings for `fan-mode` + +- 0 : Auto +- 1 : Low +- 2 : Medium +- 3 : High +- 4 : On +- 5 : Off + +## Blinds + +Window or door blind. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|--------------------------------------------------| +| `blind-state` | Number | RW | State if blind is moving up, down or stopped | +| `blind-level` | Dimmer | RW | Current blind level | +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +#### Blind Channel Mappings + +Mappings for `blind-state` + +- 0 : Stopped +- 1 : Up +- 2 : Down + +## Lights + +Light devices in several variants. +Can be light bulbs, LED stripes, remote driver and more. +Configuration contains + +| Name | Type | Description | Default | Required | +|-------------------|---------|---------------------------------------------------------------------|---------|----------| +| `id` | text | Unique id of this device / scene | N/A | yes | +| `fadeTime` | integer | Required time for fade sequnce to color or brightness | 750 | yes | +| `fadeSequence` | integer | Define sequence if several light parameters are changed at once | 0 | yes | + +`fadeTime` adjust fading time according to your device. +Current behavior shows commands are acknowledged while device is fading but not executed correctly. +So they need to be executed one after another. +Maybe an update of the DIRIGERA gateway will change the current behavior and you can reduce them afterwards. + +`fadeSequence` is only for [Color Lights](#color-lights). +Through `hsb` channel it's possible to adapt color brightness at once. +Again due to fading times they need to be executed in a sequence. +You can choose between options + +- 0: First brightness, then color +- 1: First color, then brightness + +### Lights ON OFF Behavior + +When light is ON each command will change the settings accordingly immediately. +During power OFF the lights will preserve some values until next power ON. + +| Channel | Type | Behavior | +|-----------------------|---------------|---------------------------------------------------------------------------| +| `power` | ON | Switch ON, apply last / stored values | +| `brightness` | ON | Switch ON, apply last / stored values | +| `brightness` | value > 0 | Switch ON, apply this brightness, apply last / stored values | +| `color-temperature` | ON | Switch ON, apply last / stored values | +| `color-temperature` | any | Store value, brightness stays at previous level | +| `color` | ON | Switch ON, apply last / stored values | +| `color` | value > 0 | Switch ON, apply this brightness, apply last / stored values | +| `color` | h,s,b | Store color and brightness for next ON | +| outside | | Switch ON, apply last / stored values | + +## Switch Lights + +Light with switch ON, OFF capability + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|--------------------------------------------------| +| `power` | Switch | RW | Power state of light | + +## Dimmable Lights + +Light with brightness support. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|--------------------------------------------------| +| `power` | Switch | RW | Power state of light | +| `brightness` | Dimmer | RW | Control brightness of light | + +Channel `brightness` can receive + +- ON / OFF +- numbers from 0 to 100 as percent where 0 will switch the light OFF, any other > 0 switches light ON + +## Temperature Lights + +Light with color temperature support. + +| Channel | Type | Read/Write | Description | Advanced | +|---------------------------|-----------------------|------------|------------------------------------------------------|----------| +| `power` | Switch | RW | Power state of light | | +| `brightness` | Dimmer | RW | Control brightness of light | | +| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) | | +| `color-temperature-abs` | Number:Temperature | RW | Color temperature of a bulb in Kelvin | X | + +## Color Lights + +Light with color support. + +| Channel | Type | Read/Write | Description | Advanced | +|---------------------------|-----------------------|------------|------------------------------------------------------|----------| +| `power` | Switch | RW | Power state of light | | +| `brightness` | Dimmer | RW | Brightness of light in percent | | +| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) | | +| `color-temperature-abs` | Number:Temperature | RW | Color temperature of a bulb in Kelvin | | +| `color` | Color | RW | Color of light with hue, saturation and brightness | X | + +Channel `color` can receive + +- ON / OFF +- numbers from 0 to 100 as brightness in percent where 0 will switch the light OFF, any other > 0 switches light ON +- triple values for hue, saturation, brightness + +## Power Plugs + +Power plugs in different variants. + +## Simple Plug + +Simple plug with control of power state and startup behavior. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `power` | Switch | RW | Power state of plug | + +## Power Plug + +Power plug with control of power state, startup behavior, hardware on/off button and status light. +Same channels as [Simple Plug](#simple-plug) plus following channels. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `child-lock` | Switch | RW | Child lock for button on plug | +| `disable-status-light`| Switch | RW | Disable status light on plug | + +## Smart Power Plug + +Smart plug like [Power Plug](#power-plug) plus measuring capability. + +| Channel | Type | Read/Write | Description | +|-----------------------|---------------------------|------------|----------------------------------------------| +| `electric-power` | Number:Power | R | Electric power delivered by plug | +| `energy-total` | Number:Energy | R | Total energy consumption | +| `energy-reset` | Number:Energy | R | Energy consumption since last reset | +| `reset-date` | DateTime | RW | Date and time of last reset | +| `electric-current` | Number:ElectricCurrent | R | Electric current measured by plug | +| `electric-voltage` | Number:ElectricPotential | R | Electric potential of plug | + +Smart plug provides `energy-total` measuring energy consumption over lifetime and `energy-reset` measuring energy consumption from `reset-date` till now. +Channel `reset-date` is writable and will set the date time to the timestamp of command execution. +Past and future timestamps are not possible and will be ignored. + +## Sensors + +Various sensors for detecting events and measuring. + +## Motion Sensor + +Sensor detecting motion events. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|--------------------------------------------------| +| `motion` | Switch | R | Motion detected by the device | +| `active-duration` | Number:Time | RW | Keep connected devices active for this duration | +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | +| `schedule` | Number | RW | Schedule when the sensor shall be active | +| `schedule-start` | DateTime | RW | Start time of sensor activity | +| `schedule-end` | DateTime | RW | End time of sensor activity | +| `light-preset` | String | RW | Light presets for different times of the day | + +When motion is detected via `motion` channel all connected devices from `links` channel will be active for the time configured in `active-duration`. +Standard duration is seconds if raw number is sent as command. +See [Motion Sensor Rules](#motion-sensor-rules) for further examples. + +Mappings for `schedule` + +- 0 : Always, sensor is always active +- 1 : Follow sun, sensor gets active at sunset and deactivates at sunrise +- 2 : Schedule, custom schedule with manual start and end time + +If option 1, follow sun is selected ensure you gave the permission in the IKEA Home smart app to use your GPS position to calculate times for sunrise and sunset. + +See [Light Controller](#light-controller) for light-preset`. + +## Motion Light Sensor + +Sensor detecting motion events and measures light level. +Same channels as [Motion Sensor](#motion-sensor) with an additional `illuminance` channel. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `illuminance` | Number:Illuminance | R | Illuminance in Lux | + +## Water Sensor + +Sensor to detect water leaks. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `leak` | Switch | R | Water leak detected | +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +## Contact Sensor + +Sensor tracking if windows or doors are open + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `contact` | Contact | R | State if door or window is open or closed | +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +## Air Quality Sensor + +Air measure for temperature, humidity and particles. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|------------------------------------------------------| +| `temperature` | Number:Temperature | R | Air Temperature | +| `humidity` | Number:Dimensionless | R | Air Humidity | +| `particulate-matter` | Number:Density | R | Category 2.5 particulate matter | +| `voc-index` | Number | R | Relative VOC intensity compared to recent history | + +The VOC Index mimics the human nose’s perception of odors with a relative intensity compared to recent history. +The VOC Index is also sensitive to odorless VOCs, but it cannot discriminate between them. +See more information in the [sensor description](https://sensirion.com/media/documents/02232963/6294E043/Info_Note_VOC_Index.pdf). + +## Controller + +Controller for lights, plugs, blinds, shortcuts and speakers. + +## Single Shortcut Controller + +Shortcut controller with one button. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `button1` | trigger | | Trigger of first button | +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +### Button Triggers + +Triggers for `button1` + +- SHORT_PRESSED +- DOUBLE_PRESSED +- LONG_PRESSED + +## Double Shortcut Controller + +Shortcut controller with two buttons. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `button2` | trigger | | Trigger of second button | + +Same as [Single Shortcut Controller](#single-shortcut-controller) with additional `button2` trigger channel. + +## Light Controller + +Controller to handle light attributes. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | +| `light-preset` | String | RW | Light presets for different times of the day | + + + +Channel `light-preset` provides a JSON array with time an light settings for different times. +If light is switched on by the controller the light attributes for the configured time section is used. +This only works for connected devices shown in channel `links`. + +IKEA provided some presets which can be selected but it's also possible to generate a custom schedule. +They are provided as options as strings + +- Warm +- Slowdown +- Smooth +- Bright + +This feature is from IKEA test center and not officially present in the IKEA Home smart app now. + +## Blind Controller + +Controller to open and close blinds. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +## Sound Controller + +Controller for speakers. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `battery-level` | Number:Dimensionless | R | Battery charge level in percent | + +## Speaker + +Speaker with player activities. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `media-control` | Player | RW | Media control play, pause, next, previous | +| `volume` | Dimmer | RW | Handle volume in percent | +| `mute` | Switch | R(W) | Mute current audio without stop playing | +| `shuffle` | Switch | RW | Control shuffle mode | +| `crossfade` | Switch | RW | Cross fading between tracks | +| `repeat` | Number | RW | Over-the-air overall status | +| `media-title` | String | R | Title of a played media file | +| `image` | RawType | R | Current playing track image | + +Channel `mute` should be writable but this isnn't the case now. +See [Known Limitations](#speaker-limitations). + +## Repeater + +Repeater to strengthen signal. +Sadly there's no further information like _signal strength_ available so only [OTA channels](#ota-channels) and [custom name](#other-channels) is available. + +## Scenes + +Scene from IKEA home smart app which can be triggered. + +| Channel | Type | Read/Write | Description | +|-----------------------|-----------------------|------------|----------------------------------------------| +| `trigger` | Number | RW | Trigger / undo scene execution | +| `last-trigger` | DateTime | R | Date and time when last trigger occurred | + +Scenes are defined in IKEA Home smart app and can be performed via `trigger` channel. +Two commands are defined: + +- 0 : Trigger +- 1 : Undo + +If command 0 (Trigger) is sent scene will be executed. +There's a 30 seconds time slot to send command 1 (Undo). +The countdown is updating `trigger` channel state which can be evaluated if an undo operation is still possible. +State will switch to `Undef` after countdown. + +## Known Limitations + +### Gateway Limitations + +Gateway channel `location` is reflecting the state correctly but isn't writable. +The Model says it `canReceive` command `coordinates` but in fact sending responds `http status 400`. +Channel will stay in this binding hoping a DIRIGERA software update will resolve this issue. + +### Speaker Limitations + +Speaker channel `mute` is reflecting the state correctly but isn't writable. +The Model says it `canReceive` command `isMuted` but in fact sending responds `http status 400`. +If mute is performed on Sonos App the channel is updating correctly, but sending the command fails! +Channel will stay in this binding hoping a DIRIGERA software update will resolve this issue. + +## Development and Testing + +Debugging is essential for such a binding which supports many available products and needs to support future products. +General debug messages will overflow traces and it's hard to find relevant information. +To deal with these challenges commands for [openHAB console](https://www.openhab.org/docs/administration/console.html) are provided. + +``` +Usage: openhab:dirigera token - Get token from DIRIGERA hub +Usage: openhab:dirigera json [ | all] - Print JSON data +Usage: openhab:dirigera debug [ | all] [true | false] - Enable / disable detailed debugging for specific / all devices +``` + +### `token` + +Prints the access token to communicate with DIRIGERA gateway as console output. + +``` +console> openhab:dirigera token +DIRIGERA Hub token: abcdef12345....... +``` + +With token available you can test your devices e.g. via curl commands. + +```java +curl -X PATCH https://$YOUR_IP:8443/v1/devices/$DEVICE -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' -d '[{"attributes":{"colorHue":280,"colorSaturation":1}}]' --insecure +``` + +Replace content in curl command with following variables: + +- $YOUR_IP - IP address of DIRIGERA gateway +- $DEVICE - bulb id you want to control, take it from configuration +- $TOKEN - shortly stop / start DIRIGERA bridge and search for obtained token + +### `json` + +Get capabilities and current status for one `deviceId` or all devices. +Output is shown on console as JSON String. + +``` +console> openhab:dirigera json 3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1 +{"deviceType":"light","isReachable":true,"capabilities":{"canReceive":["customName","isOn","lightLevel","colorTemperature", ...} +``` + +### `debug` + +Enables or disables detailed logging for one `deviceId` or all devices. +Answer is `Done` if command is successfully executed. +If you operate with the device you can see requests and responses in openHAB Log Viewer. +If device cannot be found answer is `Device Id xyz not found `. + +``` +console> openhab:dirigera debug all true +Done +``` + +## Full Example + +### Thing Configuration + +```java +Bridge dirigera:gateway:myhome "My wonderful Home" [ ipAddress="1.2.3.4", discovery=true ] { + Thing temperature-light living-room-bulb "Living Room Table Lamp" [ id="aaaaaaaa-bbbb-xxxx-yyyy-zzzzzzzzzzzz"] + Thing smart-plug dishwasher "Dishwasher" [ id="zzzzzzzz-yyyy-xxxx-aaaa-bbbbbbbbbbbb"] + Thing motion-sensor bedroom-motion "Bedroom Motion" [ id="zzzzzzzz-yyyy-xxxx-aaaa-ffffffffffff"] +} +``` + +### Item Configuration + +```java +Switch Bedroom_Motion_Detection { channel="dirigera:motion-sensor:myhome:bedroom-motion:motion" } +Number:Time Bedroom_Motion_Active_Duration { channel="dirigera:motion-sensor:myhome:bedroom-motion:active-duration" } +Number Bedroom_Motion_Schedule { channel="dirigera:motion-sensor:myhome:bedroom-motion:schedule" } +DateTime Bedroom_Motion_Schedule_Start { channel="dirigera:motion-sensor:myhome:bedroom-motion:schedule-start" } +DateTime Bedroom_Motion_Schedule_End { channel="dirigera:motion-sensor:myhome:bedroom-motion:schedule-end" } +Number:Dimensionless Bedroom_Motion_Battery_Level { channel="dirigera:motion-sensor:myhome:bedroom-motion:battery-level" } + +Switch Table_Lamp_Power_State { channel="dirigera:temperature-light:myhome:living-room-bulb:power" } +Dimmer Table_Lamp_Brightness { channel="dirigera:temperature-light:myhome:living-room-bulb:brightness" } +Dimmer Table_Lamp_Temperature { channel="dirigera:temperature-light:myhome:living-room-bulb:color-temperature" } +Number Table_Lamp_Startup { channel="dirigera:temperature-light:myhome:living-room-bulb:startup" } +Number Table_Lamp_OTA_Status { channel="dirigera:temperature-light:myhome:living-room-bulb:ota-status" } +Number Table_Lamp_OTA_State { channel="dirigera:temperature-light:myhome:living-room-bulb:ota-state" } +Number Table_Lamp_OTA_Progress { channel="dirigera:temperature-light:myhome:living-room-bulb:ota-progress" } + +Switch Dishwasher_Power_State { channel="dirigera:smart-plug:myhome:dishwasher:power" } +Switch Dishwasher_Child_lock { channel="dirigera:smart-plug:myhome:dishwasher:child-lock" } +Switch Dishwasher_Disable_Light { channel="dirigera:smart-plug:myhome:dishwasher:disable-light" } +Number:Power Dishwasher_Power { channel="dirigera:smart-plug:myhome:dishwasher:electric-power" } +Number:Energy Dishwasher_Energy_Total { channel="dirigera:smart-plug:myhome:dishwasher:energy-total" } +Number:Energy Dishwasher_Energy_Reset { channel="dirigera:smart-plug:myhome:dishwasher:energy-reset" } +Number:ElectricCurrent Dishwasher_Ampere { channel="dirigera:smart-plug:myhome:dishwasher:electric-current" } +Number:ElectricPotential Dishwasher_Voltage { channel="dirigera:smart-plug:myhome:dishwasher:electric-potential" } +Number Dishwasher_Startup { channel="dirigera:smart-plug:myhome:dishwasher:startup" } +Number Dishwasher_OTA_Status { channel="dirigera:smart-plug:myhome:dishwasher:ota-status" } +Number Dishwasher_OTA_State { channel="dirigera:smart-plug:myhome:dishwasher:ota-state" } +Number Dishwasher_OTA_Progress { channel="dirigera:smart-plug:myhome:dishwasher:ota-progress" } +``` + +### Rule Examples + +#### Shortcut Controller Rules + +Catch triggers from shortcut controller and trigger a scene. + +```java +rule "Shortcut Button 1 Triggers" +when + Channel 'dirigera:double-shortcut:myhome:my-shortcut-controller:button1' triggered +then + logInfo("DIRIGERA","Button 1 {}",receivedEvent) + myhome-light-scene.sendCommand(0) +end +``` + +#### Motion Sensor Rules + +Change the active duration time + +```java +rule "Sensor configuration" +when + System started +then + logInfo("DIRIGERA","Configuring IKEA sensors") + // active duration = 180 seconds + Bedroom_Motion_Active_Duration.sendCommand(180) + // active duration = 3 minutes aka 180 seconds + Bedroom_Motion_Active_Duration.sendCommand("3 min") +end +``` + + +## Credits + +This work is based on [Leggin](https://github.com/Leggin/dirigera) and [dvdgeisler](https://github.com/dvdgeisler/DirigeraClient). +Without these contributions this binding wouldn't be possible! diff --git a/bundles/org.openhab.binding.dirigera/doc/follow-sun.png b/bundles/org.openhab.binding.dirigera/doc/follow-sun.png new file mode 100644 index 00000000000..189d5766ed0 Binary files /dev/null and b/bundles/org.openhab.binding.dirigera/doc/follow-sun.png differ diff --git a/bundles/org.openhab.binding.dirigera/doc/light-presets.png b/bundles/org.openhab.binding.dirigera/doc/light-presets.png new file mode 100644 index 00000000000..01252478e11 Binary files /dev/null and b/bundles/org.openhab.binding.dirigera/doc/light-presets.png differ diff --git a/bundles/org.openhab.binding.dirigera/doc/link-candidates.png b/bundles/org.openhab.binding.dirigera/doc/link-candidates.png new file mode 100644 index 00000000000..ad65128112e Binary files /dev/null and b/bundles/org.openhab.binding.dirigera/doc/link-candidates.png differ diff --git a/bundles/org.openhab.binding.dirigera/doc/thing-properties.png b/bundles/org.openhab.binding.dirigera/doc/thing-properties.png new file mode 100644 index 00000000000..bcd910cfc0b Binary files /dev/null and b/bundles/org.openhab.binding.dirigera/doc/thing-properties.png differ diff --git a/bundles/org.openhab.binding.dirigera/pom.xml b/bundles/org.openhab.binding.dirigera/pom.xml new file mode 100644 index 00000000000..d03a2a1f107 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 5.0.0-SNAPSHOT + + + org.openhab.binding.dirigera + + openHAB Add-ons :: Bundles :: Dirigera Binding + + + + org.json + json + 20231013 + compile + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/feature/feature.xml b/bundles/org.openhab.binding.dirigera/src/main/feature/feature.xml new file mode 100644 index 00000000000..5924fcd89fa --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.dirigera/${project.version} + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/Constants.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/Constants.java new file mode 100644 index 00000000000..e39d4ec8600 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/Constants.java @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link Constants} class defines common constants, which are + * used across the whole binding. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class Constants { + public static final String BINDING_ID = "dirigera"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway"); + public static final ThingTypeUID THING_TYPE_COLOR_LIGHT = new ThingTypeUID(BINDING_ID, "color-light"); + public static final ThingTypeUID THING_TYPE_TEMPERATURE_LIGHT = new ThingTypeUID(BINDING_ID, "temperature-light"); + public static final ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "dimmable-light"); + public static final ThingTypeUID THING_TYPE_SWITCH_LIGHT = new ThingTypeUID(BINDING_ID, "switch-light"); + public static final ThingTypeUID THING_TYPE_MOTION_SENSOR = new ThingTypeUID(BINDING_ID, "motion-sensor"); + public static final ThingTypeUID THING_TYPE_LIGHT_SENSOR = new ThingTypeUID(BINDING_ID, "light-sensor"); + public static final ThingTypeUID THING_TYPE_MOTION_LIGHT_SENSOR = new ThingTypeUID(BINDING_ID, + "motion-light-sensor"); + public static final ThingTypeUID THING_TYPE_CONTACT_SENSOR = new ThingTypeUID(BINDING_ID, "contact-sensor"); + public static final ThingTypeUID THING_TYPE_SIMPLE_PLUG = new ThingTypeUID(BINDING_ID, "simple-plug"); + public static final ThingTypeUID THING_TYPE_POWER_PLUG = new ThingTypeUID(BINDING_ID, "power-plug"); + public static final ThingTypeUID THING_TYPE_SMART_PLUG = new ThingTypeUID(BINDING_ID, "smart-plug"); + public static final ThingTypeUID THING_TYPE_SPEAKER = new ThingTypeUID(BINDING_ID, "speaker"); + public static final ThingTypeUID THING_TYPE_SCENE = new ThingTypeUID(BINDING_ID, "scene"); + public static final ThingTypeUID THING_TYPE_REPEATER = new ThingTypeUID(BINDING_ID, "repeater"); + public static final ThingTypeUID THING_TYPE_LIGHT_CONTROLLER = new ThingTypeUID(BINDING_ID, "light-controller"); + public static final ThingTypeUID THING_TYPE_BLIND_CONTROLLER = new ThingTypeUID(BINDING_ID, "blind-controller"); + public static final ThingTypeUID THING_TYPE_SOUND_CONTROLLER = new ThingTypeUID(BINDING_ID, "sound-controller"); + public static final ThingTypeUID THING_TYPE_SINGLE_SHORTCUT_CONTROLLER = new ThingTypeUID(BINDING_ID, + "single-shortcut"); + public static final ThingTypeUID THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER = new ThingTypeUID(BINDING_ID, + "double-shortcut"); + public static final ThingTypeUID THING_TYPE_AIR_PURIFIER = new ThingTypeUID(BINDING_ID, "air-purifier"); + public static final ThingTypeUID THING_TYPE_AIR_QUALITY = new ThingTypeUID(BINDING_ID, "air-quality"); + public static final ThingTypeUID THING_TYPE_WATER_SENSOR = new ThingTypeUID(BINDING_ID, "water-sensor"); + public static final ThingTypeUID THING_TYPE_BLIND = new ThingTypeUID(BINDING_ID, "blind"); + public static final ThingTypeUID THING_TYPE_UNKNNOWN = new ThingTypeUID(BINDING_ID, "unkown"); + public static final ThingTypeUID THING_TYPE_NOT_FOUND = new ThingTypeUID(BINDING_ID, "not-found"); + public static final ThingTypeUID THING_TYPE_IGNORE = new ThingTypeUID(BINDING_ID, "ignore"); + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GATEWAY, + THING_TYPE_COLOR_LIGHT, THING_TYPE_TEMPERATURE_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_MOTION_SENSOR, + THING_TYPE_CONTACT_SENSOR, THING_TYPE_SIMPLE_PLUG, THING_TYPE_POWER_PLUG, THING_TYPE_SMART_PLUG, + THING_TYPE_SPEAKER, THING_TYPE_SCENE, THING_TYPE_REPEATER, THING_TYPE_LIGHT_CONTROLLER, + THING_TYPE_BLIND_CONTROLLER, THING_TYPE_SOUND_CONTROLLER, THING_TYPE_SINGLE_SHORTCUT_CONTROLLER, + THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER, THING_TYPE_MOTION_LIGHT_SENSOR, THING_TYPE_AIR_QUALITY, + THING_TYPE_AIR_PURIFIER, THING_TYPE_WATER_SENSOR, THING_TYPE_BLIND, THING_TYPE_SWITCH_LIGHT); + + public static final Set IGNORE_THING_TYPES_UIDS = Set.of(THING_TYPE_LIGHT_SENSOR, THING_TYPE_IGNORE); + + public static final List THING_PROPERTIES = List.of("model", "manufacturer", "firmwareVersion", + "hardwareVersion", "serialNumber", "productCode"); + + public static final String WS_URL = "wss://%s:8443/v1"; + public static final String BASE_URL = "https://%s:8443/v1"; + public static final String OAUTH_URL = BASE_URL + "/oauth/authorize"; + public static final String TOKEN_URL = BASE_URL + "/oauth/token"; + public static final String HOME_URL = BASE_URL + "/home"; + public static final String DEVICE_URL = BASE_URL + "/devices/%s"; + public static final String SCENE_URL = BASE_URL + "/scenes/%s"; + public static final String SCENES_URL = BASE_URL + "/scenes"; + + public static final String PROPERTY_IP_ADDRESS = "ipAddress"; + public static final String PROPERTY_DEVICES = "devices"; + public static final String PROPERTY_SCENES = "scenes"; + public static final String PROPERTY_DEVICE_ID = "id"; + public static final String PROPERTY_DEVICE_TYPE = "deviceType"; + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_TOKEN = "token"; + public static final String PROPERTY_ATTRIBUTES = "attributes"; + public static final String PROPERTY_OTA_STATUS = "otaStatus"; + public static final String PROPERTY_OTA_STATE = "otaState"; + public static final String PROPERTY_OTA_PROGRESS = "otaProgress"; + public static final String PROPERTY_BATTERY_PERCENTAGE = "batteryPercentage"; + public static final String PROPERTY_PERMIT_JOIN = "permittingJoin"; + public static final String PROPERTY_STARTUP_BEHAVIOR = "startupOnOff"; + public static final String PROPERTY_POWER_STATE = "isOn"; + public static final String PROPERTY_CUSTOM_NAME = "customName"; + public static final String PROPERTY_REMOTE_LINKS = "remoteLinks"; + + public static final String PROPERTY_EMPTY = ""; + + public static final String ATTRIBUTE_COLOR_MODE = "colorMode"; + + public static final String DEVICE_TYPE_GATEWAY = "gateway"; + public static final String DEVICE_TYPE_SPEAKER = "speaker"; + public static final String DEVICE_TYPE_REPEATER = "repeater"; + public static final String DEVICE_TYPE_AIR_PURIFIER = "airPurifier"; + public static final String DEVICE_TYPE_BLINDS = "blinds"; + public static final String TYPE_USER_SCENE = "userScene"; + public static final String TYPE_CUSTOM_SCENE = "customScene"; + + public static final String DEVICE_TYPE_LIGHT = "light"; + + public static final String DEVICE_TYPE_MOTION_SENSOR = "motionSensor"; + public static final String DEVICE_TYPE_LIGHT_SENSOR = "lightSensor"; + public static final String DEVICE_TYPE_CONTACT_SENSOR = "openCloseSensor"; + public static final String DEVICE_TYPE_ENVIRONMENT_SENSOR = "environmentSensor"; + public static final String DEVICE_TYPE_WATER_SENSOR = "waterSensor"; + public static final String DEVICE_TYPE_OUTLET = "outlet"; + + public static final String DEVICE_TYPE_LIGHT_CONTROLLER = "lightController"; + public static final String DEVICE_TYPE_BLIND_CONTROLLER = "blindsController"; + public static final String DEVICE_TYPE_SOUND_CONTROLLER = "soundController"; + public static final String DEVICE_TYPE_SHORTCUT_CONTROLLER = "shortcutController"; + + // Generic channels + public static final String CHANNEL_CUSTOM_NAME = "custom-name"; + public static final String CHANNEL_LINKS = "links"; + public static final String CHANNEL_LINK_CANDIDATES = "link-candidates"; + public static final String CHANNEL_POWER_STATE = "power"; + public static final String CHANNEL_STARTUP_BEHAVIOR = "startup"; + public static final String CHANNEL_BATTERY_LEVEL = "battery-level"; + public static final String CHANNEL_OTA_STATUS = "ota-status"; + public static final String CHANNEL_OTA_STATE = "ota-state"; + public static final String CHANNEL_OTA_PROGRESS = "ota-progress"; + + // Gateway channels + public static final String CHANNEL_LOCATION = "location"; + public static final String CHANNEL_SUNRISE = "sunrise"; + public static final String CHANNEL_SUNSET = "sunset"; + public static final String CHANNEL_PAIRING = "pairing"; + public static final String CHANNEL_STATISTICS = "statistics"; + + // Light channels + public static final String CHANNEL_LIGHT_BRIGHTNESS = "brightness"; + public static final String CHANNEL_LIGHT_TEMPERATURE = "color-temperature"; + public static final String CHANNEL_LIGHT_TEMPERATURE_ABS = "color-temperature-abs"; + + public static final String CHANNEL_LIGHT_COLOR = "color"; + public static final String CHANNEL_LIGHT_PRESET = "light-preset"; + + // Sensor channels + public static final String CHANNEL_MOTION_DETECTION = "motion"; + public static final String CHANNEL_LEAK_DETECTION = "leak"; + public static final String CHANNEL_ILLUMINANCE = "illuminance"; + public static final String CHANNEL_CONTACT = "contact"; + public static final String CHANNEL_ACTIVE_DURATION = "active-duration"; + public static final String CHANNEL_SCHEDULE = "schedule"; + public static final String CHANNEL_SCHEDULE_START = "schedule-start"; + public static final String CHANNEL_SCHEDULE_END = "schedule-end"; + + // Plug channels + public static final String CHANNEL_POWER = "electric-power"; + public static final String CHANNEL_ENERGY_TOTAL = "energy-total"; + public static final String CHANNEL_ENERGY_RESET = "energy-reset"; + public static final String CHANNEL_ENERGY_RESET_DATE = "reset-date"; + public static final String CHANNEL_CURRENT = "electric-current"; + public static final String CHANNEL_POTENTIAL = "electric-voltage"; + public static final String CHANNEL_CHILD_LOCK = "child-lock"; + public static final String CHANNEL_DISABLE_STATUS_LIGHT = "disable-status-light"; + + // Speaker channels + public static final String CHANNEL_PLAYER = "media-control"; + public static final String CHANNEL_VOLUME = "volume"; + public static final String CHANNEL_MUTE = "mute"; + public static final String CHANNEL_TRACK = "media-title"; + public static final String CHANNEL_PLAY_MODES = "modes"; + public static final String CHANNEL_SHUFFLE = "shuffle"; + public static final String CHANNEL_REPEAT = "repeat"; + public static final String CHANNEL_CROSSFADE = "crossfade"; + public static final String CHANNEL_IMAGE = "image"; + + // Scene channels + public static final String CHANNEL_TRIGGER = "trigger"; + public static final String CHANNEL_LAST_TRIGGER = "last-trigger"; + + // Air quality channels + public static final String CHANNEL_TEMPERATURE = "temperature"; + public static final String CHANNEL_HUMIDITY = "humidity"; + public static final String CHANNEL_PARTICULATE_MATTER = "particulate-matter"; + public static final String CHANNEL_VOC_INDEX = "voc-index"; + + // Air purifier channels + public static final String CHANNEL_PURIFIER_FAN_MODE = "fan-mode"; + public static final String CHANNEL_PURIFIER_FAN_SPEED = "fan-speed"; + public static final String CHANNEL_PURIFIER_FAN_RUNTIME = "fan-runtime"; + public static final String CHANNEL_PURIFIER_FAN_SEQUENCE = "fan-sequence"; + public static final String CHANNEL_PURIFIER_FILTER_ELAPSED = "filter-elapsed"; + public static final String CHANNEL_PURIFIER_FILTER_REMAIN = "filter-remain"; + public static final String CHANNEL_PURIFIER_FILTER_LIFETIME = "filter-lifetime"; + public static final String CHANNEL_PURIFIER_FILTER_ALARM = "filter-alarm"; + + // Blinds channels + public static final String CHANNEL_BLIND_LEVEL = "blind-level"; + public static final String CHANNEL_BLIND_STATE = "blind-state"; + + // Shortcut channels + public static final String CHANNEL_BUTTON_1 = "button1"; + public static final String CHANNEL_BUTTON_2 = "button2"; + + // Websocket update types + public static final String EVENT_TYPE_DEVICE_DISCOVERED = "deviceDiscovered"; + public static final String EVENT_TYPE_DEVICE_ADDED = "deviceAdded"; + public static final String EVENT_TYPE_DEVICE_CHANGE = "deviceStateChanged"; + public static final String EVENT_TYPE_DEVICE_REMOVED = "deviceRemoved"; + + public static final String EVENT_TYPE_SCENE_CREATED = "sceneCreated"; + public static final String EVENT_TYPE_SCENE_UPDATE = "sceneUpdated"; + public static final String EVENT_TYPE_SCENE_DELETED = "sceneDeleted"; + + /** + * Maps connecting device attributes to channel ids + */ + + // Mappings for ota + public static final Map OTA_STATUS_MAP = Map.of("upToDate", 0, "updateAvailable", 1); + public static final Map OTA_STATE_MAP = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("readyToCheck", 0); + put("checkInProgress", 1); + put("readyToDownload", 2); + put("downloadInProgress", 3); + put("updateInProgress", 4); + put("updateFailed", 5); + put("readyToUpdate", 6); + put("checkFailed", 7); + put("downloadFailed", 8); + put("updateComplete", 9); + put("batteryCheckFailed", 10); + } + }; + + // Mappings for startup behavior + public static final Map STARTUP_BEHAVIOR_MAPPING = Map.of("startPrevious", 0, "startOn", 1, + "startOff", 2, "startToggle", 3); + public static final Map STARTUP_BEHAVIOR_REVERSE_MAPPING = reverseStateMapping( + STARTUP_BEHAVIOR_MAPPING); + + /** + * DIRIGERA property to openHAB channel mappings + */ + public static final Map AIR_PURIFIER_MAP = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("fanMode", CHANNEL_PURIFIER_FAN_MODE); + put("motorState", CHANNEL_PURIFIER_FAN_SPEED); + put("motorRuntime", CHANNEL_PURIFIER_FAN_RUNTIME); + put("filterElapsedTime", CHANNEL_PURIFIER_FILTER_ELAPSED); + put("filterAlarmStatus", CHANNEL_PURIFIER_FILTER_ALARM); + put("filterLifetime", CHANNEL_PURIFIER_FILTER_LIFETIME); + put("statusLight", CHANNEL_DISABLE_STATUS_LIGHT); + put("childLock", CHANNEL_CHILD_LOCK); + put("currentPM25", CHANNEL_PARTICULATE_MATTER); + put(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME); + } + }; + public static final Map AIR_QUALITY_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "currentTemperature", CHANNEL_TEMPERATURE, "currentRH", CHANNEL_HUMIDITY, "currentPM25", + CHANNEL_PARTICULATE_MATTER, "vocIndex", CHANNEL_VOC_INDEX); + + public static final Map BLIND_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL); + + public static final Map BLINDS_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "blindsState", CHANNEL_BLIND_STATE, "batteryPercentage", CHANNEL_BATTERY_LEVEL, "blindsCurrentLevel", + CHANNEL_BLIND_LEVEL); + + public static final Map COLOR_LIGHT_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + PROPERTY_POWER_STATE, CHANNEL_POWER_STATE, "lightLevel", CHANNEL_LIGHT_BRIGHTNESS, "colorHue", + CHANNEL_LIGHT_COLOR, "colorSaturation", CHANNEL_LIGHT_COLOR, "colorTemperature", CHANNEL_LIGHT_TEMPERATURE, + PROPERTY_STARTUP_BEHAVIOR, CHANNEL_STARTUP_BEHAVIOR);; + + public static final Map CONTACT_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL, "isOpen", CHANNEL_CONTACT); + + public static final Map LIGHT_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL, "circadianPresets", CHANNEL_LIGHT_PRESET); + + public static final Map LIGHT_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "illuminance", CHANNEL_ILLUMINANCE); + + public static final Map MOTION_LIGHT_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL, "isDetected", CHANNEL_MOTION_DETECTION, "illuminance", + CHANNEL_ILLUMINANCE, "sensorConfig", CHANNEL_ACTIVE_DURATION, "schedule", CHANNEL_SCHEDULE, + "schedule-start", CHANNEL_SCHEDULE_START, "schedule-end", CHANNEL_SCHEDULE_END, "circadianPresets", + CHANNEL_LIGHT_PRESET); + + public static final Map MOTION_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL, "isDetected", CHANNEL_MOTION_DETECTION, "sensorConfig", + CHANNEL_ACTIVE_DURATION, "schedule", CHANNEL_SCHEDULE, "schedule-start", CHANNEL_SCHEDULE_START, + "schedule-end", CHANNEL_SCHEDULE_END, "circadianPresets", CHANNEL_LIGHT_PRESET); + + public static final Map REPEATER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME); + + public static final Map SCENE_MAP = Map.of("lastTriggered", CHANNEL_TRIGGER); + + public static final Map SHORTCUT_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL); + + public static final Map SMART_PLUG_MAP = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("isOn", CHANNEL_POWER_STATE); + put("currentActivePower", CHANNEL_POWER); + put("currentVoltage", CHANNEL_POTENTIAL); + put("currentAmps", CHANNEL_CURRENT); + put("totalEnergyConsumed", CHANNEL_ENERGY_TOTAL); + put("energyConsumedAtLastReset", CHANNEL_ENERGY_RESET); + put("timeOfLastEnergyReset", CHANNEL_ENERGY_RESET_DATE); + put("statusLight", CHANNEL_DISABLE_STATUS_LIGHT); + put("childLock", CHANNEL_CHILD_LOCK); + put(PROPERTY_STARTUP_BEHAVIOR, CHANNEL_STARTUP_BEHAVIOR); + put(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME); + } + }; + + public static final Map SOUND_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL); + + public static final Map SPEAKER_MAP = Map.of("playback", CHANNEL_PLAYER, "volume", CHANNEL_VOLUME, + "isMuted", CHANNEL_MUTE, "playbackAudio", CHANNEL_TRACK, "playbackModes", CHANNEL_PLAY_MODES, + PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME); + + public static final Map TEMPERATURE_LIGHT_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + PROPERTY_POWER_STATE, CHANNEL_POWER_STATE, "lightLevel", CHANNEL_LIGHT_BRIGHTNESS, "colorTemperature", + CHANNEL_LIGHT_TEMPERATURE, PROPERTY_STARTUP_BEHAVIOR, CHANNEL_STARTUP_BEHAVIOR); + + public static final Map WATER_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME, + "batteryPercentage", CHANNEL_BATTERY_LEVEL, "waterLeakDetected", CHANNEL_LEAK_DETECTION); + + public static Map reverseStateMapping(Map mapping) { + Map reverseMap = new HashMap<>(); + for (Map.Entry entry : mapping.entrySet()) { + reverseMap.put(entry.getValue(), entry.getKey()); + } + return reverseMap; + } + + public static Map reverse(Map mapping) { + Map reverseMap = new HashMap<>(); + for (Map.Entry entry : mapping.entrySet()) { + reverseMap.put(entry.getValue(), entry.getKey()); + } + return reverseMap; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraCommandProvider.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraCommandProvider.java new file mode 100644 index 00000000000..f505e64404c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraCommandProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicCommandDescriptionProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Dynamic provider of command options while leaving other state description fields as original. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +@Component(service = { DynamicCommandDescriptionProvider.class, DirigeraCommandProvider.class }) +public class DirigeraCommandProvider extends BaseDynamicCommandDescriptionProvider { + @Activate + public DirigeraCommandProvider(final @Reference EventPublisher eventPublisher, // + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraHandlerFactory.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraHandlerFactory.java new file mode 100644 index 00000000000..f18effa72dc --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraHandlerFactory.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService; +import org.openhab.binding.dirigera.internal.handler.DirigeraHandler; +import org.openhab.binding.dirigera.internal.handler.airpurifier.AirPurifierHandler; +import org.openhab.binding.dirigera.internal.handler.blind.BlindHandler; +import org.openhab.binding.dirigera.internal.handler.controller.BlindsControllerHandler; +import org.openhab.binding.dirigera.internal.handler.controller.DoubleShortcutControllerHandler; +import org.openhab.binding.dirigera.internal.handler.controller.LightControllerHandler; +import org.openhab.binding.dirigera.internal.handler.controller.ShortcutControllerHandler; +import org.openhab.binding.dirigera.internal.handler.controller.SoundControllerHandler; +import org.openhab.binding.dirigera.internal.handler.light.ColorLightHandler; +import org.openhab.binding.dirigera.internal.handler.light.DimmableLightHandler; +import org.openhab.binding.dirigera.internal.handler.light.SwitchLightHandler; +import org.openhab.binding.dirigera.internal.handler.light.TemperatureLightHandler; +import org.openhab.binding.dirigera.internal.handler.plug.PowerPlugHandler; +import org.openhab.binding.dirigera.internal.handler.plug.SimplePlugHandler; +import org.openhab.binding.dirigera.internal.handler.plug.SmartPlugHandler; +import org.openhab.binding.dirigera.internal.handler.repeater.RepeaterHandler; +import org.openhab.binding.dirigera.internal.handler.scene.SceneHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.AirQualityHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.ContactSensorHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.MotionLightSensorHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.MotionSensorHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.WaterSensorHandler; +import org.openhab.binding.dirigera.internal.handler.speaker.SpeakerHandler; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.storage.Storage; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link DirigeraHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.dirigera", service = ThingHandlerFactory.class) +public class DirigeraHandlerFactory extends BaseThingHandlerFactory { + private final Logger logger = LoggerFactory.getLogger(DirigeraHandlerFactory.class); + private final DirigeraStateDescriptionProvider stateProvider; + private final DirigeraDiscoveryService discoveryService; + private final DirigeraCommandProvider commandProvider; + private final LocationProvider locationProvider; + private final Storage bindingStorage; + private final HttpClient insecureClient; + + @Activate + public DirigeraHandlerFactory(@Reference StorageService storageService, + final @Reference DirigeraDiscoveryService discovery, final @Reference LocationProvider locationProvider, + final @Reference DirigeraCommandProvider commandProvider, + final @Reference DirigeraStateDescriptionProvider stateProvider) { + this.locationProvider = locationProvider; + this.commandProvider = commandProvider; + this.discoveryService = discovery; + this.stateProvider = stateProvider; + + this.insecureClient = new HttpClient(new SslContextFactory.Client(true)); + insecureClient.setUserAgentField(null); + try { + this.insecureClient.start(); + // from https://github.com/jetty-project/jetty-reactive-httpclient/issues/33#issuecomment-777771465 + insecureClient.getProtocolHandlers().remove(WWWAuthenticationProtocolHandler.NAME); + } catch (Exception e) { + // catching exception is necessary due to the signature of HttpClient.start() + logger.warn("DIRIGERA FACTORY Failed to start http client: {}", e.getMessage()); + throw new IllegalStateException("Could not create HttpClient", e); + } + bindingStorage = storageService.getStorage(BINDING_ID); + } + + @Deactivate + public void deactivate() { + try { + insecureClient.stop(); + } catch (Exception e) { + logger.warn("Failed to stop http client: {}", e.getMessage()); + } + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (THING_TYPE_GATEWAY.equals(thingTypeUID)) { + return new DirigeraHandler((Bridge) thing, insecureClient, bindingStorage, discoveryService, + locationProvider, commandProvider, bundleContext); + } else if (THING_TYPE_COLOR_LIGHT.equals(thingTypeUID)) { + return new ColorLightHandler(thing, COLOR_LIGHT_MAP, stateProvider); + } else if (THING_TYPE_TEMPERATURE_LIGHT.equals(thingTypeUID)) { + return new TemperatureLightHandler(thing, TEMPERATURE_LIGHT_MAP, stateProvider); + } else if (THING_TYPE_DIMMABLE_LIGHT.equals(thingTypeUID)) { + return new DimmableLightHandler(thing, TEMPERATURE_LIGHT_MAP); + } else if (THING_TYPE_SWITCH_LIGHT.equals(thingTypeUID)) { + return new SwitchLightHandler(thing, TEMPERATURE_LIGHT_MAP); + } else if (THING_TYPE_MOTION_SENSOR.equals(thingTypeUID)) { + return new MotionSensorHandler(thing, MOTION_SENSOR_MAP); + // } else if (THING_TYPE_LIGHT_SENSOR.equals(thingTypeUID)) { + // return new LightSensorHandler(thing, LIGHT_SENSOR_MAP); + } else if (THING_TYPE_MOTION_LIGHT_SENSOR.equals(thingTypeUID)) { + return new MotionLightSensorHandler(thing, MOTION_LIGHT_SENSOR_MAP); + } else if (THING_TYPE_CONTACT_SENSOR.equals(thingTypeUID)) { + return new ContactSensorHandler(thing, CONTACT_SENSOR_MAP); + } else if (THING_TYPE_SIMPLE_PLUG.equals(thingTypeUID)) { + return new SimplePlugHandler(thing, SMART_PLUG_MAP); + } else if (THING_TYPE_POWER_PLUG.equals(thingTypeUID)) { + return new PowerPlugHandler(thing, SMART_PLUG_MAP); + } else if (THING_TYPE_SMART_PLUG.equals(thingTypeUID)) { + return new SmartPlugHandler(thing, SMART_PLUG_MAP); + } else if (THING_TYPE_SPEAKER.equals(thingTypeUID)) { + return new SpeakerHandler(thing, SPEAKER_MAP); + } else if (THING_TYPE_SCENE.equals(thingTypeUID)) { + return new SceneHandler(thing, SCENE_MAP); + } else if (THING_TYPE_REPEATER.equals(thingTypeUID)) { + return new RepeaterHandler(thing, REPEATER_MAP); + } else if (THING_TYPE_LIGHT_CONTROLLER.equals(thingTypeUID)) { + return new LightControllerHandler(thing, LIGHT_CONTROLLER_MAP); + } else if (THING_TYPE_BLIND_CONTROLLER.equals(thingTypeUID)) { + return new BlindsControllerHandler(thing, BLIND_CONTROLLER_MAP); + } else if (THING_TYPE_SOUND_CONTROLLER.equals(thingTypeUID)) { + return new SoundControllerHandler(thing, SOUND_CONTROLLER_MAP); + } else if (THING_TYPE_SINGLE_SHORTCUT_CONTROLLER.equals(thingTypeUID)) { + return new ShortcutControllerHandler(thing, SHORTCUT_CONTROLLER_MAP, bindingStorage); + } else if (THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER.equals(thingTypeUID)) { + return new DoubleShortcutControllerHandler(thing, SHORTCUT_CONTROLLER_MAP, bindingStorage); + } else if (THING_TYPE_AIR_QUALITY.equals(thingTypeUID)) { + return new AirQualityHandler(thing, AIR_QUALITY_MAP); + } else if (THING_TYPE_WATER_SENSOR.equals(thingTypeUID)) { + return new WaterSensorHandler(thing, WATER_SENSOR_MAP); + } else if (THING_TYPE_BLIND.equals(thingTypeUID)) { + return new BlindHandler(thing, BLINDS_MAP); + } else if (THING_TYPE_AIR_PURIFIER.equals(thingTypeUID)) { + return new AirPurifierHandler(thing, AIR_PURIFIER_MAP); + } else { + logger.debug("DIRIGERA FACTORY Request for {} doesn't match {}", thingTypeUID, THING_TYPE_GATEWAY); + return null; + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraStateDescriptionProvider.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraStateDescriptionProvider.java new file mode 100644 index 00000000000..24b2a4bb8c4 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/DirigeraStateDescriptionProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.events.ThingEventFactory; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragment; +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 alert, effect, scene, and colour + * temperature channels whose capabilities are dynamically determined at runtime. + * + * @author Andrew Fiddian-Green - Initial contribution + * + */ +@NonNullByDefault +@Component(service = { DynamicStateDescriptionProvider.class, DirigeraStateDescriptionProvider.class }) +public class DirigeraStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + private Map stateDescriptionMap = new HashMap<>(); + + @Activate + public DirigeraStateDescriptionProvider(final @Reference EventPublisher eventPublisher, + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } + + @Override + public @Nullable StateDescription getStateDescription(Channel channel, + @Nullable StateDescription originalStateDescription, @Nullable Locale locale) { + StateDescription original = null; + StateDescriptionFragment fragment = stateDescriptionMap.get(channel.getUID()); + if (fragment != null) { + original = fragment.toStateDescription(); + StateDescription modified = super.getStateDescription(channel, original, locale); + if (modified == null) { + modified = original; + } + return modified; + } + return super.getStateDescription(channel, original, locale); + } + + public void setStateDescription(ChannelUID channelUid, StateDescriptionFragment stateDescriptionFragment) { + StateDescription stateDescription = stateDescriptionFragment.toStateDescription(); + if (stateDescription != null) { + StateDescriptionFragment old = stateDescriptionMap.get(channelUid); + stateDescriptionMap.put(channelUid, stateDescriptionFragment); + Set linkedItems = null; + ItemChannelLinkRegistry compareRegistry = itemChannelLinkRegistry; + if (compareRegistry != null) { + linkedItems = compareRegistry.getLinkedItemNames(channelUid); + } + postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUid, + linkedItems != null ? linkedItems : Set.of(), stateDescriptionFragment, old)); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/BaseDeviceConfiguration.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/BaseDeviceConfiguration.java new file mode 100644 index 00000000000..e16685e6a88 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/BaseDeviceConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link BaseDeviceConfiguration} configuration for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BaseDeviceConfiguration { + + public String id = ""; +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/ColorLightConfiguration.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/ColorLightConfiguration.java new file mode 100644 index 00000000000..a708cb65f6a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/ColorLightConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link ColorLightConfiguration} configuration for lights with temperature or color attributes + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ColorLightConfiguration extends BaseDeviceConfiguration { + + public int fadeTime = 750; + public int fadeSequence = 0; +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/DirigeraConfiguration.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/DirigeraConfiguration.java new file mode 100644 index 00000000000..1442cba3abf --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/config/DirigeraConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DirigeraConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DirigeraConfiguration extends BaseDeviceConfiguration { + + public String ipAddress = ""; + public boolean discovery = true; + + @Override + public String toString() { + return "IP: " + ipAddress + ", ID: " + id; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/console/DirigeraCommandExtension.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/console/DirigeraCommandExtension.java new file mode 100644 index 00000000000..d51bf8d83fd --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/console/DirigeraCommandExtension.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.console; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.dirigera.internal.Constants; +import org.openhab.binding.dirigera.internal.handler.DirigeraHandler; +import org.openhab.binding.dirigera.internal.interfaces.DebugHandler; +import org.openhab.core.io.console.Console; +import org.openhab.core.io.console.ConsoleCommandCompleter; +import org.openhab.core.io.console.StringsCompleter; +import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension; +import org.openhab.core.io.console.extensions.ConsoleCommandExtension; +import org.openhab.core.thing.ThingRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link DirigeraCommandExtension} is responsible for handling console commands. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +@Component(service = ConsoleCommandExtension.class) +public class DirigeraCommandExtension extends AbstractConsoleCommandExtension { + + private static final String CMD_TOKEN = "token"; + private static final String CMD_JSON = "json"; + private static final String CMD_DEBUG = "debug"; + private static final List COMMANDS = List.of(CMD_TOKEN, CMD_JSON, CMD_DEBUG); + + private final ThingRegistry thingRegistry; + + /** + * Provides a completer for the DIRIGERA console commands. + * + * @param thingRegistry the ThingRegistry to access things and their handlers + */ + private class DirigeraConsoleCommandCompleter implements ConsoleCommandCompleter { + @Override + public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List candidates) { + if (cursorArgumentIndex <= 0) { + return new StringsCompleter(List.of(CMD_TOKEN, CMD_JSON, CMD_DEBUG), false).complete(args, + cursorArgumentIndex, cursorPosition, candidates); + } else if (cursorArgumentIndex == 1) { + List options = new ArrayList<>(); + options.add("all"); + options.addAll(getDeviceIds()); + return new StringsCompleter(options, false).complete(args, cursorArgumentIndex, cursorPosition, + candidates); + } else if (cursorArgumentIndex == 2) { + return new StringsCompleter(List.of("true", "false"), false).complete(args, cursorArgumentIndex, + cursorPosition, candidates); + } + return false; + } + } + + /** + * Decodes the console command arguments and checks for validity. + */ + private class DirigeraConsoleCommandDecoder { + boolean valid = false; + String command = ""; + String target = ""; + boolean enable = false; + + DirigeraConsoleCommandDecoder(String[] args) { + // Check parameter count and valid command, return immediately if invalid + if (args.length == 0 || args.length > 3) { + return; + } + command = args[0].toLowerCase(); + if (!COMMANDS.contains(command)) { + return; + } + // Command is valid, check parameters + switch (command) { + case CMD_TOKEN: + // No parameters expected for token command + if (args.length == 1) { + valid = true; + } + break; + case CMD_JSON: + // Take second parameter for device ID or 'all' + if (args.length == 2) { + target = args[1].toLowerCase(); + valid = true; + } + break; + case CMD_DEBUG: + // Three parameters expected for debug command, second as target and third as boolean + if (args.length == 3) { + target = args[1].toLowerCase(); + String booleanCandidate = args[2].toLowerCase(); + if (Boolean.TRUE.toString().toLowerCase().equals(booleanCandidate) + || Boolean.FALSE.toString().toLowerCase().equals(booleanCandidate)) { + enable = Boolean.valueOf(booleanCandidate); + valid = true; + } + } + break; + } + } + } + + @Activate + public DirigeraCommandExtension(final @Reference ThingRegistry thingRegistry) { + super(Constants.BINDING_ID, "Interact with the DIRIGERA binding."); + this.thingRegistry = thingRegistry; + } + + @Override + public void execute(String[] args, Console console) { + DirigeraConsoleCommandDecoder decoder = new DirigeraConsoleCommandDecoder(args); + if (decoder.valid) { + switch (decoder.command) { + case CMD_TOKEN -> printToken(console); + case CMD_JSON -> printJSON(decoder, console); + case CMD_DEBUG -> setDebugParameters(decoder, console); + } + } else { + printUsage(console); + } + } + + private void printToken(Console console) { + for (DirigeraHandler handler : getHubs()) { + console.println(handler.getThing().getLabel() + " token: " + handler.getToken()); + } + } + + private void printJSON(DirigeraConsoleCommandDecoder decodedCommand, Console console) { + String output = null; + if ("all".equals(decodedCommand.target)) { + for (DirigeraHandler handler : getHubs()) { + output = handler.getJSON(); + } + } else { + for (DebugHandler handler : getDevices()) { + if (decodedCommand.target.equals(handler.getDeviceId())) { + output = handler.getJSON(); + } + } + } + if (output != null) { + console.println(output); + } else { + console.println("Device Id " + decodedCommand.target + " not found"); + } + } + + private void setDebugParameters(DirigeraConsoleCommandDecoder decodedCommand, Console console) { + boolean success = false; + if ("all".equals(decodedCommand.target)) { + for (DirigeraHandler handler : getHubs()) { + handler.setDebug(decodedCommand.enable, true); + success = true; + } + } else { + for (DebugHandler handler : getDevices()) { + if (decodedCommand.target.equals(handler.getDeviceId())) { + handler.setDebug(decodedCommand.enable, false); + success = true; + } + } + } + if (success) { + console.println("Done"); + } else { + console.println("Device Id " + decodedCommand.target + " not found"); + } + } + + private List getHubs() { + return thingRegistry.getAll().stream().map(thing -> thing.getHandler()) + .filter(DirigeraHandler.class::isInstance).map(DirigeraHandler.class::cast).toList(); + } + + private List getDevices() { + return thingRegistry.getAll().stream().map(thing -> thing.getHandler()).filter(DebugHandler.class::isInstance) + .map(DebugHandler.class::cast).toList(); + } + + private List getDeviceIds() { + return getDevices().stream().map(debugHandler -> debugHandler.getDeviceId()).toList(); + } + + @Override + public List getUsages() { + return Arrays.asList(buildCommandUsage(CMD_TOKEN, "Get token from DIRIGERA hub"), + buildCommandUsage(CMD_JSON + " [ | all]", "Print JSON data"), + buildCommandUsage(CMD_DEBUG + " [ | all] [true | false] ", + "Enable / disable detailed debugging for specific / all devices")); + } + + @Override + public @Nullable ConsoleCommandCompleter getCompleter() { + return new DirigeraConsoleCommandCompleter(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraDiscoveryService.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraDiscoveryService.java new file mode 100644 index 00000000000..520dede67b5 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraDiscoveryService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.discovery; + +import static org.openhab.binding.dirigera.internal.Constants.SUPPORTED_THING_TYPES_UIDS; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryService; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; + +/** + * {@link DirigeraDiscoveryService} notifies about about devices found by + * DIRIGERA hub + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +@Component(service = { DiscoveryService.class, + DirigeraDiscoveryService.class }, configurationPid = "dirigera.device.discovery") +public class DirigeraDiscoveryService extends AbstractDiscoveryService { + + @Activate + public DirigeraDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, 90); + } + + public void deviceDiscovered(DiscoveryResult result) { + thingDiscovered(result); + } + + public void deviceRemoved(DiscoveryResult result) { + thingRemoved(result.getThingUID()); + } + + @Override + protected void startScan() { + // no manual scan supported + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraMDNSDiscoveryParticipant.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraMDNSDiscoveryParticipant.java new file mode 100644 index 00000000000..2b434c29473 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/discovery/DirigeraMDNSDiscoveryParticipant.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.discovery; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.dirigera.internal.Constants; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link DirigeraMDNSDiscoveryParticipant} for mDNS discovery of DIRIGERA gateway + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "dirigera.mdns.discovery") +public class DirigeraMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant { + + private static final String SERVICE_TYPE = "_ihsp._tcp.local."; + private final Logger logger = LoggerFactory.getLogger(DirigeraMDNSDiscoveryParticipant.class); + + protected final ThingRegistry thingRegistry; + + @Activate + public DirigeraMDNSDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(Constants.THING_TYPE_GATEWAY); + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo si) { + logger.trace("DIRIGERA mDNS createResult for {} with IPs {}", si.getQualifiedNameMap(), si.getURLs()); + Inet4Address[] ipAddresses = si.getInet4Addresses(); + String gatewayName = si.getQualifiedNameMap().get(ServiceInfo.Fields.Instance); + if (gatewayName != null) { + String ipAddress = null; + if (ipAddresses.length == 0) { + // case of mDNS isn't delivering IP address try to resolve it + String domain = si.getQualifiedNameMap().get(ServiceInfo.Fields.Domain); + String gatewayHostName = gatewayName + "." + domain; + try { + InetAddress address = InetAddress.getByName(gatewayHostName); + ipAddress = address.getHostAddress(); + } catch (Exception e) { + logger.warn("DIRIGERA mDNS failed to resolve IP for {} reason {}", gatewayHostName, e.getMessage()); + } + } else if (ipAddresses.length > 0) { + ipAddress = ipAddresses[0].getHostAddress(); + } + if (ipAddress != null) { + Map properties = new HashMap<>(); + properties.put(PROPERTY_IP_ADDRESS, ipAddress); + return DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_GATEWAY, gatewayName)) + .withLabel("DIRIGERA Hub").withRepresentationProperty(PROPERTY_IP_ADDRESS) + .withProperties(properties).build(); + } + } + return null; + } + + @Override + public @Nullable ThingUID getThingUID(ServiceInfo si) { + String gatewayName = si.getQualifiedNameMap().get(ServiceInfo.Fields.Instance); + if (gatewayName != null) { + return new ThingUID(Constants.THING_TYPE_GATEWAY, gatewayName); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ApiException.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ApiException.java new file mode 100644 index 00000000000..d6777d28011 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ApiException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link ApiException} thrown in case of problems accessing API + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ApiException extends RuntimeException { + + private static final long serialVersionUID = -9075334430125847975L; + + public ApiException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/GatewayException.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/GatewayException.java new file mode 100644 index 00000000000..a5d63b5b7fb --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/GatewayException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link ApiException} thrown in case of problems accessing DIRIGERA gateway + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class GatewayException extends RuntimeException { + + private static final long serialVersionUID = -9187744844610930469L; + + public GatewayException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ModelException.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ModelException.java new file mode 100644 index 00000000000..a24bdb57029 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/exception/ModelException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.exception; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link ModelException} thrown in case of problems accessing Model + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ModelException extends RuntimeException { + + private static final long serialVersionUID = -3080953131870014248L; + + public ModelException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/BaseHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/BaseHandler.java new file mode 100644 index 00000000000..75ac50b9de5 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/BaseHandler.java @@ -0,0 +1,740 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.config.BaseDeviceConfiguration; +import org.openhab.binding.dirigera.internal.exception.GatewayException; +import org.openhab.binding.dirigera.internal.interfaces.DebugHandler; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.binding.dirigera.internal.interfaces.PowerListener; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.CommandOption; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link BaseHandler} for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BaseHandler extends BaseThingHandler implements DebugHandler { + private final Logger logger = LoggerFactory.getLogger(BaseHandler.class); + private List powerListeners = new ArrayList<>(); + private @Nullable Gateway gateway; + + // to be overwritten by child class in order to route the updates to the right instance + protected @Nullable BaseHandler child; + + // maps to route properties to channels and vice versa + protected Map property2ChannelMap; + protected Map channel2PropertyMap; + + // cache to handle each refresh command properly + protected Map channelStateMap; + + /* + * hardlinks initialized with invalid links because the first update shall trigger a link update. If it's declared + * as empty no link update will be triggered. This is necessary for startup phase. + */ + protected List hardLinks = new ArrayList<>(Arrays.asList("undef")); + protected List softLinks = new ArrayList<>(); + protected List linkCandidateTypes = new ArrayList<>(); + + /** + * Lists for canReceive and can Send capabilities + */ + protected List receiveCapabilities = new ArrayList<>(); + protected List sendCapabilities = new ArrayList<>(); + + protected State requestedPowerState = UnDefType.UNDEF; + protected State currentPowerState = UnDefType.UNDEF; + protected BaseDeviceConfiguration config; + protected String customName = ""; + protected String deviceType = ""; + protected boolean disposed = true; + protected boolean online = false; + protected boolean customDebug = false; + + public BaseHandler(Thing thing, Map mapping) { + super(thing); + config = new BaseDeviceConfiguration(); + + // mapping contains, reverse mapping for commands plus state cache + property2ChannelMap = mapping; + channel2PropertyMap = reverse(mapping); + channelStateMap = initializeCache(mapping); + } + + protected void setChildHandler(BaseHandler child) { + this.child = child; + } + + @Override + public void initialize() { + disposed = false; + config = getConfigAs(BaseDeviceConfiguration.class); + + // first get bridge as Gateway + Bridge bridge = getBridge(); + if (bridge != null) { + updateStatus(ThingStatus.UNKNOWN); + BridgeHandler handler = bridge.getHandler(); + if (handler != null) { + if (handler instanceof Gateway gw) { + gateway = gw; + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/dirigera.device.status.wrong-bridge-type"); + return; + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/dirigera.device.missing-bridge-handler"); + return; + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/dirigera.device.status.missing-bridge"); + return; + } + + if (!checkHandler()) { + // if handler doesn't match model status will be set to offline and it will stay until correction + return; + } + + if (!config.id.isBlank()) { + updateProperties(); + BaseHandler proxy = child; + if (proxy != null) { + gateway().registerDevice(proxy, config.id); + } + } + } + + private void updateProperties() { + // fill canSend and canReceive capabilities + Map modelProperties = gateway().model().getPropertiesFor(config.id); + Object canReceiveCapabilities = modelProperties.get(Model.PROPERTY_CAN_RECEIVE); + if (canReceiveCapabilities instanceof JSONArray jsonArray) { + jsonArray.forEach(capability -> { + if (!receiveCapabilities.contains(capability.toString())) { + receiveCapabilities.add(capability.toString()); + } + }); + } + Object canSendCapabilities = modelProperties.get(Model.PROPERTY_CAN_SEND); + if (canSendCapabilities instanceof JSONArray jsonArray) { + jsonArray.forEach(capability -> { + if (!sendCapabilities.contains(capability.toString())) { + sendCapabilities.add(capability.toString()); + } + }); + } + + TreeMap handlerProperties = new TreeMap<>(editProperties()); + modelProperties.forEach((key, value) -> { + handlerProperties.put(key, value.toString()); + }); + updateProperties(handlerProperties); + } + + /** + * Handling of basic commands which are the same for many devices + * - RefreshType for all channels + * - Startup behavior for lights and plugs + * - Power state for lights and plugs + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (customDebug) { + logger.info("DIRIGERA {} handleCommand channel {} command {} {}", thing.getUID(), channelUID.getAsString(), + command.toFullString(), command.getClass()); + } + if (command instanceof RefreshType) { + String channel = channelUID.getIdWithoutGroup(); + State cachedState = channelStateMap.get(channel); + if (cachedState != null) { + super.updateState(channelUID, cachedState); + } + } else { + String targetChannel = channelUID.getIdWithoutGroup(); + String targetProperty = channel2PropertyMap.get(targetChannel); + if (targetProperty != null) { + switch (targetChannel) { + case CHANNEL_STARTUP_BEHAVIOR: + if (command instanceof DecimalType decimal) { + String behaviorCommand = STARTUP_BEHAVIOR_REVERSE_MAPPING.get(decimal.intValue()); + if (behaviorCommand != null) { + JSONObject stqartupAttributes = new JSONObject(); + stqartupAttributes.put(targetProperty, behaviorCommand); + sendAttributes(stqartupAttributes); + } + break; + } + break; + case CHANNEL_POWER_STATE: + if (command instanceof OnOffType onOff) { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} OnOff command: Current {} / Wanted {}", + thing.getLabel(), currentPowerState, onOff); + } + requestedPowerState = onOff; + if (!currentPowerState.equals(onOff)) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, OnOffType.ON.equals(onOff)); + sendAttributes(attributes); + } else { + requestedPowerState = UnDefType.UNDEF; + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER Dismiss {} {}", thing.getLabel(), onOff); + } + } + } + break; + case CHANNEL_CUSTOM_NAME: + if (command instanceof StringType string) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, string.toString()); + sendAttributes(attributes); + } + break; + } + } else { + // handle channels which are not defined in device map + switch (targetChannel) { + case CHANNEL_LINKS: + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} remove connection {}", thing.getLabel(), + command.toFullString()); + } + if (command instanceof StringType string) { + linkUpdate(string.toFullString(), false); + } + break; + case CHANNEL_LINK_CANDIDATES: + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} add link {}", thing.getLabel(), + command.toFullString()); + } + if (command instanceof StringType string) { + linkUpdate(string.toFullString(), true); + } + break; + } + } + } + } + + /** + * Wrapper function to respect customDebug flag + * + * @param attributes + * @return status + */ + protected int sendAttributes(JSONObject attributes) { + int status = gateway().api().sendAttributes(config.id, attributes); + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} API call: Status {} payload {}", thing.getUID(), status, attributes); + } + return status; + } + + /** + * Wrapper function to respect customDebug flag + * + * @param attributes + * @return status + */ + protected int sendPatch(JSONObject patch) { + int status = gateway().api().sendPatch(config.id, patch); + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} API call: Status {} payload {}", thing.getUID(), status, patch); + } + return status; + } + + /** + * Handling generic channel updates for many devices. + * If they are not present in child configuration they won't be triggered. + * - Reachable flag for every device to evaluate ONLINE and OFFLINE states + * - Over the air (OTA) updates channels + * - Battery charge level + * - Startup behavior for lights and plugs + * - Power state for lights and plugs + * - custom name + * + * @param update + */ + public void handleUpdate(JSONObject update) { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} handleUpdate JSON {}", thing.getUID(), update); + } + // check online offline for each device + if (update.has(Model.REACHABLE)) { + if (update.getBoolean(Model.REACHABLE)) { + updateStatus(ThingStatus.ONLINE); + online = true; + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.device.status.not-reachable"); + online = false; + } + } + if (update.has(PROPERTY_DEVICE_TYPE) && deviceType.isBlank()) { + deviceType = update.getString(PROPERTY_DEVICE_TYPE); + } + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + // check OTA for each device + if (attributes.has(PROPERTY_OTA_STATUS)) { + createChannelIfNecessary(CHANNEL_OTA_STATUS, "ota-status", CoreItemFactory.NUMBER); + String otaStatusString = attributes.getString(PROPERTY_OTA_STATUS); + Integer otaStatus = OTA_STATUS_MAP.get(otaStatusString); + if (otaStatus != null) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), new DecimalType(otaStatus)); + } else { + logger.warn("DIRIGERA BASE_HANDLER {} Cannot decode ota status {}", thing.getLabel(), + otaStatusString); + } + } + if (attributes.has(PROPERTY_OTA_STATE)) { + createChannelIfNecessary(CHANNEL_OTA_STATE, "ota-state", CoreItemFactory.NUMBER); + String otaStateString = attributes.getString(PROPERTY_OTA_STATE); + Integer otaState = OTA_STATE_MAP.get(otaStateString); + if (otaState != null) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), new DecimalType(otaState)); + // if ota state changes also update properties to keep firmware in thing properties up to date + updateProperties(); + } else { + logger.warn("DIRIGERA BASE_HANDLER {} Cannot decode ota state {}", thing.getLabel(), + otaStateString); + } + } + if (attributes.has(PROPERTY_OTA_PROGRESS)) { + createChannelIfNecessary(CHANNEL_OTA_PROGRESS, "ota-percent", "Number:Dimensionless"); + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), + QuantityType.valueOf(attributes.getInt(PROPERTY_OTA_PROGRESS), Units.PERCENT)); + } + // battery also common, not for all but sensors and remote controller + if (attributes.has(PROPERTY_BATTERY_PERCENTAGE)) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), + QuantityType.valueOf(attributes.getInt(PROPERTY_BATTERY_PERCENTAGE), Units.PERCENT)); + } + if (attributes.has(PROPERTY_STARTUP_BEHAVIOR)) { + String startupString = attributes.getString(PROPERTY_STARTUP_BEHAVIOR); + Integer startupValue = STARTUP_BEHAVIOR_MAPPING.get(startupString); + if (startupValue != null) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), + new DecimalType(startupValue)); + } else { + logger.warn("DIRIGERA BASE_HANDLER {} Cannot decode startup behavior {}", thing.getLabel(), + startupString); + } + } + if (attributes.has(PROPERTY_POWER_STATE)) { + currentPowerState = OnOffType.from(attributes.getBoolean(PROPERTY_POWER_STATE)); + updateState(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), currentPowerState); + synchronized (powerListeners) { + if (online) { + boolean requested = currentPowerState.equals(requestedPowerState); + powerListeners.forEach(listener -> { + listener.powerChanged((OnOffType) currentPowerState, requested); + }); + requestedPowerState = UnDefType.UNDEF; + } + } + } + if (attributes.has(PROPERTY_CUSTOM_NAME) && customName.isBlank()) { + customName = attributes.getString(PROPERTY_CUSTOM_NAME); + updateState(new ChannelUID(thing.getUID(), CHANNEL_CUSTOM_NAME), StringType.valueOf(customName)); + } + } + if (update.has(PROPERTY_REMOTE_LINKS)) { + JSONArray remoteLinks = update.getJSONArray(PROPERTY_REMOTE_LINKS); + List updateList = new ArrayList<>(); + remoteLinks.forEach(link -> { + updateList.add(link.toString()); + }); + Collections.sort(updateList); + Collections.sort(hardLinks); + if (!hardLinks.equals(updateList)) { + hardLinks = updateList; + // just update internal link list and let the gateway update do all updates regarding soft links + gateway().updateLinks(); + } + } + } + + protected synchronized void createChannelIfNecessary(String channelId, String channelTypeUID, String itemType) { + if (thing.getChannel(channelId) == null) { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} create Channel {} {} {}", thing.getUID(), channelId, + channelTypeUID, itemType); + } + // https://www.openhab.org/docs/developer/bindings/#updating-the-thing-structure + ThingBuilder thingBuilder = editThing(); + // channel type UID needs to be defined in channel-types.xml + Channel channel = ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType) + .withType(new ChannelTypeUID(BINDING_ID, channelTypeUID)).build(); + updateThing(thingBuilder.withChannel(channel).build()); + } + } + + protected boolean isPowered() { + return OnOffType.ON.equals(currentPowerState) && online; + } + + /** + * Update cache for refresh, then update state + */ + @Override + protected void updateState(ChannelUID channelUID, State state) { + channelStateMap.put(channelUID.getIdWithoutGroup(), state); + if (!disposed) { + if (customDebug) { + logger.info("DIRIGERA {} updateState {} {}", thing.getUID(), channelUID, state); + } + super.updateState(channelUID, state); + } + } + + @Override + public void dispose() { + disposed = true; + online = false; + BaseHandler proxy = child; + if (proxy != null) { + gateway().unregisterDevice(proxy, config.id); + } + super.dispose(); + } + + @Override + public void handleRemoval() { + BaseHandler proxy = child; + if (proxy != null) { + gateway().deleteDevice(proxy, config.id); + } + super.handleRemoval(); + } + + public Gateway gateway() { + Gateway gw = gateway; + if (gw != null) { + return gw; + } else { + throw new GatewayException(thing.getUID() + " has no Gateway defined"); + } + } + + protected boolean checkHandler() { + // cross check if configured thing type is matching with the model + // if handler is taken from discovery this will do no harm + // but if it's created manually mismatch can happen + ThingTypeUID modelTTUID = gateway().model().identifyDeviceFromModel(config.id); + if (!thing.getThingTypeUID().equals(modelTTUID)) { + // check if id is present in model + if (THING_TYPE_NOT_FOUND.equals(modelTTUID)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, + "@text/dirigera.device.status.id-not-found" + " [\"" + config.id + "\"]"); + } else { + // String message = "Handler " + thing.getThingTypeUID() + " doesn't match with model " + modelTTUID; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/dirigera.device.status.ttuid-mismatch" + " [\"" + thing.getThingTypeUID() + "\",\"" + + modelTTUID + "\"]"); + } + return false; + } + return true; + } + + private Map initializeCache(Map mapping) { + final Map stateMap = new HashMap<>(); + mapping.forEach((key, value) -> { + stateMap.put(key, UnDefType.UNDEF); + }); + return stateMap; + } + + /** + * Evaluates if this device is a controller or sensor + * + * @return boolean + */ + protected boolean isControllerOrSensor() { + return deviceType.toLowerCase().contains("sensor") || deviceType.toLowerCase().contains("controller"); + } + + /** + * Handling of links + */ + + /** + * Update cycle of gateway is done + */ + public void updateLinksStart() { + softLinks.clear(); + } + + /** + * Get real links from device updates. Delivers a copy due to concurrent access. + * + * @return links attached to this device + */ + public List getLinks() { + return new ArrayList(hardLinks); + } + + private void linkUpdate(String linkedDeviceId, boolean add) { + /** + * link has to be set to target device like light or outlet, not to the device which triggers an action like + * lightController or motionSensor + */ + String targetDevice = ""; + String triggerDevice = ""; + List linksToSend = new ArrayList<>(); + if (isControllerOrSensor()) { + // request needs to be sent to target device + targetDevice = linkedDeviceId; + triggerDevice = config.id; + // get current links + JSONObject deviceData = gateway().model().getAllFor(targetDevice, PROPERTY_DEVICES); + if (deviceData.has(PROPERTY_REMOTE_LINKS)) { + JSONArray jsonLinks = deviceData.getJSONArray(PROPERTY_REMOTE_LINKS); + jsonLinks.forEach(link -> { + linksToSend.add(link.toString()); + }); + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} links for {} {}", thing.getLabel(), + gateway().model().getCustonNameFor(targetDevice), linksToSend); + } + // this is sensor branch so add link of sensor + if (add) { + if (!linksToSend.contains(triggerDevice)) { + linksToSend.add(triggerDevice); + } else { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} already linked {}", thing.getLabel(), + gateway().model().getCustonNameFor(triggerDevice)); + } + } + } else { + if (linksToSend.contains(triggerDevice)) { + linksToSend.remove(triggerDevice); + } else { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} no link to remove {}", thing.getLabel(), + gateway().model().getCustonNameFor(triggerDevice)); + } + } + } + } else { + if (customDebug) { + logger.info("DIRIGERA BASE_HANDLER {} has no remoteLinks", thing.getLabel()); + } + } + } else { + // send update to this device + targetDevice = config.id; + triggerDevice = linkedDeviceId; + if (add) { + hardLinks.add(triggerDevice); + } else { + hardLinks.remove(triggerDevice); + } + linksToSend.addAll(hardLinks); + } + JSONArray newLinks = new JSONArray(linksToSend); + JSONObject attributes = new JSONObject(); + attributes.put(PROPERTY_REMOTE_LINKS, newLinks); + gateway().api().sendPatch(targetDevice, attributes); + // after api command remoteLinks property will be updated and trigger new linkUpadte + } + + /** + * Adds a soft link towards the device which has the link stored in his attributes + * + * @param device id of the device which contains this link + */ + public void addSoftlink(String id) { + if (!softLinks.contains(id) && !config.id.equals(id)) { + softLinks.add(id); + } + } + + /** + * Update cycle of gateway is done + */ + public void updateLinksDone() { + if (hasLinksOrCandidates()) { + createChannelIfNecessary(CHANNEL_LINKS, CHANNEL_LINKS, CoreItemFactory.STRING); + createChannelIfNecessary(CHANNEL_LINK_CANDIDATES, CHANNEL_LINK_CANDIDATES, CoreItemFactory.STRING); + updateLinks(); + // The candidates needs to be evaluated by child class + // - blindController needs blinds and vice versa + // - soundCotroller needs speakers and vice versa + // - lightController needs light and outlet and vice versa + // So assure "linkCandidateTypes" are overwritten by child class with correct types + updateCandidateLinks(); + } + } + + protected void updateLinks() { + List display = new ArrayList<>(); + List linkCommandOptions = new ArrayList<>(); + List allLinks = new ArrayList<>(); + allLinks.addAll(hardLinks); + allLinks.addAll(softLinks); + Collections.sort(allLinks); + allLinks.forEach(link -> { + String customName = gateway().model().getCustonNameFor(link); + if (!gateway().isKnownDevice(link)) { + // if device isn't present in OH attach this suffix + customName += " (!)"; + } + display.add(customName); + linkCommandOptions.add(new CommandOption(link, customName)); + }); + ChannelUID channelUUID = new ChannelUID(thing.getUID(), CHANNEL_LINKS); + gateway().getCommandProvider().setCommandOptions(channelUUID, linkCommandOptions); + logger.trace("DIRIGERA BASE_HANDLER {} links {}", thing.getLabel(), display); + updateState(channelUUID, StringType.valueOf(display.toString())); + } + + protected void updateCandidateLinks() { + List possibleCandidates = gateway().model().getDevicesForTypes(linkCandidateTypes); + List candidates = new ArrayList<>(); + possibleCandidates.forEach(entry -> { + if (!hardLinks.contains(entry) && !softLinks.contains(entry)) { + candidates.add(entry); + } + }); + + List display = new ArrayList<>(); + List candidateOptions = new ArrayList<>(); + Collections.sort(candidates); + candidates.forEach(candidate -> { + String customName = gateway().model().getCustonNameFor(candidate); + if (!gateway().isKnownDevice(candidate)) { + // if device isn't present in OH attach this suffix + customName += " (!)"; + } + display.add(customName); + candidateOptions.add(new CommandOption(candidate, customName)); + }); + ChannelUID channelUUID = new ChannelUID(thing.getUID(), CHANNEL_LINK_CANDIDATES); + gateway().getCommandProvider().setCommandOptions(channelUUID, candidateOptions); + updateState(channelUUID, StringType.valueOf(display.toString())); + } + + /** + * Check is any outgoing or incoming links or candidates are available + * + * @return true if one of the above conditions is true + */ + private boolean hasLinksOrCandidates() { + return (!hardLinks.isEmpty() || !softLinks.isEmpty() + || !gateway().model().getDevicesForTypes(linkCandidateTypes).isEmpty()); + } + + public void addPowerListener(PowerListener listener) { + synchronized (powerListeners) { + powerListeners.add(listener); + } + } + + public void removePowerListener(PowerListener listener) { + synchronized (powerListeners) { + powerListeners.remove(listener); + } + } + + /** + * Debug commands for console access + */ + + @Override + public String getJSON() { + if (THING_TYPE_SCENE.equals(thing.getThingTypeUID())) { + return gateway().api().readScene(config.id).toString(); + } else { + return gateway().api().readDevice(config.id).toString(); + } + } + + @Override + public String getToken() { + return gateway().getToken(); + } + + @Override + public void setDebug(boolean debug, boolean all) { + if (all) { + ((DebugHandler) gateway()).setDebug(debug, all); + } else { + customDebug = debug; + } + } + + @Override + public String getDeviceId() { + return config.id; + } + + /** + * for unit testing + */ + @Override + public @Nullable ThingHandlerCallback getCallback() { + return super.getCallback(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DeviceUpdate.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DeviceUpdate.java new file mode 100644 index 00000000000..f92e978d0d6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DeviceUpdate.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link DeviceUpdate} element handled in device update queue + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DeviceUpdate { + public enum Action { + ADD, + DISPOSE, + REMOVE, + LINKS; + } + + public @Nullable BaseHandler handler; + public String deviceId; + public Action action; + + public DeviceUpdate(@Nullable BaseHandler handler, String deviceId, Action action) { + this.handler = handler; + this.deviceId = deviceId; + this.action = action; + } + + /** + * Link updates are equal because they are generic, all others false + * + * @param other + * @return + */ + @Override + public boolean equals(@Nullable Object other) { + boolean result = false; + if (other instanceof DeviceUpdate otherDeviceUpdate) { + result = this.action.equals(otherDeviceUpdate.action) && this.deviceId.equals(otherDeviceUpdate.deviceId); + BaseHandler thisProxyHandler = this.handler; + BaseHandler otherProxyHandler = otherDeviceUpdate.handler; + if (result && thisProxyHandler != null && otherProxyHandler != null) { + result = thisProxyHandler.equals(otherProxyHandler); + } + } + return result; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DirigeraHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DirigeraHandler.java new file mode 100644 index 00000000000..5ae0bea082b --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/DirigeraHandler.java @@ -0,0 +1,1052 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.DirigeraCommandProvider; +import org.openhab.binding.dirigera.internal.config.DirigeraConfiguration; +import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService; +import org.openhab.binding.dirigera.internal.exception.ApiException; +import org.openhab.binding.dirigera.internal.exception.ModelException; +import org.openhab.binding.dirigera.internal.interfaces.DebugHandler; +import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.binding.dirigera.internal.model.DirigeraModel; +import org.openhab.binding.dirigera.internal.network.DirigeraAPIImpl; +import org.openhab.binding.dirigera.internal.network.Websocket; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PointType; +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.storage.Storage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.CommandOption; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.openhab.core.util.StringUtils; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link DirigeraHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DirigeraHandler extends BaseBridgeHandler implements Gateway, DebugHandler { + + private final Logger logger = LoggerFactory.getLogger(DirigeraHandler.class); + + // Can be overwritten by Unit test for mocking API + protected Map channelStateMap = new HashMap<>(); + protected Class apiProvider = DirigeraAPIImpl.class; + + private final Map deviceTree = new HashMap<>(); + private final DirigeraDiscoveryService discoveryService; + private final DirigeraCommandProvider commandProvider; + private final BundleContext bundleContext; + private final Websocket websocket; + + private Optional api = Optional.empty(); + private Optional model = Optional.empty(); + private Optional> watchdog = Optional.empty(); + private Optional> updater = Optional.empty(); + private Optional> detectionSchedule = Optional.empty(); + + // private ScheduledExecutorService sequentialScheduler; + private List knownDevices = new ArrayList<>(); + private ArrayList websocketQueue = new ArrayList<>(); + private ArrayList deviceModificationQueue = new ArrayList<>(); + private DirigeraConfiguration config; + private Storage storage; + private HttpClient httpClient; + private String token = PROPERTY_EMPTY; + private Instant sunriseInstant = Instant.MAX; + private Instant sunsetInstant = Instant.MIN; + private Instant peakRecognitionTime = Instant.MIN; + private int websocketQueueSizePeak = 0; + private int deviceUpdateQueueSizePeak = 0; + private boolean updateRunning = false; + private boolean customDebug = false; + + public static final DeviceUpdate LINK_UPDATE = new DeviceUpdate(null, "", DeviceUpdate.Action.LINKS); + public static long detectionTimeSeonds = 5; + + public DirigeraHandler(Bridge bridge, HttpClient insecureClient, Storage bindingStorage, + DirigeraDiscoveryService discoveryManager, LocationProvider locationProvider, + DirigeraCommandProvider commandProvider, BundleContext bundleContext) { + super(bridge); + this.discoveryService = discoveryManager; + this.httpClient = insecureClient; + this.storage = bindingStorage; + this.commandProvider = commandProvider; + this.bundleContext = bundleContext; + config = new DirigeraConfiguration(); + websocket = new Websocket(this, insecureClient); + + List locationOptions = new ArrayList<>(); + locationOptions.add(new CommandOption("", "Remove location")); + PointType location = locationProvider.getLocation(); + if (location != null) { + locationOptions.add(new CommandOption(location.toFullString(), "Home location")); + } + commandProvider.setCommandOptions(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), locationOptions); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (customDebug) { + logger.info("DIRIGERA HANDLER command {} : {} {}", channelUID, command.toFullString(), command.getClass()); + } + String channel = channelUID.getIdWithoutGroup(); + if (command instanceof RefreshType) { + State cachedState = channelStateMap.get(channel); + if (cachedState != null) { + super.updateState(channelUID, cachedState); + } + } else if (CHANNEL_PAIRING.equals(channel)) { + JSONObject permissionAttributes = new JSONObject(); + permissionAttributes.put(PROPERTY_PERMIT_JOIN, OnOffType.ON.equals(command)); + api().sendAttributes(config.id, permissionAttributes); + } else if (CHANNEL_LOCATION.equals(channel)) { + PointType coordinatesPoint = null; + if (command instanceof PointType point) { + coordinatesPoint = point; + } else if (command instanceof StringType string) { + if (string.toFullString().isBlank()) { + String nullCoordinates = model().getTemplate(Model.TEMPLATE_NULL_COORDINATES); + JSONObject patchCoordinates = new JSONObject(nullCoordinates); + if (customDebug) { + logger.info("DIRIGERA HANDLER send null coordinates {}", patchCoordinates); + } + api().sendPatch(config.id, patchCoordinates); + } else { + try { + coordinatesPoint = new PointType(string.toFullString()); + } catch (IllegalArgumentException exception) { + logger.warn("DIRIGERA HANDLER wrong home location format {} : {}", string, + exception.getMessage()); + } + } + } + if (coordinatesPoint != null) { + String coordinatesTemplate = model().getTemplate(Model.TEMPLATE_COORDINATES); + String coordinates = String.format(coordinatesTemplate, coordinatesPoint.getLatitude().toFullString(), + coordinatesPoint.getLongitude().toFullString()); + if (customDebug) { + logger.info("DIRIGERA HANDLER send coordinates {}", coordinates); + } + api().sendPatch(config.id, new JSONObject(coordinates)); + } + } + } + + @Override + public void initialize() { + config = getConfigAs(DirigeraConfiguration.class); + if (config.ipAddress.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/dirigera.device.status.missing-ip"); + } else { + // do this asynchronous in case of token and other parameters needs to be + // obtained via Rest API calls + updateStatus(ThingStatus.UNKNOWN); + scheduler.execute(this::doInitialize); + } + } + + private void doInitialize() { + // for discovery known device are stored in storage in order not to report them + // again and again through DiscoveryService + getKnownDevicesFromStorage(); + token = getTokenFromStorage(); + // Step 1 - check if token is stored or pairing is needed + if (token.isBlank()) { + // if token isn't recovered from storage begin pairing process + logger.debug("DIRIGERA HANDLER no token in storage"); + String codeVerifier = generateCodeVerifier(); + String challenge = generateCodeChallenge(codeVerifier); + String code = getCode(challenge); + if (!code.isBlank()) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NOT_YET_READY, + "@text/dirigera.gateway.status.pairing-button"); + /** + * Approx 3 minutes possible to push DIRIGERA button + */ + Instant stopAuth = Instant.now().plusSeconds(180); + while (Instant.now().isBefore(stopAuth) && token.isBlank()) { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + Thread.interrupted(); + return; + } + token = getToken(code, codeVerifier); + if (!token.isBlank()) { + logger.debug("DIRIGERA HANDLER token {} received", token); + storeToken(token); + } + } + } + } else { + logger.debug("DIRIGERA HANDLER obtained token {} from storage", token); + } + + // Step 2 - if token is fine start initializing, else set status pairing retry + if (!token.isBlank()) { + // now create api and model + try { + DirigeraAPI apiProviderInstance = (DirigeraAPI) apiProvider + .getConstructor(HttpClient.class, Gateway.class).newInstance(httpClient, this); + if (apiProviderInstance != null) { + api = Optional.of(apiProviderInstance); + } else { + throw (new InstantiationException(apiProvider.descriptorString())); + } + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + // this will not happen - DirirgeraAPIIMpl tested with mocks in unit tests + logger.error("Error {}", apiProvider.descriptorString()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/dirigera.gateway.status.api-error" + " [\"" + apiProvider.descriptorString() + "\"]"); + return; + } + Model houseModel = new DirigeraModel(this); + model = Optional.of(houseModel); + modelUpdate(); // initialize model + websocket.initialize(); + // checks API access and starts websocket + // status will be set ONLINE / OFFLINE based on the connection result + connectGateway(); + // start watchdog to check gateway connection and start recovery if necessary + watchdog = Optional.of(scheduler.scheduleWithFixedDelay(this::watchdog, 15, 15, TimeUnit.SECONDS)); + // infrequent updates for gateway itself + updater = Optional.of(scheduler.scheduleWithFixedDelay(this::updateGateway, 1, 15, TimeUnit.MINUTES)); + } else { + // status "retry pairing" if token is still blank + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NOT_YET_READY, + "@text/dirigera.gateway.status.pairing-retry"); + } + } + + @Override + public void dispose() { + super.dispose(); + watchdog.ifPresent(watchdogSchedule -> { + watchdogSchedule.cancel(false); + }); + updater.ifPresent(refresher -> { + refresher.cancel(false); + }); + websocket.dispose(); + } + + @Override + public void handleRemoval() { + storage.remove(config.ipAddress); + super.handleRemoval(); + } + + private void updateProperties() { + Map propertiesMap = model().getPropertiesFor(config.id); + TreeMap currentProperties = new TreeMap<>(editProperties()); + propertiesMap.forEach((key, value) -> { + currentProperties.put(key, value.toString()); + }); + updateProperties(currentProperties); + } + + private String getTokenFromStorage() { + JSONObject gatewayStorageJson = getStorageJson(); + if (gatewayStorageJson.has(PROPERTY_TOKEN)) { + String token = gatewayStorageJson.getString(PROPERTY_TOKEN); + if (!token.isBlank()) { + return token; + } + } + return PROPERTY_EMPTY; + } + + private void storeToken(String token) { + if (!config.ipAddress.isBlank()) { + JSONObject tokenStore = new JSONObject(); + tokenStore.put(PROPERTY_TOKEN, token); + storage.put(config.ipAddress, tokenStore.toString()); + } + } + + private void getKnownDevicesFromStorage() { + JSONObject gatewayStorageJson = getStorageJson(); + if (gatewayStorageJson.has(PROPERTY_DEVICES)) { + String knownDeviceString = gatewayStorageJson.getString(PROPERTY_DEVICES); + JSONArray arr = new JSONArray(knownDeviceString); + knownDevices = arr.toList(); + } + } + + private void storeKnownDevices() { + JSONObject gatewayStorageJson = getStorageJson(); + JSONArray toStoreArray = new JSONArray(knownDevices); + gatewayStorageJson.put(PROPERTY_DEVICES, toStoreArray.toString()); + storage.put(config.ipAddress, gatewayStorageJson.toString()); + } + + private JSONObject getStorageJson() { + if (!config.ipAddress.isBlank()) { + String gatewayStorageObject = storage.get(config.ipAddress); + if (gatewayStorageObject != null) { + JSONObject gatewayStorageJson = new JSONObject(gatewayStorageObject.toString()); + return gatewayStorageJson; + } + } + return new JSONObject(); + } + + /** + * Everything to handle pairing process + */ + + private String getCode(String challenge) { + try { + MultiMap<@Nullable String> baseParams = new MultiMap<>(); + baseParams.put("audience", "homesmart.local"); + baseParams.put("response_type", "code"); + baseParams.put("code_challenge", challenge); + baseParams.put("code_challenge_method", "S256"); + + String url = String.format(OAUTH_URL, config.ipAddress); + Request codeRequest = httpClient.newRequest(url).param("audience", "homesmart.local") + .param("response_type", "code").param("code_challenge", challenge) + .param("code_challenge_method", "S256"); + + ContentResponse response = codeRequest.timeout(10, TimeUnit.SECONDS).send(); + int responseStatus = response.getStatus(); + if (responseStatus != 200) { + String reason = response.getReason(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + responseStatus + " - " + reason + "\"]"); + return ""; + } + String responseString = response.getContentAsString(); + JSONObject codeResponse = new JSONObject(responseString); + String code = codeResponse.getString("code"); + return code; + } catch (InterruptedException | TimeoutException | ExecutionException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + e.getMessage() + "\"]"); + return ""; + } + } + + private String getToken(String code, String codeVerifier) { + try { + MultiMap<@Nullable String> baseParams = new MultiMap<>(); + baseParams.put("code", code); + baseParams.put("name", "openHAB"); + baseParams.put("grant_type", "authorization_code"); + baseParams.put("code_verifier", codeVerifier); + + String url = String.format(TOKEN_URL, config.ipAddress); + String urlEncoded = UrlEncoded.encode(baseParams, StandardCharsets.UTF_8, false); + Request tokenRequest = httpClient.POST(url) + .header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded") + .content(new StringContentProvider("application/x-www-form-urlencoded", urlEncoded, + StandardCharsets.UTF_8)) + .followRedirects(true).timeout(10, TimeUnit.SECONDS); + + ContentResponse response = tokenRequest.send(); + logger.debug("DIRIGERA HANDLER token response {} : {}", response.getStatus(), + response.getContentAsString()); + int responseStatus = response.getStatus(); + if (responseStatus != 200) { + return ""; + } + String responseString = response.getContentAsString(); + JSONObject tokenResponse = new JSONObject(responseString); + String accessToken = tokenResponse.getString("access_token"); + return accessToken; + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("DIRIGERA HANDLER exception fetching token {}", e.getMessage()); + return ""; + } + } + + private String generateCodeChallenge(String codeVerifier) { + try { + MessageDigest digest = MessageDigest.getInstance("sha256"); + byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(hash); + } catch (NoSuchAlgorithmException e) { + logger.warn("DIRIGERA HANDLER error creating code challenge {}", e.getMessage()); + return ""; + } + } + + private String generateCodeVerifier() { + return StringUtils.getRandomAlphanumeric(128); + } + + /** + * Distributing the device update towards the correct function + */ + /** + * Distributing the device update towards the correct function + */ + private void doDeviceUpdate() { + DeviceUpdate deviceUpdate = null; + synchronized (deviceModificationQueue) { + if (deviceUpdateQueueSizePeak < deviceModificationQueue.size()) { + deviceUpdateQueueSizePeak = deviceModificationQueue.size(); + logger.trace("DIRIGERA HANDLER Peak device updates {}", deviceUpdateQueueSizePeak); + peakRecognitionTime = Instant.now(); + } + if (!deviceModificationQueue.isEmpty()) { + deviceUpdate = deviceModificationQueue.remove(0); + /** + * check if other link update is still in queue. If yes dismiss this request and + * wait for latest one + */ + if (deviceUpdate.action.equals(DeviceUpdate.Action.LINKS) + && deviceModificationQueue.contains(deviceUpdate)) { + deviceUpdate = null; + logger.warn("DIRIGERA HANDLER Dismiss link update, there's a later one scheduled"); + } + } + } + if (deviceUpdate != null) { + BaseHandler handler = deviceUpdate.handler; + try { + switch (deviceUpdate.action) { + case ADD: + if (handler != null) { + startUpdate(); + doRegisterDevice(handler, deviceUpdate.deviceId); + } + break; + case DISPOSE: + if (handler != null) { + startUpdate(); + doUnregisterDevice(handler, deviceUpdate.deviceId); + } + break; + case REMOVE: + if (handler != null) { + startUpdate(); + doDeleteDevice(handler, deviceUpdate.deviceId); + } + break; + case LINKS: + startUpdate(); + doUpdateLinks(); + break; + } + } finally { + endUpdate(); + } + } + synchronized (deviceModificationQueue) { + if (deviceModificationQueue.isEmpty() && !Instant.MIN.equals(peakRecognitionTime)) { + logger.trace("DIRIGERA HANDLER Peak to zero time {} ms", + Duration.between(peakRecognitionTime, Instant.now()).toMillis()); + peakRecognitionTime = Instant.MIN; + } + } + } + + private void startUpdate() { + synchronized (this) { + while (updateRunning) { + try { + this.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + updateRunning = true; + } + } + + private void endUpdate() { + synchronized (this) { + updateRunning = false; + this.notifyAll(); + } + } + + @Override + public void registerDevice(BaseHandler deviceHandler, String deviceId) { + synchronized (deviceModificationQueue) { + deviceModificationQueue.add(new DeviceUpdate(deviceHandler, deviceId, DeviceUpdate.Action.ADD)); + } + scheduler.execute(this::doDeviceUpdate); + } + + /** + * register a running device + */ + private void doRegisterDevice(BaseHandler deviceHandler, String deviceId) { + if (!deviceId.isBlank()) { + // if id isn't known yet - store it + if (!knownDevices.contains(deviceId)) { + knownDevices.add(deviceId); + storeKnownDevices(); + } + } + deviceTree.put(deviceId, deviceHandler); + } + + @Override + public void unregisterDevice(BaseHandler deviceHandler, String deviceId) { + synchronized (deviceModificationQueue) { + deviceModificationQueue.add(new DeviceUpdate(deviceHandler, deviceId, DeviceUpdate.Action.DISPOSE)); + } + scheduler.execute(this::doDeviceUpdate); + } + + /** + * unregister device, not running but still available + */ + private void doUnregisterDevice(BaseHandler deviceHandler, String deviceId) { + // unregister from dispose but don't remove it from known devices + deviceTree.remove(deviceId); + } + + @Override + public void deleteDevice(BaseHandler deviceHandler, String deviceId) { + synchronized (deviceModificationQueue) { + deviceModificationQueue.add(new DeviceUpdate(deviceHandler, deviceId, DeviceUpdate.Action.REMOVE)); + } + scheduler.execute(this::doDeviceUpdate); + } + + /** + * Called by all device on handleRe + */ + private void doDeleteDevice(BaseHandler deviceHandler, String deviceId) { + deviceTree.remove(deviceId); + // removal of handler - store known devices + knownDevices.remove(deviceId); + storeKnownDevices(); + // before new detection the handler needs to be removed - now were in removing + // state + // for complex devices several removes are done so don't trigger detection every + // time + detectionSchedule.ifPresentOrElse(previousSchedule -> { + if (!previousSchedule.isDone()) { + previousSchedule.cancel(true); + } + detectionSchedule = Optional + .of(scheduler.schedule(model.get()::detection, detectionTimeSeonds, TimeUnit.SECONDS)); + }, () -> { + detectionSchedule = Optional + .of(scheduler.schedule(model.get()::detection, detectionTimeSeonds, TimeUnit.SECONDS)); + }); + } + + /** + * Interface to Model called if device isn't found anymore + */ + @Override + public void deleteDevice(String deviceId) { + BaseHandler activeHandler = deviceTree.remove(deviceId); + if (activeHandler != null) { + // if a handler is attached the check will fail and update the status to GONE + activeHandler.checkHandler(); + } + // removal of handler - store new known devices + if (knownDevices.contains(deviceId)) { + knownDevices.remove(deviceId); + storeKnownDevices(); + } + } + + @Override + public DirigeraAPI api() throws ApiException { + if (api.isEmpty()) { + throw new ApiException("No API available yet"); + } + return api.get(); + } + + @Override + public Model model() throws ModelException { + if (model.isEmpty()) { + throw new ModelException("No Model available yet"); + } + return model.get(); + } + + @Override + public DirigeraDiscoveryService discovery() { + return discoveryService; + } + + @Override + public BundleContext getBundleContext() { + return bundleContext; + } + + @Override + public boolean isKnownDevice(String id) { + return knownDevices.contains(id); + } + + @Override + public DirigeraCommandProvider getCommandProvider() { + return commandProvider; + } + + @Override + public String getIpAddress() { + return config.ipAddress; + } + + @Override + public boolean discoveryEnabled() { + return config.discovery; + } + + @Override + public void updateLinks() { + synchronized (deviceModificationQueue) { + deviceModificationQueue.add(LINK_UPDATE); + } + scheduler.execute(this::doDeviceUpdate); + } + + private void doUpdateLinks() { + // first clear start update cycle, softlinks are cleared before + synchronized (deviceTree) { + deviceTree.forEach((id, handler) -> { + handler.updateLinksStart(); + }); + // then update all links + deviceTree.forEach((id, handler) -> { + List links = handler.getLinks(); + if (!links.isEmpty()) { + if (customDebug) { + logger.info("DIRIGERA HANDLER links found for {} {}", handler.getThing().getLabel(), + links.size()); + } + } + links.forEach(link -> { + // assure investigated handler is different from target handler + if (!id.equals(link)) { + BaseHandler targetHandler = deviceTree.get(link); + if (targetHandler != null) { + targetHandler.addSoftlink(id); + } else { + if (customDebug) { + logger.info("DIRIGERA HANDLER no targethandler found to link {} to {}", id, link); + } + } + } + }); + }); + // finish update cycle so handler can update states + deviceTree.forEach((id, handler) -> { + handler.updateLinksDone(); + }); + } + } + + private void watchdog() { + // check updater is still active - maybe an exception caused termination + updater.ifPresentOrElse(refresher -> { + if (refresher.isDone()) { + updater = Optional.of(scheduler.scheduleWithFixedDelay(this::updateGateway, 1, 15, TimeUnit.MINUTES)); + } + }, () -> { + updater = Optional.of(scheduler.scheduleWithFixedDelay(this::updateGateway, 1, 15, TimeUnit.MINUTES)); + }); + // check websocket + if (websocket.isRunning()) { + Map pingPongMap = websocket.getPingPongMap(); + if (pingPongMap.size() > 1) { // at least 2 shall be missing before watchdog trigger + logger.debug("DIRIGERA HANDLER Watchdog Ping Pong Panic - {} pings not answered", pingPongMap.size()); + websocket.stop(); + String message = "ping not answered"; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + message + "\"]"); + scheduler.execute(this::connectGateway); + } else { + // good case - ping socket and check in next call for answers + websocket.ping(); + } + } else { + String message = "try to recover"; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + message + "\"]"); + scheduler.execute(this::connectGateway); + } + } + + /** + * Establish connections + * 1) Check API response + * 2) Start websocket and wait for positive answer + * + * @see websocketConnected + */ + private void connectGateway() { + JSONObject gatewayInfo = api().readDevice(config.id); + // check if API call was successful, otherwise starting websocket doesn't make + // sense + if (!gatewayInfo.has(DirigeraAPI.HTTP_ERROR_FLAG)) { + if (!websocket.isRunning()) { + if (customDebug) { + logger.info("DIRIGERA HANDLER WS restart necessary"); + } + websocket.start(); + // onConnect shall switch to ONLINE! + } // else websocket is running fine + } else { + String message = gatewayInfo.getInt(DirigeraAPI.HTTP_ERROR_STATUS) + " - " + + gatewayInfo.getString(DirigeraAPI.HTTP_ERROR_MESSAGE); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + message + "\"]"); + } + } + + private void updateGateway() { + JSONObject gatewayInfo = api().readDevice(config.id); + if (!gatewayInfo.has(DirigeraAPI.HTTP_ERROR_FLAG)) { + handleUpdate(gatewayInfo); + } else { + String message = gatewayInfo.getInt(DirigeraAPI.HTTP_ERROR_STATUS) + " - " + + gatewayInfo.getString(DirigeraAPI.HTTP_ERROR_MESSAGE); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + message + "\"]"); + } + } + + private void configureGateway() { + if (config.id.isBlank()) { + List gatewayList = model().getDevicesForTypes(List.of(DEVICE_TYPE_GATEWAY)); + if (gatewayList.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/dirigera.gateway.status.no-gateway"); + } else if (gatewayList.size() > 1) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, + "@text/dirigera.gateway.status.ambiguous-gateway"); + } else { + String id = gatewayList.get(0); + Configuration configUpdate = editConfiguration(); + configUpdate.put(PROPERTY_DEVICE_ID, id); + updateConfiguration(configUpdate); + // get fresh config after update + config = getConfigAs(DirigeraConfiguration.class); + updateProperties(); + } + } + } + + @Override + public void websocketConnected(boolean connected, String reason) { + if (connected) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + reason + "\"]"); + } + } + + @Override + public void websocketUpdate(String update) { + synchronized (websocketQueue) { + websocketQueue.add(update); + } + scheduler.execute(this::doUpdate); + } + + private void doUpdate() { + String json = ""; + synchronized (websocketQueue) { + if (websocketQueueSizePeak < websocketQueue.size()) { + websocketQueueSizePeak = websocketQueue.size(); + logger.trace("DIRIGERA HANDLER Websocket update queue size peak {}", websocketQueueSizePeak); + } + if (!websocketQueue.isEmpty()) { + json = websocketQueue.remove(0); + } + } + if (!json.isBlank()) { + JSONObject update; + JSONObject data; + String targetId; + try { + update = new JSONObject(json); + data = update.getJSONObject("data"); + targetId = data.getString("id"); + } catch (JSONException exception) { + logger.debug("DIRIGERA HANDLER cannot decode update {} {}", exception.getMessage(), json); + return; + } + + String type = update.getString(PROPERTY_TYPE); + switch (type) { + case EVENT_TYPE_SCENE_CREATED: + case EVENT_TYPE_SCENE_DELETED: + if (data.has(PROPERTY_TYPE)) { + String dataType = data.getString(PROPERTY_TYPE); + if (dataType.equals(TYPE_CUSTOM_SCENE)) { + // don't handle custom scenes so break + break; + } + } + case EVENT_TYPE_DEVICE_ADDED: + case EVENT_TYPE_DEVICE_REMOVED: + // update model - it will take control on newly added, changed and removed + // devices + if (customDebug) { + logger.info("DIRIGERA HANDLER device added / removed {}", json); + } + modelUpdate(); + break; + case EVENT_TYPE_DEVICE_CHANGE: + case EVENT_TYPE_SCENE_UPDATE: + if (targetId != null) { + if (targetId.equals(config.id)) { + this.handleUpdate(data); + } else { + BaseHandler targetHandler = deviceTree.get(targetId); + if (targetHandler != null) { + targetHandler.handleUpdate(data); + } else { + // special case: if custom name is changed in attributes force model update + // in order to present the updated name in discovery + if (data.has(PROPERTY_ATTRIBUTES)) { + JSONObject attributes = data.getJSONObject(PROPERTY_ATTRIBUTES); + if (attributes.has(Model.CUSTOM_NAME)) { + if (customDebug) { + logger.info("DIRIGERA HANDLER possible name change detected {}", + attributes.getString(Model.CUSTOM_NAME)); + } + modelUpdate(); + } + } + } + } + } + break; + default: + logger.debug("DIRIGERA HANDLER unkown type {} for websocket update {}", type, update); + } + } + } + + /** + * Called in 3 different situations + * 1) initialize + * 2) adding / removing devices or scenes + * 3) renaming devices or scenes + */ + private void modelUpdate() { + Instant modelUpdateStartTime = Instant.now(); + int status = model().update(); + if (status != 200) { + logger.warn("DIRIGERA HANDLER Model update failed {}", status); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/dirigera.gateway.status.comm-error" + " [\"" + status + "\"]"); + return; + } + long durationUpdateTime = Duration.between(modelUpdateStartTime, Instant.now()).toMillis(); + websocket.increase(Websocket.MODEL_UPDATES); + websocket.getStatistics().put(Websocket.MODEL_UPDATE_TIME, durationUpdateTime + " ms"); + websocket.getStatistics().put(Websocket.MODEL_UPDATE_LAST, Instant.now()); + // updateState(new ChannelUID(thing.getUID(), CHANNEL_JSON), + // StringType.valueOf(model().getModelString())); + configureGateway(); + updateGateway(); + } + + private void handleUpdate(JSONObject data) { + // websocket statistics for each update + updateState(new ChannelUID(thing.getUID(), CHANNEL_STATISTICS), + StringType.valueOf(websocket.getStatistics().toString())); + + if (data.has(Model.ATTRIBUTES)) { + JSONObject attributes = data.getJSONObject(Model.ATTRIBUTES); + // check ota for each device + if (attributes.has(PROPERTY_CUSTOM_NAME)) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_CUSTOM_NAME), + StringType.valueOf(attributes.getString(PROPERTY_CUSTOM_NAME))); + } + if (attributes.has(PROPERTY_PERMIT_JOIN)) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_PAIRING), + OnOffType.from(attributes.getBoolean(PROPERTY_PERMIT_JOIN))); + } + if (!attributes.isNull("coordinates")) { + JSONObject coordinates = attributes.getJSONObject("coordinates"); + if (coordinates.has("latitude") && coordinates.has("longitude")) { + PointType homeLocation = new PointType( + coordinates.getDouble("latitude") + "," + coordinates.getDouble("longitude")); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), homeLocation); + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), UnDefType.UNDEF); + } + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), UnDefType.UNDEF); + } + if (attributes.has(PROPERTY_OTA_STATUS)) { + String otaStatusString = attributes.getString(PROPERTY_OTA_STATUS); + Integer otaStatus = OTA_STATUS_MAP.get(otaStatusString); + if (otaStatus != null) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), new DecimalType(otaStatus)); + } else { + logger.warn("DIRIGERA HANDLER {} Cannot decode ota status {}", thing.getLabel(), otaStatusString); + } + } + if (attributes.has(PROPERTY_OTA_STATE)) { + String otaStateString = attributes.getString(PROPERTY_OTA_STATE); + if (OTA_STATE_MAP.containsKey(otaStateString)) { + Integer otaState = OTA_STATE_MAP.get(otaStateString); + if (otaState != null) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), new DecimalType(otaState)); + // if ota state changes also update properties to keep firmware in thing + // properties up to date + updateProperties(); + } else { + logger.debug("DIRIGERA HANDLER {} Cannot decode ota state {}", thing.getLabel(), + otaStateString); + } + } else { + logger.debug("DIRIGERA HANDLER {} Cannot decode ota state {}", thing.getLabel(), otaStateString); + } + } + if (attributes.has(PROPERTY_OTA_PROGRESS)) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), + QuantityType.valueOf(attributes.getInt(PROPERTY_OTA_PROGRESS), Units.PERCENT)); + } + // sunrise & sunset + if (!attributes.isNull("nextSunRise")) { + String sunRiseString = attributes.getString("nextSunRise"); + if (sunRiseString != null) { + sunriseInstant = Instant.parse(sunRiseString); + updateState(new ChannelUID(thing.getUID(), CHANNEL_SUNRISE), new DateTimeType(sunriseInstant)); + } + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_SUNRISE), UnDefType.UNDEF); + } + if (!attributes.isNull("nextSunSet")) { + String sunsetString = attributes.getString("nextSunSet"); + if (sunsetString != null) { + sunsetInstant = Instant.parse(attributes.getString("nextSunSet")); + updateState(new ChannelUID(thing.getUID(), CHANNEL_SUNSET), new DateTimeType(sunsetInstant)); + } + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_SUNSET), UnDefType.UNDEF); + } + } + } + + @Override + public @Nullable Instant getSunriseDateTime() { + if (sunriseInstant.equals(Instant.MAX)) { + return null; + } + return sunriseInstant; + } + + @Override + public @Nullable Instant getSunsetDateTime() { + if (sunsetInstant.equals(Instant.MIN)) { + return null; + } + return sunsetInstant; + } + + /** + * Update cache for refresh, then update state + */ + + @Override + protected void updateState(ChannelUID channelUID, State state) { + channelStateMap.put(channelUID.getIdWithoutGroup(), state); + super.updateState(channelUID, state); + } + + /** + * Debug commands for console access + */ + + @Override + public String getJSON() { + String json = api().readHome().toString(); + return json; + } + + @Override + public String getToken() { + return token; + } + + @Override + public void setDebug(boolean debug, boolean all) { + customDebug = debug; + if (all) { + deviceTree.forEach((key, handler) -> { + handler.setDebug(debug, false); + }); + } + } + + @Override + public String getDeviceId() { + return config.id; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/airpurifier/AirPurifierHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/airpurifier/AirPurifierHandler.java new file mode 100644 index 00000000000..5deb70524e7 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/airpurifier/AirPurifierHandler.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.airpurifier; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DecimalType; +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.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; + +/** + * The {@link AirPurifierHandler} for handling air cleaning devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class AirPurifierHandler extends BaseHandler { + + /** + * see + * https://github.com/dvdgeisler/DirigeraClient/blob/a760b4419a8b1adf469d14a6ce4e750e52d4d540/dirigera-client-api/src/main/java/de/dvdgeisler/iot/dirigera/client/api/model/device/airpurifier/AirPurifierFanMode.java#L5 + **/ + public static final Map FAN_MODES = Map.of("auto", 0, "low", 1, "medium", 2, "high", 3, "on", 4, + "off", 5); + /** + * see + * https://github.com/Leggin/dirigera/blob/790a3151d8b61151dcd31f2194297dc8d4d89640/src/dirigera/devices/air_purifier.py#L61 + **/ + public static final int FAN_SPEED_MAX = 50; + public static Map fanModeToState = reverseStateMapping(FAN_MODES); + + public AirPurifierHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + String targetProperty = channel2PropertyMap.get(channel); + if (targetProperty != null) { + switch (channel) { + case CHANNEL_CHILD_LOCK: + case CHANNEL_DISABLE_STATUS_LIGHT: + if (command instanceof OnOffType onOff) { + JSONObject onOffAttributes = new JSONObject(); + onOffAttributes.put(targetProperty, OnOffType.ON.equals(onOff)); + super.sendAttributes(onOffAttributes); + } + break; + case CHANNEL_PURIFIER_FAN_SPEED: + if (command instanceof PercentType percent) { + long speedAbs = Math.round(percent.intValue() * FAN_SPEED_MAX / 100.0); + JSONObject fanSpeedAttributes = new JSONObject(); + fanSpeedAttributes.put(targetProperty, speedAbs); + super.sendAttributes(fanSpeedAttributes); + } + break; + case CHANNEL_PURIFIER_FAN_MODE: + if (command instanceof DecimalType decimal) { + int fanMode = decimal.intValue(); + String fanModeAttribute = fanModeToState.get(fanMode); + if (fanModeAttribute != null) { + JSONObject fanModeAttributes = new JSONObject(); + fanModeAttributes.put(targetProperty, fanModeAttribute); + super.sendAttributes(fanModeAttributes); + } + } + break; + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_PURIFIER_FAN_MODE: + String fanMode = attributes.getString(key); + Integer fanModeNumber = FAN_MODES.get(fanMode); + if (fanModeNumber != null) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + new DecimalType(fanModeNumber)); + } + break; + case CHANNEL_PURIFIER_FAN_SPEED: + float speed = attributes.getFloat(key); + speed = Math.max(Math.min(speed, FAN_SPEED_MAX), 0); + int percent = Math.round(speed * 100 / FAN_SPEED_MAX); + updateState(new ChannelUID(thing.getUID(), targetChannel), new PercentType(percent)); + break; + case CHANNEL_PURIFIER_FAN_RUNTIME: + case CHANNEL_PURIFIER_FILTER_LIFETIME: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.MINUTE)); + break; + case CHANNEL_PURIFIER_FILTER_ELAPSED: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.MINUTE)); + State lifeTimeState = channelStateMap.get(CHANNEL_PURIFIER_FILTER_LIFETIME); + if (lifeTimeState != null && lifeTimeState instanceof QuantityType) { + int elapsed = attributes.getInt(key); + int lifetime = ((QuantityType) lifeTimeState).intValue(); + updateState(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FILTER_REMAIN), + QuantityType.valueOf(lifetime - elapsed, Units.MINUTE)); + } + break; + case CHANNEL_PARTICULATE_MATTER: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.MICROGRAM_PER_CUBICMETRE)); + break; + case CHANNEL_PURIFIER_FILTER_ALARM: + case CHANNEL_CHILD_LOCK: + case CHANNEL_DISABLE_STATUS_LIGHT: + updateState(new ChannelUID(thing.getUID(), targetChannel), + OnOffType.from(attributes.getBoolean(key))); + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/blind/BlindHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/blind/BlindHandler.java new file mode 100644 index 00000000000..d1500304579 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/blind/BlindHandler.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.blind; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link BlindHandler} for Window / Door blinds + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BlindHandler extends BaseHandler { + private final Logger logger = LoggerFactory.getLogger(BlindHandler.class); + public static final Map BLIND_STATES = Map.of("stopped", 0, "up", 1, "down", 2); + public static Map blindNumberToState = reverseStateMapping(BLIND_STATES); + + public BlindHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_BLIND_CONTROLLER); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String channel = channelUID.getIdWithoutGroup(); + if (command instanceof RefreshType) { + super.handleCommand(channelUID, command); + } else { + String targetProperty = channel2PropertyMap.get(channel); + if (targetProperty != null) { + switch (channel) { + case CHANNEL_BLIND_STATE: + if (command instanceof DecimalType state) { + String commandAttribute = blindNumberToState.get(state.intValue()); + if (commandAttribute != null) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, commandAttribute); + super.sendAttributes(attributes); + } else { + logger.warn("DIRIGERA BLIND_DEVICE Blind state unknown {}", state.intValue()); + } + } + break; + case CHANNEL_BLIND_LEVEL: + if (command instanceof PercentType percent) { + JSONObject attributes = new JSONObject(); + attributes.put("blindsTargetLevel", percent.intValue()); + super.sendAttributes(attributes); + } + break; + } + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_BLIND_STATE: + String blindState = attributes.getString(key); + Integer stateValue = BLIND_STATES.get(blindState); + if (stateValue != null) { + updateState(new ChannelUID(thing.getUID(), targetChannel), new DecimalType(stateValue)); + } else { + logger.warn("DIRIGERA BLIND_DEVICE Blind state unknown {}", blindState); + } + break; + case CHANNEL_BLIND_LEVEL: + updateState(new ChannelUID(thing.getUID(), targetChannel), + new PercentType(attributes.getInt(key))); + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BaseShortcutController.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BaseShortcutController.java new file mode 100644 index 00000000000..a6b6dc4763a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BaseShortcutController.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.PROPERTY_DEVICE_ID; + +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.storage.Storage; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link BaseShortcutController} for triggering scenes + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BaseShortcutController extends BaseHandler { + private final Logger logger = LoggerFactory.getLogger(BaseShortcutController.class); + + private Storage storage; + public Map sceneMapping = new HashMap<>(); + private Map triggerTimes = new HashMap<>(); + + private static final String SINGLE_PRESS = "singlePress"; + private static final String DOUBLE_PRESS = "doublePress"; + private static final String LONG_PRESS = "longPress"; + private static final List CLICK_PATTERNS = List.of(SINGLE_PRESS, DOUBLE_PRESS, LONG_PRESS); + + public BaseShortcutController(Thing thing, Map mapping, Storage bindingStorage) { + super(thing, mapping); + super.setChildHandler(this); + this.storage = bindingStorage; + } + + public void initializeScenes(String deviceId, String channel) { + // check scenes + CLICK_PATTERNS.forEach(pattern -> { + String patternKey = deviceId + ":" + channel + ":" + pattern; + if (!sceneMapping.containsKey(patternKey)) { + String patternSceneId = storage.get(patternKey); + if (patternSceneId != null) { + sceneMapping.put(patternKey, patternSceneId); + } else { + String uuid = getUID(); + String createdUUID = gateway().api().createScene(uuid, pattern, deviceId); + if (uuid.equals(createdUUID)) { + storage.put(patternKey, createdUUID); + sceneMapping.put(patternKey, createdUUID); + } else { + logger.warn("DIRIGERA BASE_SHORTCUT_CONTROLLER scene create failed for {}", patternKey); + } + } + } + + // after all check if scene is created and register for updates + String sceneId = sceneMapping.get(patternKey); + if (sceneId != null) { + gateway().registerDevice(this, sceneId); + } + }); + } + + @Override + public void dispose() { + sceneMapping.forEach((key, value) -> { + BaseHandler proxy = child; + if (proxy != null) { + gateway().unregisterDevice(proxy, value); + } + }); + super.dispose(); + } + + @Override + public void handleRemoval() { + sceneMapping.forEach((key, value) -> { + // cleanup storage and hub + BaseHandler proxy = child; + if (proxy != null) { + gateway().deleteDevice(proxy, value); + } + gateway().api().deleteScene(value); + storage.remove(key); + }); + super.handleRemoval(); + } + + private String getUID() { + String uuid = UUID.randomUUID().toString(); + while (gateway().model().has(uuid)) { + uuid = UUID.randomUUID().toString(); + } + return uuid; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(PROPERTY_DEVICE_ID) && update.has("triggers")) { + // first check if trigger happened + String sceneId = update.getString(PROPERTY_DEVICE_ID); + JSONArray triggers = update.getJSONArray("triggers"); + boolean triggered = false; + for (int i = 0; i < triggers.length(); i++) { + JSONObject triggerObject = triggers.getJSONObject(i); + if (triggerObject.has("triggeredAt")) { + String triggerTimeString = triggerObject.getString("triggeredAt"); + Instant triggerTime = Instant.parse(triggerTimeString); + Instant lastTriggered = triggerTimes.get(sceneId); + if (lastTriggered != null) { + if (triggerTime.isAfter(lastTriggered)) { + triggerTimes.put(sceneId, triggerTime); + triggered = true; + } + } else { + triggered = true; + triggerTimes.put(sceneId, triggerTime); + break; + } + } + } + // if triggered deliver + if (triggered) { + sceneMapping.forEach((key, value) -> { + if (sceneId.equals(value)) { + String[] channelPattern = key.split(":"); + String pattern = ""; + switch (channelPattern[2]) { + case SINGLE_PRESS: + pattern = "SHORT_PRESSED"; + break; + case DOUBLE_PRESS: + pattern = "DOUBLE_PRESSED"; + break; + case LONG_PRESS: + pattern = "LONG_PRESSED"; + break; + } + if (!pattern.isBlank()) { + triggerChannel(new ChannelUID(thing.getUID(), channelPattern[1]), pattern); + } + } + }); + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BlindsControllerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BlindsControllerHandler.java new file mode 100644 index 00000000000..16017184432 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/BlindsControllerHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.DEVICE_TYPE_BLINDS; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link BlindsControllerHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BlindsControllerHandler extends BaseHandler { + + public BlindsControllerHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_BLINDS); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag, no more special handling + super.handleUpdate(update); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/DoubleShortcutControllerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/DoubleShortcutControllerHandler.java new file mode 100644 index 00000000000..496ec18588e --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/DoubleShortcutControllerHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.core.storage.Storage; +import org.openhab.core.thing.Thing; + +/** + * The {@link DoubleShortcutControllerHandler} for triggering scenes + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DoubleShortcutControllerHandler extends BaseShortcutController { + public TreeMap relations = new TreeMap<>(); + + public DoubleShortcutControllerHandler(Thing thing, Map mapping, Storage bindingStorage) { + super(thing, mapping, bindingStorage); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + + // now register at gateway all device and scene ids + String relationId = gateway().model().getRelationId(config.id); + relations = gateway().model().getRelations(relationId); + Entry firstEntry = relations.firstEntry(); + String firstDeviceId = firstEntry.getKey(); + super.initializeScenes(firstDeviceId, CHANNEL_BUTTON_1); + gateway().registerDevice(this, firstDeviceId); + values = gateway().api().readDevice(firstDeviceId); + handleUpdate(values); + // double shortcut controller has 2 devices + Entry secondEntry = relations.higherEntry(firstEntry.getKey()); + String secondDeviceId = secondEntry.getKey(); + super.initializeScenes(secondDeviceId, CHANNEL_BUTTON_2); + gateway().registerDevice(this, secondDeviceId); + values = gateway().api().readDevice(secondDeviceId); + handleUpdate(values); + } + } + + @Override + public void dispose() { + // remove device mapping + relations.forEach((key, value) -> { + gateway().unregisterDevice(this, key); + }); + // super removes scene mapping + super.dispose(); + } + + @Override + public void handleRemoval() { + // delete device mapping + relations.forEach((key, value) -> { + gateway().deleteDevice(this, key); + }); + // super deletes scenes from model + super.handleRemoval(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/LightControllerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/LightControllerHandler.java new file mode 100644 index 00000000000..5df229ec550 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/LightControllerHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link LightControllerHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class LightControllerHandler extends BaseHandler { + + public LightControllerHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT, DEVICE_TYPE_OUTLET); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String targetChannel = channelUID.getIdWithoutGroup(); + switch (targetChannel) { + case CHANNEL_LIGHT_PRESET: + if (command instanceof StringType string) { + JSONArray presetValues = new JSONArray(); + // handle the standard presets from IKEA app, custom otherwise without consistency check + switch (string.toFullString()) { + case "Off": + // fine - array stays empty + break; + case "Warm": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_WARM)); + break; + case "Slowdown": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_SLOWDOWN)); + break; + case "Smooth": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_SMOOTH)); + break; + case "Bright": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_BRIGHT)); + break; + default: + presetValues = new JSONArray(string.toFullString()); + } + JSONObject preset = new JSONObject(); + preset.put("circadianPresets", presetValues); + super.sendAttributes(preset); + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + switch (key) { + case "circadianPresets": + if (attributes.has("circadianPresets")) { + JSONArray lightPresets = attributes.getJSONArray("circadianPresets"); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_PRESET), + StringType.valueOf(lightPresets.toString())); + } + break; + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/ShortcutControllerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/ShortcutControllerHandler.java new file mode 100644 index 00000000000..beead38a03a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/ShortcutControllerHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_BUTTON_1; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.core.storage.Storage; +import org.openhab.core.thing.Thing; + +/** + * The {@link ShortcutControllerHandler} for triggering scenes + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ShortcutControllerHandler extends BaseShortcutController { + + public ShortcutControllerHandler(Thing thing, Map mapping, Storage bindingStorage) { + super(thing, mapping, bindingStorage); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + super.initializeScenes(config.id, CHANNEL_BUTTON_1); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/SoundControllerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/SoundControllerHandler.java new file mode 100644 index 00000000000..58aca664921 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/controller/SoundControllerHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.openhab.binding.dirigera.internal.Constants.DEVICE_TYPE_SPEAKER; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.thing.Thing; + +/** + * The {@link SoundControllerHandler} for controlling SYMFONSIK speakers + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SoundControllerHandler extends BaseHandler { + + public SoundControllerHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_SPEAKER); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/BaseLight.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/BaseLight.java new file mode 100644 index 00000000000..74561ae8bf9 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/BaseLight.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.config.ColorLightConfiguration; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.PowerListener; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link BaseLight} for handling light commands in a controlled way + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class BaseLight extends BaseHandler implements PowerListener { + private final Logger logger = LoggerFactory.getLogger(BaseLight.class); + + protected ColorLightConfiguration lightConfig = new ColorLightConfiguration(); + protected Map lastUserMode = new HashMap<>(); + + private List lightRequestQueue = new ArrayList<>(); + private Instant readyForNextCommand = Instant.now(); + private JSONObject placeHolder = new JSONObject(); + private boolean executingCommand = false; + + public BaseLight(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT_CONTROLLER, DEVICE_TYPE_MOTION_SENSOR); + } + + @Override + public void initialize() { + super.initialize(); + lightConfig = getConfigAs(ColorLightConfiguration.class); + super.addPowerListener(this); + } + + @Override + public void dispose() { + super.removePowerListener(this); + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String channel = channelUID.getIdWithoutGroup(); + if (CHANNEL_POWER_STATE.equals(channel) && (command instanceof OnOffType onOff)) { + // route power state into queue instead of direct switch on / off + addOnOffCommand(OnOffType.ON.equals(onOff)); + } else { + super.handleCommand(channelUID, command); + } + } + + protected void addOnOffCommand(boolean on) { + LightCommand command; + if (on) { + command = new LightCommand(placeHolder, LightCommand.Action.ON); + } else { + command = new LightCommand(placeHolder, LightCommand.Action.OFF); + } + synchronized (lightRequestQueue) { + lightRequestQueue.add(command); + if (customDebug) { + logger.info("DIRIGERA BASE_LIGHT {} add command {}", thing.getLabel(), command.toString()); + } + } + scheduler.execute(this::executeCommand); + } + + protected void addCommand(@Nullable LightCommand command) { + if (command == null) { + return; + } + synchronized (lightRequestQueue) { + lightRequestQueue.add(command); + if (customDebug) { + logger.info("DIRIGERA BASE_LIGHT {} add command {}", thing.getLabel(), command.toString()); + } + } + scheduler.execute(this::executeCommand); + } + + /** + * execute commands in the order and delays of the lightRequestQueue + */ + protected void executeCommand() { + LightCommand request = null; + synchronized (lightRequestQueue) { + if (lightRequestQueue.isEmpty()) { + return; + } + + // wait for next time window and previous command is fully executed + while (readyForNextCommand.isAfter(Instant.now()) || executingCommand) { + try { + lightRequestQueue.wait(50); + } catch (InterruptedException e) { + lightRequestQueue.clear(); + Thread.interrupted(); + return; + } + } + + /* + * get command from queue and check if it needs to be executed + * if several requests of the same kind e.g. 5 brightness requests are in only the last one shall be + * executed + */ + if (!lightRequestQueue.isEmpty()) { + request = lightRequestQueue.remove(0); + } else { + lightRequestQueue.notifyAll(); + return; + } + if (lightRequestQueue.contains(request)) { + lightRequestQueue.notifyAll(); + return; + } + // now execute command + executingCommand = true; + } + if (customDebug) { + logger.info("DIRIGERA BASE_LIGHT {} execute {}", thing.getLabel(), request); + } + int addonMillis = 0; + switch (request.action) { + case ON: + super.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.ON); + addonMillis = lightConfig.fadeTime; + break; + case OFF: + super.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.OFF); + break; + case BRIGHTNESS: + case TEMPERATURE: + case COLOR: + super.sendAttributes(request.request); + if (isPowered()) { + addonMillis = lightConfig.fadeTime; + } + break; + } + // after command is sent to API add the time + readyForNextCommand = Instant.now().plus(addonMillis, ChronoUnit.MILLIS); + synchronized (lightRequestQueue) { + executingCommand = false; + lightRequestQueue.notifyAll(); + } + } + + protected void changeProperty(LightCommand.Action action, JSONObject request) { + LightCommand requestedCommand = new LightCommand(request, action); + if (isPowered()) { + addCommand(requestedCommand); + } else { + lastUserMode.put(action, requestedCommand); + switch (action) { + case COLOR: + addCommand(requestedCommand); + lastUserMode.remove(LightCommand.Action.TEMPERATURE); + break; + case TEMPERATURE: + addCommand(requestedCommand); + lastUserMode.remove(LightCommand.Action.COLOR); + break; + case BRIGHTNESS: + case ON: + case OFF: + default: + break; + } + logger.trace("DIRIGERA BASE_LIGHT {} last user mode settings {}", thing.getLabel(), lastUserMode); + } + } + + @Override + public void powerChanged(OnOffType power, boolean requested) { + // apply lum settings according to configuration in the right sequence if power changed to ON + if (OnOffType.ON.equals(power)) { + if (!requested) { + addOnOffCommand(true); + } + if (customDebug) { + logger.info("DIRIGERA BASE_LIGHT {} last user mode restore {}", thing.getLabel(), lastUserMode); + } + LightCommand brightnessCommand = lastUserMode.remove(LightCommand.Action.BRIGHTNESS); + LightCommand colorCommand = lastUserMode.remove(LightCommand.Action.COLOR); + LightCommand temperatureCommand = lastUserMode.remove(LightCommand.Action.TEMPERATURE); + switch (lightConfig.fadeSequence) { + case 0: + addCommand(brightnessCommand); + addCommand(colorCommand); + addCommand(temperatureCommand); + break; + case 1: + addCommand(colorCommand); + addCommand(temperatureCommand); + addCommand(brightnessCommand); + break; + } + } else { + // assure settings are clean for next startup + lastUserMode.clear(); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/ColorLightHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/ColorLightHandler.java new file mode 100644 index 00000000000..ff739204aa1 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/ColorLightHandler.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.binding.dirigera.internal.model.ColorModel; +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.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ColorLightHandler} for lights with hue, saturation and brightness + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ColorLightHandler extends TemperatureLightHandler { + private final Logger logger = LoggerFactory.getLogger(ColorLightHandler.class); + + private HSBType hsbStateReflection = new HSBType(); // proxy to reflect state to end user + private HSBType hsbDevice = new HSBType(); // strictly holding values which were received via update + private String colorMode = ""; + + public ColorLightHandler(Thing thing, Map mapping, DirigeraStateDescriptionProvider stateProvider) { + super(thing, mapping, stateProvider); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void dispose() { + super.dispose(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + if (CHANNEL_LIGHT_COLOR.equals(channel)) { + if (command instanceof HSBType hsb) { + // respect sequence + switch (lightConfig.fadeSequence) { + case 0: + brightnessCommand(hsb); + colorCommand(hsb); + break; + case 1: + colorCommand(hsb); + brightnessCommand(hsb); + break; + } + hsbStateReflection = hsb; + updateState(channelUID, hsb); + } else if (command instanceof OnOffType) { + super.addOnOffCommand(OnOffType.ON.equals(command)); + } else if (command instanceof PercentType percent) { + int requestedBrightness = percent.intValue(); + if (requestedBrightness == 0) { + super.addOnOffCommand(false); + } else { + brightnessCommand(new HSBType("0,0," + requestedBrightness)); + super.addOnOffCommand(true); + } + } + } + if (CHANNEL_LIGHT_TEMPERATURE.equals(channel) || CHANNEL_LIGHT_TEMPERATURE_ABS.equals(channel)) { + long kelvin = -1; + HSBType colorTemp = null; + if (command instanceof PercentType percent) { + kelvin = super.getKelvin(percent.intValue()); + colorTemp = ColorModel.kelvin2Hsb(kelvin); + } else if (command instanceof QuantityType number) { + kelvin = number.intValue(); + colorTemp = ColorModel.kelvin2Hsb(kelvin); + } + // there are color lights which cannot handle tempera HSB {}t ,kelvin,colorTempure as stored in capabilities + // in this case calculate color which is fitting to temperature + if (colorTemp != null && !receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY)) { + HSBType colorTempAdaption = new HSBType(colorTemp.getHue(), colorTemp.getSaturation(), + hsbDevice.getBrightness()); + if (customDebug) { + logger.info("DIRIGERA COLOR_LIGHT {} handle temperature as color {}", thing.getLabel(), + colorTempAdaption); + } + colorCommand(colorTempAdaption); + } + } + } + + /** + * Send hue and saturation to light device in case of difference is more than 2% + * + * @param hsb as requested color + * @return true if color request is sent, false otherwise + */ + private void colorCommand(HSBType hsb) { + if (!"color".equals(colorMode) || !ColorModel.closeTo(hsb, hsbDevice, 0.02)) { + JSONObject colorAttributes = new JSONObject(); + colorAttributes.put("colorHue", hsb.getHue().intValue()); + colorAttributes.put("colorSaturation", hsb.getSaturation().intValue() / 100.0); + super.changeProperty(LightCommand.Action.COLOR, colorAttributes); + } + } + + private void brightnessCommand(HSBType hsb) { + int requestedBrightness = hsb.getBrightness().intValue(); + int currentBrightness = hsbDevice.getBrightness().intValue(); + if (Math.abs(requestedBrightness - currentBrightness) > 1) { + if (requestedBrightness > 0) { + JSONObject brightnessattributes = new JSONObject(); + brightnessattributes.put("lightLevel", hsb.getBrightness().intValue()); + super.changeProperty(LightCommand.Action.BRIGHTNESS, brightnessattributes); + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + boolean deliverHSB = false; + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + if (ATTRIBUTE_COLOR_MODE.equals(key)) { + colorMode = attributes.getString(key); + } + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + // apply and update to hsbCurrent, only in case !isOn deliver fake brightness HSBs + switch (targetChannel) { + case CHANNEL_LIGHT_COLOR: + switch (key) { + case "colorHue": + double hueValue = attributes.getInt(key); + hsbDevice = new HSBType(new DecimalType(hueValue), hsbDevice.getSaturation(), + hsbDevice.getBrightness()); + hsbStateReflection = new HSBType(new DecimalType(hueValue), + hsbStateReflection.getSaturation(), hsbStateReflection.getBrightness()); + deliverHSB = true; + break; + case "colorSaturation": + int saturationValue = Math.round(attributes.getFloat(key) * 100); + hsbDevice = new HSBType(hsbDevice.getHue(), new PercentType(saturationValue), + hsbDevice.getBrightness()); + hsbStateReflection = new HSBType(hsbStateReflection.getHue(), + new PercentType(saturationValue), hsbStateReflection.getBrightness()); + deliverHSB = true; + break; + case "lightLevel": + int brightnessValue = attributes.getInt(key); + // device needs the right values + hsbDevice = new HSBType(hsbDevice.getHue(), hsbDevice.getSaturation(), + new PercentType(brightnessValue)); + hsbStateReflection = new HSBType(hsbStateReflection.getHue(), + hsbStateReflection.getSaturation(), new PercentType(brightnessValue)); + deliverHSB = true; + break; + } + break; + } + } + } + if (deliverHSB) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_COLOR), hsbStateReflection); + if (!receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY)) { + // if color light doesn't support native light temperature converted values are taken + long kelvin = Math.min(colorTemperatureMin, + Math.max(colorTemperatureMax, ColorModel.hsb2Kelvin(hsbStateReflection))); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE), + new PercentType(getPercent(kelvin))); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), + QuantityType.valueOf(kelvin, Units.KELVIN)); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/DimmableLightHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/DimmableLightHandler.java new file mode 100644 index 00000000000..8bedfb00dee --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/DimmableLightHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * {@link DimmableLightHandler} for lights with brightness + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DimmableLightHandler extends BaseLight { + protected int currentBrightness = 0; + + public DimmableLightHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + String targetProperty = channel2PropertyMap.get(channel); + if (targetProperty != null) { + switch (channel) { + case CHANNEL_LIGHT_BRIGHTNESS: + if (command instanceof PercentType percent) { + int percentValue = percent.intValue(); + // switch on or off depending on brightness ... + if (percentValue > 0) { + // first change brightness to be stored for power ON ... + if (Math.abs(percentValue - currentBrightness) > 1) { + JSONObject brightnessAttributes = new JSONObject(); + brightnessAttributes.put(targetProperty, percent.intValue()); + super.changeProperty(LightCommand.Action.BRIGHTNESS, brightnessAttributes); + } + // .. then switch power + if (!isPowered()) { + super.addOnOffCommand(true); + } + } else { + super.addOnOffCommand(false); + } + } else if (command instanceof OnOffType onOff) { + super.addOnOffCommand(OnOffType.ON.equals(onOff)); + } + break; + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_LIGHT_BRIGHTNESS: + // set new currentBrightness as received and continue with update depending on power state + currentBrightness = attributes.getInt(key); + case CHANNEL_POWER_STATE: + /** + * Power state changed + * on - report last received brightness + * off - deliver brightness 0 + */ + if (isPowered()) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_BRIGHTNESS), + new PercentType(currentBrightness)); + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_BRIGHTNESS), + new PercentType(0)); + } + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/LightCommand.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/LightCommand.java new file mode 100644 index 00000000000..e36320684fd --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/LightCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONObject; + +/** + * The {@link LightCommand} is holding all information to execute a new light command + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class LightCommand { + public enum Action { + ON, + BRIGHTNESS, + TEMPERATURE, + COLOR, + OFF + } + + public JSONObject request; + public Action action; + + public LightCommand(JSONObject request, Action action) { + this.request = request; + this.action = action; + } + + /** + * Link updates are equal because they are generic, all others false + * + * @param other + * @return + */ + @Override + public boolean equals(@Nullable Object other) { + return (other instanceof LightCommand command && action.equals(command.action)); + } + + @Override + public String toString() { + return this.action + ": " + this.request.toString(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/SwitchLightHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/SwitchLightHandler.java new file mode 100644 index 00000000000..7674d56ca17 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/SwitchLightHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.core.thing.Thing; + +/** + * {@link SwitchLightHandler} for lights which can only be switched on / off + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SwitchLightHandler extends BaseLight { + + public SwitchLightHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/TemperatureLightHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/TemperatureLightHandler.java new file mode 100644 index 00000000000..74a0eda3bf0 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/light/TemperatureLightHandler.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.light; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.math.BigDecimal; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.binding.dirigera.internal.interfaces.Model; +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.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateDescriptionFragment; +import org.openhab.core.types.StateDescriptionFragmentBuilder; + +/** + * {@link TemperatureLightHandler} for lights with brightness and color temperature + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class TemperatureLightHandler extends DimmableLightHandler { + private PercentType currentColorTemp = new PercentType(); + + protected final DirigeraStateDescriptionProvider stateProvider; + // default values of "standard IKEA lamps" from JSON + protected int colorTemperatureMin = 4000; + protected int colorTemperatureMax = 2202; + protected int range = colorTemperatureMin - colorTemperatureMax; + + public TemperatureLightHandler(Thing thing, Map mapping, + DirigeraStateDescriptionProvider stateProvider) { + super(thing, mapping); + super.setChildHandler(this); + this.stateProvider = stateProvider; + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + JSONObject attributes = values.getJSONObject(Model.ATTRIBUTES); + // check for settings of color temperature in attributes + TreeMap properties = new TreeMap<>(editProperties()); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + if ("colorTemperatureMin".equals(key)) { + colorTemperatureMin = attributes.getInt(key); + properties.put("colorTemperatureMin", String.valueOf(colorTemperatureMin)); + } else if ("colorTemperatureMax".equals(key)) { + colorTemperatureMax = attributes.getInt(key); + properties.put("colorTemperatureMax", String.valueOf(colorTemperatureMax)); + } + } + StateDescriptionFragment fragment = StateDescriptionFragmentBuilder.create() + .withMinimum(BigDecimal.valueOf(colorTemperatureMax)) + .withMaximum(BigDecimal.valueOf(colorTemperatureMin)).withStep(BigDecimal.valueOf(100)) + .withPattern("%.0f K").withReadOnly(false).build(); + stateProvider.setStateDescription(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), fragment); + updateProperties(properties); + range = colorTemperatureMin - colorTemperatureMax; + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + String targetProperty = channel2PropertyMap.get(channel); + switch (channel) { + case CHANNEL_LIGHT_TEMPERATURE_ABS: + targetProperty = "colorTemperature"; + case CHANNEL_LIGHT_TEMPERATURE: + long kelvinValue = -1; + int percentValue = -1; + if (command instanceof PercentType percent) { + percentValue = percent.intValue(); + kelvinValue = getKelvin(percent.intValue()); + } else if (command instanceof QuantityType number) { + kelvinValue = number.intValue(); + percentValue = getPercent(kelvinValue); + } else if (command instanceof OnOffType onOff) { + super.addOnOffCommand(OnOffType.ON.equals(onOff)); + } + /* + * some color lights which inherit this temperature light don't have the temperature capability. + * As workaround child class ColorLightHandler is handling color temperature + */ + if (receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY) && percentValue != -1 + && kelvinValue != -1) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, kelvinValue); + super.changeProperty(LightCommand.Action.TEMPERATURE, attributes); + if (!isPowered()) { + // fake event for power OFF + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE), + new PercentType(percentValue)); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), + QuantityType.valueOf(kelvinValue, Units.KELVIN)); + } + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_LIGHT_TEMPERATURE: + int kelvin = attributes.getInt(key); + // seems some lamps are delivering temperature values out of range + // keep it in range with min/max + kelvin = Math.min(kelvin, colorTemperatureMin); + kelvin = Math.max(kelvin, colorTemperatureMax); + int percent = getPercent(kelvin); + currentColorTemp = new PercentType(percent); + updateState(new ChannelUID(thing.getUID(), targetChannel), currentColorTemp); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), + QuantityType.valueOf(kelvin, Units.KELVIN)); + break; + } + } + } + } + } + + protected long getKelvin(int percent) { + return Math.round(colorTemperatureMin - (range * percent / 100)); + } + + protected int getPercent(long kelvin) { + return Math.min(100, Math.max(0, Math.round(100 - ((kelvin - colorTemperatureMax) * 100 / range)))); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/PowerPlugHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/PowerPlugHandler.java new file mode 100644 index 00000000000..21f61f0673e --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/PowerPlugHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.plug; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link PowerPlugHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class PowerPlugHandler extends SimplePlugHandler { + public PowerPlugHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + // update of values is handled in super class + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + String targetProperty = channel2PropertyMap.get(channel); + if (targetProperty != null) { + switch (channel) { + case CHANNEL_CHILD_LOCK: + case CHANNEL_DISABLE_STATUS_LIGHT: + if (command instanceof OnOffType onOff) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, OnOffType.ON.equals(onOff)); + super.sendAttributes(attributes); + } + break; + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_CHILD_LOCK: + case CHANNEL_DISABLE_STATUS_LIGHT: + updateState(new ChannelUID(thing.getUID(), targetChannel), + OnOffType.from(attributes.getBoolean(key))); + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SimplePlugHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SimplePlugHandler.java new file mode 100644 index 00000000000..86450702875 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SimplePlugHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.plug; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.thing.Thing; + +/** + * The {@link SimplePlugHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SimplePlugHandler extends BaseHandler { + public SimplePlugHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT_CONTROLLER, DEVICE_TYPE_MOTION_SENSOR); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + // handling of first update, also for PowerPlug and SmartPlug child classes! + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SmartPlugHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SmartPlugHandler.java new file mode 100644 index 00000000000..5751390e379 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/plug/SmartPlugHandler.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.plug; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.time.Instant; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link SmartPlugHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SmartPlugHandler extends PowerPlugHandler { + private double totalEnergy = -1; + private double resetEnergy = -1; + + public SmartPlugHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + // update of values is handled in super class + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String channel = channelUID.getIdWithoutGroup(); + switch (channel) { + case CHANNEL_ENERGY_RESET_DATE: + if (command instanceof DateTimeType) { + scheduler.schedule(this::energyReset, 250, TimeUnit.MILLISECONDS); + } + } + } + + private void energyReset() { + JSONObject reset = new JSONObject("{\"energyConsumedAtLastReset\": 0}"); + super.sendAttributes(reset); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_POWER: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.WATT)); + break; + case CHANNEL_CURRENT: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.AMPERE)); + break; + case CHANNEL_POTENTIAL: + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getDouble(key), Units.VOLT)); + break; + case CHANNEL_ENERGY_TOTAL: + totalEnergy = attributes.getDouble(key); + updateState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTAL), + QuantityType.valueOf(totalEnergy, Units.KILOWATT_HOUR)); + if (totalEnergy >= 0 && resetEnergy >= 0) { + double diff = totalEnergy - resetEnergy; + updateState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET), + QuantityType.valueOf(diff, Units.KILOWATT_HOUR)); + } + break; + case CHANNEL_ENERGY_RESET: + resetEnergy = attributes.getDouble(key); + if (totalEnergy >= 0 && resetEnergy >= 0) { + double diff = totalEnergy - resetEnergy; + updateState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET), + QuantityType.valueOf(diff, Units.KILOWATT_HOUR)); + } + break; + case CHANNEL_ENERGY_RESET_DATE: + String dateTime = attributes.getString(key); + Instant restTime = Instant.parse(dateTime); + updateState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET_DATE), + new DateTimeType(restTime)); + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/repeater/RepeaterHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/repeater/RepeaterHandler.java new file mode 100644 index 00000000000..30038a4ac84 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/repeater/RepeaterHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.repeater; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link RepeaterHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class RepeaterHandler extends BaseHandler { + + public RepeaterHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag, no more special handling + super.handleUpdate(update); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/scene/SceneHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/scene/SceneHandler.java new file mode 100644 index 00000000000..e0d6a707ca5 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/scene/SceneHandler.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.scene; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; + +/** + * The {@link SceneHandler} for triggering defined scenes + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SceneHandler extends BaseHandler { + private Instant lastTrigger = Instant.MAX; + private int undoDuration = 30; + + public SceneHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // no link support for Scenes + hardLinks = Arrays.asList(); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readScene(config.id); + handleUpdate(values); + + if (values.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, + "@text/dirigera.scene.status.scene-not-found"); + } else { + updateStatus(ThingStatus.ONLINE); + } + + // check if different undo duration is configured + if (values.has("undoAllowedDuration")) { + undoDuration = values.getInt("undoAllowedDuration"); + } + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + if (CHANNEL_TRIGGER.equals(channelUID.getIdWithoutGroup())) { + if (command instanceof DecimalType decimal) { + int commandNumber = decimal.intValue(); + switch (commandNumber) { + case 0: + gateway().api().triggerScene(config.id, "trigger"); + lastTrigger = Instant.now(); + scheduler.schedule(this::countDown, 1, TimeUnit.SECONDS); + break; + case 1: + gateway().api().triggerScene(config.id, "undo"); + lastTrigger = Instant.MAX; + updateState(new ChannelUID(thing.getUID(), CHANNEL_TRIGGER), UnDefType.UNDEF); + break; + } + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has("lastTriggered")) { + Instant lastRiggeredInstant = Instant.parse(update.getString("lastTriggered")); + DateTimeType dtt = new DateTimeType(lastRiggeredInstant); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LAST_TRIGGER), dtt); + } + } + + private void countDown() { + long seconds = Duration.between(lastTrigger, Instant.now()).toSeconds(); + if (seconds >= 0 && seconds <= 30) { + long countDown = undoDuration - seconds; + updateState(new ChannelUID(thing.getUID(), CHANNEL_TRIGGER), new DecimalType(countDown)); + scheduler.schedule(this::countDown, 1, TimeUnit.SECONDS); + } else { + updateState(new ChannelUID(thing.getUID(), CHANNEL_TRIGGER), UnDefType.UNDEF); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/AirQualityHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/AirQualityHandler.java new file mode 100644 index 00000000000..503c3a1bf22 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/AirQualityHandler.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link AirQualityHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class AirQualityHandler extends BaseHandler { + + public AirQualityHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // no link support for Scenes + hardLinks = Arrays.asList(); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_TEMPERATURE: + double temperature = Math.round(attributes.getDouble(key) * 10) / 10.0; + updateState(new ChannelUID(thing.getUID(), CHANNEL_TEMPERATURE), + QuantityType.valueOf(temperature, SIUnits.CELSIUS)); + break; + case CHANNEL_HUMIDITY: + updateState(new ChannelUID(thing.getUID(), CHANNEL_HUMIDITY), + QuantityType.valueOf(attributes.getDouble(key), Units.PERCENT)); + break; + case CHANNEL_PARTICULATE_MATTER: + updateState(new ChannelUID(thing.getUID(), CHANNEL_PARTICULATE_MATTER), + QuantityType.valueOf(attributes.getDouble(key), Units.MICROGRAM_PER_CUBICMETRE)); + break; + case CHANNEL_VOC_INDEX: + updateState(new ChannelUID(thing.getUID(), CHANNEL_VOC_INDEX), + new DecimalType(attributes.getDouble(key))); + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/ContactSensorHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/ContactSensorHandler.java new file mode 100644 index 00000000000..778b4293781 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/ContactSensorHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_CONTACT; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link ContactSensorHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ContactSensorHandler extends BaseHandler { + + public ContactSensorHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // no link support for Scenes + hardLinks = Arrays.asList(); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_CONTACT: + OpenClosedType state = OpenClosedType.CLOSED; + if (attributes.getBoolean(key)) { + state = OpenClosedType.OPEN; + } + updateState(new ChannelUID(thing.getUID(), targetChannel), state); + break; + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/LightSensorHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/LightSensorHandler.java new file mode 100644 index 00000000000..eb159e08032 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/LightSensorHandler.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_ILLUMINANCE; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link LightSensorHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class LightSensorHandler extends BaseHandler { + public LightSensorHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + // handle reachable flag + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + if (CHANNEL_ILLUMINANCE.equals(targetChannel)) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getInt(key), Units.LUX)); + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionLightSensorHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionLightSensorHandler.java new file mode 100644 index 00000000000..13bb22529cf --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionLightSensorHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; + +/** + * The {@link MotionLightSensorHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class MotionLightSensorHandler extends MotionSensorHandler { + + private TreeMap relations = new TreeMap<>(); + + public MotionLightSensorHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + // assure deviceType is set from main device + if (values.has(PROPERTY_DEVICE_TYPE)) { + deviceType = values.getString(PROPERTY_DEVICE_TYPE); + } + + // get all relations and register + String relationId = gateway().model().getRelationId(config.id); + relations = gateway().model().getRelations(relationId); + // register for updates of twin devices + relations.forEach((key, value) -> { + gateway().registerDevice(this, key); + JSONObject relationValues = gateway().api().readDevice(key); + handleUpdate(relationValues); + }); + } + } + + @Override + public void dispose() { + relations.forEach((key, value) -> { + gateway().unregisterDevice(this, key); + }); + super.dispose(); + } + + @Override + public void handleRemoval() { + relations.forEach((key, value) -> { + gateway().deleteDevice(this, key); + }); + super.handleRemoval(); + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + if (CHANNEL_ILLUMINANCE.equals(targetChannel)) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(attributes.getInt(key), Units.LUX)); + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionSensorHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionSensorHandler.java new file mode 100644 index 00000000000..70d3ef76102 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/MotionSensorHandler.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MotionSensorHandler} + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class MotionSensorHandler extends BaseHandler { + + private final Logger logger = LoggerFactory.getLogger(MotionSensorHandler.class); + private final String timeFormat = "HH:mm"; + private String startTime = "20:00"; + private String endTime = "07:00"; + + public MotionSensorHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT, DEVICE_TYPE_OUTLET); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + String targetChannel = channelUID.getIdWithoutGroup(); + switch (targetChannel) { + case CHANNEL_ACTIVE_DURATION: + int seconds = -1; + if (command instanceof DecimalType decimal) { + seconds = decimal.intValue(); + } else if (command instanceof QuantityType quantity) { + QuantityType secondsQunatity = quantity.toUnit(Units.SECOND); + if (secondsQunatity != null) { + seconds = secondsQunatity.intValue(); + } + } + if (seconds > 0) { + String updateData = String + .format(gateway().model().getTemplate(Model.TEMPLATE_SENSOR_DURATION_UPDATE), seconds); + sendPatch(new JSONObject(updateData)); + } + break; + case CHANNEL_SCHEDULE: + if (command instanceof DecimalType decimal) { + switch (decimal.intValue()) { + case 0: + gateway().api().sendPatch(config.id, + new JSONObject(gateway().model().getTemplate(Model.TEMPLATE_SENSOR_ALWQAYS_ON))); + break; + case 1: + gateway().api().sendPatch(config.id, + new JSONObject(gateway().model().getTemplate(Model.TEMPLATE_SENSOR_FOLLOW_SUN))); + break; + case 2: + String template = gateway().model().getTemplate(Model.TEMPLATE_SENSOR_SCHEDULE_ON); + gateway().api().sendPatch(config.id, + new JSONObject(String.format(template, startTime, endTime))); + break; + } + } + break; + case CHANNEL_SCHEDULE_START: + String startSchedule = gateway().model().getTemplate(Model.TEMPLATE_SENSOR_SCHEDULE_ON); + if (command instanceof StringType string) { + // take string as it is, no consistency check + startTime = string.toFullString(); + } else if (command instanceof DateTimeType dateTime) { + startTime = dateTime.format(timeFormat, ZoneId.systemDefault()); + } + gateway().api().sendPatch(config.id, new JSONObject(String.format(startSchedule, startTime, endTime))); + break; + case CHANNEL_SCHEDULE_END: + String endSchedule = gateway().model().getTemplate(Model.TEMPLATE_SENSOR_SCHEDULE_ON); + if (command instanceof StringType string) { + endTime = string.toFullString(); + // take string as it is, no consistency check + } else if (command instanceof DateTimeType dateTime) { + endTime = dateTime.format(timeFormat, ZoneId.systemDefault()); + } + gateway().api().sendPatch(config.id, new JSONObject(String.format(endSchedule, startTime, endTime))); + break; + case CHANNEL_LIGHT_PRESET: + if (command instanceof StringType string) { + JSONArray presetValues = new JSONArray(); + // handle the standard presets from IKEA app, custom otherwise without consistency check + switch (string.toFullString()) { + case "Off": + // fine - array stays empty + break; + case "Warm": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_WARM)); + break; + case "Slowdown": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_SLOWDOWN)); + break; + case "Smooth": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_SMOOTH)); + break; + case "Bright": + presetValues = new JSONArray( + gateway().model().getTemplate(Model.TEMPLATE_LIGHT_PRESET_BRIGHT)); + break; + default: + presetValues = new JSONArray(string.toFullString()); + } + JSONObject preset = new JSONObject(); + preset.put("circadianPresets", presetValues); + super.sendAttributes(preset); + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + switch (targetChannel) { + case CHANNEL_MOTION_DETECTION: + updateState(new ChannelUID(thing.getUID(), targetChannel), + OnOffType.from(attributes.getBoolean(key))); + break; + case CHANNEL_ACTIVE_DURATION: + if (attributes.has("sensorConfig")) { + JSONObject sensorConfig = attributes.getJSONObject("sensorConfig"); + if (sensorConfig.has("onDuration")) { + int duration = sensorConfig.getInt("onDuration"); + updateState(new ChannelUID(thing.getUID(), targetChannel), + QuantityType.valueOf(duration, Units.SECOND)); + } + } + break; + } + } + // no direct channel mapping - sensor mapping is deeply nested :( + switch (key) { + case "circadianPresets": + if (attributes.has("circadianPresets")) { + JSONArray lightPresets = attributes.getJSONArray("circadianPresets"); + updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_PRESET), + StringType.valueOf(lightPresets.toString())); + } + break; + case "sensorConfig": + if (attributes.has("sensorConfig")) { + JSONObject sensorConfig = attributes.getJSONObject("sensorConfig"); + if (sensorConfig.has("scheduleOn")) { + boolean scheduled = sensorConfig.getBoolean("scheduleOn"); + if (scheduled) { + // examine schedule + if (sensorConfig.has("schedule")) { + JSONObject schedule = sensorConfig.getJSONObject("schedule"); + if (schedule.has("onCondition") && schedule.has("offCondition")) { + JSONObject onCondition = schedule.getJSONObject("onCondition"); + JSONObject offCondition = schedule.getJSONObject("offCondition"); + if (onCondition.has("time")) { + String onTime = onCondition.getString("time"); + String offTime = offCondition.getString("time"); + if ("sunset".equals(onTime)) { + // finally it's identified to follow the sun + updateState(new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE), + new DecimalType(1)); + Instant sunsetDateTime = gateway().getSunsetDateTime(); + if (sunsetDateTime != null) { + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_START), + new DateTimeType(sunsetDateTime)); + } else { + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_START), + UnDefType.UNDEF); + logger.warn( + "MOTION_SENSOR Location not activated in IKEA App - cannot follow sun"); + } + Instant sunriseDateTime = gateway().getSunriseDateTime(); + if (sunriseDateTime != null) { + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_END), + new DateTimeType(sunriseDateTime)); + } else { + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_END), + UnDefType.UNDEF); + logger.warn( + "MOTION_SENSOR Location not activated in IKEA App - cannot follow sun"); + } + } else { + // custom times - even worse parsing + String[] onHourMinute = onTime.split(":"); + String[] offHourMinute = offTime.split(":"); + if (onHourMinute.length == 2 && offHourMinute.length == 2) { + int onHour = Integer.parseInt(onHourMinute[0]); + int onMinute = Integer.parseInt(onHourMinute[1]); + int offHour = Integer.parseInt(offHourMinute[0]); + int offMinute = Integer.parseInt(offHourMinute[1]); + updateState(new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE), + new DecimalType(2)); + ZonedDateTime on = ZonedDateTime.now().withHour(onHour) + .withMinute(onMinute); + ZonedDateTime off = ZonedDateTime.now().withHour(offHour) + .withMinute(offMinute); + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_START), + new DateTimeType(on)); + updateState( + new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_END), + new DateTimeType(off)); + } + } + } + } + } + } else { + // always active + updateState(new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE), new DecimalType(0)); + updateState(new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_START), + UnDefType.UNDEF); + updateState(new ChannelUID(thing.getUID(), CHANNEL_SCHEDULE_END), UnDefType.UNDEF); + } + } + } + break; + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/WaterSensorHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/WaterSensorHandler.java new file mode 100644 index 00000000000..98f8219191f --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/sensor/WaterSensorHandler.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_LEAK_DETECTION; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; + +/** + * The {@link WaterSensorHandler} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class WaterSensorHandler extends BaseHandler { + + public WaterSensorHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // no link support for Scenes + hardLinks = Arrays.asList(); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + super.handleCommand(channelUID, command); + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + // now device specific + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + if (CHANNEL_LEAK_DETECTION.equals(targetChannel)) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + OnOffType.from(attributes.getBoolean(key))); + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/speaker/SpeakerHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/speaker/SpeakerHandler.java new file mode 100644 index 00000000000..8f7796afd85 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/handler/speaker/SpeakerHandler.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.speaker; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.NextPreviousType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.PlayPauseType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link SpeakerHandler} to control speaker devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class SpeakerHandler extends BaseHandler { + + public SpeakerHandler(Thing thing, Map mapping) { + super(thing, mapping); + super.setChildHandler(this); + // links of types which can be established towards this device + linkCandidateTypes = List.of(DEVICE_TYPE_SOUND_CONTROLLER); + } + + @Override + public void initialize() { + super.initialize(); + if (super.checkHandler()) { + JSONObject values = gateway().api().readDevice(config.id); + handleUpdate(values); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String channel = channelUID.getIdWithoutGroup(); + if (command instanceof RefreshType) { + super.handleCommand(channelUID, command); + } else { + String targetProperty = channel2PropertyMap.get(channel); + if (targetProperty != null) { + switch (channel) { + case CHANNEL_PLAYER: + if (command instanceof PlayPauseType playPause) { + String playState = (PlayPauseType.PLAY.equals(playPause) ? "playbackPlaying" + : "playbackPaused"); + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, playState); + super.sendAttributes(attributes); + } else if (command instanceof NextPreviousType nextPrevious) { + String playState = (NextPreviousType.NEXT.equals(nextPrevious) ? "playbackNext" + : "playbackPrevious"); + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, playState); + super.sendAttributes(attributes); + } + break; + case CHANNEL_VOLUME: + if (command instanceof PercentType percent) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, percent.intValue()); + super.sendAttributes(attributes); + } + break; + case CHANNEL_MUTE: + if (command instanceof OnOffType onOff) { + JSONObject attributes = new JSONObject(); + attributes.put(targetProperty, OnOffType.ON.equals(onOff)); + super.sendAttributes(attributes); + } + break; + } + } else { + // handle channels not in map due to deeper nesting objects + switch (channel) { + case CHANNEL_SHUFFLE: + if (command instanceof OnOffType onOff) { + JSONObject mode = new JSONObject(); + mode.put("shuffle", OnOffType.ON.equals(onOff)); + JSONObject attributes = new JSONObject(); + attributes.put("playbackModes", mode); + super.sendAttributes(attributes); + } + break; + case CHANNEL_CROSSFADE: + if (command instanceof OnOffType onOff) { + JSONObject mode = new JSONObject(); + mode.put("crossfade", OnOffType.ON.equals(onOff)); + JSONObject attributes = new JSONObject(); + attributes.put("playbackModes", mode); + super.sendAttributes(attributes); + } + break; + case CHANNEL_REPEAT: + if (command instanceof DecimalType decimal) { + int repeatModeInt = decimal.intValue(); + String repeatModeStr = ""; + switch (repeatModeInt) { + case 0: + repeatModeStr = "off"; + break; + case 1: + repeatModeStr = "playItem"; + break; + case 2: + repeatModeStr = "playlist"; + break; + } + if (!repeatModeStr.isBlank()) { + JSONObject mode = new JSONObject(); + mode.put("repeat", repeatModeStr); + JSONObject attributes = new JSONObject(); + attributes.put("playbackModes", mode); + super.sendAttributes(attributes); + } + } + break; + } + } + } + } + + @Override + public void handleUpdate(JSONObject update) { + super.handleUpdate(update); + if (update.has(Model.ATTRIBUTES)) { + JSONObject attributes = update.getJSONObject(Model.ATTRIBUTES); + Iterator attributesIterator = attributes.keys(); + while (attributesIterator.hasNext()) { + String key = attributesIterator.next(); + String targetChannel = property2ChannelMap.get(key); + if (targetChannel != null) { + if (CHANNEL_PLAYER.equals(targetChannel)) { + String playerState = attributes.getString(key); + switch (playerState) { + case "playbackPlaying": + updateState(new ChannelUID(thing.getUID(), targetChannel), PlayPauseType.PLAY); + break; + case "playbackIdle": + case "playbackPaused": + updateState(new ChannelUID(thing.getUID(), targetChannel), PlayPauseType.PAUSE); + break; + } + } else if (CHANNEL_VOLUME.equals(targetChannel)) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + new PercentType(attributes.getInt(key))); + } else if (CHANNEL_MUTE.equals(targetChannel)) { + updateState(new ChannelUID(thing.getUID(), targetChannel), + OnOffType.from(attributes.getBoolean(key))); + } else if (CHANNEL_PLAY_MODES.equals(targetChannel)) { + JSONObject playbackModes = attributes.getJSONObject(key); + if (playbackModes.has("crossfade")) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_CROSSFADE), + OnOffType.from(playbackModes.getBoolean("crossfade"))); + } + if (playbackModes.has("shuffle")) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_SHUFFLE), + OnOffType.from(playbackModes.getBoolean("shuffle"))); + } + if (playbackModes.has("repeat")) { + String repeatMode = playbackModes.getString("repeat"); + int playMode = -1; + switch (repeatMode) { + case "off": + playMode = 0; + break; + case "playItem": + playMode = 1; + break; + case "playlist": + playMode = 2; + break; + } + if (playMode != -1) { + updateState(new ChannelUID(thing.getUID(), CHANNEL_REPEAT), new DecimalType(playMode)); + } + } + + } else if (CHANNEL_TRACK.equals(targetChannel)) { + // track is nested into attributes playItem + State track = UnDefType.UNDEF; + State image = UnDefType.UNDEF; + JSONObject audio = attributes.getJSONObject(key); + if (audio.has("playItem")) { + JSONObject playItem = audio.getJSONObject("playItem"); + if (playItem.has("title")) { + track = new StringType(playItem.getString("title")); + } + if (playItem.has("imageURL")) { + String imageURL = playItem.getString("imageURL"); + image = gateway().api().getImage(imageURL); + } + } else if (audio.has("playlist")) { + JSONObject playlist = audio.getJSONObject("playlist"); + if (playlist.has("title")) { + track = new StringType(playlist.getString("title")); + } + } + updateState(new ChannelUID(thing.getUID(), targetChannel), track); + updateState(new ChannelUID(thing.getUID(), CHANNEL_IMAGE), image); + } + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DebugHandler.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DebugHandler.java new file mode 100644 index 00000000000..f368f7c46e4 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DebugHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.interfaces; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.binding.ThingHandler; + +/** + * {@link DebugHandler} interface to control debugging via rule actions + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public interface DebugHandler extends ThingHandler { + + /** + * Returns the token associated with the DIRIGERA gateway. Regardless on which device this action is called the + * token from gateway (bridge) is returned. + * + * @return token as String + */ + String getToken(); + + /** + * Returns the JSON representation at this time for a specific device. If action is called on gateway a snapshot + * from all connected devices is returned. + * + * @return device JSON at this time + */ + String getJSON(); + + /** + * Enables / disables debug for one specific device. If enabled messages are logged on info level regarding + * - commands send via openHAB + * - state updates of openHAB + * - API requests with payload towards gateway + * - push notifications from gateway + * - API responses from gateway + * + * @param debug boolean flag enabling or disabling debug messages + */ + void setDebug(boolean debug, boolean all); + + /** + * Returns the device ID of the device this handler is associated with. + * + * @return device ID as String + */ + String getDeviceId(); +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DirigeraAPI.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DirigeraAPI.java new file mode 100644 index 00000000000..1a565239c4a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/DirigeraAPI.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.interfaces; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.core.types.State; + +/** + * {@link DirigeraAPI} high level interface to communicate with the gateway. These are comfort functions fitting to the + * needs of the handlers. Each function is synchronized so no parallel calls will be established towards gateway. + * Rationale: + * Several times seen that gateway goes into a "quite mode" during monkey testing. It's still accepting commands but no + * more updates were received. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public interface DirigeraAPI { + + /** JSON key for error flag, value shall be boolean */ + static final String HTTP_ERROR_FLAG = "http-error-flag"; + + /** JSON key for error flag, value shall be int */ + static final String HTTP_ERROR_STATUS = "http-error-status"; + + /** JSON key for error message, value shall be String */ + static final String HTTP_ERROR_MESSAGE = "http-error-message"; + + /** + * Read complete home model. + * + * @return JSONObject with data. In case of error the JSONObject is filled with error data + */ + JSONObject readHome(); + + /** + * Read all data for one specific deviceId. + * + * @param deviceId to query + * @return JSONObject with data. In case of error the JSONObject is filled with error data + */ + JSONObject readDevice(String deviceId); + + /** + * Read all data for one specific scene. + * + * @param sceneId to query + * @return JSONObject with data. In case of error the JSONObject is filled with error data + */ + JSONObject readScene(String sceneId); + + /** + * Read all data for one specific scene. + * + * @param sceneId to query + * @param trigger to send + * @return JSONObject with data. In case of error the JSONObject is filled with error data + */ + void triggerScene(String sceneId, String trigger); + + /** + * Send attributes to a device + * + * @param deviceId to update + * @param attributes to send + * @return Integer of http response status + */ + int sendAttributes(String deviceId, JSONObject attributes); + + /** + * Send patch with other data than attributes to a device + * + * @param deviceId to update + * @param data to send + * @return Integer of http response status + */ + int sendPatch(String deviceId, JSONObject data); + + /** + * Creating a scene from scene template for a click pattern of a controller + * + * @param uuid of the scene to be created + * @param clickPattern which shall trigger the scene + * @param controllerId which delivering the clickPattern + * @return String uuid of the created scene + */ + String createScene(String uuid, String clickPattern, String controllerId); + + /** + * Delete scene of given uuid + * + * @param uuid of the scene to be deleted + */ + void deleteScene(String uuid); + + /** + * Get image from an url. + * + * @return RawType in case of successful call, UndefType.UNDEF in case of error + */ + State getImage(String imageURL); +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Gateway.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Gateway.java new file mode 100644 index 00000000000..9d94194e3cf --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Gateway.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.interfaces; + +import java.time.Instant; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.dirigera.internal.DirigeraCommandProvider; +import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService; +import org.openhab.binding.dirigera.internal.exception.ApiException; +import org.openhab.binding.dirigera.internal.exception.ModelException; +import org.openhab.binding.dirigera.internal.handler.BaseHandler; +import org.openhab.core.thing.Thing; +import org.osgi.framework.BundleContext; + +/** + * The {@link Gateway} Gateway interface to access data from other instances. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public interface Gateway { + + /** + * Get the thing attached to this gateway. + * + * @return Thing + */ + Thing getThing(); + + /** + * Get IP address from gateway for API calls and WebSocket connections. + * + * @return ip address as String + */ + String getIpAddress(); + + /** + * Get token associated to this gateway for API calls and WebSocket connections. + * + * @return token as String + */ + String getToken(); + + /** + * Get CommandProvider associated to this binding. For links and link candidates the command options are filled with + * the right link options. + * + * @return DirigeraCommandProvider as DynamicCommandDescriptionProvider + */ + DirigeraCommandProvider getCommandProvider(); + + /** + * Returns the configuration setting if discovery is enabled. + * + * @return boolean discovery flag + */ + boolean discoveryEnabled(); + + /** + * Register a handler with the given deviceId reflecting a device or scene. Shall be called during + * initialization. + * + * This function is handled asynchronous. + * + * @param deviceHandler handler of this binding + * @param deviceId connected device id + */ + void registerDevice(BaseHandler deviceHandler, String deviceId); + + /** + * Unregister a handler associated with the given deviceId reflecting a device or scene. Shall be called + * during dispose. + * + * This function is handled asynchronous. + * + * @param deviceHandler handler of this binding + * @param deviceId connected device id + */ + void unregisterDevice(BaseHandler deviceHandler, String deviceId); + + /** + * Deletes an openHAB handler associated with the given deviceId reflecting a device or scene. Shall be called + * during handleRemoval. + * + * This function is handled asynchronous. + * + * @param deviceHandler handler of this binding + * @param deviceId connected device id + */ + void deleteDevice(BaseHandler deviceHandler, String deviceId); + + /** + * Deletes a device or scene detected by the model. A device can be deleted without openHAB interaction in IKEA Home + * smart app and openHAB needs to be informed about this removal to update ThingStatus accordingly. + * + * @param deviceId device id to be removed + */ + void deleteDevice(String deviceId); + + /** + * Check if device id is known in the gateway namely if a handler is created or not. + * + * @param deviceId connected device id + */ + boolean isKnownDevice(String deviceId); + + /** + * Update websocket connected statues. + * + * @param boolean connected + * @param reason as String + */ + void websocketConnected(boolean connected, String reason); + + /** + * Update from websocket regarding changed data. + * + * This function is handled asynchronous. + * + * @param String content of update + */ + void websocketUpdate(String update); + + /** + * Update links for all devices. Devices which are storing the links (hard link) are responsible to detect changes. + * If change is detected the linked device will be updated with a soft link. + * + * This function is handled asynchronous. + * + * @param String content of update + */ + void updateLinks(); + + /** + * Next sunrise ZonedDateTime. Value is presented if gateway allows access to GPS position. Handler needs to take + * care regarding null values. + * + * @return next sunrise as ZonedDateTime + */ + @Nullable + Instant getSunriseDateTime(); + + /** + * Next sunset ZonedDateTime. Value is presented if gateway allows access to GPS position. Handler needs to take + * care regarding null values. + * + * @return next sunrise as ZonedDateTime + */ + @Nullable + Instant getSunsetDateTime(); + + /** + * Comfort access towards API which is only present after initialization. + * + * @throws ApiMissingException + * @return DirigeraAPI + */ + DirigeraAPI api() throws ApiException; + + /** + * Comfort access towards Model which is only present after initialization. + * + * @throws ModelMissingException + * @return Model + */ + Model model() throws ModelException; + + /** + * Comfort access towards DirigeraDiscoveryManager. + * + * @return DirigeraDiscoveryManager + */ + DirigeraDiscoveryService discovery(); + + /** + * Comfort access towards DirigeraDiscoveryManager. + * + * @return DirigeraDiscoveryManager + */ + BundleContext getBundleContext(); +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Model.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Model.java new file mode 100644 index 00000000000..2b44646ce5c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/Model.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.interfaces; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link Model} is representing the structural data of the gateway. Concrete values e.g. temperature of devices + * shall not be accessed. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public interface Model { + + static final String REACHABLE = "isReachable"; + static final String ATTRIBUTES = "attributes"; + static final String CAPABILITIES = "capabilities"; + static final String PROPERTY_CAN_RECEIVE = "canReceive"; + static final String PROPERTY_CAN_SEND = "canSend"; + static final String SCENES = "scenes"; + static final String CUSTOM_NAME = "customName"; + static final String DEVICE_MODEL = "model"; + static final String DEVICE_TYPE = "deviceType"; + static final String PROPERTY_RELATION_ID = "relationId"; + + static final String COLOR_TEMPERATURE_CAPABILITY = "colorTemperature"; + + static final String TEMPLATE_LIGHT_PRESET_BRIGHT = "/json/light-presets/bright.json"; + static final String TEMPLATE_LIGHT_PRESET_SLOWDOWN = "/json/light-presets/slowdown.json"; + static final String TEMPLATE_LIGHT_PRESET_SMOOTH = "/json/light-presets/smooth.json"; + static final String TEMPLATE_LIGHT_PRESET_WARM = "/json/light-presets/warm.json"; + static final String TEMPLATE_SENSOR_ALWQAYS_ON = "/json/sensor-config/always-on.json"; + static final String TEMPLATE_SENSOR_DURATION_UPDATE = "/json/sensor-config/duration-update.json"; + static final String TEMPLATE_SENSOR_FOLLOW_SUN = "/json/sensor-config/follow-sun.json"; + static final String TEMPLATE_SENSOR_SCHEDULE_ON = "/json/sensor-config/schedule-on.json"; + static final String TEMPLATE_CLICK_SCENE = "/json/scenes/click-scene.json"; + static final String TEMPLATE_COORDINATES = "/json/gateway/coordinates.json"; + static final String TEMPLATE_NULL_COORDINATES = "/json/gateway/null-coordinates.json"; + + /** + * Get structure model as JSON String. + * + * @see json channel + * @return JSON String + */ + String getModelString(); + + /** + * Model update will be performed with API request. Relative expensive operation depending on number of connected + * devices. Call triggers + * - startup + * - add / remove device to DIRIGERA gateway, not openHAB + * - custom name changes for Discovery updates + */ + int update(); + + /** + * Starts a new detection without model update. If handlers are removed they shall appear in discovery again. + */ + void detection(); + + /** + * Get all id's for a specific type. Used to identify link candidates for a specific device. + * - LightController needs lights and plugs and vice versa + * - BlindController needs blinds and vice versa + * - SoundController needs speakers and vice versa + * + * @param types as list of types to query + * @return list of matching device id's + */ + List getDevicesForTypes(List types); + + /** + * Returns a list of all device id's. + * + * @return list of all connected devices + */ + List getAllDeviceIds(); + + /** + * Returns a list with resolved relation id's. There are complex device registering more than one id with different + * type. This binding combines them in one handler. + * - MotionLightHandler + * - DoubleShortcutControllerHandler + * + * @return list of device id's without related devices + */ + List getResolvedDeviceList(); + + /** + * Get all stored information for one device or scene. + * + * @param id to query + * @param type device or scene + * @return data as JSON + */ + JSONObject getAllFor(String id, String type); + + /** + * Gets all relations marked into relationId property + * Rationale: + * VALLHORN Motion Sensor registers 2 devices + * - Motion Sensor + * - Light Sensor + * + * Shortcut Controller with 2 buttons registers 2 controllers + * They shall not be splitted in 2 different things so one Thing shall receive updates for both id's + * + * Use TreeMap to sort device id's so suffix _1 comes before _2 + * + * @param relationId + * @return List of id's with same serial number + */ + TreeMap getRelations(String relationId); + + /** + * Get relationId for a given device id + * + * @param id to check + * @return same id if no relations are found or relationId + */ + String getRelationId(String id); + + /** + * Identify device which is present in model with openHAB ThingTypeUID. + * + * @param id to identify + * @return ThingTypeUID + */ + ThingTypeUID identifyDeviceFromModel(String id); + + /** + * Check if given id is present in devices or scenes. + * + * @param id to check + * @return true if id is found + */ + boolean has(String id); + + /** + * Get the custom name configured in IKEA Smart home app. + * + * @param id to query + * @return name as String + */ + String getCustonNameFor(String id); + + /** + * Properties Map for Discovery + * + * @param id to query + * @return Map with attributes for Thing properties + */ + Map getPropertiesFor(String id); + + /** + * Read a resource file from this bundle. Some presets and commands sent to API shall not be implemented + * in code if they are just needing minor String replacements. + * Root path in project is src/main/resources. Line breaks and white spaces will + * + * @return + */ + String getTemplate(String name); +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/PowerListener.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/PowerListener.java new file mode 100644 index 00000000000..bdcd52325b1 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/interfaces/PowerListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.interfaces; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.OnOffType; + +/** + * {@link PowerListener} for notifications of device power events + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public interface PowerListener { + + /** + * Informs if power state of device has changed. + * + * @param power new power state + * @param requested flag showing if new power state was requested by OH user command or from outside (e.g wall + * mounted switch) + */ + void powerChanged(OnOffType power, boolean requested); +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/ColorModel.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/ColorModel.java new file mode 100644 index 00000000000..5651911f57f --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/ColorModel.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.model; + +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.util.ColorUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ColorModel} converts colors according to DIRIGERA values. openHAB ColorUtil conversion uses XY + * transformations which visually are not matching e.g. for kelvin2HSB values. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class ColorModel { + private static final Logger LOGGER = LoggerFactory.getLogger(ColorModel.class); + + private static final TreeMap MAPPING_RGB_TEMPERETATURE = new TreeMap<>(); + private static final TreeMap MAPPING_TEMPERETATURE_RGB = new TreeMap<>(); + private static final int MAX_HUE = 360; + private static final int MAX_SAT = 100; + + /** + * Simulate color-temperature if color light doesn't have the "canReceive" "colorTemperature" capability + * https://www.npmjs.com/package/color-temperature?activeTab=code + * + * @param kelvin + * @return color temperature as HSBType + */ + private static int[] kelvin2RGB(long kelvin) { + double temperature = kelvin / 100.0; + double red; + double green; + double blue; + /* Calculate red */ + if (temperature <= 66.0) { + red = 255; + } else { + red = temperature - 60.0; + red = 329.698727446 * Math.pow(red, -0.1332047592); + if (red < 0) { + red = 0; + } + if (red > 255) { + red = 255; + } + } + /* Calculate green */ + if (temperature <= 66.0) { + green = temperature; + green = 99.4708025861 * Math.log(green) - 161.1195681661; + if (green < 0) { + green = 0; + } + if (green > 255) { + green = 255; + } + } else { + green = temperature - 60.0; + green = 288.1221695283 * Math.pow(green, -0.0755148492); + if (green < 0) { + green = 0; + } + if (green > 255) { + green = 255; + } + } + /* Calculate blue */ + if (temperature >= 66.0) { + blue = 255; + } else { + if (temperature <= 19.0) { + blue = 0; + } else { + blue = temperature - 10; + blue = 138.5177312231 * Math.log(blue) - 305.0447927307; + if (blue < 0) { + blue = 0; + } + if (blue > 255) { + blue = 255; + } + } + } + return new int[] { (int) Math.round(red), (int) Math.round(green), (int) Math.round(blue) }; + } + + private static void init() { + if (MAPPING_RGB_TEMPERETATURE.isEmpty()) { + for (int i = 1000; i < 10001; i = i + 10) { + int rgbEncoding = encodeRGBValue(kelvin2RGB(i)); + MAPPING_RGB_TEMPERETATURE.put(rgbEncoding, i); + MAPPING_TEMPERETATURE_RGB.put(i, rgbEncoding); + } + } + } + + private static int encodeRGBValue(int[] rgb) { + return rgb[0] * 1000000 + rgb[1] * 1000 + rgb[2]; + } + + private static int[] decodeRGBValue(int encoded) { + int part = encoded; + int red = part / 1000000; + part -= red * 1000000; + int green = part / 1000; + part -= green * 1000; + int blue = part; + return new int[] { red, green, blue }; + } + + public static HSBType kelvin2Hsb(long kelvin) { + init(); + Entry entry = MAPPING_TEMPERETATURE_RGB.ceilingEntry((int) kelvin); + if (entry == null) { + entry = MAPPING_TEMPERETATURE_RGB.floorEntry((int) kelvin); + if (entry == null) { + // this path cannot be entered if tables isn't empty which is prevent by init call + LOGGER.warn("DIRIGERA COLOR_MODEL no rgb mapping found for {}", kelvin); + return new HSBType(); + } + } + int encoded = entry.getValue(); + int[] rgb = decodeRGBValue(encoded); + return ColorUtil.rgbToHsb(rgb); + } + + public static long hsb2Kelvin(HSBType hsb) { + init(); + HSBType compare = new HSBType(hsb.getHue(), hsb.getSaturation(), PercentType.HUNDRED); + int rgb[] = ColorUtil.hsbToRgb(compare); + int key = encodeRGBValue(rgb); + Entry entry = MAPPING_RGB_TEMPERETATURE.ceilingEntry(key); + if (entry == null) { + entry = MAPPING_RGB_TEMPERETATURE.floorEntry(key); + if (entry == null) { + // this path cannot be entered if tables isn't empty which is prevent by init call + LOGGER.warn("DIRIGERA COLOR_MODEL no kelvin mapping found for {}", compare); + return -1; + } + } + return entry.getValue(); + } + + public static boolean closeTo(HSBType refHSB, HSBType compareHSB, double percent) { + double hueDistance = Math.abs(refHSB.getHue().doubleValue() - compareHSB.getHue().doubleValue()); + double saturationDistance = Math + .abs(refHSB.getSaturation().doubleValue() - compareHSB.getSaturation().doubleValue()); + return ((hueDistance < (MAX_HUE * percent) || hueDistance > (MAX_HUE - (MAX_HUE * percent))) + && saturationDistance < (MAX_SAT * percent)); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/DirigeraModel.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/DirigeraModel.java new file mode 100644 index 00000000000..6feeb594f9d --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/model/DirigeraModel.java @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.model; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link DirigeraModel} is representing the structural data of the devices connected to gateway. Concrete values of + * devices shall not be accessed. + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DirigeraModel implements Model { + private final Logger logger = LoggerFactory.getLogger(DirigeraModel.class); + + private Map resultMap = new HashMap<>(); + private Map templates = new HashMap<>(); + private List devices = new ArrayList<>(); + private JSONObject model = new JSONObject(); + private Gateway gateway; + + public DirigeraModel(Gateway gateway) { + this.gateway = gateway; + } + + @Override + public synchronized String getModelString() { + return model.toString(); + } + + @Override + public synchronized int update() { + Instant startTime = Instant.now(); + JSONObject home = gateway.api().readHome(); + // call finished with error code ... + if (home.has(DirigeraAPI.HTTP_ERROR_FLAG)) { + int status = home.getInt(DirigeraAPI.HTTP_ERROR_STATUS); + logger.warn("DIRIGERA MODEL received model with error code {} - don't take it", status); + return status; + } else if (home.isEmpty()) { + // ... call finished with unchecked exception ... + return 500; + } else { + // ... call finished with success + model = home; + detection(); + } + logger.trace("DIRIGERA MODEL full update {} ms", Duration.between(startTime, Instant.now()).toMillis()); + return 200; + } + + @Override + public synchronized void detection() { + if (gateway.discoveryEnabled()) { + List previousDevices = new ArrayList<>(); + previousDevices.addAll(devices); + + // first get devices + List foundDevices = new ArrayList<>(); + foundDevices.addAll(getResolvedDeviceList()); + foundDevices.addAll(getAllSceneIds()); + devices.clear(); + devices.addAll(foundDevices); + previousDevices.forEach(deviceId -> { + boolean known = gateway.isKnownDevice(deviceId); + boolean removed = !foundDevices.contains(deviceId); + if (removed) { + removedDeviceScene(deviceId); + } else { + if (!known) { + addedDeviceScene(deviceId); + } // don't update known devices + } + }); + foundDevices.removeAll(previousDevices); + foundDevices.forEach(deviceId -> { + boolean known = gateway.isKnownDevice(deviceId); + if (!known) { + addedDeviceScene(deviceId); + } + }); + } + } + + /** + * Returns list with resolved relations + * + * @return + */ + @Override + public synchronized List getResolvedDeviceList() { + List deviceList = new ArrayList<>(); + if (!model.isNull(PROPERTY_DEVICES)) { + JSONArray devices = model.getJSONArray(PROPERTY_DEVICES); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + String deviceId = entry.getString(PROPERTY_DEVICE_ID); + String relationId = getRelationId(deviceId); + if (!deviceId.equals(relationId)) { + TreeMap relationMap = getRelations(relationId); + // store for complex devices store result with first found id + relationId = relationMap.firstKey(); + } + if (!deviceList.contains(relationId)) { + deviceList.add(relationId); + } + } + } + return deviceList; + } + + /** + * Returns list with all device id's + * + * @return + */ + @Override + public synchronized List getAllDeviceIds() { + List deviceList = new ArrayList<>(); + if (!model.isNull(PROPERTY_DEVICES)) { + JSONArray devices = model.getJSONArray(PROPERTY_DEVICES); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + deviceList.add(entry.getString(PROPERTY_DEVICE_ID)); + } + } + return deviceList; + } + + private List getAllSceneIds() { + List sceneList = new ArrayList<>(); + if (!model.isNull(SCENES)) { + JSONArray scenes = model.getJSONArray(SCENES); + Iterator sceneIterator = scenes.iterator(); + while (sceneIterator.hasNext()) { + JSONObject entry = (JSONObject) sceneIterator.next(); + if (entry.has(PROPERTY_TYPE)) { + if ("userScene".equals(entry.getString(PROPERTY_TYPE))) { + if (entry.has(PROPERTY_DEVICE_ID)) { + String id = entry.getString(PROPERTY_DEVICE_ID); + sceneList.add(id); + } + } + } + } + } + return sceneList; + } + + private void addedDeviceScene(String id) { + DiscoveryResult result = identifiy(id); + if (result != null) { + gateway.discovery().deviceDiscovered(result); + resultMap.put(id, result); + } + } + + private void removedDeviceScene(String id) { + DiscoveryResult deliveredResult = resultMap.remove(id); + if (deliveredResult != null) { + gateway.discovery().deviceRemoved(deliveredResult); + } + // inform gateway to remove device and update handler accordingly + gateway.deleteDevice(id); + } + + @Override + public synchronized List getDevicesForTypes(List types) { + List candidates = new ArrayList<>(); + types.forEach(type -> { + JSONArray addons = getIdsForType(type); + addons.forEach(entry -> { + candidates.add(entry.toString()); + }); + }); + return candidates; + } + + private JSONArray getIdsForType(String type) { + JSONArray returnArray = new JSONArray(); + if (!model.isNull(PROPERTY_DEVICES)) { + JSONArray devices = model.getJSONArray(PROPERTY_DEVICES); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + if (!entry.isNull(PROPERTY_DEVICE_TYPE) && !entry.isNull(PROPERTY_DEVICE_ID)) { + if (type.equals(entry.get(PROPERTY_DEVICE_TYPE))) { + returnArray.put(entry.get(PROPERTY_DEVICE_ID)); + } + } + } + } + return returnArray; + } + + private boolean hasAttribute(String id, String attribute) { + JSONObject deviceObject = getAllFor(id, PROPERTY_DEVICES); + if (deviceObject.has(ATTRIBUTES)) { + JSONObject attributes = deviceObject.getJSONObject(ATTRIBUTES); + return attributes.has(attribute); + } + return false; + } + + @Override + public synchronized JSONObject getAllFor(String id, String type) { + JSONObject returnObject = new JSONObject(); + if (model.has(type)) { + JSONArray devices = model.getJSONArray(type); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + if (id.equals(entry.get(PROPERTY_DEVICE_ID))) { + return entry; + } + } + } + return returnObject; + } + + @Override + public synchronized String getCustonNameFor(String id) { + JSONObject deviceObject = getAllFor(id, PROPERTY_DEVICES); + if (deviceObject.has(ATTRIBUTES)) { + JSONObject attributes = deviceObject.getJSONObject(ATTRIBUTES); + if (attributes.has(CUSTOM_NAME)) { + String customName = attributes.getString(CUSTOM_NAME); + if (!customName.isBlank()) { + return customName; + } + } + if (attributes.has(DEVICE_MODEL)) { + String deviceModel = attributes.getString(DEVICE_MODEL); + if (!deviceModel.isBlank()) { + return deviceModel; + } + } + if (deviceObject.has(DEVICE_TYPE)) { + return deviceObject.getString(DEVICE_TYPE); + } + // 3 fallback options + } + // not found yet - check scenes + JSONObject sceneObject = getAllFor(id, PROPERTY_SCENES); + if (sceneObject.has("info")) { + JSONObject info = sceneObject.getJSONObject("info"); + if (info.has("name")) { + String name = info.getString("name"); + if (!name.isBlank()) { + return name; + } + } + } + + return id; + } + + @Override + public synchronized Map getPropertiesFor(String id) { + final Map properties = new HashMap<>(); + JSONObject deviceObject = getAllFor(id, PROPERTY_DEVICES); + // get manufacturer, model and version data + if (deviceObject.has(ATTRIBUTES)) { + JSONObject attributes = deviceObject.getJSONObject(ATTRIBUTES); + THING_PROPERTIES.forEach(property -> { + if (attributes.has(property)) { + properties.put(property, attributes.get(property)); + } + }); + } + // put id in as representation property + properties.put(PROPERTY_DEVICE_ID, id); + // add capabilities + if (deviceObject.has(CAPABILITIES)) { + JSONObject capabilities = deviceObject.getJSONObject(CAPABILITIES); + if (capabilities.has(PROPERTY_CAN_RECEIVE)) { + properties.put(PROPERTY_CAN_RECEIVE, capabilities.getJSONArray(PROPERTY_CAN_RECEIVE)); + } + if (capabilities.has(PROPERTY_CAN_SEND)) { + properties.put(PROPERTY_CAN_SEND, capabilities.getJSONArray(PROPERTY_CAN_SEND)); + } + } + + return properties; + } + + @Override + public synchronized TreeMap getRelations(String relationId) { + final TreeMap relationsMap = new TreeMap<>(); + List allDevices = getAllDeviceIds(); + allDevices.forEach(deviceId -> { + JSONObject data = getAllFor(deviceId, PROPERTY_DEVICES); + if (data.has(Model.PROPERTY_RELATION_ID)) { + String relation = data.getString(Model.PROPERTY_RELATION_ID); + if (relationId.equals(relation)) { + String relationDeviceId = data.getString(PROPERTY_DEVICE_ID); + String deviceType = data.getString(PROPERTY_DEVICE_TYPE); + if (relationDeviceId != null && deviceType != null) { + relationsMap.put(relationDeviceId, deviceType); + } + } + } + }); + return relationsMap; + } + + private @Nullable DiscoveryResult identifiy(String id) { + ThingTypeUID ttuid = identifyDeviceFromModel(id); + // don't report gateway, unknown devices and light sensors connected to motion sensors + if (!THING_TYPE_GATEWAY.equals(ttuid) && !THING_TYPE_UNKNNOWN.equals(ttuid) + && !THING_TYPE_LIGHT_SENSOR.equals(ttuid) && !THING_TYPE_IGNORE.equals(ttuid)) { + // check if it's a simple or complex device + String relationId = getRelationId(id); + String firstDeviceId = id; + if (!id.equals(relationId)) { + // complex device + TreeMap relationMap = getRelations(relationId); + // take name from first ordered entry + firstDeviceId = relationMap.firstKey(); + } + // take name and properties from first found id + String customName = getCustonNameFor(firstDeviceId); + Map propertiesMap = getPropertiesFor(firstDeviceId); + return DiscoveryResultBuilder.create(new ThingUID(ttuid, gateway.getThing().getUID(), firstDeviceId)) + .withBridge(gateway.getThing().getUID()).withProperties(propertiesMap) + .withRepresentationProperty(PROPERTY_DEVICE_ID).withLabel(customName).build(); + } + return null; + } + + /** + * Identify device which is present in model + * + * @param id + * @return + */ + @Override + public synchronized ThingTypeUID identifyDeviceFromModel(String id) { + JSONObject entry = getAllFor(id, PROPERTY_DEVICES); + if (entry.isEmpty()) { + entry = getAllFor(id, PROPERTY_SCENES); + } + if (entry.isEmpty()) { + return THING_TYPE_NOT_FOUND; + } else { + return identifyDeviceFromJSON(id, entry); + } + } + + private ThingTypeUID identifyDeviceFromJSON(String id, JSONObject data) { + String typeDeviceType = ""; + if (data.has(Model.PROPERTY_RELATION_ID)) { + return identifiyComplexDevice(data.getString(Model.PROPERTY_RELATION_ID)); + } else if (data.has(PROPERTY_DEVICE_TYPE)) { + String deviceType = data.getString(PROPERTY_DEVICE_TYPE); + typeDeviceType = deviceType; + switch (deviceType) { + case DEVICE_TYPE_GATEWAY: + return THING_TYPE_GATEWAY; + case DEVICE_TYPE_LIGHT: + if (data.has(CAPABILITIES)) { + JSONObject capabilities = data.getJSONObject(CAPABILITIES); + List capabilityList = new ArrayList<>(); + if (capabilities.has(PROPERTY_CAN_RECEIVE)) { + JSONArray receiveProperties = capabilities.getJSONArray(PROPERTY_CAN_RECEIVE); + receiveProperties.forEach(capability -> { + capabilityList.add(capability.toString()); + }); + } + if (capabilityList.contains("colorHue")) { + return THING_TYPE_COLOR_LIGHT; + } else if (capabilityList.contains("colorTemperature")) { + return THING_TYPE_TEMPERATURE_LIGHT; + } else if (capabilityList.contains("lightLevel")) { + return THING_TYPE_DIMMABLE_LIGHT; + } else if (capabilityList.contains("isOn")) { + return THING_TYPE_SWITCH_LIGHT; + } else { + logger.warn("DIRIGERA MODEL cannot identify light {}", data); + } + } else { + logger.warn("DIRIGERA MODEL cannot identify light {}", data); + } + break; + case DEVICE_TYPE_MOTION_SENSOR: + return THING_TYPE_MOTION_SENSOR; + case DEVICE_TYPE_LIGHT_SENSOR: + return THING_TYPE_LIGHT_SENSOR; + case DEVICE_TYPE_CONTACT_SENSOR: + return THING_TYPE_CONTACT_SENSOR; + case DEVICE_TYPE_OUTLET: + if (hasAttribute(id, "currentActivePower")) { + return THING_TYPE_SMART_PLUG; + } else if (hasAttribute(id, "childLock")) { + return THING_TYPE_POWER_PLUG; + } else { + return THING_TYPE_SIMPLE_PLUG; + } + case DEVICE_TYPE_SPEAKER: + return THING_TYPE_SPEAKER; + case DEVICE_TYPE_REPEATER: + return THING_TYPE_REPEATER; + case DEVICE_TYPE_LIGHT_CONTROLLER: + return THING_TYPE_LIGHT_CONTROLLER; + case DEVICE_TYPE_ENVIRONMENT_SENSOR: + return THING_TYPE_AIR_QUALITY; + case DEVICE_TYPE_WATER_SENSOR: + return THING_TYPE_WATER_SENSOR; + case DEVICE_TYPE_AIR_PURIFIER: + return THING_TYPE_AIR_PURIFIER; + case DEVICE_TYPE_BLINDS: + return THING_TYPE_BLIND; + case DEVICE_TYPE_BLIND_CONTROLLER: + return THING_TYPE_BLIND_CONTROLLER; + case DEVICE_TYPE_SOUND_CONTROLLER: + return THING_TYPE_SOUND_CONTROLLER; + case DEVICE_TYPE_SHORTCUT_CONTROLLER: + return THING_TYPE_SINGLE_SHORTCUT_CONTROLLER; + } + } else { + // device type is empty, check for scene + if (!data.isNull(PROPERTY_TYPE)) { + String type = data.getString(PROPERTY_TYPE); + typeDeviceType = type + "/" + typeDeviceType; // just for logging + switch (type) { + case TYPE_USER_SCENE: + return THING_TYPE_SCENE; + case TYPE_CUSTOM_SCENE: + return THING_TYPE_IGNORE; + } + } + } + logger.warn("DIRIGERA MODEL Unsupported device {} with data {} {}", typeDeviceType, data, id); + return THING_TYPE_UNKNNOWN; + } + + private ThingTypeUID identifiyComplexDevice(String relationId) { + Map relationsMap = getRelations(relationId); + if (relationsMap.size() == 2 && relationsMap.containsValue("lightSensor") + && relationsMap.containsValue("motionSensor")) { + return THING_TYPE_MOTION_LIGHT_SENSOR; + } else if (relationsMap.size() == 2 && relationsMap.containsValue("shortcutController")) { + for (Iterator iterator = relationsMap.keySet().iterator(); iterator.hasNext();) { + if (!"shortcutController".equals(relationsMap.get(iterator.next()))) { + return THING_TYPE_UNKNNOWN; + } + } + return THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER; + } else if (relationsMap.size() == 1 && relationsMap.containsValue("gatewy")) { + return THING_TYPE_GATEWAY; + } else { + return THING_TYPE_UNKNNOWN; + } + } + + /** + * Get relationId for a given device id + * + * @param id to check + * @return same id if no relations are found or relationId + */ + @Override + public synchronized String getRelationId(String id) { + JSONObject dataObject = getAllFor(id, PROPERTY_DEVICES); + if (dataObject.has(PROPERTY_RELATION_ID)) { + return dataObject.getString(PROPERTY_RELATION_ID); + } + return id; + } + + /** + * Check if given id is present in devices or scenes + * + * @param id to check + * @return true if id is found + */ + @Override + public synchronized boolean has(String id) { + return getAllDeviceIds().contains(id) || getAllSceneIds().contains(id); + } + + @Override + public String getTemplate(String name) { + String template = templates.get(name); + if (template == null) { + template = getResourceFile(name); + if (!template.isBlank()) { + templates.put(name, template); + } else { + logger.warn("DIRIGERA MODEL empty template for {}", name); + template = "{}"; + } + } + return template; + } + + private String getResourceFile(String fileName) { + try { + Bundle myself = gateway.getBundleContext().getBundle(); + // do this check for unit tests to avoid NullPointerException + if (myself != null) { + URL url = myself.getResource(fileName); + InputStream input = url.openStream(); + // https://www.baeldung.com/java-scanner-usedelimiter + try (Scanner scanner = new Scanner(input).useDelimiter("\\A")) { + String result = scanner.hasNext() ? scanner.next() : ""; + String resultReplaceAll = result.replaceAll("[\\n\\r\\s]", ""); + scanner.close(); + return resultReplaceAll; + } + } else { + // only unit testing + return Files.readString(Paths.get("src/main/resources" + fileName)); + } + } catch (IOException e) { + logger.warn("DIRIGERA MODEL no template found for {}", fileName); + } + return ""; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/DirigeraAPIImpl.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/DirigeraAPIImpl.java new file mode 100644 index 00000000000..018822f703c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/DirigeraAPIImpl.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.network; + +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.RawType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link DirigeraAPIImpl} provides easy access towards REST API + * + * @author Bernd Weymann - Initial contribution + */ +@WebSocket +@NonNullByDefault +public class DirigeraAPIImpl implements DirigeraAPI { + private final Logger logger = LoggerFactory.getLogger(DirigeraAPIImpl.class); + + private static final String GENREAL_LOCK = "lock"; + private Set activeCallers = new TreeSet<>(); + private HttpClient httpClient; + private Gateway gateway; + + public DirigeraAPIImpl(HttpClient httpClient, Gateway gateway) { + this.httpClient = httpClient; + this.gateway = gateway; + } + + private Request addAuthorizationHeader(Request sourceRequest) { + if (!gateway.getToken().isBlank()) { + return sourceRequest.header(HttpHeader.AUTHORIZATION, "Bearer " + gateway.getToken()); + } else { + logger.warn("DIRIGERA API Cannot operate with token {}", gateway.getToken()); + return sourceRequest; + } + } + + @Override + public JSONObject readHome() { + String url = String.format(HOME_URL, gateway.getIpAddress()); + JSONObject statusObject = new JSONObject(); + startCalling(GENREAL_LOCK); + try { + Request homeRequest = httpClient.newRequest(url); + ContentResponse response = addAuthorizationHeader(homeRequest).timeout(10, TimeUnit.SECONDS).send(); + int responseStatus = response.getStatus(); + if (responseStatus == 200) { + statusObject = new JSONObject(response.getContentAsString()); + } else { + statusObject = getErrorJson(responseStatus, response.getReason()); + } + return statusObject; + } catch (InterruptedException | TimeoutException | ExecutionException | JSONException e) { + logger.warn("DIRIGERA API Exception calling {}", url); + statusObject = getErrorJson(500, e.getMessage()); + return statusObject; + } finally { + endCalling(GENREAL_LOCK); + } + } + + @Override + public JSONObject readDevice(String deviceId) { + String url = String.format(DEVICE_URL, gateway.getIpAddress(), deviceId); + JSONObject statusObject = new JSONObject(); + startCalling(deviceId); + try { + Request homeRequest = httpClient.newRequest(url); + ContentResponse response = addAuthorizationHeader(homeRequest).timeout(10, TimeUnit.SECONDS).send(); + int responseStatus = response.getStatus(); + if (responseStatus == 200) { + statusObject = new JSONObject(response.getContentAsString()); + } else { + statusObject = getErrorJson(responseStatus, response.getReason()); + } + return statusObject; + } catch (InterruptedException | TimeoutException | ExecutionException | JSONException e) { + logger.warn("DIRIGERA API Exception calling {}", url); + statusObject = getErrorJson(500, e.getMessage()); + return statusObject; + } finally { + endCalling(deviceId); + } + } + + @Override + public void triggerScene(String sceneId, String trigger) { + String url = String.format(SCENE_URL, gateway.getIpAddress(), sceneId) + "/" + trigger; + startCalling(sceneId); + try { + Request homeRequest = httpClient.POST(url); + ContentResponse response = addAuthorizationHeader(homeRequest).timeout(10, TimeUnit.SECONDS).send(); + int responseStatus = response.getStatus(); + if (responseStatus != 200 && responseStatus != 202) { + logger.warn("DIRIGERA API Scene trigger failed with {}", responseStatus); + } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("DIRIGERA API Exception calling {}", url); + } finally { + endCalling(sceneId); + } + } + + @Override + public int sendAttributes(String id, JSONObject attributes) { + JSONObject data = new JSONObject(); + data.put(Model.ATTRIBUTES, attributes); + return sendPatch(id, data); + } + + @Override + public int sendPatch(String id, JSONObject data) { + String url = String.format(DEVICE_URL, gateway.getIpAddress(), id); + // pack attributes into data json and then into an array + JSONArray dataArray = new JSONArray(); + dataArray.put(data); + StringContentProvider stringProvider = new StringContentProvider("application/json", dataArray.toString(), + StandardCharsets.UTF_8); + Request deviceRequest = httpClient.newRequest(url).method("PATCH") + .header(HttpHeader.CONTENT_TYPE, "application/json").content(stringProvider); + + int responseStatus = 500; + startCalling(id); + try { + ContentResponse response = addAuthorizationHeader(deviceRequest).timeout(10, TimeUnit.SECONDS).send(); + responseStatus = response.getStatus(); + if (responseStatus == 200 || responseStatus == 202) { + logger.debug("DIRIGERA API send finished {} with {} {}", url, dataArray, responseStatus); + } else { + logger.warn("DIRIGERA API send failed {} with {} {}", url, dataArray, responseStatus); + } + return responseStatus; + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("DIRIGERA API send failed {} failed {} {}", url, dataArray, e.getMessage()); + return responseStatus; + } finally { + endCalling(id); + } + } + + @Override + public State getImage(String imageURL) { + State image = UnDefType.UNDEF; + startCalling(GENREAL_LOCK); + try { + ContentResponse response = httpClient.GET(imageURL); + if (response.getStatus() == 200) { + String mimeType = response.getMediaType(); + if (mimeType == null) { + mimeType = RawType.DEFAULT_MIME_TYPE; + } + image = new RawType(response.getContent(), mimeType); + } else { + logger.warn("DIRIGERA API call to {} failed {}", imageURL, response.getStatus()); + } + return image; + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.warn("DIRIGERA API call to {} failed {}", imageURL, e.getMessage()); + return image; + } finally { + endCalling(GENREAL_LOCK); + } + } + + @Override + public JSONObject readScene(String sceneId) { + String url = String.format(SCENE_URL, gateway.getIpAddress(), sceneId); + JSONObject statusObject = new JSONObject(); + Request homeRequest = httpClient.newRequest(url); + startCalling(GENREAL_LOCK); + try { + ContentResponse response = addAuthorizationHeader(homeRequest).timeout(10, TimeUnit.SECONDS).send(); + int responseStatus = response.getStatus(); + if (responseStatus == 200) { + statusObject = new JSONObject(response.getContentAsString()); + } else { + statusObject = getErrorJson(responseStatus, response.getReason()); + } + return statusObject; + } catch (InterruptedException | TimeoutException | ExecutionException | JSONException e) { + logger.warn("DIRIGERA API Exception calling {}", url); + statusObject = getErrorJson(-1, e.getMessage()); + return statusObject; + } finally { + endCalling(GENREAL_LOCK); + } + } + + @Override + public String createScene(String uuid, String clickPattern, String controllerId) { + String url = String.format(SCENES_URL, gateway.getIpAddress()); + String sceneTemplate = gateway.model().getTemplate(Model.TEMPLATE_CLICK_SCENE); + String payload = String.format(sceneTemplate, uuid, "openHAB Shortcut Proxy", clickPattern, "0", controllerId); + StringContentProvider stringProvider = new StringContentProvider("application/json", payload, + StandardCharsets.UTF_8); + Request sceneCreateRequest = httpClient.newRequest(url).method("POST") + .header(HttpHeader.CONTENT_TYPE, "application/json").content(stringProvider); + + int responseStatus = 500; + String responseUUID = ""; + int retryCounter = 3; + startCalling(GENREAL_LOCK); + try { + while (retryCounter > 0 && !uuid.equals(responseUUID)) { + try { + ContentResponse response = addAuthorizationHeader(sceneCreateRequest).timeout(10, TimeUnit.SECONDS) + .send(); + responseStatus = response.getStatus(); + if (responseStatus == 200 || responseStatus == 202) { + logger.debug("DIRIGERA API send {} to {} delivered", payload, url); + String responseString = response.getContentAsString(); + JSONObject responseJSON = new JSONObject(responseString); + responseUUID = responseJSON.getString(PROPERTY_DEVICE_ID); + break; + } else { + logger.warn("DIRIGERA API send {} to {} failed with status {}", payload, url, + response.getStatus()); + } + } catch (InterruptedException | TimeoutException | ExecutionException | JSONException e) { + logger.warn("DIRIGERA API call to {} failed {}", url, e.getMessage()); + } + logger.debug("DIRIGERA API createScene failed {} retries remaining", retryCounter); + retryCounter--; + } + return responseUUID; + } finally { + endCalling(GENREAL_LOCK); + } + } + + @Override + public void deleteScene(String uuid) { + String url = String.format(SCENES_URL, gateway.getIpAddress()) + "/" + uuid; + Request sceneDeleteRequest = httpClient.newRequest(url).method("DELETE"); + int responseStatus = 500; + int retryCounter = 3; + startCalling(GENREAL_LOCK); + try { + while (retryCounter > 0 && responseStatus != 200 && responseStatus != 202) { + try { + ContentResponse response = addAuthorizationHeader(sceneDeleteRequest).timeout(10, TimeUnit.SECONDS) + .send(); + responseStatus = response.getStatus(); + if (responseStatus == 200 || responseStatus == 202) { + logger.debug("DIRIGERA API delete {} performed", url); + break; + } else { + logger.warn("DIRIGERA API send {} failed with status {}", url, response.getStatus()); + } + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("DIRIGERA API call to {} failed {}", url, e.getMessage()); + } + logger.debug("DIRIGERA API deleteScene failed with status {}, {} retries remaining", responseStatus, + retryCounter); + retryCounter--; + } + } finally { + endCalling(GENREAL_LOCK); + } + } + + public JSONObject getErrorJson(int status, @Nullable String message) { + String error = String.format( + "{\"http-error-flag\":true,\"http-error-status\":%s,\"http-error-message\":\"%s\"}", status, message); + return new JSONObject(error); + } + + private void startCalling(String uuid) { + synchronized (this) { + while (activeCallers.contains(uuid)) { + try { + this.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // abort execution + return; + } + } + activeCallers.add(uuid); + } + } + + private void endCalling(String uuid) { + synchronized (this) { + activeCallers.remove(uuid); + this.notifyAll(); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/Websocket.java b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/Websocket.java new file mode 100644 index 00000000000..93c208ea455 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/java/org/openhab/binding/dirigera/internal/network/Websocket.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.network; + +import static org.openhab.binding.dirigera.internal.Constants.WS_URL; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link Websocket} listens to device changes + * + * @author Bernd Weymann - Initial contribution + */ +@WebSocket +@NonNullByDefault +public class Websocket { + private final Logger logger = LoggerFactory.getLogger(Websocket.class); + private final Map pingPongMap = new HashMap<>(); + + private static final String STARTS = "starts"; + private static final String STOPS = "stops"; + private static final String DISCONNECTS = "disconnetcs"; + private static final String ERRORS = "errors"; + private static final String PINGS = "pings"; + private static final String PING_LATENCY = "pingLatency"; + private static final String PING_LAST = "lastPing"; + private static final String MESSAGES = "messages"; + public static final String MODEL_UPDATES = "modelUpdates"; + public static final String MODEL_UPDATE_TIME = "modelUpdateDuration"; + public static final String MODEL_UPDATE_LAST = "lastModelUpdate"; + + private Optional websocketClient = Optional.empty(); + private Optional session = Optional.empty(); + private JSONObject statistics = new JSONObject(); + private HttpClient httpClient; + private Gateway gateway; + private boolean disposed = false; + + public Websocket(Gateway gateway, HttpClient httpClient) { + this.gateway = gateway; + this.httpClient = httpClient; + } + + public void initialize() { + disposed = false; + } + + public void start() { + if ("unit-test".equals(gateway.getToken())) { + // handle unit tests online + gateway.websocketConnected(true, "unit test"); + return; + } + if (disposed) { + logger.debug("DIRIGERA WS start rejected, disposed {}", disposed); + return; + } + increase(STARTS); + internalStop(); // don't count this internal stopping + try { + pingPongMap.clear(); + WebSocketClient client = new WebSocketClient(httpClient); + client.setMaxIdleTimeout(0); + + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setHeader("Authorization", "Bearer " + gateway.getToken()); + + String websocketURL = String.format(WS_URL, gateway.getIpAddress()); + logger.trace("DIRIGERA WS start {}", websocketURL); + websocketClient = Optional.of(client); + client.start(); + client.connect(this, new URI(websocketURL), request); + } catch (Exception t) { + // catch Exceptions of start stop and declare communication error + logger.warn("DIRIGERA WS handling exception: {}", t.getMessage()); + } + } + + public boolean isRunning() { + return websocketClient.isPresent() && session.isPresent() && session.get().isOpen(); + } + + public void stop() { + increase(STOPS); + internalStop(); + } + + private void internalStop() { + session.ifPresent(session -> { + session.close(); + }); + websocketClient.ifPresent(client -> { + try { + client.stop(); + client.destroy(); + } catch (Exception e) { + logger.warn("DIRIGERA WS exception stopping running client"); + } + }); + websocketClient = Optional.empty(); + this.session = Optional.empty(); + } + + public void dispose() { + internalStop(); + disposed = true; + } + + public void ping() { + session.ifPresentOrElse((session) -> { + try { + // build ping message + String pingId = UUID.randomUUID().toString(); + pingPongMap.put(pingId, Instant.now()); + session.getRemote().sendPing(ByteBuffer.wrap(pingId.getBytes())); + increase(PINGS); + } catch (IOException e) { + logger.warn("DIRIGERA WS ping failed with exception {}", e.getMessage()); + } + }, () -> { + logger.debug("DIRIGERA WS ping found no session - restart websocket"); + }); + } + + /** + * endpoints + */ + + @OnWebSocketMessage + public void onTextMessage(String message) { + increase(MESSAGES); + gateway.websocketUpdate(message); + } + + @OnWebSocketFrame + public void onFrame(Frame frame) { + if (Frame.Type.PONG.equals(frame.getType())) { + ByteBuffer buffer = frame.getPayload(); + byte[] bytes = new byte[frame.getPayloadLength()]; + for (int i = 0; i < frame.getPayloadLength(); i++) { + bytes[i] = buffer.get(i); + } + String paylodString = new String(bytes); + Instant sent = pingPongMap.remove(paylodString); + if (sent != null) { + long durationMS = Duration.between(sent, Instant.now()).toMillis(); + statistics.put(PING_LATENCY, durationMS); + statistics.put(PING_LAST, Instant.now()); + } else { + logger.debug("DIRIGERA WS receiced pong without ping {}", paylodString); + } + } else if (Frame.Type.PING.equals(frame.getType())) { + session.ifPresentOrElse((session) -> { + logger.trace("DIRIGERA onPing "); + ByteBuffer buffer = frame.getPayload(); + try { + session.getRemote().sendPong(buffer); + } catch (IOException e) { + logger.warn("DIRIGERA WS onPing answer exception {}", e.getMessage()); + } + }, () -> { + logger.debug("DIRIGERA WS onPing answer cannot be initiated"); + }); + } + } + + @OnWebSocketConnect + public void onConnect(Session session) { + logger.debug("DIRIGERA WS onConnect"); + this.session = Optional.of(session); + session.setIdleTimeout(-1); + gateway.websocketConnected(true, "connected"); + } + + @OnWebSocketClose + public void onDisconnect(Session session, int statusCode, String reason) { + logger.debug("DIRIGERA WS onDisconnect Status {} Reason {}", statusCode, reason); + this.session = Optional.empty(); + increase(DISCONNECTS); + gateway.websocketConnected(false, reason); + } + + @OnWebSocketError + public void onError(Throwable t) { + String message = t.getMessage(); + logger.warn("DIRIGERA WS onError {}", message); + this.session = Optional.empty(); + if (message == null) { + message = "unknown"; + } + increase(ERRORS); + gateway.websocketConnected(false, message); + } + + /** + * Helper functions + */ + + public JSONObject getStatistics() { + return statistics; + } + + public void increase(String key) { + if (statistics.has(key)) { + int counter = statistics.getInt(key); + statistics.put(key, ++counter); + } else { + statistics.put(key, 1); + } + } + + public Map getPingPongMap() { + return pingPongMap; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 00000000000..6b4ad97c347 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,21 @@ + + + + binding + DIRIGERA Binding + IKEA Smarthome binding for DIRIGERA Gateway + local + + + mdns + + + mdnsServiceType + _ihsp._tcp.local. + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/base-device.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/base-device.xml new file mode 100644 index 00000000000..8f91b1304c9 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/base-device.xml @@ -0,0 +1,13 @@ + + + + + + + Unique id of this device + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/color-light.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/color-light.xml new file mode 100644 index 00000000000..aa998734366 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/color-light.xml @@ -0,0 +1,27 @@ + + + + + + + Unique id of this device + + + + Required time for fade sequnce to color or brightness + 750 + + + + Define sequence if several light parameters are changed at once + + + + + 0 + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/gateway.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/gateway.xml new file mode 100644 index 00000000000..45eeb15a857 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/gateway.xml @@ -0,0 +1,22 @@ + + + + + + + Gateway IP Address + + + + Unique id of this gateway + + + + Configure if paired devices shall be detected by discovery + true + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/light-device.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/light-device.xml new file mode 100644 index 00000000000..6a55a46561a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/config/light-device.xml @@ -0,0 +1,18 @@ + + + + + + + Unique id of this device + + + + Required time for fade sequnce to color or brightness + 750 + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/i18n/dirigera.properties b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/i18n/dirigera.properties new file mode 100644 index 00000000000..1a34c2f8edc --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/i18n/dirigera.properties @@ -0,0 +1,388 @@ +# add-on + +addon.dirigera.name = DIRIGERA Binding +addon.dirigera.description = IKEA Smarthome binding for DIRIGERA Gateway + +# thing types + +thing-type.dirigera.air-purifier.label = Air Purifier +thing-type.dirigera.air-purifier.description = Air cleaning device with particle filter +thing-type.dirigera.air-purifier.channel.fan-mode.label = Fan Mode +thing-type.dirigera.air-purifier.channel.fan-mode.description = Fan on, off, speed or automatic behavior +thing-type.dirigera.air-purifier.channel.fan-runtime.label = Fan Runtime +thing-type.dirigera.air-purifier.channel.fan-runtime.description = Fan runtime in minutes +thing-type.dirigera.air-purifier.channel.fan-speed.label = Fan Speed +thing-type.dirigera.air-purifier.channel.fan-speed.description = Manual regulation of fan speed +thing-type.dirigera.air-purifier.channel.filter-alarm.label = Filter Alarm +thing-type.dirigera.air-purifier.channel.filter-alarm.description = Filter alarm signal +thing-type.dirigera.air-purifier.channel.filter-elapsed.label = Filter Elapsed +thing-type.dirigera.air-purifier.channel.filter-elapsed.description = Filter elapsed time in minutes +thing-type.dirigera.air-purifier.channel.filter-lifetime.label = Filter Lifetime +thing-type.dirigera.air-purifier.channel.filter-lifetime.description = Filter lifetime in minutes +thing-type.dirigera.air-purifier.channel.filter-remain.label = Filter Remain +thing-type.dirigera.air-purifier.channel.filter-remain.description = Remaining filter time in minutes +thing-type.dirigera.air-purifier.channel.particulate-matter.label = Particulate Matter +thing-type.dirigera.air-purifier.channel.particulate-matter.description = Category 2.5 particulate matter +thing-type.dirigera.air-quality.label = Air Quality +thing-type.dirigera.air-quality.description = Air measure for temperature, humidity and particles +thing-type.dirigera.air-quality.channel.humidity.label = Humidity +thing-type.dirigera.air-quality.channel.humidity.description = Atmospheric humidity in percent +thing-type.dirigera.air-quality.channel.particulate-matter.label = Particulate Matter +thing-type.dirigera.air-quality.channel.particulate-matter.description = Category 2.5 particulate matter +thing-type.dirigera.air-quality.channel.temperature.label = Temperature +thing-type.dirigera.air-quality.channel.temperature.description = Current indoor temperature +thing-type.dirigera.air-quality.channel.voc-index.label = VOC Index +thing-type.dirigera.air-quality.channel.voc-index.description = Relative VOC intensity compared to recent history +thing-type.dirigera.blind-controller.label = Blinds Controller +thing-type.dirigera.blind-controller.description = Controller to open and close blinds +thing-type.dirigera.blind-controller.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.blind-controller.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.blind.label = Blind +thing-type.dirigera.blind.description = Window or door blind +thing-type.dirigera.blind.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.blind.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.color-light.label = Color Light +thing-type.dirigera.color-light.description = Light with color support +thing-type.dirigera.color-light.channel.brightness.label = Brightness +thing-type.dirigera.color-light.channel.brightness.description = Brightness of light in percent +thing-type.dirigera.color-light.channel.color.label = Color +thing-type.dirigera.color-light.channel.color.description = Color of light with hue, saturation and brightness +thing-type.dirigera.color-light.channel.color-temperature.label = Color Temperature +thing-type.dirigera.color-light.channel.color-temperature.description = Color temperature from cold (0 %) to warm (100 %) +thing-type.dirigera.color-light.channel.power.label = Light Powered +thing-type.dirigera.color-light.channel.power.description = Power state of light +thing-type.dirigera.contact-sensor.label = Contact Sensor +thing-type.dirigera.contact-sensor.description = Sensor tracking if windows or doors are open +thing-type.dirigera.contact-sensor.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.contact-sensor.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.contact-sensor.channel.contact.label = Contact State +thing-type.dirigera.contact-sensor.channel.contact.description = State if door or window is open or closed +thing-type.dirigera.dimmable-light.label = Dimmable Light +thing-type.dirigera.dimmable-light.description = Light with brightness support +thing-type.dirigera.dimmable-light.channel.brightness.label = Brightness +thing-type.dirigera.dimmable-light.channel.brightness.description = Brightness of light in percent +thing-type.dirigera.dimmable-light.channel.power.label = Light Powered +thing-type.dirigera.dimmable-light.channel.power.description = Power state of light +thing-type.dirigera.double-shortcut.label = Two Button Shortcut +thing-type.dirigera.double-shortcut.description = Shortcut controller with two buttons +thing-type.dirigera.double-shortcut.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.double-shortcut.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.double-shortcut.channel.button1.label = Button 1 Trigger +thing-type.dirigera.double-shortcut.channel.button1.description = Trigger of first button +thing-type.dirigera.double-shortcut.channel.button2.label = Button 2 Trigger +thing-type.dirigera.double-shortcut.channel.button2.description = Trigger of second button +thing-type.dirigera.gateway.label = DIRIGERA Gateway +thing-type.dirigera.gateway.description = IKEA Gateway for smart products +thing-type.dirigera.gateway.channel.location.label = Home Location +thing-type.dirigera.gateway.channel.location.description = Location in latitude, longitude coordinates +thing-type.dirigera.gateway.channel.ota-progress.label = OTA Progress +thing-type.dirigera.gateway.channel.ota-progress.description = Over-the-air update progress +thing-type.dirigera.gateway.channel.ota-state.label = OTA State +thing-type.dirigera.gateway.channel.ota-state.description = Over-the-air current state +thing-type.dirigera.gateway.channel.ota-status.label = OTA Status +thing-type.dirigera.gateway.channel.ota-status.description = Over-the-air overall status +thing-type.dirigera.gateway.channel.pairing.label = Pairing +thing-type.dirigera.gateway.channel.pairing.description = Sets DIRIGERA hub into pairing mode +thing-type.dirigera.gateway.channel.statistics.label = Gateway Statistics +thing-type.dirigera.gateway.channel.statistics.description = Several statistics about gateway activities +thing-type.dirigera.gateway.channel.sunrise.label = Sunrise +thing-type.dirigera.gateway.channel.sunrise.description = Date and time of next sunrise +thing-type.dirigera.gateway.channel.sunset.label = Sunset +thing-type.dirigera.gateway.channel.sunset.description = Date and time of next sunset +thing-type.dirigera.light-controller.label = Light Controller +thing-type.dirigera.light-controller.description = Controller to handle light attributes +thing-type.dirigera.light-controller.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.light-controller.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.light-controller.channel.light-preset.label = Light Preset +thing-type.dirigera.light-controller.channel.light-preset.description = Light presets for different times of the day +thing-type.dirigera.light-sensor.label = Light Sensor +thing-type.dirigera.light-sensor.description = Sensor measuring illuminance in your room +thing-type.dirigera.motion-light-sensor.label = Motion Light Sensor +thing-type.dirigera.motion-light-sensor.description = Sensor detecting motion events and measures light level +thing-type.dirigera.motion-light-sensor.channel.active-duration.label = Active Duration +thing-type.dirigera.motion-light-sensor.channel.active-duration.description = Keep connected devices active for this duration +thing-type.dirigera.motion-light-sensor.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.motion-light-sensor.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.motion-light-sensor.channel.illuminance.label = Illuminance +thing-type.dirigera.motion-light-sensor.channel.illuminance.description = Illuminance in Lux +thing-type.dirigera.motion-light-sensor.channel.light-preset.label = Light Preset +thing-type.dirigera.motion-light-sensor.channel.light-preset.description = Light presets for different times of the day +thing-type.dirigera.motion-light-sensor.channel.motion.label = Motion Detected +thing-type.dirigera.motion-light-sensor.channel.motion.description = Motion detected by the device +thing-type.dirigera.motion-light-sensor.channel.schedule.label = Activity Schedule +thing-type.dirigera.motion-light-sensor.channel.schedule.description = Schedule when the sensor shall be active +thing-type.dirigera.motion-light-sensor.channel.schedule-end.label = Activity Schedule End +thing-type.dirigera.motion-light-sensor.channel.schedule-end.description = End time of sensor activity +thing-type.dirigera.motion-light-sensor.channel.schedule-start.label = Activity Schedule Start +thing-type.dirigera.motion-light-sensor.channel.schedule-start.description = Start time of sensor activity +thing-type.dirigera.motion-sensor.label = Motion Sensor +thing-type.dirigera.motion-sensor.description = Sensor detecting motion events +thing-type.dirigera.motion-sensor.channel.active-duration.label = Active Duration +thing-type.dirigera.motion-sensor.channel.active-duration.description = Keep connected devices active for this duration +thing-type.dirigera.motion-sensor.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.motion-sensor.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.motion-sensor.channel.light-preset.label = Light Preset +thing-type.dirigera.motion-sensor.channel.light-preset.description = Light presets for different times of the day +thing-type.dirigera.motion-sensor.channel.motion.label = Detection Flag +thing-type.dirigera.motion-sensor.channel.motion.description = Flag if detection happened +thing-type.dirigera.motion-sensor.channel.schedule.label = Activity Schedule +thing-type.dirigera.motion-sensor.channel.schedule.description = Schedule when the sensor shall be active +thing-type.dirigera.motion-sensor.channel.schedule-end.label = Activity Schedule End +thing-type.dirigera.motion-sensor.channel.schedule-end.description = End time of sensor activity +thing-type.dirigera.motion-sensor.channel.schedule-start.label = Activity Schedule Start +thing-type.dirigera.motion-sensor.channel.schedule-start.description = Start time of sensor activity +thing-type.dirigera.power-plug.label = Power Plug +thing-type.dirigera.power-plug.description = Power plug with control of power state, startup behavior, hardware on/off button and status light +thing-type.dirigera.power-plug.channel.power.label = Plug Powered +thing-type.dirigera.power-plug.channel.power.description = Power state of plug +thing-type.dirigera.repeater.label = Repeater +thing-type.dirigera.repeater.description = Repeater to strengthen signal +thing-type.dirigera.scene.label = Scene +thing-type.dirigera.scene.description = Scene from IKEA home smart App which can be triggered +thing-type.dirigera.scene.channel.last-trigger.label = Last Trigger +thing-type.dirigera.scene.channel.last-trigger.description = Date and time when last trigger occurred +thing-type.dirigera.scene.channel.trigger.label = Scene Trigger +thing-type.dirigera.scene.channel.trigger.description = Perform / undo scene execution +thing-type.dirigera.simple-plug.label = Simple Plug +thing-type.dirigera.simple-plug.description = Simple plug with control of power state and startup behavior +thing-type.dirigera.simple-plug.channel.power.label = Plug Powered +thing-type.dirigera.simple-plug.channel.power.description = Power state of plug +thing-type.dirigera.single-shortcut.label = Single Button Shortcut +thing-type.dirigera.single-shortcut.description = Shortcut controller with one button +thing-type.dirigera.single-shortcut.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.single-shortcut.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.single-shortcut.channel.button1.label = Button 1 Trigger +thing-type.dirigera.single-shortcut.channel.button1.description = Trigger of first button +thing-type.dirigera.smart-plug.label = Smart Power Plug +thing-type.dirigera.smart-plug.description = Power plug with electricity measurements +thing-type.dirigera.smart-plug.channel.electric-current.label = Plug Current +thing-type.dirigera.smart-plug.channel.electric-current.description = Electric current measured by plug +thing-type.dirigera.smart-plug.channel.electric-power.label = Electric Power +thing-type.dirigera.smart-plug.channel.electric-power.description = Electric power delivered by plug +thing-type.dirigera.smart-plug.channel.electric-voltage.label = Plug Voltage +thing-type.dirigera.smart-plug.channel.electric-voltage.description = Electric potential of plug +thing-type.dirigera.smart-plug.channel.energy-reset.label = Energy since Reset +thing-type.dirigera.smart-plug.channel.energy-reset.description = Energy consumption since last reset +thing-type.dirigera.smart-plug.channel.energy-total.label = Total Energy +thing-type.dirigera.smart-plug.channel.energy-total.description = Total energy consumption +thing-type.dirigera.smart-plug.channel.power.label = Plug Powered +thing-type.dirigera.smart-plug.channel.power.description = Power state of plug +thing-type.dirigera.smart-plug.channel.reset-date.label = Reset Date Time +thing-type.dirigera.smart-plug.channel.reset-date.description = Date and time of last reset +thing-type.dirigera.sound-controller.label = Sound Controller +thing-type.dirigera.sound-controller.description = Controller for speakers +thing-type.dirigera.sound-controller.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.sound-controller.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.speaker.label = Speaker +thing-type.dirigera.speaker.description = Speaker with player activities +thing-type.dirigera.speaker.channel.crossfade.label = Cross Fade +thing-type.dirigera.speaker.channel.crossfade.description = Cross fading between tracks +thing-type.dirigera.speaker.channel.image.label = Image +thing-type.dirigera.speaker.channel.image.description = Current playing track image +thing-type.dirigera.speaker.channel.media-control.label = Media Control +thing-type.dirigera.speaker.channel.media-control.description = Media control play, pause, next, previous +thing-type.dirigera.speaker.channel.media-title.label = Media Title +thing-type.dirigera.speaker.channel.media-title.description = Title of a played media file +thing-type.dirigera.speaker.channel.mute.label = Mute Control +thing-type.dirigera.speaker.channel.mute.description = Mute current audio without stop playing +thing-type.dirigera.speaker.channel.repeat.label = Repeat +thing-type.dirigera.speaker.channel.repeat.description = Repeat Mode +thing-type.dirigera.speaker.channel.shuffle.label = Shuffle +thing-type.dirigera.speaker.channel.shuffle.description = Control shuffle mode +thing-type.dirigera.speaker.channel.volume.label = Volume Control +thing-type.dirigera.speaker.channel.volume.description = Control volume in percent +thing-type.dirigera.switch-light.label = Switch Light +thing-type.dirigera.switch-light.description = Light with switch ON, OFF capability +thing-type.dirigera.switch-light.channel.power.label = Light Powered +thing-type.dirigera.switch-light.channel.power.description = Power state of light +thing-type.dirigera.temperature-light.label = Temperature Light +thing-type.dirigera.temperature-light.description = Light with color temperature support +thing-type.dirigera.temperature-light.channel.brightness.label = Light Brightness +thing-type.dirigera.temperature-light.channel.brightness.description = Brightness of light in percent +thing-type.dirigera.temperature-light.channel.color-temperature.label = Color Temperature +thing-type.dirigera.temperature-light.channel.color-temperature.description = Color temperature from cold (0 %) to warm (100 %) +thing-type.dirigera.temperature-light.channel.power.label = Light Powered +thing-type.dirigera.temperature-light.channel.power.description = Power state of light +thing-type.dirigera.water-sensor.label = Water Sensor +thing-type.dirigera.water-sensor.description = Sensor to detect water leaks +thing-type.dirigera.water-sensor.channel.battery-level.label = Battery Charge Level +thing-type.dirigera.water-sensor.channel.battery-level.description = Battery charge level in percent +thing-type.dirigera.water-sensor.channel.leak.label = Leak Detection +thing-type.dirigera.water-sensor.channel.leak.description = Water leak detection + +# thing types config + +thing-type.config.dirigera.base-device.id.label = Device Id +thing-type.config.dirigera.base-device.id.description = Unique id of this device +thing-type.config.dirigera.color-light.fadeSequence.label = Fade Sequence +thing-type.config.dirigera.color-light.fadeSequence.description = Define sequence if several light parameters are changed at once +thing-type.config.dirigera.color-light.fadeSequence.option.0 = First brightness, then color +thing-type.config.dirigera.color-light.fadeSequence.option.1 = First color, then brightness +thing-type.config.dirigera.color-light.fadeTime.label = Fade Time +thing-type.config.dirigera.color-light.fadeTime.description = Required time for fade sequnce to color or brightness +thing-type.config.dirigera.color-light.id.label = Device Id +thing-type.config.dirigera.color-light.id.description = Unique id of this device +thing-type.config.dirigera.gateway.discovery.label = Discovery +thing-type.config.dirigera.gateway.discovery.description = Configure if paired devices shall be detected by discovery +thing-type.config.dirigera.gateway.id.label = Device Id +thing-type.config.dirigera.gateway.id.description = Unique id of this gateway +thing-type.config.dirigera.gateway.ipAddress.label = IP Address +thing-type.config.dirigera.gateway.ipAddress.description = Gateway IP Address +thing-type.config.dirigera.light-device.fadeTime.label = Fade Time +thing-type.config.dirigera.light-device.fadeTime.description = Required time for fade sequnce to color or brightness +thing-type.config.dirigera.light-device.id.label = Device Id +thing-type.config.dirigera.light-device.id.description = Unique id of this device + +# channel types + +channel-type.dirigera.alarm.label = Alarm Switch +channel-type.dirigera.blind-dimmer.label = Blind Level +channel-type.dirigera.blind-dimmer.description = Current blind level +channel-type.dirigera.blind-state.label = Blind State +channel-type.dirigera.blind-state.description = State if blind is moving up, down or stopped +channel-type.dirigera.blind-state.state.option.0 = Stopped +channel-type.dirigera.blind-state.state.option.1 = Up +channel-type.dirigera.blind-state.state.option.2 = Down +channel-type.dirigera.blind-state.command.option.0 = Stopped +channel-type.dirigera.blind-state.command.option.1 = Up +channel-type.dirigera.blind-state.command.option.2 = Down +channel-type.dirigera.child-lock.label = Child Lock +channel-type.dirigera.child-lock.description = Child lock for button on device +channel-type.dirigera.contact.label = Contact +channel-type.dirigera.custom-name.label = Custom Name +channel-type.dirigera.custom-name.description = Name given from IKEA home smart +channel-type.dirigera.datetime-reset.label = Date Time +channel-type.dirigera.datetime-reset.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM +channel-type.dirigera.datetime-reset.command.option.0 = Reset now +channel-type.dirigera.datetime.label = Date Time +channel-type.dirigera.datetime.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM +channel-type.dirigera.dimmer.label = Dimmer +channel-type.dirigera.disable-status-light.label = Disable Status Light +channel-type.dirigera.disable-status-light.description = Disable status light on device +channel-type.dirigera.duration.label = Time +channel-type.dirigera.duration.command.option.1 min = 1 minute +channel-type.dirigera.duration.command.option.3 min = 3 minutes +channel-type.dirigera.duration.command.option.5 min = 5 minutes +channel-type.dirigera.duration.command.option.10 min = 10 minutes +channel-type.dirigera.duration.command.option.15 min = 15 minutes +channel-type.dirigera.duration.command.option.20 min = 20 minutes +channel-type.dirigera.duration.command.option.30 min = 30 minutes +channel-type.dirigera.duration.command.option.40 min = 40 minutes +channel-type.dirigera.duration.command.option.60 min = 60 minutes +channel-type.dirigera.fan-mode.label = Fan Mode +channel-type.dirigera.fan-mode.state.option.0 = Auto +channel-type.dirigera.fan-mode.state.option.1 = Low +channel-type.dirigera.fan-mode.state.option.2 = Medium +channel-type.dirigera.fan-mode.state.option.3 = High +channel-type.dirigera.fan-mode.state.option.4 = On +channel-type.dirigera.fan-mode.state.option.5 = Off +channel-type.dirigera.fan-mode.command.option.0 = Auto +channel-type.dirigera.fan-mode.command.option.1 = Low +channel-type.dirigera.fan-mode.command.option.2 = Medium +channel-type.dirigera.fan-mode.command.option.3 = High +channel-type.dirigera.fan-mode.command.option.4 = On +channel-type.dirigera.fan-mode.command.option.5 = Off +channel-type.dirigera.illuminance.label = Illuminance +channel-type.dirigera.illuminance.description = Illuminance in Lux +channel-type.dirigera.image.label = Image +channel-type.dirigera.light-preset.label = Light Preset +channel-type.dirigera.light-preset.command.option.Off = Off +channel-type.dirigera.light-preset.command.option.Warm = Warm +channel-type.dirigera.light-preset.command.option.Slowdown = Slowdown +channel-type.dirigera.light-preset.command.option.Smooth = Smooth +channel-type.dirigera.light-preset.command.option.Bright = Bright +channel-type.dirigera.link-candidates.label = Link Candidates +channel-type.dirigera.link-candidates.description = Candidates which can be linked +channel-type.dirigera.links.label = Links +channel-type.dirigera.links.description = Linked controllers and sensors +channel-type.dirigera.ota-percent.label = OTA Progress +channel-type.dirigera.ota-percent.description = Over-the-air update progress +channel-type.dirigera.ota-state.label = OTA State +channel-type.dirigera.ota-state.description = Over-the-air current state +channel-type.dirigera.ota-state.state.option.0 = Ready to check +channel-type.dirigera.ota-state.state.option.1 = Check in progress +channel-type.dirigera.ota-state.state.option.2 = Ready to download +channel-type.dirigera.ota-state.state.option.3 = Download in progress +channel-type.dirigera.ota-state.state.option.4 = Update in progress +channel-type.dirigera.ota-state.state.option.5 = Update failed +channel-type.dirigera.ota-state.state.option.6 = Ready to update +channel-type.dirigera.ota-state.state.option.7 = Check failed +channel-type.dirigera.ota-state.state.option.8 = Download failed +channel-type.dirigera.ota-state.state.option.9 = Update complete +channel-type.dirigera.ota-state.state.option.10 = Battery check failed +channel-type.dirigera.ota-status.label = OTA Status +channel-type.dirigera.ota-status.description = Over-the-air overall status +channel-type.dirigera.ota-status.state.option.0 = Up to date +channel-type.dirigera.ota-status.state.option.1 = Update available +channel-type.dirigera.pm25.label = Particulate Matter category 2.5 +channel-type.dirigera.repeat.label = Repeat Options +channel-type.dirigera.repeat.state.option.0 = Off +channel-type.dirigera.repeat.state.option.1 = Title +channel-type.dirigera.repeat.state.option.2 = Playlist +channel-type.dirigera.repeat.command.option.0 = Off +channel-type.dirigera.repeat.command.option.1 = Title +channel-type.dirigera.repeat.command.option.2 = Playlist +channel-type.dirigera.scene-trigger.label = Scene Trigger +channel-type.dirigera.scene-trigger.command.option.0 = Trigger +channel-type.dirigera.scene-trigger.command.option.1 = Undo +channel-type.dirigera.schedule-end-time.label = Schedule Time +channel-type.dirigera.schedule-end-time.state.pattern = %1$tH:%1$tM +channel-type.dirigera.schedule-end-time.command.option.04:00 = 4:00 +channel-type.dirigera.schedule-end-time.command.option.04:30 = 4:30 +channel-type.dirigera.schedule-end-time.command.option.05:00 = 5:00 +channel-type.dirigera.schedule-end-time.command.option.05:30 = 5:30 +channel-type.dirigera.schedule-end-time.command.option.06:00 = 6:00 +channel-type.dirigera.schedule-end-time.command.option.06:30 = 6:30 +channel-type.dirigera.schedule-end-time.command.option.07:00 = 7:00 +channel-type.dirigera.schedule-end-time.command.option.07:30 = 7:30 +channel-type.dirigera.schedule-end-time.command.option.08:00 = 8:00 +channel-type.dirigera.schedule-start-time.label = Schedule Time +channel-type.dirigera.schedule-start-time.state.pattern = %1$tH:%1$tM +channel-type.dirigera.schedule-start-time.command.option.16:00 = 16:00 +channel-type.dirigera.schedule-start-time.command.option.16:30 = 16:30 +channel-type.dirigera.schedule-start-time.command.option.17:00 = 17:00 +channel-type.dirigera.schedule-start-time.command.option.17:30 = 17:30 +channel-type.dirigera.schedule-start-time.command.option.18:00 = 18:00 +channel-type.dirigera.schedule-start-time.command.option.18:30 = 18:30 +channel-type.dirigera.schedule-start-time.command.option.19:00 = 19:00 +channel-type.dirigera.schedule-start-time.command.option.19:30 = 19:30 +channel-type.dirigera.schedule-start-time.command.option.20:00 = 20:00 +channel-type.dirigera.sensor-schedule.label = Sensor Schedule +channel-type.dirigera.sensor-schedule.state.option.0 = Always +channel-type.dirigera.sensor-schedule.state.option.1 = Follow Sun +channel-type.dirigera.sensor-schedule.state.option.2 = Time schedule +channel-type.dirigera.sensor-schedule.command.option.0 = Always +channel-type.dirigera.sensor-schedule.command.option.1 = Follow Sun +channel-type.dirigera.sensor-schedule.command.option.2 = Time schedule +channel-type.dirigera.startup.label = Startup Behavior +channel-type.dirigera.startup.description = Startup behavior after power cutoff +channel-type.dirigera.startup.state.option.0 = Previous +channel-type.dirigera.startup.state.option.1 = On +channel-type.dirigera.startup.state.option.2 = Off +channel-type.dirigera.startup.state.option.3 = Toggle +channel-type.dirigera.startup.command.option.0 = Previous +channel-type.dirigera.startup.command.option.1 = On +channel-type.dirigera.startup.command.option.2 = Off +channel-type.dirigera.startup.command.option.3 = Toggle +channel-type.dirigera.switch-ro.label = On Off Switch +channel-type.dirigera.switch.label = On Off Switch +channel-type.dirigera.text.label = Simple Text +channel-type.dirigera.time.label = Time +channel-type.dirigera.voc.label = VOC Index + +# thing status types + +dirigera.device.status.missing-ip = No IP Address configured +dirigera.device.status.wrong-bridge-handler = BridgeHandler isn't a Gateway +dirigera.device.status.missing-bridge-handler = BridgeHandler is missing +dirigera.device.status.missing-bridge = No Bridge configured +dirigera.device.status.not-reachable = Device not reachable +dirigera.device.status.api-error = API {0} cannot be created +dirigera.device.status.id-not-found = Device id {0} not found +dirigera.device.status.ttuid-mismatch = Handler {0} doesn't match with model {1} +dirigera.gateway.status.pairing-button = Press Button on DIRIGERA Gateway +dirigera.gateway.status.pairing-retry = Pairing failed. Stop and start bridge to initialize new pairing. +dirigera.gateway.status.comm-error = Gateway HTTP Status {0} +dirigera.gateway.status.no-gateway = No Gateway found +dirigera.gateway.status.ambiguous-gateway = More than one Gateway found +dirigera.scene.status.scene-not-found = Scene not found diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-purifier.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-purifier.xml new file mode 100644 index 00000000000..7aa67979b90 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-purifier.xml @@ -0,0 +1,55 @@ + + + + + + + + + + Air cleaning device with particle filter + + + + + Fan on, off, speed or automatic behavior + + + + Manual regulation of fan speed + + + + Fan runtime in minutes + + + + Filter elapsed time in minutes + + + + Remaining filter time in minutes + + + + Filter lifetime in minutes + + + + Filter alarm signal + + + + Category 2.5 particulate matter + + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-quality.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-quality.xml new file mode 100644 index 00000000000..c5462215c96 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/air-quality.xml @@ -0,0 +1,37 @@ + + + + + + + + + + Air measure for temperature, humidity and particles + + + + + Current indoor temperature + + + + Atmospheric humidity in percent + + + + Category 2.5 particulate matter + + + + Relative VOC intensity compared to recent history + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind-controller.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind-controller.xml new file mode 100644 index 00000000000..a89f67bfb20 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind-controller.xml @@ -0,0 +1,25 @@ + + + + + + + + + + Controller to open and close blinds + + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind.xml new file mode 100644 index 00000000000..e00f8e62957 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/blind.xml @@ -0,0 +1,27 @@ + + + + + + + + + + Window or door blind + + + + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/channel-types.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/channel-types.xml new file mode 100644 index 00000000000..87ff524a948 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/channel-types.xml @@ -0,0 +1,347 @@ + + + + + String + + + + String + + Name given from IKEA home smart + + + String + + + + + + + + + + + + + Number + + + + + + + + + + + + + + + + veto + + + Number + + + + + + + + + + + + + + + + veto + + + Number + + Startup behavior after power cutoff + + + + + + + + + + + + + + + + + veto + + + Switch + + Disable status light on device + veto + + + Switch + + Child lock for button on device + veto + + + Switch + + veto + + + Switch + + + + + Switch + + Alarm + + Alarm + Water + + + + + Number:Illuminance + + Illuminance in Lux + + Measurement + Light + + + + + Contact + + Contact + + OpenState + + + + + Dimmer + + veto + + + Image + + + + + DateTime + + + + + + + + + + + + + + + + + + DateTime + + + + + + + + + + + + + + + + veto + + + DateTime + + + + + DateTime + + + + + + + + veto + + + Number + + + + + + + + veto + + + Number:Density + + + + + Number + + + + + Number:Time + + + + Number:Time + + + + + + + + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + + + Number + + State if blind is moving up, down or stopped + + + + + + + + + + + + + + + + + Dimmer + + Current blind level + Rollershutter + + OpenLevel + + veto + + + Number + + Over-the-air overall status + + + + + + + + + Number + + Over-the-air current state + + + + + + + + + + + + + + + + + + Number:Dimensionless + + Over-the-air update progress + + + + String + + Linked controllers and sensors + veto + + + String + + Candidates which can be linked + veto + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/color-light.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/color-light.xml new file mode 100644 index 00000000000..9da75868178 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/color-light.xml @@ -0,0 +1,47 @@ + + + + + + + + + + Light with color support + + + + + Power state of light + veto + + + + Brightness of light in percent + veto + + + + Color temperature from cold (0 %) to warm (100 %) + veto + + + + Color temperature of a bulb in Kelvin + veto + + + + Color of light with hue, saturation and brightness + veto + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/contact-sensor.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/contact-sensor.xml new file mode 100644 index 00000000000..bbc30b5566a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/contact-sensor.xml @@ -0,0 +1,29 @@ + + + + + + + + + + Sensor tracking if windows or doors are open + + + + + State if door or window is open or closed + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/dimmable-light.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/dimmable-light.xml new file mode 100644 index 00000000000..74cf5422eda --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/dimmable-light.xml @@ -0,0 +1,32 @@ + + + + + + + + + + Light with brightness support + + + + + Power state of light + veto + + + + Brightness of light in percent + veto + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/double-shortcut.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/double-shortcut.xml new file mode 100644 index 00000000000..e1caade55b6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/double-shortcut.xml @@ -0,0 +1,33 @@ + + + + + + + + + + Shortcut controller with two buttons + + + + + Trigger of first button + + + + Trigger of second button + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/gateway.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/gateway.xml new file mode 100644 index 00000000000..19521d7d2f2 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/gateway.xml @@ -0,0 +1,49 @@ + + + + + + IKEA Gateway for smart products + + + + + + Location in latitude, longitude coordinates + + + + Date and time of next sunrise + + + + Date and time of next sunset + + + + Sets DIRIGERA hub into pairing mode + + + + Over-the-air overall status + + + + Over-the-air current state + + + + Over-the-air update progress + + + + Several statistics about gateway activities + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-controller.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-controller.xml new file mode 100644 index 00000000000..132e7f4e82a --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-controller.xml @@ -0,0 +1,29 @@ + + + + + + + + + + Controller to handle light attributes + + + + + Light presets for different times of the day + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-sensor.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-sensor.xml new file mode 100644 index 00000000000..6cb06222793 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/light-sensor.xml @@ -0,0 +1,21 @@ + + + + + + + + + + Sensor measuring illuminance in your room + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-light-sensor.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-light-sensor.xml new file mode 100644 index 00000000000..30d3c9477b3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-light-sensor.xml @@ -0,0 +1,53 @@ + + + + + + + + + + Sensor detecting motion events and measures light level + + + + + Motion detected by the device + + + + Keep connected devices active for this duration + + + + Illuminance in Lux + + + + Battery charge level in percent + + + + Schedule when the sensor shall be active + + + + Start time of sensor activity + + + + End time of sensor activity + + + + Light presets for different times of the day + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-sensor.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-sensor.xml new file mode 100644 index 00000000000..16c7be0c399 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/motion-sensor.xml @@ -0,0 +1,49 @@ + + + + + + + + + + Sensor detecting motion events + + + + + Flag if detection happened + + + + Keep connected devices active for this duration + + + + Battery charge level in percent + + + + Schedule when the sensor shall be active + + + + Start time of sensor activity + + + + End time of sensor activity + + + + Light presets for different times of the day + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/power-plug.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/power-plug.xml new file mode 100644 index 00000000000..65f4d294109 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/power-plug.xml @@ -0,0 +1,29 @@ + + + + + + + + + + Power plug with control of power state, startup behavior, hardware on/off button and status light + + + + + Power state of plug + veto + + + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/repeater.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/repeater.xml new file mode 100644 index 00000000000..1899b94ebd3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/repeater.xml @@ -0,0 +1,21 @@ + + + + + + + + + + Repeater to strengthen signal + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/scene.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/scene.xml new file mode 100644 index 00000000000..488fec03b31 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/scene.xml @@ -0,0 +1,28 @@ + + + + + + + + + + Scene from IKEA home smart App which can be triggered + + + + + Perform / undo scene execution + + + + Date and time when last trigger occurred + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/simple-plug.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/simple-plug.xml new file mode 100644 index 00000000000..732273e4646 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/simple-plug.xml @@ -0,0 +1,27 @@ + + + + + + + + + + Simple plug with control of power state and startup behavior + + + + + Power state of plug + veto + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/single-shortcut.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/single-shortcut.xml new file mode 100644 index 00000000000..246d73a90ab --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/single-shortcut.xml @@ -0,0 +1,29 @@ + + + + + + + + + + Shortcut controller with one button + + + + + Trigger of first button + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/smart-plug.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/smart-plug.xml new file mode 100644 index 00000000000..d91e2262118 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/smart-plug.xml @@ -0,0 +1,53 @@ + + + + + + + + + + Power plug with electricity measurements + + + + + Power state of plug + veto + + + + + + Electric power delivered by plug + + + + Total energy consumption + + + + Energy consumption since last reset + + + + Date and time of last reset + + + + Electric current measured by plug + + + + Electric potential of plug + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/sound-controller.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/sound-controller.xml new file mode 100644 index 00000000000..9d3ad303633 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/sound-controller.xml @@ -0,0 +1,25 @@ + + + + + + + + + + Controller for speakers + + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/speaker.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/speaker.xml new file mode 100644 index 00000000000..7d0b7b72703 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/speaker.xml @@ -0,0 +1,53 @@ + + + + + + + + + + Speaker with player activities + + + + + Media control play, pause, next, previous + + + + Control volume in percent + + + + Mute current audio without stop playing + + + + Control shuffle mode + + + + Cross fading between tracks + + + + Repeat Mode + + + + Title of a played media file + + + + Current playing track image + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/switch-light.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/switch-light.xml new file mode 100644 index 00000000000..0944f421206 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/switch-light.xml @@ -0,0 +1,26 @@ + + + + + + + + + + Light with switch ON, OFF capability + + + + + Power state of light + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/temperature-light.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/temperature-light.xml new file mode 100644 index 00000000000..823bb38f6ad --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/temperature-light.xml @@ -0,0 +1,42 @@ + + + + + + + + + + Light with color temperature support + + + + + Power state of light + veto + + + + Brightness of light in percent + veto + + + + Color temperature from cold (0 %) to warm (100 %) + veto + + + + Color temperature of a bulb in Kelvin + veto + + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/water-sensor.xml b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/water-sensor.xml new file mode 100644 index 00000000000..f994e3daa3e --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/OH-INF/thing/water-sensor.xml @@ -0,0 +1,29 @@ + + + + + + + + + + Sensor to detect water leaks + + + + + Water leak detection + + + + Battery charge level in percent + + + + + + + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/coordinates.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/coordinates.json new file mode 100644 index 00000000000..628cecbb728 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/coordinates.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "coordinates": { + "latitude": %s, + "longitude": %s, + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/null-coordinates.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/null-coordinates.json new file mode 100644 index 00000000000..c395985b90c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/gateway/null-coordinates.json @@ -0,0 +1,5 @@ +{ + "attributes": { + "coordinates": {} + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/bright.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/bright.json new file mode 100644 index 00000000000..c931aa92ff1 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/bright.json @@ -0,0 +1,32 @@ +[ + { + "startTime": "07:00", + "lightLevel": 25, + "colorTemperature": 3000 + }, + { + "startTime": "09:00", + "lightLevel": 75, + "colorTemperature": 3900 + }, + { + "startTime": "13:00", + "lightLevel": 100, + "colorTemperature": 4000 + }, + { + "startTime": "17:00", + "lightLevel": 70, + "colorTemperature": 4000 + }, + { + "startTime": "20:00", + "lightLevel": 20, + "colorTemperature": 3900 + }, + { + "startTime": "22:00", + "lightLevel": 1, + "colorTemperature": 3000 + } +] diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/slowdown.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/slowdown.json new file mode 100644 index 00000000000..5336689b95c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/slowdown.json @@ -0,0 +1,33 @@ +[ + { + "startTime": "05:00", + "lightLevel": 100, + "colorTemperature": 4000 + }, + { + "startTime": "10:00", + "lightLevel": 100, + "colorTemperature": 3800 + }, + { + "startTime": "17:00", + "lightLevel": 75, + "colorTemperature": 3500 + }, + { + "startTime": "20:00", + "lightLevel": 45, + "colorTemperature": 3000 + }, + { + "startTime": "22:00", + "lightLevel": 20, + "colorTemperature": 2400 + }, + { + "startTime": "23:00", + "lightLevel": 1, + "colorTemperature": 2200 + } +] + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/smooth.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/smooth.json new file mode 100644 index 00000000000..822eaa11ebc --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/smooth.json @@ -0,0 +1,32 @@ +[ + { + "startTime": "06:00", + "lightLevel": 50, + "colorTemperature": 2400 + }, + { + "startTime": "10:00", + "lightLevel": 100, + "colorTemperature": 4000 + }, + { + "startTime": "16:00", + "lightLevel": 100, + "colorTemperature": 3500 + }, + { + "startTime": "20:00", + "lightLevel": 50, + "colorTemperature": 3000 + }, + { + "startTime": "22:00", + "lightLevel": 20, + "colorTemperature": 2300 + }, + { + "startTime": "23:00", + "lightLevel": 1, + "colorTemperature": 2300 + } +] diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/warm.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/warm.json new file mode 100644 index 00000000000..7a739564396 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/light-presets/warm.json @@ -0,0 +1,32 @@ +[ + { + "startTime": "07:00", + "lightLevel": 25, + "colorTemperature": 3000 + }, + { + "startTime": "09:00", + "lightLevel": 75, + "colorTemperature": 3600 + }, + { + "startTime": "13:00", + "lightLevel": 100, + "colorTemperature": 4000 + }, + { + "startTime": "17:00", + "lightLevel": 70, + "colorTemperature": 3600 + }, + { + "startTime": "20:00", + "lightLevel": 20, + "colorTemperature": 3000 + }, + { + "startTime": "22:00", + "lightLevel": 1, + "colorTemperature": 2200 + } +] diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/scenes/click-scene.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/scenes/click-scene.json new file mode 100644 index 00000000000..210b6491177 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/scenes/click-scene.json @@ -0,0 +1,22 @@ +{ + "id": "%s", + "type": "customScene", + "info": { + "name": "%s", + "icon": "scenes_home_filled" + }, + "triggers": [ + { + "type": "controller", + "trigger": { + "controllerType": "shortcutController", + "clickPattern": "%s", + "buttonIndex": %s, + "deviceId": "%s" + } + } + ], + "actions": [], + "commands": [] +} + \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/always-on.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/always-on.json new file mode 100644 index 00000000000..a20e377156b --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/always-on.json @@ -0,0 +1,7 @@ +{ + "attributes": { + "sensorConfig": { + "scheduleOn": false + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/duration-update.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/duration-update.json new file mode 100644 index 00000000000..d690d9c1acd --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/duration-update.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "sensorConfig": { + "onDuration": %s + } + } +} + diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/follow-sun.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/follow-sun.json new file mode 100644 index 00000000000..1a1a5021fbf --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/follow-sun.json @@ -0,0 +1,15 @@ +{ + "attributes": { + "sensorConfig": { + "scheduleOn": true, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/schedule-on.json b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/schedule-on.json new file mode 100644 index 00000000000..0d8129ac8d0 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/main/resources/json/sensor-config/schedule-on.json @@ -0,0 +1,15 @@ +{ + "attributes": { + "sensorConfig": { + "scheduleOn": true, + "schedule": { + "onCondition": { + "time": "%s" + }, + "offCondition": { + "time": "%s" + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/FileReader.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/FileReader.java new file mode 100644 index 00000000000..6d2f1cd4976 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/FileReader.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * {@link FileReader} reads from file into String + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class FileReader { + + public static String readFileInString(String filename) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "CP1252"));) { + StringBuffer buf = new StringBuffer(); + String sCurrentLine; + + while ((sCurrentLine = br.readLine()) != null) { + buf.append(sCurrentLine); + } + return buf.toString(); + } catch (IOException e) { + // fail if file cannot be read + fail("File " + filename + " not found"); + } + return "ERR"; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/TestGeneric.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/TestGeneric.java new file mode 100644 index 00000000000..0be31f8f712 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/TestGeneric.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.light.LightCommand; +import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.network.DirigeraAPIImpl; +import org.openhab.core.common.ThreadPoolManager; + +/** + * {@link TestGeneric} some basic tests + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestGeneric { + static String output = "fine!"; + + @Test + void testStringFormatWithNull() { + try { + String error = String.format( + "{\"http-error-flag\":true,\"http-error-status\":%s,\"http-error-message\":\"%s\"}", "5", null); + JSONObject errorJSON = new JSONObject(error); + assertFalse(errorJSON.isNull("http-error-message")); + } catch (Exception e) { + fail(); + } + } + + @Test + void lightCommandQueueTest() { + ArrayList lightRequestQueue = new ArrayList<>(); + JSONObject dummy1 = new JSONObject(); + dummy1.put("dunny1", false); + LightCommand brightness1 = new LightCommand(dummy1, LightCommand.Action.BRIGHTNESS); + lightRequestQueue.add(brightness1); + JSONObject dummy2 = new JSONObject(); + dummy2.put("dunny2", true); + LightCommand brightness2 = new LightCommand(dummy2, LightCommand.Action.BRIGHTNESS); + assertTrue(lightRequestQueue.contains(brightness1)); + assertTrue(lightRequestQueue.contains(brightness2)); + assertTrue(brightness1.equals(brightness2)); + JSONObject dummy3 = null; + assertFalse(brightness1.equals(dummy3)); + LightCommand color = new LightCommand(dummy2, LightCommand.Action.COLOR); + assertFalse(lightRequestQueue.contains(color)); + } + + @Test + void testApiJsonException() { + HttpClient httpMock = mock(HttpClient.class); + Request requestMock = mock(Request.class); + when(httpMock.isRunning()).thenReturn(true); + when(httpMock.getSslContextFactory()).thenReturn(new SslContextFactory.Client(true)); + when(httpMock.newRequest(anyString())).thenReturn(requestMock); + when(requestMock.timeout(10, TimeUnit.SECONDS)).thenReturn(requestMock); + when(requestMock.header(HttpHeader.AUTHORIZATION, "Bearer 1234")).thenReturn(requestMock); + + ContentResponse response = mock(ContentResponse.class); + when(response.getStatus()).thenReturn(200); + // response will force a JSON format exception in API implementation + when(response.getContentAsString()).thenReturn("{\"rubbish\":true,butNoJson:\"false\",]}"); + + try { + when(requestMock.send()).thenReturn(response); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + fail(); + } + + Gateway gateway = mock(Gateway.class); + when(gateway.getToken()).thenReturn("1234"); + DirigeraAPIImpl api = new DirigeraAPIImpl(httpMock, gateway); + JSONObject apiResponse = api.readDevice("abc"); + assertNotNull(apiResponse); + // test completeness and status of error message + assertTrue(apiResponse.has(DirigeraAPI.HTTP_ERROR_FLAG)); + assertTrue(apiResponse.has(DirigeraAPI.HTTP_ERROR_STATUS)); + assertEquals(500, apiResponse.getInt(DirigeraAPI.HTTP_ERROR_STATUS)); + assertTrue(apiResponse.has(DirigeraAPI.HTTP_ERROR_MESSAGE)); + } + + @Test + void testThreadpoolExcpetion() { + ScheduledExecutorService ses = ThreadPoolManager.getScheduledPool("test"); + ScheduledFuture sf = ses.scheduleWithFixedDelay(this::printOutput, 0, 50, TimeUnit.MILLISECONDS); + sleep(); + assertFalse(sf.isDone()); + output = "throw"; + sleep(); + assertTrue(sf.isDone()); + } + + void printOutput() { + if ("throw".equals(output)) { + throw new UnsupportedOperationException("crash"); + } + } + + void sleep() { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + fail(); + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/DirigeraBridgeProvider.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/DirigeraBridgeProvider.java new file mode 100644 index 00000000000..353a1f19603 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/DirigeraBridgeProvider.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.Constants; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DicoveryServiceMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.binding.dirigera.internal.mock.DirigeraHandlerManipulator; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.storage.Storage; +import org.openhab.core.test.storage.VolatileStorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.BridgeImpl; +import org.openhab.core.thing.internal.ThingImpl; + +/** + * {@link DirigeraBridgeProvider} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +public class DirigeraBridgeProvider { + public static Bridge prepareSimuBridge() { + return prepareSimuBridge("src/test/resources/home/home.json", false, List.of()); + } + + /** + * Prepare bridge which can be used with DirigraAPISimu Provider + * + * @return Bridge + */ + public static Bridge prepareSimuBridge(String homeFile, boolean discovery, List knownDevicesd) { + String ipAddress = "1.2.3.4"; + HttpClient httpMock = mock(HttpClient.class); + DirigeraAPISimu.fileName = homeFile; + /** + * Prepare persistence + */ + // prepare persistence data + VolatileStorageService storageService = new VolatileStorageService(); + Storage mockStorage = storageService.getStorage(Constants.BINDING_ID); + + JSONObject storageObject = new JSONObject(); + JSONArray knownDevices = new JSONArray(knownDevicesd); + knownDevices.put("594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1"); + storageObject.put(PROPERTY_DEVICES, knownDevices.toString()); + storageObject.put(PROPERTY_TOKEN, "unit-test"); + mockStorage.put(ipAddress, storageObject.toString()); + + // prepare instances + BridgeImpl hubBridge = new BridgeImpl(THING_TYPE_GATEWAY, new ThingUID(BINDING_ID + ":" + "gateway:9876")); + hubBridge.setBridgeUID(new ThingUID(BINDING_ID + ":" + "gateway:9876")); + + /** + * new version with api simulation in background + */ + DirigeraHandlerManipulator hubHandler = new DirigeraHandlerManipulator(hubBridge, httpMock, mockStorage, + new DicoveryServiceMock()); + hubBridge.setHandler(hubHandler); + CallbackMock bridgeCallback = new CallbackMock(); + hubHandler.setCallback(bridgeCallback); + + // set handler to full configured with token, ipAddress and if + Map config = new HashMap<>(); + config.put("ipAddress", ipAddress); + config.put("id", "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1"); + config.put("discovery", discovery); + hubHandler.handleConfigurationUpdate(config); + + hubHandler.initialize(); + bridgeCallback.waitForOnline(); + return hubBridge; + } + + public static ThingHandler createHandler(ThingTypeUID thingTypeUID, Bridge hubBridge, String deviceId) { + VolatileStorageService storageService = new VolatileStorageService(); + HandlerFactoryMock hfm = new HandlerFactoryMock(storageService); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setLabel("Unit Test Device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + ThingHandler handler = hfm.createHandler(thing); + assertNotNull(handler); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + return handler; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestGateway.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestGateway.java new file mode 100644 index 00000000000..66187e5ea7f --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestGateway.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_LOCATION; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.binding.dirigera.internal.mock.DirigeraHandlerManipulator; +import org.openhab.core.library.types.PointType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * {@link TestGateway} for checking gateway use cases + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestGateway { + private static String deviceId = "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1"; + + private static DirigeraHandler handler = mock(DirigeraHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + private static String mockFile = "src/test/resources/gateway/home-with-coordinates.json"; + + @Test + void testBridgeCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(mockFile, false, List.of()); + ThingHandler bridgeHandler = hubBridge.getHandler(); + assertTrue(bridgeHandler instanceof DirigeraHandlerManipulator); + handler = (DirigeraHandlerManipulator) bridgeHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = ((DirigeraHandlerManipulator) handler).getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testWithCoordinates() { + mockFile = "src/test/resources/gateway/home-with-coordinates.json"; + testBridgeCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + + State locationPoint = callback.getState("dirigera:gateway:9876:location"); + assertNotNull(locationPoint); + assertTrue(locationPoint instanceof PointType); + assertEquals("9.876,1.234", ((PointType) locationPoint).toFullString(), "Location Point"); + } + + @Test + void testWithoutCoordinates() { + mockFile = "src/test/resources/gateway/home-without-coordinates.json"; + testBridgeCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + + State locationPoint = callback.getState("dirigera:gateway:9876:location"); + assertNotNull(locationPoint); + assertTrue(locationPoint instanceof UnDefType); + } + + @Test + void testCommands() { + testWithCoordinates(); + + // remove location from gateway with empty string + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.EMPTY); + String patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"coordinates\":{}}}", patch, "Empty Coordinates"); + DirigeraAPISimu.patchMap.clear(); + + // set new location with valid coordinates + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.valueOf("9.123,1.987")); + patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"coordinates\":{\"latitude\":9.123,\"longitude\":1.987}}}", patch, + "Valid Coordinates"); + DirigeraAPISimu.patchMap.clear(); + + // nothing send if value is invalid + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.valueOf("wrong coding")); + patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNull(patch, "Wrong coordinates"); + DirigeraAPISimu.patchMap.clear(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestWrongHandler.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestWrongHandler.java new file mode 100644 index 00000000000..c1cc00aab59 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/TestWrongHandler.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.sensor.ContactSensorHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.internal.ThingImpl; + +/** + * {@link TestWrongHandler} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestWrongHandler { + @Test + void testWrongHandlerForId() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_CONTACT_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + ContactSensorHandler handler = new ContactSensorHandler(thing, CONTACT_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "5ac5e131-44a4-4d75-be78-759a095d31fb_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + ThingStatusInfo status = callback.getStatus(); + assertEquals(ThingStatus.OFFLINE, status.getStatus(), "OFFLINE"); + assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, status.getStatusDetail(), "Config Error"); + String description = status.getDescription(); + assertNotNull(description); + assertTrue( + "@text/dirigera.device.status.ttuid-mismatch [\"dirigera:contact-sensor\",\"dirigera:motion-light-sensor\"]" + .equals(description), + "Description"); + } + + @Test + void testMissingId() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_CONTACT_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + ContactSensorHandler handler = new ContactSensorHandler(thing, CONTACT_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "5ac5e131-1234-4d75-be78-759a095d31fb_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + ThingStatusInfo status = callback.getStatus(); + assertEquals(ThingStatus.OFFLINE, status.getStatus(), "OFFLINE"); + assertEquals(ThingStatusDetail.GONE, status.getStatusDetail(), "Device disappeared"); + String description = status.getDescription(); + assertNotNull(description); + assertTrue("@text/dirigera.device.status.id-not-found [\"5ac5e131-1234-4d75-be78-759a095d31fb_1\"]" + .equals(description), "Description"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/airpurifier/TestAirPurifier.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/airpurifier/TestAirPurifier.java new file mode 100644 index 00000000000..4189a9af5bb --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/airpurifier/TestAirPurifier.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.airpurifier; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.library.types.DecimalType; +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.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestAirPurifier} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestAirPurifier { + private static String deviceId = "a8319695-0729-428c-9465-aadc0b738995"; + private static ThingTypeUID thingTypeUID = THING_TYPE_AIR_PURIFIER; + + private static AirPurifierHandler handler = mock(AirPurifierHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof AirPurifierHandler); + handler = (AirPurifierHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testInitialization() { + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + checkAirPurifierStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CHILD_LOCK), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_DISABLE_STATUS_LIGHT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FILTER_ALARM), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FILTER_ELAPSED), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FILTER_LIFETIME), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_RUNTIME), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_MODE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_SPEED), RefreshType.REFRESH); + checkAirPurifierStates(callback); + } + + @Test + void testCommands() { + testHandlerCreation(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_MODE), new DecimalType(4)); + String patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"fanMode\":\"on\"}}", patch, "Fan Mode on"); + + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_SPEED), new PercentType(23)); + patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"motorState\":12}}", patch, "Fan Speed"); + + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_SPEED), new PercentType(100)); + patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"motorState\":50}}", patch, "Fan Speed"); + } + + @Test + void testDump() { + testHandlerCreation(); + assertEquals("unit-test", handler.getToken()); + } + + void checkAirPurifierStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:air-purifier:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:air-purifier:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:air-purifier:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State disableLightState = callback.getState("dirigera:air-purifier:test-device:disable-status-light"); + assertNotNull(disableLightState); + assertTrue(disableLightState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((disableLightState)), "Status Light Disabled"); + State childlockState = callback.getState("dirigera:air-purifier:test-device:child-lock"); + assertNotNull(childlockState); + assertTrue(childlockState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((childlockState)), "Child Lock enabled"); + + State filterAlarmState = callback.getState("dirigera:air-purifier:test-device:filter-alarm"); + assertNotNull(filterAlarmState); + assertTrue(filterAlarmState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((filterAlarmState)), "Filter Alarm"); + State filterElapsedState = callback.getState("dirigera:air-purifier:test-device:filter-elapsed"); + assertNotNull(filterElapsedState); + assertTrue(filterElapsedState instanceof QuantityType); + assertEquals(Units.MINUTE, ((QuantityType) filterElapsedState).getUnit()); + assertEquals(193540, ((QuantityType) filterElapsedState).intValue()); + State filterLifetimedState = callback.getState("dirigera:air-purifier:test-device:filter-lifetime"); + assertNotNull(filterLifetimedState); + assertTrue(filterLifetimedState instanceof QuantityType); + assertEquals(Units.MINUTE, ((QuantityType) filterLifetimedState).getUnit()); + assertEquals(259200, ((QuantityType) filterLifetimedState).intValue()); + + State motorRuntimeState = callback.getState("dirigera:air-purifier:test-device:fan-runtime"); + assertNotNull(motorRuntimeState); + assertTrue(motorRuntimeState instanceof QuantityType); + assertEquals(Units.MINUTE, ((QuantityType) motorRuntimeState).getUnit()); + assertEquals(472283, ((QuantityType) motorRuntimeState).intValue()); + State fanSpeedState = callback.getState("dirigera:air-purifier:test-device:fan-speed"); + assertNotNull(fanSpeedState); + assertTrue(fanSpeedState instanceof PercentType); + assertEquals(20, ((PercentType) fanSpeedState).intValue()); + State fanModeState = callback.getState("dirigera:air-purifier:test-device:fan-mode"); + assertNotNull(fanModeState); + assertTrue(fanModeState instanceof DecimalType); + assertEquals(0, ((DecimalType) fanModeState).intValue()); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/blind/TestBlindHandler.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/blind/TestBlindHandler.java new file mode 100644 index 00000000000..e5b563158b6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/blind/TestBlindHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.blind; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestBlindHandler} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestBlindHandler { + String deviceId = "eadfad54-9d23-4475-92b6-0ee3d6f8b481_1"; + ThingTypeUID thingTypeUID = THING_TYPE_BLIND; + + private static BlindHandler handler = mock(BlindHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof BlindHandler); + handler = (BlindHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testInitialization() { + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + checkBlindStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BLIND_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BLIND_LEVEL), RefreshType.REFRESH); + checkBlindStates(callback); + } + + @Test + void testCommands() { + testHandlerCreation(); + // DirigeraAPISimu api = (DirigeraAPISimu) ((DirigeraHandler) hubBridge.getHandler()).api(); + handler.handleCommand(new ChannelUID(thing.getUID(), "blind-level"), new PercentType(20)); + String patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"blindsTargetLevel\":20}}", patch, "Target Blind Level"); + } + + void checkBlindStates(CallbackMock callback) { + State decimalState = callback.getState("dirigera:blind:test-device:blind-state"); + assertNotNull(decimalState); + assertTrue(decimalState instanceof DecimalType); + assertEquals(0, ((DecimalType) decimalState).intValue(), "Blind State"); + + State currentLevelState = callback.getState("dirigera:blind:test-device:blind-level"); + assertNotNull(currentLevelState); + assertTrue(currentLevelState instanceof PercentType); + assertEquals(60, ((PercentType) currentLevelState).intValue(), "Blind Level"); + + State batteryState = callback.getState("dirigera:blind:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(92, ((QuantityType) batteryState).intValue(), "Battery level"); + + // test ota + State otaStatus = callback.getState("dirigera:blind:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:blind:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:blind:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestBlindController.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestBlindController.java new file mode 100644 index 00000000000..f751078b610 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestBlindController.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestBlindController} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestBlindController { + String deviceId = "9f96eced-7674-4b9f-bbf9-b9575d888638_1"; + ThingTypeUID thingTypeUID = THING_TYPE_BLIND_CONTROLLER; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof BlindsControllerHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + BlindsControllerHandler handler = new BlindsControllerHandler(thing, BLIND_CONTROLLER_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkStates(callback); + } + + void checkStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:blind-controller:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:blind-controller:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:blind-controller:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State batteryState = callback.getState("dirigera:blind-controller:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(65, ((QuantityType) batteryState).intValue(), "Battery level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestDoubleShortcutController.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestDoubleShortcutController.java new file mode 100644 index 00000000000..609a84d98e7 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestDoubleShortcutController.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.FileReader; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link TestDoubleShortcutController} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestDoubleShortcutController { + private final Logger logger = LoggerFactory.getLogger(TestDoubleShortcutController.class); + + String deviceId = "854bdf30-86b8-48f5-b070-16ff5ab12be4_1"; + ThingTypeUID thingTypeUID = THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER; + + private static DoubleShortcutControllerHandler handler = mock(DoubleShortcutControllerHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof DoubleShortcutControllerHandler); + handler = (DoubleShortcutControllerHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + } + + @Test + void testInitialization() { + testHandlerCreation(); + + handler.initialize(); + callback.waitForOnline(); + checkStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkStates(callback); + } + + void checkStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:double-shortcut:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:double-shortcut:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:double-shortcut:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State batteryState = callback.getState("dirigera:double-shortcut:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(89, ((QuantityType) batteryState).intValue(), "Battery level"); + } + + @Test + void testTriggers() { + testInitialization(); + String updateSequence = FileReader + .readFileInString("src/test/resources/websocket/scene-pressed/scene-trigger-sequence.json"); + JSONArray sequence = new JSONArray(updateSequence); + Map sceneMapping = handler.sceneMapping; + // adapt id of scene to match created one + String sceneId = sceneMapping.get(deviceId + ":" + CHANNEL_BUTTON_1 + ":singlePress"); + assertNotNull(sceneId); + JSONObject first = sequence.getJSONObject(0).getJSONObject("data"); + first.put(PROPERTY_DEVICE_ID, sceneId); + handler.handleUpdate(first); + JSONObject second = sequence.getJSONObject(1).getJSONObject("data"); + second.put(PROPERTY_DEVICE_ID, sceneId); + handler.handleUpdate(second); + assertEquals("SHORT_PRESSED", callback.triggerMap.get("dirigera:double-shortcut:test-device:button1"), + "Pressed trigger sent"); + } + + @Test + void testRemoval() { + logger.warn("####### REMOVAL START ############"); + DirigeraAPISimu.scenesAdded.clear(); + DirigeraAPISimu.scenesDeleted.clear(); + testTriggers(); + logger.warn("####### TRIGGER CALLED ############"); + handler.dispose(); + handler.handleRemoval(); + Collections.sort(DirigeraAPISimu.scenesAdded); + Collections.sort(DirigeraAPISimu.scenesDeleted); + assertEquals(6, DirigeraAPISimu.scenesAdded.size(), "Scenes added size"); + assertEquals(6, DirigeraAPISimu.scenesDeleted.size(), "Scenes removed size"); + assertEquals(DirigeraAPISimu.scenesAdded, DirigeraAPISimu.scenesDeleted, "Scenes added equals scnes removed"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestLightController.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestLightController.java new file mode 100644 index 00000000000..73d64f2a9f3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestLightController.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestLightController} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestLightController { + String deviceId = "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1"; + ThingTypeUID thingTypeUID = THING_TYPE_LIGHT_CONTROLLER; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof LightControllerHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + LightControllerHandler handler = new LightControllerHandler(thing, LIGHT_CONTROLLER_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkLightControllerStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkLightControllerStates(callback); + } + + void checkLightControllerStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:light-controller:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:light-controller:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:light-controller:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State batteryState = callback.getState("dirigera:light-controller:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(85, ((QuantityType) batteryState).intValue(), "Battery level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestShortcutController.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestShortcutController.java new file mode 100644 index 00000000000..b2303d4620c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestShortcutController.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestShortcutController} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestShortcutController { + String deviceId = "92dbcea1-3d7e-4d6a-a009-bdf3a1ae6691_1"; + ThingTypeUID thingTypeUID = THING_TYPE_SINGLE_SHORTCUT_CONTROLLER; + + private static ShortcutControllerHandler handler = mock(ShortcutControllerHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof ShortcutControllerHandler); + handler = (ShortcutControllerHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testInitialization() { + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + checkStates(callback); + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkStates(callback); + } + + void checkStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:single-shortcut:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:single-shortcut:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:single-shortcut:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State batteryState = callback.getState("dirigera:single-shortcut:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(100, ((QuantityType) batteryState).intValue(), "Battery level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestSoundController.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestSoundController.java new file mode 100644 index 00000000000..3610f7542d0 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/controller/TestSoundController.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.controller; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSoundController} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestSoundController { + String deviceId = "cec4c170-7846-4e22-b681-d8a912181cca_1"; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(THING_TYPE_SOUND_CONTROLLER)); + ThingImpl thing = new ThingImpl(THING_TYPE_SOUND_CONTROLLER, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof SoundControllerHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(THING_TYPE_SOUND_CONTROLLER, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + SoundControllerHandler handler = new SoundControllerHandler(thing, SOUND_CONTROLLER_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkStates(callback); + } + + void checkStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:sound-controller:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:sound-controller:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:sound-controller:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State batteryState = callback.getState("dirigera:sound-controller:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(90, ((QuantityType) batteryState).intValue(), "Battery level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestColorLight.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestColorLight.java new file mode 100644 index 00000000000..ee687bb05ea --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestColorLight.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.lights; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.light.ColorLightHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.events.EventPublisher; +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.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestColorLight} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestColorLight { + String deviceId = "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1"; + ThingTypeUID thingTypeUID = THING_TYPE_COLOR_LIGHT; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof ColorLightHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + ColorLightHandler handler = new ColorLightHandler(thing, COLOR_LIGHT_MAP, + new DirigeraStateDescriptionProvider(mock(EventPublisher.class), mock(ItemChannelLinkRegistry.class), + mock(ChannelTypeI18nLocalizationService.class))); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkColorLightStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_COLOR), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), RefreshType.REFRESH); + checkColorLightStates(callback); + } + + void checkColorLightStates(CallbackMock callback) { + State onOffState = callback.getState("dirigera:color-light:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((onOffState)), "Power State"); + State hsbState = callback.getState("dirigera:color-light:test-device:color"); + assertNotNull(hsbState); + assertTrue(hsbState instanceof HSBType); + assertEquals(119, ((HSBType) hsbState).getHue().intValue(), "Hue"); + assertEquals(70, ((HSBType) hsbState).getSaturation().intValue(), "Saturation"); + // brightness of device is 100 (previous state) but due to power OFF state it's reflected as 0 + assertEquals(0, ((HSBType) hsbState).getBrightness().intValue(), "Brightness"); + // assertEquals(100, ((HSBType) hsbState).getBrightness().intValue(), "Brightness"); + + // test ota + State otaStatus = callback.getState("dirigera:color-light:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:color-light:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:color-light:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + State startupState = callback.getState("dirigera:color-light:test-device:startup"); + assertNotNull(startupState); + assertTrue(startupState instanceof DecimalType); + assertEquals(1, ((DecimalType) startupState).intValue(), "Startup Behavior"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestDimmableLight.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestDimmableLight.java new file mode 100644 index 00000000000..257b68bdbd0 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestDimmableLight.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.lights; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.light.DimmableLightHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +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.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link TestDimmableLight} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestDimmableLight { + private final Logger logger = LoggerFactory.getLogger(TestDimmableLight.class); + String deviceId = "eb9a4367-9e23-4d37-9566-401a7ae7caf0_1"; + ThingTypeUID thingTypeUID = THING_TYPE_DIMMABLE_LIGHT; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof DimmableLightHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + DimmableLightHandler handler = new DimmableLightHandler(thing, TEMPERATURE_LIGHT_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + config.put("fadeTime", 0); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkLightStates(callback); + + callback.clear(); + logger.warn("Refresh"); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_BRIGHTNESS), RefreshType.REFRESH); + logger.warn("Check"); + checkLightStates(callback); + } + + void checkLightStates(CallbackMock callback) { + State onOffState = callback.getState("dirigera:dimmable-light:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((onOffState)), "Power State"); + + State brightnessState = callback.getState("dirigera:dimmable-light:test-device:brightness"); + assertNotNull(brightnessState); + assertTrue(brightnessState instanceof PercentType); + // device brightness is 100 but due to isOn=false brightness shall reflect 0 + assertEquals(0, ((PercentType) brightnessState).intValue(), "Brightness"); + + // test ota + State otaStatus = callback.getState("dirigera:dimmable-light:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:dimmable-light:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:dimmable-light:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestSwitchLight.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestSwitchLight.java new file mode 100644 index 00000000000..cff77fafd00 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestSwitchLight.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.lights; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.light.SwitchLightHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSwitchLight} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestSwitchLight { + String deviceId = "eb9a4367-9e23-4d37-9566-401a7ae7caf0_2"; + ThingTypeUID thingTypeUID = THING_TYPE_SWITCH_LIGHT; + + private static SwitchLightHandler handler = mock(SwitchLightHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof SwitchLightHandler); + handler = (SwitchLightHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testInitialization() { + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + chackStatus(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), RefreshType.REFRESH); + chackStatus(callback); + } + + @Test + void testCommands() { + DirigeraAPISimu.patchMap.clear(); + testHandlerCreation(); + // DirigeraAPISimu api = (DirigeraAPISimu) ((DirigeraHandler) hubBridge.getHandler()).api(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.ON); + DirigeraAPISimu.waitForPatch(); + String patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"isOn\":true}}", patch, "Power on"); + DirigeraAPISimu.patchMap.clear(); + + // simulate feedback + handler.handleUpdate(new JSONObject("{\"attributes\": {\"isOn\": true}}")); + + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.OFF); + DirigeraAPISimu.waitForPatch(); + patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"isOn\":false}}", patch, "Power off"); + } + + void chackStatus(CallbackMock callback) { + State onOffState = callback.getState("dirigera:switch-light:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertEquals(OnOffType.OFF, onOffState, "Power Status"); + + State startupState = callback.getState("dirigera:switch-light:test-device:startup"); + assertNotNull(startupState); + assertTrue(startupState instanceof DecimalType); + assertEquals(1, ((DecimalType) startupState).intValue(), "Startup State"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestTemperatureLight.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestTemperatureLight.java new file mode 100644 index 00000000000..383efa8d8a3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/lights/TestTemperatureLight.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.lights; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.light.TemperatureLightHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.library.types.DecimalType; +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.unit.Units; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestTemperatureLight} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestTemperatureLight { + + String deviceId = "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1"; + ThingTypeUID thingTypeUID = THING_TYPE_TEMPERATURE_LIGHT; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof TemperatureLightHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + TemperatureLightHandler handler = new TemperatureLightHandler(thing, TEMPERATURE_LIGHT_MAP, + new DirigeraStateDescriptionProvider(mock(EventPublisher.class), mock(ItemChannelLinkRegistry.class), + mock(ChannelTypeI18nLocalizationService.class))); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkLightStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_BRIGHTNESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), RefreshType.REFRESH); + checkLightStates(callback); + } + + void checkLightStates(CallbackMock callback) { + State onOffState = callback.getState("dirigera:temperature-light:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((onOffState)), "On"); + + State temperatureState = callback.getState("dirigera:temperature-light:test-device:color-temperature"); + assertNotNull(temperatureState); + assertTrue(temperatureState instanceof PercentType); + assertEquals(0, ((PercentType) temperatureState).intValue(), "Temperature"); + + State temperatureAbsState = callback.getState("dirigera:temperature-light:test-device:color-temperature-abs"); + assertNotNull(temperatureAbsState); + assertTrue(temperatureAbsState instanceof QuantityType); + assertEquals(4000, ((QuantityType) temperatureAbsState).intValue(), "Temperature Abs"); + + State brightnessState = callback.getState("dirigera:temperature-light:test-device:brightness"); + assertNotNull(brightnessState); + assertTrue(brightnessState instanceof PercentType); + assertEquals(49, ((PercentType) brightnessState).intValue(), "Brightness"); + + // test ota + State otaStatus = callback.getState("dirigera:temperature-light:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:temperature-light:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:temperature-light:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/repeater/TestRepeater.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/repeater/TestRepeater.java new file mode 100644 index 00000000000..988dfe53c91 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/repeater/TestRepeater.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.repeater; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestRepeater} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestRepeater { + + @Test + void testRepeater() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_REPEATER, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + RepeaterHandler handler = new RepeaterHandler(thing, REPEATER_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "044b63e7-999d-4caa-8a76-fb8cfd32b381_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkRepeaterStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + checkRepeaterStates(callback); + } + + void checkRepeaterStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:repeater:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:repeater:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:repeater:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/scene/TestScenes.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/scene/TestScenes.java new file mode 100644 index 00000000000..c1edc1bc3c6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/scene/TestScenes.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.scene; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.time.ZoneId; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSCene} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestScene { + + @Test + void testSceneHandler() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_SCENE, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + SceneHandler handler = new SceneHandler(thing, SCENE_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkSceneStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LAST_TRIGGER), RefreshType.REFRESH); + checkSceneStates(callback); + } + + void checkSceneStates(CallbackMock callback) { + State dateTimeState = callback.getState("dirigera:scene:test-device:last-trigger"); + assertNotNull(dateTimeState); + assertTrue(dateTimeState instanceof DateTimeType); + assertEquals("2024-10-16T02:21:15.977+0200", + ((DateTimeType) dateTimeState).toFullString(ZoneId.of("Europe/Berlin")), "Last trigger"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestAirQualityDevice.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestAirQualityDevice.java new file mode 100644 index 00000000000..d146426a0e6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestAirQualityDevice.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestAirQualityDevice} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestAirQualityDevice { + + @Test + void testAirQualityDeviceWithSimuBridge() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + testAirQuality(hubBridge); + } + + void testAirQuality(Bridge hubBridge) { + ThingImpl thing = new ThingImpl(THING_TYPE_AIR_QUALITY, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + AirQualityHandler handler = new AirQualityHandler(thing, AIR_QUALITY_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "f80cac12-65a4-47b4-9f68-a0456a349a43_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkAirQualityStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_TEMPERATURE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_HUMIDITY), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PARTICULATE_MATTER), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_VOC_INDEX), RefreshType.REFRESH); + checkAirQualityStates(callback); + } + + void checkAirQualityStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:air-quality:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:air-quality:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:air-quality:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State temperatureState = callback.getState("dirigera:air-quality:test-device:temperature"); + assertNotNull(temperatureState); + assertTrue(temperatureState instanceof QuantityType); + assertTrue(((QuantityType) temperatureState).getUnit().equals(SIUnits.CELSIUS)); + assertEquals(20, ((QuantityType) temperatureState).intValue(), "Temperature"); + + State humidityState = callback.getState("dirigera:air-quality:test-device:humidity"); + assertNotNull(humidityState); + assertTrue(humidityState instanceof QuantityType); + assertTrue(((QuantityType) humidityState).getUnit().equals(Units.PERCENT)); + assertEquals(76, ((QuantityType) humidityState).intValue(), "Hunidity"); + State ppmState = callback.getState("dirigera:air-quality:test-device:particulate-matter"); + assertNotNull(ppmState); + assertTrue(ppmState instanceof QuantityType); + assertTrue(((QuantityType) ppmState).getUnit().equals(Units.MICROGRAM_PER_CUBICMETRE)); + assertEquals(11, ((QuantityType) ppmState).intValue(), "ppm"); + State vocState = callback.getState("dirigera:air-quality:test-device:voc-index"); + assertNotNull(vocState); + assertTrue(vocState instanceof DecimalType); + assertEquals(100, ((DecimalType) vocState).intValue(), "VOC Index"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestContactDevice.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestContactDevice.java new file mode 100644 index 00000000000..357ff17dcfe --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestContactDevice.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestContactDevice} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestContactDevice { + @Test + void testContactDevice() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_CONTACT_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + ContactSensorHandler handler = new ContactSensorHandler(thing, CONTACT_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkContactStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CONTACT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkContactStates(callback); + } + + void checkContactStates(CallbackMock callback) { + State batteryState = callback.getState("dirigera:contact-sensor:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(84, ((QuantityType) batteryState).intValue(), "Battery level"); + State openCloseState = callback.getState("dirigera:contact-sensor:test-device:contact"); + assertNotNull(openCloseState); + assertTrue(openCloseState instanceof OpenClosedType); + assertTrue(OpenClosedType.CLOSED.equals((openCloseState)), "Closed"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestLightSensor.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestLightSensor.java new file mode 100644 index 00000000000..49e2793f590 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestLightSensor.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestLightSensor} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestLightSensor { + + void testLightSensorDevice() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_LIGHT_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + LightSensorHandler handler = new LightSensorHandler(thing, LIGHT_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + + State luxState = callback.getState("dirigera:light-sensor:test-device:illuminance"); + assertNotNull(luxState); + assertTrue(luxState instanceof QuantityType); + assertTrue(((QuantityType) luxState).getUnit().equals(Units.LUX)); + assertEquals(1, ((QuantityType) luxState).intValue(), "Lux level"); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ILLUMINANCE), RefreshType.REFRESH); + luxState = callback.getState("dirigera:light-sensor:test-device:illuminance"); + assertNotNull(luxState); + assertTrue(luxState instanceof QuantityType); + assertTrue(((QuantityType) luxState).getUnit().equals(Units.LUX)); + assertEquals(1, ((QuantityType) luxState).intValue(), "Lux level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionLightSensor.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionLightSensor.java new file mode 100644 index 00000000000..850d5583e24 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionLightSensor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +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.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestMotionLightSensor} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestMotionLightSensor { + + @Test + void testMotionLightSensorDevice() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_MOTION_LIGHT_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + MotionLightSensorHandler handler = new MotionLightSensorHandler(thing, MOTION_LIGHT_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "5ac5e131-44a4-4d75-be78-759a095d31fb_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkMotionLightStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ILLUMINANCE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_MOTION_DETECTION), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CUSTOM_NAME), RefreshType.REFRESH); + checkMotionLightStates(callback); + } + + void checkMotionLightStates(CallbackMock callback) { + State batteryState = callback.getState("dirigera:motion-light-sensor:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(85, ((QuantityType) batteryState).intValue(), "Battery level"); + State onOffState = callback.getState("dirigera:motion-light-sensor:test-device:motion"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((onOffState)), "Off"); + State luxState = callback.getState("dirigera:motion-light-sensor:test-device:illuminance"); + assertNotNull(luxState); + assertTrue(luxState instanceof QuantityType); + assertTrue(((QuantityType) luxState).getUnit().equals(Units.LUX)); + assertEquals(1, ((QuantityType) luxState).intValue(), "Lux level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionSensor.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionSensor.java new file mode 100644 index 00000000000..bf4449f998c --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestMotionSensor.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +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.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestMotionSensor} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestMotionSensor { + + @Test + void testMotionSensorDevice() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_MOTION_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + MotionSensorHandler handler = new MotionSensorHandler(thing, MOTION_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "ee61c57f-8efa-44f4-ba8a-d108ae054138_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkMotionStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_MOTION_DETECTION), RefreshType.REFRESH); + checkMotionStates(callback); + + // check commands + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ACTIVE_DURATION), new DecimalType(10)); + // assertEquals(String.format(MotionSensorHandler.DURATION_UPDATE, 10), + // DirigeraAPISimu.patchMap.get("ee61c57f-8efa-44f4-ba8a-d108ae054138_1"), "10 seconds"); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ACTIVE_DURATION), QuantityType.valueOf("3 min")); + // assertEquals(String.format(MotionSensorHandler.DURATION_UPDATE, 180), + // DirigeraAPISimu.patchMap.get("ee61c57f-8efa-44f4-ba8a-d108ae054138_1"), "10 seconds"); + } + + void checkMotionStates(CallbackMock callback) { + State batteryState = callback.getState("dirigera:motion-sensor:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(20, ((QuantityType) batteryState).intValue(), "Battery level"); + State onOffState = callback.getState("dirigera:motion-sensor:test-device:motion"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((onOffState)), "Motion detected"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestWaterSensor.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestWaterSensor.java new file mode 100644 index 00000000000..0ba7cc890c3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/sensor/TestWaterSensor.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.sensor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +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.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestWaterSensor} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestWaterSensor { + @Test + void testWaterSensor() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_WATER_SENSOR, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + WaterSensorHandler handler = new WaterSensorHandler(thing, WATER_SENSOR_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "9af826ad-a8ad-40bf-8aed-125300bccd20_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkWaterSensorStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LEAK_DETECTION), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_BATTERY_LEVEL), RefreshType.REFRESH); + checkWaterSensorStates(callback); + } + + void checkWaterSensorStates(CallbackMock callback) { + // test ota & battery + State otaStatus = callback.getState("dirigera:water-sensor:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:water-sensor:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:water-sensor:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State onOffState = callback.getState("dirigera:water-sensor:test-device:leak"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((onOffState)), "Off"); + State batteryState = callback.getState("dirigera:water-sensor:test-device:battery-level"); + assertNotNull(batteryState); + assertTrue(batteryState instanceof QuantityType); + assertTrue(((QuantityType) batteryState).getUnit().equals(Units.PERCENT)); + assertEquals(55, ((QuantityType) batteryState).intValue(), "Battery level"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/speaker/TestSpeaker.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/speaker/TestSpeaker.java new file mode 100644 index 00000000000..49aafcd3869 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handler/speaker/TestSpeaker.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handler.speaker; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSpeaker} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestSpeaker { + @Test + void testSpeakerDevice() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(THING_TYPE_SPEAKER, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + SpeakerHandler handler = new SpeakerHandler(thing, SPEAKER_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", "338bb721-35bb-4775-8cd0-ba70fc37ab10_1"); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkSpeakerStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CROSSFADE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_SHUFFLE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_MUTE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_REPEAT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_VOLUME), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_TRACK), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_IMAGE), RefreshType.REFRESH); + checkSpeakerStates(callback); + } + + void checkSpeakerStates(CallbackMock callback) { + State crossfadeState = callback.getState("dirigera:speaker:test-device:crossfade"); + assertNotNull(crossfadeState); + assertTrue(crossfadeState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((crossfadeState)), "Crossfade Off"); + State shuffleState = callback.getState("dirigera:speaker:test-device:shuffle"); + assertNotNull(shuffleState); + assertTrue(shuffleState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((shuffleState)), "Shuffle Off"); + State muteState = callback.getState("dirigera:speaker:test-device:mute"); + assertNotNull(muteState); + assertTrue(muteState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((muteState)), "Mute Off"); + State repeatState = callback.getState("dirigera:speaker:test-device:repeat"); + assertNotNull(repeatState); + assertTrue(repeatState instanceof DecimalType); + assertEquals(0, ((DecimalType) repeatState).intValue(), "Repeat setting"); + State volumeState = callback.getState("dirigera:speaker:test-device:volume"); + assertNotNull(volumeState); + assertTrue(volumeState instanceof PercentType); + assertEquals(16, ((PercentType) volumeState).intValue(), "Volume"); + State trackState = callback.getState("dirigera:speaker:test-device:media-title"); + assertNotNull(trackState); + assertTrue(trackState instanceof StringType); + assertTrue(((StringType) trackState).toFullString().startsWith("The Anjunadeep Edition")); + State pictureState = callback.getState("dirigera:speaker:test-device:image"); + assertNotNull(pictureState); + assertTrue(pictureState instanceof RawType); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestPowerPlug.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestPowerPlug.java new file mode 100644 index 00000000000..e7db115a206 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestPowerPlug.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handlerplug; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.plug.PowerPlugHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +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.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestPowerPlug} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestPowerPlug { + String deviceId = "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1"; + ThingTypeUID thingTypeUID = THING_TYPE_POWER_PLUG; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof PowerPlugHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + PowerPlugHandler handler = new PowerPlugHandler(thing, SMART_PLUG_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkPowerPlugStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CHILD_LOCK), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_DISABLE_STATUS_LIGHT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), RefreshType.REFRESH); + checkPowerPlugStates(callback); + } + + void checkPowerPlugStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:power-plug:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:power-plug:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:power-plug:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State disableLightState = callback.getState("dirigera:power-plug:test-device:disable-status-light"); + assertNotNull(disableLightState); + assertTrue(disableLightState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((disableLightState)), "Disable Light On"); + State childlockState = callback.getState("dirigera:power-plug:test-device:child-lock"); + assertNotNull(childlockState); + assertTrue(childlockState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((childlockState)), "Child Lock Off"); + State onOffState = callback.getState("dirigera:power-plug:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((onOffState)), "Power Off"); + State startupState = callback.getState("dirigera:power-plug:test-device:startup"); + assertNotNull(startupState); + assertTrue(startupState instanceof DecimalType); + assertEquals(0, ((DecimalType) startupState).intValue(), "Startup Behavior"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSimplePlug.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSimplePlug.java new file mode 100644 index 00000000000..5ce5d1b92ba --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSimplePlug.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handlerplug; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.plug.SimplePlugHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock; +import org.openhab.core.library.types.DecimalType; +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.storage.StorageService; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSimplePlug} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestSimplePlug { + String deviceId = "6379a590-dc0a-47b5-b35b-7b46dfefd282_1"; + ThingTypeUID thingTypeUID = THING_TYPE_SIMPLE_PLUG; + + @Test + void testHandlerCreation() { + HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class)); + assertTrue(hfm.supportsThingType(thingTypeUID)); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + ThingHandler th = hfm.createHandler(thing); + assertNotNull(th); + assertTrue(th instanceof SimplePlugHandler); + } + + @Test + void testInitialization() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingImpl thing = new ThingImpl(thingTypeUID, "test-device"); + thing.setBridgeUID(hubBridge.getBridgeUID()); + SimplePlugHandler handler = new SimplePlugHandler(thing, SMART_PLUG_MAP); + CallbackMock callback = new CallbackMock(); + callback.setBridge(hubBridge); + handler.setCallback(callback); + + // set the right id + Map config = new HashMap<>(); + config.put("id", deviceId); + handler.handleConfigurationUpdate(config); + + handler.initialize(); + callback.waitForOnline(); + checkPowerPlugStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CHILD_LOCK), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_DISABLE_STATUS_LIGHT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), RefreshType.REFRESH); + checkPowerPlugStates(callback); + } + + void checkPowerPlugStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:simple-plug:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:simple-plug:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:simple-plug:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State disableLightState = callback.getState("dirigera:simple-plug:test-device:disable-light"); + assertNull(disableLightState); + State childlockState = callback.getState("dirigera:simple-plug:test-device:child-lock"); + assertNull(childlockState); + State onOffState = callback.getState("dirigera:simple-plug:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((onOffState)), "Power On"); + State startupState = callback.getState("dirigera:simple-plug:test-device:startup"); + assertNotNull(startupState); + assertTrue(startupState instanceof DecimalType); + assertEquals(0, ((DecimalType) startupState).intValue(), "Startup Behavior"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSmartPlug.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSmartPlug.java new file mode 100644 index 00000000000..cd153b02987 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/handlerplug/TestSmartPlug.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.handlerplug; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.plug.SmartPlugHandler; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +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.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; + +/** + * {@link TestSmartPlug} Tests device handler creation, initializing and refresh of channels + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestSmartPlug { + String deviceId = "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1"; + ThingTypeUID thingTypeUID = THING_TYPE_SMART_PLUG; + + private static SmartPlugHandler handler = mock(SmartPlugHandler.class); + private static CallbackMock callback = mock(CallbackMock.class); + private static Thing thing = mock(Thing.class); + + @Test + void testHandlerCreation() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json", + false, List.of()); + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId); + assertTrue(factoryHandler instanceof SmartPlugHandler); + handler = (SmartPlugHandler) factoryHandler; + thing = handler.getThing(); + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + callback = (CallbackMock) proxyCallback; + handler.initialize(); + callback.waitForOnline(); + } + + @Test + void testInitialization() { + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + checkSmartPlugStates(callback); + + callback.clear(); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_PROGRESS), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CHILD_LOCK), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_DISABLE_STATUS_LIGHT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_CURRENT), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POTENTIAL), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_STARTUP_BEHAVIOR), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTAL), RefreshType.REFRESH); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET), RefreshType.REFRESH); + checkSmartPlugStates(callback); + } + + @Test + void testCommands() { + DirigeraAPISimu.patchMap.clear(); + testHandlerCreation(); + assertNotNull(handler); + assertNotNull(thing); + assertNotNull(callback); + handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET_DATE), new DateTimeType()); + DirigeraAPISimu.waitForPatch(); + String patch = DirigeraAPISimu.patchMap.get(deviceId); + assertNotNull(patch); + assertEquals("{\"attributes\":{\"energyConsumedAtLastReset\":0}}", patch, "Reset Power"); + DirigeraAPISimu.patchMap.clear(); + } + + void checkSmartPlugStates(CallbackMock callback) { + State otaStatus = callback.getState("dirigera:smart-plug:test-device:ota-status"); + assertNotNull(otaStatus); + assertTrue(otaStatus instanceof DecimalType); + assertEquals(0, ((DecimalType) otaStatus).intValue(), "OTA Status"); + State otaState = callback.getState("dirigera:smart-plug:test-device:ota-state"); + assertNotNull(otaState); + assertTrue(otaState instanceof DecimalType); + assertEquals(0, ((DecimalType) otaState).intValue(), "OTA State"); + State otaProgess = callback.getState("dirigera:smart-plug:test-device:ota-progress"); + assertNotNull(otaProgess); + assertTrue(otaProgess instanceof QuantityType); + assertTrue(((QuantityType) otaProgess).getUnit().equals(Units.PERCENT)); + assertEquals(0, ((QuantityType) otaProgess).intValue(), "OTA Progress"); + + State disableLightState = callback.getState("dirigera:smart-plug:test-device:disable-status-light"); + assertNotNull(disableLightState); + assertTrue(disableLightState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((disableLightState)), "Disable Light Off"); + State childlockState = callback.getState("dirigera:smart-plug:test-device:child-lock"); + assertNotNull(childlockState); + assertTrue(childlockState instanceof OnOffType); + assertTrue(OnOffType.OFF.equals((childlockState)), "Child Lock Off"); + State onOffState = callback.getState("dirigera:smart-plug:test-device:power"); + assertNotNull(onOffState); + assertTrue(onOffState instanceof OnOffType); + assertTrue(OnOffType.ON.equals((onOffState)), "Power On"); + + // Measurement channels + State ampereState = callback.getState("dirigera:smart-plug:test-device:electric-current"); + assertNotNull(ampereState); + assertTrue(ampereState instanceof QuantityType); + assertTrue(((QuantityType) ampereState).getUnit().equals(Units.AMPERE)); + assertEquals(0, ((QuantityType) ampereState).intValue(), "Ampere"); + State voltState = callback.getState("dirigera:smart-plug:test-device:electric-voltage"); + assertNotNull(voltState); + assertTrue(voltState instanceof QuantityType); + assertTrue(((QuantityType) voltState).getUnit().equals(Units.VOLT)); + assertEquals(236, ((QuantityType) voltState).intValue(), "Volt"); + State powerState = callback.getState("dirigera:smart-plug:test-device:electric-power"); + assertNotNull(powerState); + assertTrue(powerState instanceof QuantityType); + assertTrue(((QuantityType) powerState).getUnit().equals(Units.WATT)); + assertEquals(0, ((QuantityType) powerState).intValue(), "Watt"); + State energyTotalState = callback.getState("dirigera:smart-plug:test-device:energy-total"); + assertNotNull(energyTotalState); + assertTrue(energyTotalState instanceof QuantityType); + assertTrue(((QuantityType) energyTotalState).getUnit().equals(Units.KILOWATT_HOUR)); + assertEquals(0, ((QuantityType) energyTotalState).intValue(), "Watt"); + State energyReset = callback.getState("dirigera:smart-plug:test-device:energy-reset"); + assertNotNull(energyReset); + assertTrue(energyReset instanceof QuantityType); + assertTrue(((QuantityType) energyReset).getUnit().equals(Units.KILOWATT_HOUR)); + assertEquals(0, ((QuantityType) energyReset).intValue(), "Watt"); + + State startupState = callback.getState("dirigera:smart-plug:test-device:startup"); + assertNotNull(startupState); + assertTrue(startupState instanceof DecimalType); + assertEquals(0, ((DecimalType) startupState).intValue(), "Startup Behavior"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/CallbackMock.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/CallbackMock.java new file mode 100644 index 00000000000..02587622a66 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/CallbackMock.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.TimeSeries; + +/** + * The {@link CallbackMock} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class CallbackMock implements ThingHandlerCallback { + private @Nullable Bridge bridge; + private ThingStatusInfo status = ThingStatusInfoBuilder.create(ThingStatus.OFFLINE).build(); + public Map stateMap = new HashMap<>(); + public Map triggerMap = new HashMap<>(); + + public void clear() { + stateMap.clear(); + } + + public @Nullable State getState(String channel) { + return stateMap.get(channel); + } + + @Override + public void stateUpdated(ChannelUID channelUID, State state) { + stateMap.put(channelUID.getAsString(), state); + } + + @Override + public void postCommand(ChannelUID channelUID, Command command) { + } + + @Override + public void sendTimeSeries(ChannelUID channelUID, TimeSeries timeSeries) { + } + + @Override + public void statusUpdated(Thing thing, ThingStatusInfo thingStatus) { + synchronized (this) { + status = thingStatus; + this.notifyAll(); + } + } + + public ThingStatusInfo getStatus() { + return status; + } + + public void waitForOnline() { + synchronized (this) { + Instant start = Instant.now(); + Instant check = Instant.now(); + while (!ThingStatus.ONLINE.equals(status.getStatus()) && Duration.between(start, check).getSeconds() < 10) { + try { + this.wait(1000); + } catch (InterruptedException e) { + fail("Interruppted waiting for ONLINE"); + } + check = Instant.now(); + } + } + // if method is exited without reaching ONLINE e.g. through timeout fail + if (!ThingStatus.ONLINE.equals(status.getStatus())) { + fail("waitForOnline just reached status " + status); + } + } + + @Override + public void thingUpdated(Thing thing) { + } + + @Override + public void validateConfigurationParameters(Thing thing, Map configurationParameters) { + } + + @Override + public void validateConfigurationParameters(Channel channel, Map configurationParameters) { + } + + @Override + public @Nullable ConfigDescription getConfigDescription(ChannelTypeUID channelTypeUID) { + return null; + } + + @Override + public @Nullable ConfigDescription getConfigDescription(ThingTypeUID thingTypeUID) { + return null; + } + + @Override + public void configurationUpdated(Thing thing) { + } + + @Override + public void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration) { + } + + @Override + public void channelTriggered(Thing thing, ChannelUID channelUID, String event) { + triggerMap.put(channelUID.getAsString(), event); + } + + @Override + public ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelTypeUID channelTypeUID) { + return ChannelBuilder.create(new ChannelUID("handler:test")); + } + + @Override + public ChannelBuilder editChannel(Thing thing, ChannelUID channelUID) { + return ChannelBuilder.create(new ChannelUID("handler:test")); + } + + @Override + public List createChannelBuilders(ChannelGroupUID channelGroupUID, + ChannelGroupTypeUID channelGroupTypeUID) { + return List.of(); + } + + @Override + public boolean isChannelLinked(ChannelUID channelUID) { + return false; + } + + @Override + public @Nullable Bridge getBridge(ThingUID bridgeUID) { + return bridge; + } + + public void setBridge(Bridge bridge) { + this.bridge = bridge; + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DicoveryServiceMock.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DicoveryServiceMock.java new file mode 100644 index 00000000000..873b56d0c8b --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DicoveryServiceMock.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; + +/** + * The {@link DicoveryServiceMock} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DicoveryServiceMock extends DirigeraDiscoveryService { + public Map discoveries = new HashMap<>(); + public Map deletes = new HashMap<>(); + + @Override + public void deviceDiscovered(final DiscoveryResult discoveryResult) { + synchronized (this) { + // logger.warn("Discovery thingDiscovered {}", discoveryResult); + String id = discoveryResult.getThingUID().getId(); + discoveries.put(id, discoveryResult); + this.notifyAll(); + } + } + + @Override + public void deviceRemoved(final DiscoveryResult discoveryResult) { + // logger.warn("Discovery thingRemoved {}", discoveryResult); + String id = discoveryResult.getThingUID().getId(); + DiscoveryResult remover = discoveries.remove(id); + // assertNotNull(remover); + if (remover != null) { + deletes.put(id, remover); + } + } + + public void waitForDetection() { + synchronized (this) { + if (discoveries.isEmpty()) { + try { + wait(5000); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraAPISimu.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraAPISimu.java new file mode 100644 index 00000000000..84124766033 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraAPISimu.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.openhab.binding.dirigera.internal.Constants.PROPERTY_DEVICE_ID; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openhab.binding.dirigera.internal.FileReader; +import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.interfaces.Model; +import org.openhab.core.library.types.RawType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link DirigeraAPISimu} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DirigeraAPISimu implements DirigeraAPI { + private static JSONObject model = new JSONObject(); + + public static String fileName = "src/test/resources/home/home.json"; + public static Map patchMap = new HashMap<>(); + public static List scenesAdded = new ArrayList<>(); + public static List scenesDeleted = new ArrayList<>(); + + public DirigeraAPISimu(HttpClient client, Gateway gateway) { + } + + @Override + public JSONObject readHome() { + String modelString = FileReader.readFileInString(fileName); + model = new JSONObject(modelString); + return model; + } + + @Override + public JSONObject readDevice(String deviceId) { + JSONObject returnObject = new JSONObject(); + if (model.has("devices")) { + JSONArray devices = model.getJSONArray("devices"); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + if (deviceId.equals(entry.get(PROPERTY_DEVICE_ID))) { + return entry; + } + } + } + return returnObject; + } + + @Override + public void triggerScene(String sceneId, String trigger) { + } + + @Override + public int sendAttributes(String id, JSONObject attributes) { + JSONObject data = new JSONObject(); + data.put(Model.ATTRIBUTES, attributes); + return sendPatch(id, data); + } + + @Override + public int sendPatch(String id, JSONObject attributes) { + synchronized (patchMap) { + patchMap.put(id, attributes.toString()); + patchMap.notifyAll(); + } + return 200; + } + + @Override + public State getImage(String imageURL) { + Path path = Paths.get("src/test/resources/coverart/sonos-radio-cocktail-hour.avif"); + try { + byte[] imageData = Files.readAllBytes(path); + return new RawType(imageData, RawType.DEFAULT_MIME_TYPE); + } catch (IOException e) { + fail("getting image"); + } + return UnDefType.UNDEF; + } + + @Override + public JSONObject readScene(String sceneId) { + JSONObject returnObject = new JSONObject(); + if (model.has("devices")) { + JSONArray devices = model.getJSONArray("scenes"); + Iterator entries = devices.iterator(); + while (entries.hasNext()) { + JSONObject entry = (JSONObject) entries.next(); + if (sceneId.equals(entry.get(PROPERTY_DEVICE_ID))) { + return entry; + } + } + } + return returnObject; + } + + @Override + public String createScene(String uuid, String clickPattern, String controllerId) { + scenesAdded.add(uuid); + return uuid; + } + + @Override + public void deleteScene(String uuid) { + scenesDeleted.add(uuid); + } + + public static void waitForPatch() { + synchronized (patchMap) { + if (patchMap.isEmpty()) { + try { + patchMap.wait(5000); + } catch (InterruptedException e) { + fail(); + } + } + } + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraHandlerManipulator.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraHandlerManipulator.java new file mode 100644 index 00000000000..de932221716 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/DirigeraHandlerManipulator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.mockito.Mockito.mock; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.dirigera.internal.DirigeraCommandProvider; +import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService; +import org.openhab.binding.dirigera.internal.handler.DirigeraHandler; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.storage.Storage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.osgi.framework.BundleContext; + +/** + * The {@link DirigeraHandlerManipulator} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class DirigeraHandlerManipulator extends DirigeraHandler { + + public DirigeraHandlerManipulator(Bridge bridge, HttpClient insecureClient, Storage bindingStorage, + DirigeraDiscoveryService discoveryService) { + super(bridge, insecureClient, bindingStorage, discoveryService, mock(LocationProvider.class), + mock(DirigeraCommandProvider.class), mock(BundleContext.class)); + // Changes the class of the provider. During initialize this class will be used for instantiation + super.apiProvider = DirigeraAPISimu.class; + } + + /** + * for unit testing + */ + @Override + public @Nullable ThingHandlerCallback getCallback() { + return super.getCallback(); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/HandlerFactoryMock.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/HandlerFactoryMock.java new file mode 100644 index 00000000000..a66a36df11d --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/HandlerFactoryMock.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.mockito.Mockito.mock; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.dirigera.internal.DirigeraCommandProvider; +import org.openhab.binding.dirigera.internal.DirigeraHandlerFactory; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.storage.StorageService; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; + +/** + * The {@link HandlerFactoryMock} basic DeviceHandler for all devices + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class HandlerFactoryMock extends DirigeraHandlerFactory { + public HandlerFactoryMock(StorageService storageService) { + super(storageService, new DicoveryServiceMock(), mock(LocationProvider.class), + mock(DirigeraCommandProvider.class), new DirigeraStateDescriptionProvider(mock(EventPublisher.class), + mock(ItemChannelLinkRegistry.class), mock(ChannelTypeI18nLocalizationService.class))); + } + + @Override + public @Nullable ThingHandler createHandler(Thing thing) { + return super.createHandler(thing); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/TemperatureLightHandlerMock.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/TemperatureLightHandlerMock.java new file mode 100644 index 00000000000..6311debea57 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/mock/TemperatureLightHandlerMock.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.mock; + +import static org.mockito.Mockito.mock; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider; +import org.openhab.binding.dirigera.internal.handler.light.TemperatureLightHandler; +import org.openhab.core.thing.Thing; + +/** + * {@link TemperatureLightHandlerMock} mock for accessing protected methods + * + * @author Bernd Weymann - Initial contribution + */ +@NonNullByDefault +public class TemperatureLightHandlerMock extends TemperatureLightHandler { + public TemperatureLightHandlerMock() { + super(mock(Thing.class), Map.of(), mock(DirigeraStateDescriptionProvider.class)); + } + + @Override + public long getKelvin(int percent) { + return super.getKelvin(percent); + } + + @Override + public int getPercent(long kelvin) { + return super.getPercent(kelvin); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestColorModel.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestColorModel.java new file mode 100644 index 00000000000..f8a69760198 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestColorModel.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.model; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.mock.TemperatureLightHandlerMock; +import org.openhab.core.library.types.HSBType; + +/** + * {@link TestColorModel} some basic tests + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestColorModel { + + @Test + void hsbCloseTo() { + // test with 2% means max distance between 2 values is 360 * 0.02 = 7.2 + HSBType first = new HSBType("50, 100, 100"); + HSBType second = new HSBType("57.1, 100, 100"); + assertTrue(ColorModel.closeTo(first, second, 0.02), "Hue is close"); + assertTrue(ColorModel.closeTo(second, first, 0.02), "Hue is close"); + second = new HSBType("57.3, 100, 100"); + assertFalse(ColorModel.closeTo(first, second, 0.02), "Hue bit over boundary"); + assertFalse(ColorModel.closeTo(second, first, 0.02), "Hue bit over boundary"); + + // test boundary at min and max hue values + first = new HSBType("359, 100, 100"); + second = new HSBType("6.1, 100, 100"); + assertTrue(ColorModel.closeTo(first, second, 0.02), "Hue is close"); + assertTrue(ColorModel.closeTo(second, first, 0.02), "Hue is close"); + second = new HSBType("6.3, 100, 100"); + assertFalse(ColorModel.closeTo(first, second, 0.02), "Hue bit over boundary"); + assertFalse(ColorModel.closeTo(second, first, 0.02), "Hue bit over boundary"); + + // test saturation + first = new HSBType("359, 50, 100"); + second = new HSBType("359, 51.9, 100"); + assertTrue(ColorModel.closeTo(first, second, 0.02), "Saturation is close"); + assertTrue(ColorModel.closeTo(second, first, 0.02), "Saturation is close"); + second = new HSBType("359, 52.1, 100"); + assertFalse(ColorModel.closeTo(first, second, 0.02), "Saturation bit over boundary"); + assertFalse(ColorModel.closeTo(second, first, 0.02), "Saturation bit over boundary"); + } + + @Test + void testKelvinToHSB() { + TemperatureLightHandlerMock handler = new TemperatureLightHandlerMock(); + HSBType hsb = ColorModel.kelvin2Hsb(6200); + long kelvinCalculated = ColorModel.hsb2Kelvin(hsb); + assertEquals(0, handler.getPercent(kelvinCalculated), "Below boundary"); + + hsb = ColorModel.kelvin2Hsb(1000); + kelvinCalculated = ColorModel.hsb2Kelvin(hsb); + assertEquals(100, handler.getPercent(kelvinCalculated), "Above boundary"); + + hsb = ColorModel.kelvin2Hsb(2200 + 900); + kelvinCalculated = ColorModel.hsb2Kelvin(hsb); + assertEquals(50, handler.getPercent(kelvinCalculated), 2, "Middle ~50% temperature"); + + for (int kelvinInput = 2000; kelvinInput < 6501; kelvinInput++) { + hsb = ColorModel.kelvin2Hsb(kelvinInput); + kelvinCalculated = ColorModel.hsb2Kelvin(hsb); + // assure all values has max difference of 50 + assertEquals(kelvinInput, kelvinCalculated, 50, "Diff " + (kelvinInput - kelvinCalculated)); + } + + // test if kelvin is matching with IKEA TRADFRI bulb values + hsb = ColorModel.kelvin2Hsb(2200); + assertEquals(29.7, hsb.getHue().doubleValue(), 0.1, "Hue for 2200 K"); + assertEquals(84.7, hsb.getSaturation().doubleValue(), 0.1, "Saturation for 2200 K"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestModel.java b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestModel.java new file mode 100644 index 00000000000..fc549c4918f --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/java/org/openhab/binding/dirigera/internal/model/TestModel.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2010-2025 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.dirigera.internal.model; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.dirigera.internal.Constants.*; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.dirigera.internal.FileReader; +import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider; +import org.openhab.binding.dirigera.internal.handler.DirigeraHandler; +import org.openhab.binding.dirigera.internal.handler.sensor.WaterSensorHandler; +import org.openhab.binding.dirigera.internal.interfaces.Gateway; +import org.openhab.binding.dirigera.internal.mock.CallbackMock; +import org.openhab.binding.dirigera.internal.mock.DicoveryServiceMock; +import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; + +/** + * {@link TestModel} some basic tests + * + * @author Bernd Weymann - Initial Contribution + */ +@NonNullByDefault +class TestModel { + + @Test + void testCustomName() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + // test device with given custom name + assertEquals("Loft Floor Lamp", gateway.model().getCustonNameFor("891790db-8c17-483a-a1a6-c85bffd3a373_1"), + "Floor Lamp name"); + // test device without custom name - take model name + assertEquals("VALLHORN Wireless Motion Sensor", + gateway.model().getCustonNameFor("5ac5e131-44a4-4d75-be78-759a095d31fb_3"), "Motion Sensor name"); + // test device without custom name and no model name + assertEquals("light", gateway.model().getCustonNameFor("c27faa27-4c18-464f-81a0-a31ce57d83d5_1"), "Lamp"); + } + + @Test + void testMotionSensors() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + // VALLHORN + String vallhornId = "5ac5e131-44a4-4d75-be78-759a095d31fb_1"; + ThingTypeUID motionLightUID = gateway.model().identifyDeviceFromModel(vallhornId); + assertEquals(THING_TYPE_MOTION_LIGHT_SENSOR, motionLightUID, "VALLHORN TTUID"); + Map relationsMap = gateway.model().getRelations("5ac5e131-44a4-4d75-be78-759a095d31fb"); + assertEquals(2, relationsMap.size(), "Relations"); + assertTrue(relationsMap.containsKey("5ac5e131-44a4-4d75-be78-759a095d31fb_1"), "Motion Sensor"); + assertEquals("motionSensor", relationsMap.get("5ac5e131-44a4-4d75-be78-759a095d31fb_1"), "Motion Sensor"); + assertTrue(relationsMap.containsKey("5ac5e131-44a4-4d75-be78-759a095d31fb_3"), "Light Sensor"); + assertEquals("lightSensor", relationsMap.get("5ac5e131-44a4-4d75-be78-759a095d31fb_3"), "Light Sensor"); + + // TRADFRI + String tradfriId = "ee61c57f-8efa-44f4-ba8a-d108ae054138_1"; + ThingTypeUID motionUID = gateway.model().identifyDeviceFromModel(tradfriId); + assertEquals(THING_TYPE_MOTION_SENSOR, motionUID, "TRADFRI TTUID"); + relationsMap = gateway.model().getRelations(tradfriId); + assertEquals(0, relationsMap.size(), "Twins"); + } + + @Test + void testPlugs() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + // VALLHORN + String tretaktId = "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1"; + ThingTypeUID plugTTUID = gateway.model().identifyDeviceFromModel(tretaktId); + assertEquals(THING_TYPE_POWER_PLUG, plugTTUID, "TRETAKT TTUID"); + + // TRADFRI + String inspelningId = "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1"; + ThingTypeUID motionUID = gateway.model().identifyDeviceFromModel(inspelningId); + assertEquals(THING_TYPE_SMART_PLUG, motionUID, "INSPELNING TTUID"); + } + + @Test + void testSceneName() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + String lightSceneId = "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb"; + ThingTypeUID sceneTTUID = gateway.model().identifyDeviceFromModel(lightSceneId); + assertEquals(THING_TYPE_SCENE, sceneTTUID, "Scene TTUID"); + } + + @Test + void testInitialDiscovery() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/home/home.json", true, + List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(25, discovery.discoveries.size(), "Initial discoveries"); + } + + @Test + void testDiscoveryDisabled() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/home/home.json", false, + List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertTrue(discovery.discoveries.isEmpty(), "Discovery disabled"); + } + + @Test + void testKnownDevices() { + String knownDevice = "9af826ad-a8ad-40bf-8aed-125300bccd20_1"; + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/home/home.json", true, + List.of(knownDevice)); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(24, discovery.discoveries.size(), "Initial discoveries"); + assertFalse(discovery.discoveries.containsKey(knownDevice)); + } + + @Test + void testDiscoveryAfterHandlerRemoval() { + Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/home/home-one-device.json", + true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(1, discovery.discoveries.size(), "Initial discoveries"); + + ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(THING_TYPE_WATER_SENSOR, hubBridge, + "9af826ad-a8ad-40bf-8aed-125300bccd20_1"); + assertTrue(factoryHandler instanceof WaterSensorHandler); + WaterSensorHandler handler = (WaterSensorHandler) factoryHandler; + ThingHandlerCallback proxyCallback = handler.getCallback(); + assertNotNull(proxyCallback); + assertTrue(proxyCallback instanceof CallbackMock); + CallbackMock callback = (CallbackMock) proxyCallback; + callback.waitForOnline(); + + DirigeraHandler.detectionTimeSeonds = 0; + discovery.discoveries.clear(); + assertEquals(0, discovery.discoveries.size(), "Cleanup after handler creation"); + handler.dispose(); + handler.handleRemoval(); + discovery.waitForDetection(); + assertEquals(1, discovery.discoveries.size(), "After removal new discovery result shall be present "); + } + + @Test + void testResolvedRelations() { + Bridge hubBridge = DirigeraBridgeProvider + .prepareSimuBridge("src/test/resources/websocket/device-added/home-before.json", true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + List all = gateway.model().getAllDeviceIds(); + List resolved = gateway.model().getResolvedDeviceList(); + + all.removeAll(resolved); + // 2 resolved devices + assertEquals(2, all.size(), "2 devices resolved"); + } + + @Test + void testDeviceAdded() { + Bridge hubBridge = DirigeraBridgeProvider + .prepareSimuBridge("src/test/resources/websocket/device-added/home-before.json", true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(25, discovery.discoveries.size(), "Initial discoveries"); + + // Prepare update message + String update = FileReader.readFileInString("src/test/resources/websocket/device-added/device-added.json"); + // prepare mock + DirigeraAPISimu.fileName = "src/test/resources/websocket/device-added/home-after.json"; + try { + gateway.websocketUpdate(update); + // give the gateway some time to handle the message + Thread.sleep(500); + } catch (InterruptedException e) { + fail(); + } + assertEquals(25 + 1, discovery.discoveries.size(), "One more discovery"); + } + + @Test + void testDeviceRemoved() { + Bridge hubBridge = DirigeraBridgeProvider + .prepareSimuBridge("src/test/resources/websocket/device-removed/home-before.json", true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(26, discovery.discoveries.size(), "Initial discoveries"); + + // Prepare update message + String update = FileReader.readFileInString("src/test/resources/websocket/device-removed/device-removed.json"); + // prepare mock + DirigeraAPISimu.fileName = "src/test/resources/websocket/device-removed/home-after.json"; + try { + gateway.websocketUpdate(update); + // give the gateway some time to handle the message + Thread.sleep(500); + } catch (InterruptedException e) { + fail(); + } + assertEquals(26 - 1, discovery.discoveries.size(), "One less discovery"); + assertEquals(1, discovery.deletes.size(), "One deletion"); + } + + @Test + void testSceneCreated() { + Bridge hubBridge = DirigeraBridgeProvider + .prepareSimuBridge("src/test/resources/websocket/scene-created/home-before.json", true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(24, discovery.discoveries.size(), "Initial discoveries"); + + // Prepare update message + String update = FileReader.readFileInString("src/test/resources/websocket/scene-created/scene-created.json"); + // prepare mock + DirigeraAPISimu.fileName = "src/test/resources/websocket/scene-created/home-after.json"; + try { + gateway.websocketUpdate(update); + // give the gateway some time to handle the message + Thread.sleep(500); + } catch (InterruptedException e) { + fail(); + } + assertEquals(24 + 1, discovery.discoveries.size(), "One more discovery"); + } + + @Test + void testSceneDeleted() { + Bridge hubBridge = DirigeraBridgeProvider + .prepareSimuBridge("src/test/resources/websocket/scene-deleted/home-before.json", true, List.of()); + Gateway gateway = (Gateway) hubBridge.getHandler(); + assertNotNull(gateway); + + DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery(); + assertEquals(25, discovery.discoveries.size(), "Initial discoveries"); + + // Prepare update message + String update = FileReader.readFileInString("src/test/resources/websocket/scene-deleted/scene-deleted.json"); + // prepare mock + DirigeraAPISimu.fileName = "src/test/resources/websocket/scene-deleted/home-after.json"; + try { + gateway.websocketUpdate(update); + // give the gateway some time to handle the message + Thread.sleep(500); + } catch (InterruptedException e) { + fail(); + } + assertEquals(25 - 1, discovery.discoveries.size(), "One more discovery"); + assertEquals(1, discovery.deletes.size(), "One deletion"); + } +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/coverart/sonos-radio-cocktail-hour.avif b/bundles/org.openhab.binding.dirigera/src/test/resources/coverart/sonos-radio-cocktail-hour.avif new file mode 100644 index 00000000000..10d2afd2f8b Binary files /dev/null and b/bundles/org.openhab.binding.dirigera/src/test/resources/coverart/sonos-radio-cocktail-hour.avif differ diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/home-all-devices.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/home-all-devices.json new file mode 100644 index 00000000000..894547ea910 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/home-all-devices.json @@ -0,0 +1,2090 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_1", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "Clicky", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "batteryPercentage": 89, + "switchLabel": "Shortcut 1", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_2", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "switchLabel": "Shortcut 2", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "eb9a4367-9e23-4d37-9566-401a7ae7caf0_1", + "type": "light", + "deviceType": "light", + "createdAt": "2022-11-01T08:33:27.000Z", + "isReachable": true, + "lastSeen": "2022-11-12T10:31:23.000Z", + "customIcon": "lighting_spot_chandelier", + "attributes": { + "customName": "Vasken", + "model": "TRADFRI Driver 30W", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "B4E3F9FFFE128FF2", + "productCode": "ICPSHC2430EUIL1", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 100, + "identifyStarted": "2022-11-01T08:34:45.000Z", + "identifyPeriod": 15, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel" + ] + }, + "room": { + "id": "45e3cc5e-8c64-478b-ac32-25762b431db4", + "name": "Kjøkken", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [ + "d0aa3515-cef7-465f-8bcf-19caf59f1a98_1" + ], + "isHidden": false + }, + { + "id": "eb9a4367-9e23-4d37-9566-401a7ae7caf0_2", + "type": "light", + "deviceType": "light", + "createdAt": "2022-11-01T08:33:27.000Z", + "isReachable": true, + "lastSeen": "2022-11-12T10:31:23.000Z", + "customIcon": "lighting_spot_chandelier", + "attributes": { + "customName": "Vasken", + "model": "TRADFRI Driver 30W", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "B4E3F9FFFE128FF2", + "productCode": "ICPSHC2430EUIL1", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 100, + "identifyStarted": "2022-11-01T08:34:45.000Z", + "identifyPeriod": 15, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn" + ] + }, + "room": { + "id": "45e3cc5e-8c64-478b-ac32-25762b431db4", + "name": "Kjøkken", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [ + "d0aa3515-cef7-465f-8bcf-19caf59f1a98_1" + ], + "isHidden": false + }, + { + "id": "92dbcea1-3d7e-4d6a-a009-bdf3a1ae6691_1", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2022-11-11T16:46:15.000Z", + "isReachable": true, + "lastSeen": "2022-11-11T16:47:24.000Z", + "attributes": { + "customName": "Fjärrkontroll 1", + "firmwareVersion": "2.3.015", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI SHORTCUT Button", + "productCode": "E1812", + "serialNumber": "84BA20FFFEAD3E56", + "batteryPercentage": 100, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel", + "blindsState" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "decd27b3-f54a-4211-9a8f-e7bf70f832eb", + "name": "A", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "cec4c170-7846-4e22-b681-d8a912181cca_1", + "type": "controller", + "deviceType": "soundController", + "createdAt": "2022-11-08T02:11:49.000Z", + "isReachable": true, + "lastSeen": "2022-11-09T17:53:12.000Z", + "attributes": { + "customName": "Remote 2", + "model": "SYMFONISK Sound Controller", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "serialNumber": "943469FFFE636247", + "productCode": "E1744", + "batteryPercentage": 90, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "297071f0-e98a-4ba0-8c05-9b88a1dbc6c4", + "name": "Living Room ", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9f96eced-7674-4b9f-bbf9-b9575d888638_1", + "type": "controller", + "deviceType": "blindsController", + "createdAt": "2022-11-01T22:43:56.000Z", + "isReachable": true, + "lastSeen": "2022-11-17T21:48:45.000Z", + "attributes": { + "customName": "Office Blinder Remote ", + "model": "TRADFRI open/close remote", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "serialNumber": "5C0272FFFE6C613C", + "productCode": "E1766", + "batteryPercentage": 65, + "isOn": false, + "lightLevel": 1, + "blindsCurrentLevel": 0, + "blindsState": "", + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel", + "blindsState" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "36e2e693-a042-4119-84ec-42d45a347534", + "name": "Office", + "color": "ikea_blue_no_63", + "icon": "rooms_display" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "eadfad54-9d23-4475-92b6-0ee3d6f8b481_1", + "type": "blinds", + "deviceType": "blinds", + "createdAt": "2022-11-01T22:32:41.000Z", + "isReachable": true, + "lastSeen": "2022-11-18T22:11:51.000Z", + "attributes": { + "customName": "Office Blinder", + "model": "PRAKTLYSING cellular blind", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.088", + "hardwareVersion": "1", + "serialNumber": "0C4314FFFE293771", + "productCode": "E2021", + "batteryPercentage": 92, + "blindsTargetLevel": 60, + "blindsCurrentLevel": 60, + "blindsState": "stopped", + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "blindsCurrentLevel", + "blindsTargetLevel", + "blindsState" + ] + }, + "room": { + "id": "36e2e693-a042-4119-84ec-42d45a347534", + "name": "Office", + "color": "ikea_blue_no_63", + "icon": "rooms_display" + }, + "deviceSet": [], + "remoteLinks": [ + "9f96eced-7674-4b9f-bbf9-b9575d888638_1" + ], + "isHidden": false + }, + { + "id": "a8319695-0729-428c-9465-aadc0b738995", + "type": "airPurifier", + "deviceType": "airPurifier", + "createdAt": "2022-11-05T22:40:42.000Z", + "isReachable": true, + "lastSeen": "2022-11-16T17:46:45.000Z", + "attributes": { + "customName": "Luftrenare hall", + "model": "STARKVIND Air purifier", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.001", + "hardwareVersion": "1", + "serialNumber": "XXXXXXXXXXXX", + "productCode": "E2007", + "fanMode": "auto", + "fanModeSequence": "lowMediumHighAuto", + "motorState": 10, + "motorRuntime": 472283, + "filterElapsedTime": 193540, + "filterAlarmStatus": false, + "filterLifetime": 259200, + "childLock": false, + "statusLight": true, + "currentPM25": 8, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "fanMode", + "fanModeSequence", + "motorState", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "a7301b11-95f6-4ee3-a764-22d4ea52d161", + "name": "Hall", + "color": "ikea_orange_no_11", + "icon": "rooms_coat_hanger" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "6379a590-dc0a-47b5-b35b-7b46dfefd282_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2022-11-11T17:05:54.000Z", + "isReachable": true, + "lastSeen": "2022-11-11T17:06:02.000Z", + "attributes": { + "customName": "Uttag 1", + "firmwareVersion": "2.3.089", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI control outlet", + "productCode": "E1603", + "serialNumber": "D0CF5EFFFEEF2B3F", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel" + ] + }, + "room": { + "id": "decd27b3-f54a-4211-9a8f-e7bf70f832eb", + "name": "A", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2224", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/inspelning.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/inspelning.json new file mode 100644 index 00000000000..a95f036f1ba --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/inspelning.json @@ -0,0 +1,59 @@ +{ + "deviceType": "outlet", + "remoteLinks": [], + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": false, + "lastSeen": "2024-10-10T10:47:13.000Z", + "capabilities": { + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ], + "canSend": [] + }, + "attributes": { + "identifyPeriod": 0, + "permittingJoin": false, + "currentAmps": 0.003000000026077032, + "currentVoltage": 75.5999984741211, + "otaScheduleEnd": "00:00", + "currentActivePower": 0, + "otaStatus": "upToDate", + "totalEnergyConsumedLastUpdated": "2024-10-08T20:46:36.000Z", + "manufacturer": "IKEA of Sweden", + "statusLight": false, + "otaState": "readyToCheck", + "isOn": false, + "totalEnergyConsumed": 0, + "model": "INSPELNING Smart plug", + "hardwareVersion": "1", + "firmwareVersion": "2.4.45", + "serialNumber": "ECF64CFFFEF28EC3", + "otaScheduleStart": "00:00", + "otaProgress": 0, + "startUpCurrentLevel": -1, + "customName": "Smart-Home-Steckdose 1", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "lightLevel": 100, + "productCode": "E2206", + "childLock": false, + "startupOnOff": "startPrevious" + }, + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/praktlysing.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/praktlysing.json new file mode 100644 index 00000000000..3decd88ace6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/praktlysing.json @@ -0,0 +1,48 @@ +{ + "id": "eadfad54-9d23-4475-92b6-0ee3d6f8b481_1", + "type": "blinds", + "deviceType": "blinds", + "createdAt": "2022-11-01T22:32:41.000Z", + "isReachable": true, + "lastSeen": "2022-11-18T22:11:51.000Z", + "attributes": { + "customName": "Office Blinder", + "model": "PRAKTLYSING cellular blind", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.088", + "hardwareVersion": "1", + "serialNumber": "0C4314FFFE293771", + "productCode": "E2021", + "batteryPercentage": 92, + "blindsTargetLevel": 60, + "blindsCurrentLevel": 60, + "blindsState": "stopped", + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "blindsCurrentLevel", + "blindsTargetLevel", + "blindsState" + ] + }, + "room": { + "id": "36e2e693-a042-4119-84ec-42d45a347534", + "name": "Office", + "color": "ikea_blue_no_63", + "icon": "rooms_display" + }, + "deviceSet": [], + "remoteLinks": [ + "9f96eced-7674-4b9f-bbf9-b9575d888638_1" + ], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/somrig-shortcut-cotroller.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/somrig-shortcut-cotroller.json new file mode 100644 index 00000000000..6c32290c434 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/somrig-shortcut-cotroller.json @@ -0,0 +1,91 @@ +[ + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_1", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "Clicky", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "batteryPercentage": 89, + "switchLabel": "Shortcut 1", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_2", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "switchLabel": "Shortcut 2", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/starkvind.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/starkvind.json new file mode 100644 index 00000000000..95bcb3fd612 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/starkvind.json @@ -0,0 +1,56 @@ +{ + "id": "a8319695-0729-428c-9465-aadc0b738995", + "type": "airPurifier", + "deviceType": "airPurifier", + "createdAt": "2022-11-05T22:40:42.000Z", + "isReachable": true, + "lastSeen": "2022-11-16T17:46:45.000Z", + "attributes": { + "customName": "Luftrenare hall", + "model": "STARKVIND Air purifier", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.001", + "hardwareVersion": "1", + "serialNumber": "XXXXXXXXXXXX", + "productCode": "E2007", + "fanMode": "auto", + "fanModeSequence": "lowMediumHighAuto", + "motorState": 10, + "motorRuntime": 472283, + "filterElapsedTime": 193540, + "filterAlarmStatus": false, + "filterLifetime": 259200, + "childLock": false, + "statusLight": true, + "currentPM25": 8, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "fanMode", + "fanModeSequence", + "motorState", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "a7301b11-95f6-4ee3-a764-22d4ea52d161", + "name": "Hall", + "color": "ikea_orange_no_11", + "icon": "rooms_coat_hanger" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/symfonsik-sound-controller.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/symfonsik-sound-controller.json new file mode 100644 index 00000000000..ab2cb5f0752 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/symfonsik-sound-controller.json @@ -0,0 +1,45 @@ +{ + "id": "cec4c170-7846-4e22-b681-d8a912181cca_1", + "type": "controller", + "deviceType": "soundController", + "createdAt": "2022-11-08T02:11:49.000Z", + "isReachable": true, + "lastSeen": "2022-11-09T17:53:12.000Z", + "attributes": { + "customName": "Remote 2", + "model": "SYMFONISK Sound Controller", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "serialNumber": "943469FFFE636247", + "productCode": "E1744", + "batteryPercentage": 90, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "297071f0-e98a-4ba0-8c05-9b88a1dbc6c4", + "name": "Living Room ", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfir-shortcut-controller.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfir-shortcut-controller.json new file mode 100644 index 00000000000..379833b345d --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfir-shortcut-controller.json @@ -0,0 +1,46 @@ +{ + "id": "92dbcea1-3d7e-4d6a-a009-bdf3a1ae6691_1", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2022-11-11T16:46:15.000Z", + "isReachable": true, + "lastSeen": "2022-11-11T16:47:24.000Z", + "attributes": { + "customName": "Fjärrkontroll 1", + "firmwareVersion": "2.3.015", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI SHORTCUT Button", + "productCode": "E1812", + "serialNumber": "84BA20FFFEAD3E56", + "batteryPercentage": 100, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel", + "blindsState" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "decd27b3-f54a-4211-9a8f-e7bf70f832eb", + "name": "A", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-blinds-controller.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-blinds-controller.json new file mode 100644 index 00000000000..a6c0b834221 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-blinds-controller.json @@ -0,0 +1,48 @@ + { + "id": "9f96eced-7674-4b9f-bbf9-b9575d888638_1", + "type": "controller", + "deviceType": "blindsController", + "createdAt": "2022-11-01T22:43:56.000Z", + "isReachable": true, + "lastSeen": "2022-11-17T21:48:45.000Z", + "attributes": { + "customName": "Office Blinder Remote ", + "model": "TRADFRI open/close remote", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "serialNumber": "5C0272FFFE6C613C", + "productCode": "E1766", + "batteryPercentage": 65, + "isOn": false, + "lightLevel": 1, + "blindsCurrentLevel": 0, + "blindsState": "", + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel", + "blindsState" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "36e2e693-a042-4119-84ec-42d45a347534", + "name": "Office", + "color": "ikea_blue_no_63", + "icon": "rooms_display" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-color-lightbulb.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-color-lightbulb.json new file mode 100644 index 00000000000..e754a14a393 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-color-lightbulb.json @@ -0,0 +1,61 @@ + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Dining Room", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": {} +} diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-light-driver.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-light-driver.json new file mode 100644 index 00000000000..0a2218dccf6 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-light-driver.json @@ -0,0 +1,49 @@ +{ + "id": "eb9a4367-9e23-4d37-9566-401a7ae7caf0_1", + "type": "light", + "deviceType": "light", + "createdAt": "2022-11-01T08:33:27.000Z", + "isReachable": true, + "lastSeen": "2022-11-12T10:31:23.000Z", + "customIcon": "lighting_spot_chandelier", + "attributes": { + "customName": "Vasken", + "model": "TRADFRI Driver 30W", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "B4E3F9FFFE128FF2", + "productCode": "ICPSHC2430EUIL1", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 100, + "identifyStarted": "2022-11-01T08:34:45.000Z", + "identifyPeriod": 15, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel" + ] + }, + "room": { + "id": "45e3cc5e-8c64-478b-ac32-25762b431db4", + "name": "Kjøkken", + "color": "ikea_green_no_65", + "icon": "rooms_arm_chair" + }, + "deviceSet": [], + "remoteLinks": [ + "d0aa3515-cef7-465f-8bcf-19caf59f1a98_1" + ], + "isHidden": false +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-outlet.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-outlet.json new file mode 100644 index 00000000000..9e9774042f7 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tradfri-outlet.json @@ -0,0 +1,42 @@ +{ + "id" : "6379a590-dc0a-47b5-b35b-7b46dfefd282_1", + "type" : "outlet", + "deviceType" : "outlet", + "createdAt" : "2022-11-11T17:05:54.000Z", + "isReachable" : true, + "lastSeen" : "2022-11-11T17:06:02.000Z", + "attributes" : { + "customName" : "Uttag 1", + "firmwareVersion" : "2.3.089", + "hardwareVersion" : "1", + "manufacturer" : "IKEA of Sweden", + "model" : "TRADFRI control outlet", + "productCode" : "E1603", + "serialNumber" : "D0CF5EFFFEEF2B3F", + "isOn" : true, + "startupOnOff" : "startPrevious", + "lightLevel" : 100, + "identifyPeriod" : 0, + "identifyStarted" : "2000-01-01T00:00:00.000Z", + "permittingJoin" : false, + "otaPolicy" : "autoUpdate", + "otaProgress" : 0, + "otaScheduleEnd" : "00:00", + "otaScheduleStart" : "00:00", + "otaState" : "readyToCheck", + "otaStatus" : "upToDate" + }, + "capabilities" : { + "canSend" : [ ], + "canReceive" : [ "customName", "isOn", "lightLevel" ] + }, + "room" : { + "id" : "decd27b3-f54a-4211-9a8f-e7bf70f832eb", + "name" : "A", + "color" : "ikea_green_no_65", + "icon" : "rooms_arm_chair" + }, + "deviceSet" : [ ], + "remoteLinks" : [ ], + "isHidden" : false + } \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tretakt-plug.json b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tretakt-plug.json new file mode 100644 index 00000000000..3bb904070dc --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/devices/tretakt-plug.json @@ -0,0 +1,45 @@ + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-with-coordinates.json b/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-with-coordinates.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-with-coordinates.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-without-coordinates.json b/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-without-coordinates.json new file mode 100644 index 00000000000..b3caf2ad974 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/gateway/home-without-coordinates.json @@ -0,0 +1,1605 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": null, + "nextSunRise": null, + "homestate": "home", + "countryCode": "XZ", + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": null, + "nextSunRise": null, + "homestate": "home", + "countryCode": "XZ", + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-one-device.json b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-one-device.json new file mode 100644 index 00000000000..d7e813003a5 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-one-device.json @@ -0,0 +1,174 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-with-double-shortcut.json b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-with-double-shortcut.json new file mode 100644 index 00000000000..8cd7c3943ae --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home-with-double-shortcut.json @@ -0,0 +1,259 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-27T20:14:00.811Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.5", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2024-10-17T02:30:32.000Z", + "identifyPeriod": 65534, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 100, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-28T16:10:00.000Z", + "nextSunRise": "2024-10-28T06:11:00.000Z", + "homestate": "home", + "countryCode": "EN", + "coordinates": { + "latitude": 1.23, + "longitude": 9.876, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_1", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "Clicky", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "batteryPercentage": 89, + "switchLabel": "Shortcut 1", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "854bdf30-86b8-48f5-b070-16ff5ab12be4_2", + "relationId": "854bdf30-86b8-48f5-b070-16ff5ab12be4", + "type": "controller", + "deviceType": "shortcutController", + "createdAt": "2024-10-17T14:23:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-27T18:11:55.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.21", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "SOMRIG shortcut button", + "productCode": "E2213", + "serialNumber": "ECF64CFFFE1EE52F", + "switchLabel": "Shortcut 2", + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [ + "singlePress", + "longPress", + "doublePress" + ], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [ + ], + "scenes": [ + ], + "rooms": [ + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + } + ], + "deviceSets": [], + "adaptiveProfiles": [ + { + "id": "f7fd67f5-4c3f-401b-9dea-211fba48a844", + "adaptiveSchedule": [ + { + "startTime": "07:00", + "lightLevel": 25, + "colorTemperature": 3000 + }, + { + "startTime": "09:00", + "lightLevel": 75, + "colorTemperature": 3900 + }, + { + "startTime": "13:00", + "lightLevel": 100, + "colorTemperature": 4000 + }, + { + "startTime": "17:00", + "lightLevel": 70, + "colorTemperature": 4000 + }, + { + "startTime": "20:00", + "lightLevel": 20, + "colorTemperature": 3900 + }, + { + "startTime": "22:00", + "lightLevel": 1, + "colorTemperature": 3000 + } + ] + } + ] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/home/home.json b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/home/home.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/device-added.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/device-added.json new file mode 100644 index 00000000000..0ff6d092067 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/device-added.json @@ -0,0 +1,51 @@ +{ + "data": { + "deviceType": "outlet", + "remoteLinks": [], + "createdAt": "2024-10-17T21:17:45.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T21:17:48.000Z", + "capabilities": { + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ], + "canSend": [] + }, + "attributes": { + "identifyPeriod": 0, + "permittingJoin": false, + "serialNumber": "D44867FFFE140EDE", + "otaScheduleStart": "00:00", + "otaProgress": 0, + "startUpCurrentLevel": -1, + "otaScheduleEnd": "00:00", + "customName": "", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "manufacturer": "IKEA of Sweden", + "lightLevel": 100, + "productCode": "E2204", + "statusLight": true, + "otaState": "readyToCheck", + "childLock": false, + "isOn": false, + "hardwareVersion": "1", + "model": "TRETAKT Smart plug", + "startupOnOff": "startPrevious", + "firmwareVersion": "2.4.4" + }, + "id": "339a37ea-791d-4b5d-8a1d-21d068578421_1", + "type": "outlet", + "deviceSet": [] + }, + "specversion": "3.163.0", + "id": "382f5a61-0b3a-4c55-b601-d89c003ac27c", + "time": "2024-10-17T21:17:48.000Z", + "source": "urn:com:ikea:homesmart:iotc:zigbee", + "type": "deviceAdded" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-after.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-after.json new file mode 100644 index 00000000000..590771a4cf9 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-after.json @@ -0,0 +1,1659 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "deviceType": "outlet", + "remoteLinks": [], + "createdAt": "2024-10-17T21:17:45.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T21:17:48.000Z", + "capabilities": { + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ], + "canSend": [] + }, + "attributes": { + "identifyPeriod": 0, + "permittingJoin": false, + "serialNumber": "D44867FFFE140EDE", + "otaScheduleStart": "00:00", + "otaProgress": 0, + "startUpCurrentLevel": -1, + "otaScheduleEnd": "00:00", + "customName": "", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "manufacturer": "IKEA of Sweden", + "lightLevel": 100, + "productCode": "E2204", + "statusLight": true, + "otaState": "readyToCheck", + "childLock": false, + "isOn": false, + "hardwareVersion": "1", + "model": "TRETAKT Smart plug", + "startupOnOff": "startPrevious", + "firmwareVersion": "2.4.4" + }, + "id": "339a37ea-791d-4b5d-8a1d-21d068578421_1", + "type": "outlet", + "deviceSet": [] + }, + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-before.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-before.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-added/home-before.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/device-removed.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/device-removed.json new file mode 100644 index 00000000000..9c997074ea3 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/device-removed.json @@ -0,0 +1,51 @@ +{ + "data": { + "deviceType": "outlet", + "remoteLinks": [], + "createdAt": "2024-10-17T21:17:45.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T21:17:48.000Z", + "capabilities": { + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ], + "canSend": [] + }, + "attributes": { + "identifyPeriod": 0, + "permittingJoin": false, + "serialNumber": "D44867FFFE140EDE", + "otaScheduleStart": "00:00", + "otaProgress": 0, + "startUpCurrentLevel": -1, + "otaScheduleEnd": "00:00", + "customName": "", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "manufacturer": "IKEA of Sweden", + "lightLevel": 100, + "productCode": "E2204", + "statusLight": true, + "otaState": "readyToCheck", + "childLock": false, + "isOn": false, + "hardwareVersion": "1", + "model": "TRETAKT Smart plug", + "startupOnOff": "startPrevious", + "firmwareVersion": "2.4.4" + }, + "id": "339a37ea-791d-4b5d-8a1d-21d068578421_1", + "type": "outlet", + "deviceSet": [] + }, + "specversion": "3.163.0", + "id": "382f5a61-0b3a-4c55-b601-d89c003ac27c", + "time": "2024-10-17T21:17:48.000Z", + "source": "urn:com:ikea:homesmart:iotc:zigbee", + "type": "deviceRemoved" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-after.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-after.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-after.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-before.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-before.json new file mode 100644 index 00000000000..590771a4cf9 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/device-removed/home-before.json @@ -0,0 +1,1659 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "deviceType": "outlet", + "remoteLinks": [], + "createdAt": "2024-10-17T21:17:45.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T21:17:48.000Z", + "capabilities": { + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ], + "canSend": [] + }, + "attributes": { + "identifyPeriod": 0, + "permittingJoin": false, + "serialNumber": "D44867FFFE140EDE", + "otaScheduleStart": "00:00", + "otaProgress": 0, + "startUpCurrentLevel": -1, + "otaScheduleEnd": "00:00", + "customName": "", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "manufacturer": "IKEA of Sweden", + "lightLevel": 100, + "productCode": "E2204", + "statusLight": true, + "otaState": "readyToCheck", + "childLock": false, + "isOn": false, + "hardwareVersion": "1", + "model": "TRETAKT Smart plug", + "startupOnOff": "startPrevious", + "firmwareVersion": "2.4.4" + }, + "id": "339a37ea-791d-4b5d-8a1d-21d068578421_1", + "type": "outlet", + "deviceSet": [] + }, + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-after.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-after.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-after.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-before.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-before.json new file mode 100644 index 00000000000..0f9ac019715 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/home-before.json @@ -0,0 +1,1543 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/scene-created.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/scene-created.json new file mode 100644 index 00000000000..22c32f76469 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-created/scene-created.json @@ -0,0 +1,79 @@ +{ + "data": { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + "specversion": "3.163.0", + "id": "382f5a61-0b3a-4c55-b601-d89c003ac27c", + "time": "2024-10-17T21:17:48.000Z", + "source": "urn:com:ikea:homesmart:iotc:zigbee", + "type": "sceneCreated" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-after.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-after.json new file mode 100644 index 00000000000..0f9ac019715 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-after.json @@ -0,0 +1,1543 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-before.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-before.json new file mode 100644 index 00000000000..038ccd33080 --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/home-before.json @@ -0,0 +1,1615 @@ +{ + "user": { + "uid": "e86c72ef-93f8-4121-bd21-b634ba5d4f51", + "name": "openHAB", + "audience": "homesmart.local", + "email": "", + "createdTimestamp": "2024-10-08T14:14:33.000Z", + "verifiedUid": "", + "role": "home_member", + "remoteUser": 0 + }, + "hub": { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "environment": "prod" + }, + "devices": [ + { + "id": "9af826ad-a8ad-40bf-8aed-125300bccd20_1", + "type": "sensor", + "deviceType": "waterSensor", + "createdAt": "2024-10-17T12:38:02.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T14:22:17.000Z", + "attributes": { + "customName": "Washy", + "firmwareVersion": "1.0.7", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "BADRING Water Leakage Sensor", + "productCode": "E2202", + "serialNumber": "D44867FFFE147386", + "batteryPercentage": 55, + "waterLeakDetected": false, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "deviceType": "environmentSensor", + "remoteLinks": [], + "createdAt": "2024-10-17T08:00:25.000Z", + "isReachable": true, + "lastSeen": "2024-10-17T08:07:42.000Z", + "capabilities": { + "canReceive": [ + "customName" + ], + "canSend": [] + }, + "attributes": { + "maxMeasuredPM25": 999, + "identifyPeriod": 0, + "currentTemperature": 20, + "permittingJoin": false, + "serialNumber": "28DBA7FFFEDFFBF6", + "minMeasuredPM25": 0, + "otaScheduleStart": "00:00", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "customName": "Cleany", + "currentPM25": 11, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "otaPolicy": "autoUpdate", + "otaStatus": "upToDate", + "vocIndex": 100, + "manufacturer": "IKEA of Sweden", + "productCode": "E2112", + "otaState": "readyToCheck", + "hardwareVersion": "1", + "model": "VINDSTYRKA", + "firmwareVersion": "1.0.11", + "currentRH": 76 + }, + "id": "f80cac12-65a4-47b4-9f68-a0456a349a43_1", + "type": "sensor", + "deviceSet": [], + "room": { + "color": "ikea_green_no_65", + "name": "Loft", + "icon": "rooms_sofa", + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d" + }, + "isHidden": false + }, + { + "id": "5ffa4a1e-6fe2-4a24-b0af-b216937c4e8c_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:04:54.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:08:02.000Z", + "attributes": { + "customName": "Lampe Rechts", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "30FB10FFFEE44843", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 54, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-11T11:33:27.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T11:22:41.000Z", + "attributes": { + "customName": "Remote Control Loft", + "firmwareVersion": "2.4.16", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "Remote Control N2", + "productCode": "E2001", + "serialNumber": "94B216FFFE6F9BA6", + "batteryPercentage": 85, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "044b63e7-999d-4caa-8a76-fb8cfd32b381_1", + "type": "repeater", + "deviceType": "repeater", + "createdAt": "2024-10-10T10:10:37.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T10:12:28.000Z", + "attributes": { + "customName": "Reaper", + "model": "TRADFRI Signal Repeater", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "14B457FFFEF8DC61", + "productCode": "E1746", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "45165c60-0c1a-4b9f-ae09-c4a6220d1420_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T18:09:58.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T20:02:56.000Z", + "attributes": { + "customName": "Lampe Links", + "model": "TRADFRI bulb E14 WS candle 470lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "3.0.21", + "hardwareVersion": "1", + "serialNumber": "881A14FFFE479E82", + "productCode": "LED2107C4", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 51, + "startUpCurrentLevel": -1, + "colorTemperature": 2188, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "temperature", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [ + "5ac5e131-44a4-4d75-be78-759a095d31fb_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T17:41:34.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "customIcon": "lighting_pendant_light", + "attributes": { + "customName": "Esszimmer Hinten", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE5FFFA2", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 92, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 0.7, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:20:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:01.000Z", + "attributes": { + "customName": "Loft Floor Lamp", + "model": "TRADFRI bulb E27 CWS opal 600lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.3.086", + "hardwareVersion": "1", + "serialNumber": "EC1BBDFFFEC17EA1", + "productCode": "LED1624G9E27EU", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 119.9981689453125, + "colorSaturation": 1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "338bb721-35bb-4775-8cd0-ba70fc37ab10_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-04T17:21:49.446Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:09:15.285Z", + "attributes": { + "customName": "Loft", + "model": "SYMFONISK Bookshelf S21", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.3.3-2.0", + "serialNumber": "34-7E-5C-F5-45-88:F", + "productCode": "S21", + "identifyStarted": "2024-10-04T17:21:49.447Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-13T21:09:15.284Z", + "playbackAudio": { + "serviceType": "sonos", + "providerType": "Mixcloud", + "playItem": { + "id": "eyJhY2NvdW50VHlwZSI6InNvbm9zIiwiY29udGVudFR5cGUiOiJjb250YWluZXIiLCJjb250ZW50Ijp7ImNvbnRhaW5lciI6eyJhY2NvdW50SWQiOiJzbl8zIiwib2JqZWN0SWQiOiJjbG91ZGNhc3Q6MjE3OTQzNDcxNCIsInNlcnZpY2VJZCI6IjE4MSIsInR5cGUiOiJ0cmFjayJ9fX0=", + "title": "The Anjunadeep Edition 521 with Leaving Laurel & yehno", + "artist": "Anjunadeep", + "imageURL": "http://192.168.1.95:1400/getaa?s=1&u=x-sonos-http%3acloudcast%253a2179434714.unknown%3fsid%3d181%26flags%3d0%26sn%3d3", + "duration": 3617000 + } + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-13T21:09:15.284Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": true, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": true, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 16, + "isMuted": false, + "audioGroup": "1f386990-bb4f-4a00-befd-646b5ef52c47" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "03c716a9-900e-4bb0-8694-eaf837bfa3fb_1", + "type": "speaker", + "deviceType": "speaker", + "createdAt": "2024-10-06T09:06:12.272Z", + "isReachable": false, + "lastSeen": "2024-10-08T16:30:36.504Z", + "attributes": { + "customName": "Moritz", + "model": "SYMFONISK Table lamp S20", + "manufacturer": "Sonos, Inc.", + "firmwareVersion": "80.1-56190", + "hardwareVersion": "1.20.2.3-2.0", + "serialNumber": "78-28-CA-81-8E-F2:1", + "productCode": "S20", + "identifyStarted": "2024-10-06T09:06:12.272Z", + "identifyPeriod": 0, + "playback": "playbackIdle", + "playbackLastChangedTimestamp": "2024-10-06T09:06:12.272Z", + "playbackAudio": { + + }, + "playbackPosition": { + "position": 0, + "timestamp": "2024-10-06T09:06:12.032Z" + }, + "playbackAvailableActions": { + "crossfade": true, + "pause": false, + "repeat": [ + "off", + "playItem", + "playlist" + ], + "seek": false, + "shuffle": true, + "playbackNext": false, + "playbackPrev": false + }, + "playbackModes": { + "crossfade": false, + "repeat": "off", + "shuffle": false + }, + "volume": 15, + "isMuted": false, + "audioGroup": "71aa115c-fadd-4c43-aaf7-d537fa939638" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "playback", + "playbackAudio", + "volume", + "isMuted" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": true + }, + { + "id": "07cca6c2-f2b6-4f57-bfd9-a788a16d1eef_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-09T21:23:19.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:22:08.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Sensor Kitchen", + "model": "PARASOLL Door/Window Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "serialNumber": "D44867FFFEC2D1F0", + "productCode": "E2013", + "batteryPercentage": 84, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-04T18:00:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:17:02.000Z", + "customIcon": "lighting_cone_pendant", + "attributes": { + "customName": "Esszimmer Vorne", + "model": "TRADFRI bulb E27 CWS globe 806lm", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.38", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFEC9113D", + "productCode": "LED2109G6", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "colorHue": 0, + "colorSaturation": 1, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "a4c6a33a-9c6a-44bf-bdde-f99aff00eca4_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-16T12:57:26.000Z", + "isReachable": true, + "lastSeen": "2024-10-16T13:00:58.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "2.4.4", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRETAKT Smart plug", + "productCode": "E2204", + "serialNumber": "D44867FFFE140EDE", + "isOn": false, + "startupOnOff": "startPrevious", + "lightLevel": 100, + "startUpCurrentLevel": -1, + "childLock": false, + "statusLight": true, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "childLock", + "statusLight" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1", + "type": "outlet", + "deviceType": "outlet", + "createdAt": "2024-10-08T20:43:31.000Z", + "isReachable": true, + "lastSeen": "2024-10-10T14:40:38.000Z", + "attributes": { + "customName": "Smart-Home-Steckdose 1", + "model": "INSPELNING Smart plug", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.4.45", + "hardwareVersion": "1", + "serialNumber": "ECF64CFFFEF28EC3", + "productCode": "E2206", + "isOn": true, + "startupOnOff": "startPrevious", + "lightLevel": 1, + "startUpCurrentLevel": -1, + "currentVoltage": 236.8000030517578, + "currentAmps": 0.003000000026077032, + "currentActivePower": 0, + "totalEnergyConsumed": 0.01899999938905239, + "totalEnergyConsumedLastUpdated": "2024-10-10T14:40:05.000Z", + "energyConsumedAtLastReset": 0, + "timeOfLastEnergyReset": "2024-10-08T20:10:31.000Z", + "childLock": false, + "statusLight": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "energyConsumedAtLastReset", + "childLock", + "statusLight" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-09T17:38:43.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T17:18:25.000Z", + "attributes": { + "customName": "", + "model": "", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.1.10", + "hardwareVersion": "1", + "serialNumber": "7CC6B6FFFE66D457", + "productCode": "L2112", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 95, + "startUpCurrentLevel": -1, + "colorHue": 29.9981689453125, + "colorSaturation": 0.6496062992125984, + "colorTemperature": 2702, + "colorTemperatureMin": 4000, + "colorTemperatureMax": 2202, + "startupTemperature": -1, + "colorMode": "color", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoUpdate", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature", + "colorHue", + "colorSaturation" + ] + }, + "room": { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + "deviceSet": [], + "remoteLinks": [ + "22e4b77b-9a60-4727-944b-0d5e3e33b58f_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_1", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "Motion Sensor Gäste WC", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "batteryPercentage": 85, + "isOn": false, + "motionDetectedDelay": 20, + "isDetected": false, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": true, + "onDuration": 300, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "5ac5e131-44a4-4d75-be78-759a095d31fb_3", + "relationId": "5ac5e131-44a4-4d75-be78-759a095d31fb", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-09T17:29:53.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T18:39:59.000Z", + "attributes": { + "customName": "", + "model": "VALLHORN Wireless Motion Sensor", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "serialNumber": "8C65A3FFFE35C8D4", + "productCode": "E2134", + "illuminance": 1, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1", + "relationId": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "type": "gateway", + "deviceType": "gateway", + "createdAt": "2024-03-08T06:39:04.019Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:29:00.066Z", + "attributes": { + "customName": "Townhouse", + "model": "DIRIGERA Hub for smart products", + "manufacturer": "IKEA of Sweden", + "firmwareVersion": "2.660.2", + "hardwareVersion": "P2.5", + "serialNumber": "594197c3-23c9-4dc7-a6ca-1fe6a8455d29", + "identifyStarted": "2000-01-01T00:00:00.000Z", + "identifyPeriod": 0, + "otaStatus": "upToDate", + "otaState": "readyToCheck", + "otaProgress": 0, + "otaPolicy": "autoDownload", + "otaScheduleStart": "00:00", + "otaScheduleEnd": "00:00", + "permittingJoin": false, + "backendConnected": true, + "backendConnectionPersistent": true, + "backendOnboardingComplete": true, + "backendRegion": "eu-central-1", + "backendCountryCode": "DE", + "userConsents": [ + { + "name": "analytics", + "value": "disabled" + }, + { + "name": "diagnostics", + "value": "enabled" + } + ], + "logLevel": 3, + "coredump": false, + "timezone": "Europe/Berlin", + "nextSunSet": "2024-10-14T16:37:00.000Z", + "nextSunRise": "2024-10-14T05:48:00.000Z", + "homestate": "home", + "countryCode": "XZ", + "coordinates": { + "latitude": 9.876, + "longitude": 1.234, + "accuracy": -1 + }, + "isOn": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "permittingJoin", + "userConsents", + "logLevel", + "time", + "timezone", + "countryCode", + "coordinates" + ] + }, + "deviceSet": [], + "remoteLinks": [] + }, + { + "id": "a1e1eacc-2dcf-45bd-9f93-62a436b6a7ed_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:46:04.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:06:16.000Z", + "attributes": { + "customName": "Bathroom Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x90", + "productCode": "", + "serialNumber": "804B50FFFEF79502", + "isOn": true, + "startupOnOff": "startOn", + "lightLevel": 49, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "073b40df-e6a0-486d-99a3-6134effe4c59_1", + "type": "light", + "deviceType": "light", + "createdAt": "2024-10-10T13:48:44.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:21:17.000Z", + "customIcon": "lighting_fan", + "attributes": { + "customName": "HWR Panel", + "firmwareVersion": "2.3.087", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "FLOALT panel WS 30x30", + "productCode": "", + "serialNumber": "BC33ACFFFE9761AA", + "isOn": false, + "startupOnOff": "startOn", + "lightLevel": 73, + "startUpCurrentLevel": -1, + "colorMode": "temperature", + "startupTemperature": -1, + "colorTemperature": 4000, + "colorTemperatureMax": 2202, + "colorTemperatureMin": 4000, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate" + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName", + "isOn", + "lightLevel", + "colorTemperature" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [ + "ee61c57f-8efa-44f4-ba8a-d108ae054138_1" + ], + "isHidden": false, + "adaptiveProfile": { + + } + }, + { + "id": "ee61c57f-8efa-44f4-ba8a-d108ae054138_1", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:56:23.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T23:18:17.000Z", + "attributes": { + "customName": "HWR Motion", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI motion sensor", + "productCode": "E1745", + "serialNumber": "BC33ACFFFE8739CC", + "batteryPercentage": 20, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "isDetected": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "sensorConfig": { + "scheduleOn": true, + "onDuration": 180, + "schedule": { + "onCondition": { + "time": "sunset" + }, + "offCondition": { + "time": "sunrise" + } + } + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "3267c0f3-069a-4894-b5d0-a30d26386af8_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:00:00.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T13:31:10.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Door Dining Room", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "D44867FFFEB36824", + "batteryPercentage": 82, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "bab217dc-5d46-42af-a3a4-6a53b9a953da_1", + "type": "sensor", + "deviceType": "openCloseSensor", + "createdAt": "2024-10-10T14:05:20.000Z", + "isReachable": true, + "lastSeen": "2024-10-13T21:36:48.000Z", + "customIcon": "placement_door", + "attributes": { + "customName": "Entry Door", + "firmwareVersion": "1.0.19", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "PARASOLL Door/Window Sensor", + "productCode": "E2013", + "serialNumber": "F84477FFFEDC7871", + "batteryPercentage": 81, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "isOpen": false, + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_1", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "sensor", + "deviceType": "motionSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "Bedroom Motion", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "batteryPercentage": 84, + "isOn": false, + "isDetected": false, + "motionDetectedDelay": 20, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false, + "sensorConfig": { + "scheduleOn": false, + "onDuration": 180 + }, + "circadianPresets": [] + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "ca856a7d-a715-42f7-84a1-7caae41e6ff2_3", + "relationId": "ca856a7d-a715-42f7-84a1-7caae41e6ff2", + "type": "unknown", + "deviceType": "lightSensor", + "createdAt": "2024-10-10T14:24:07.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T05:15:44.000Z", + "attributes": { + "customName": "", + "firmwareVersion": "1.0.64", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "VALLHORN Wireless Motion Sensor", + "productCode": "E2134", + "serialNumber": "8C65A3FFFE355FB0", + "illuminance": 1, + "identifyPeriod": 0, + "identifyStarted": "2000-01-01T00:00:00.000Z", + "permittingJoin": false + }, + "capabilities": { + "canSend": [], + "canReceive": [ + "customName" + ] + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + }, + { + "id": "9e9cb6b1-7981-443a-bd1e-5301815a25bd_1", + "type": "controller", + "deviceType": "lightController", + "createdAt": "2024-10-13T20:09:35.000Z", + "isReachable": true, + "lastSeen": "2024-10-14T00:32:59.000Z", + "attributes": { + "customName": "HWR Remote", + "firmwareVersion": "24.4.5", + "hardwareVersion": "1", + "manufacturer": "IKEA of Sweden", + "model": "TRADFRI remote control", + "productCode": "E1810", + "serialNumber": "BC33ACFFFE9B308C", + "batteryPercentage": 30, + "isOn": false, + "lightLevel": 1, + "permittingJoin": false, + "otaPolicy": "autoUpdate", + "otaProgress": 0, + "otaScheduleEnd": "00:00", + "otaScheduleStart": "00:00", + "otaState": "readyToCheck", + "otaStatus": "upToDate", + "circadianPresets": [] + }, + "capabilities": { + "canSend": [ + "isOn", + "lightLevel" + ], + "canReceive": [ + "customName" + ] + }, + "room": { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + "deviceSet": [], + "remoteLinks": [], + "isHidden": false + } + ], + "users": [], + "scenes": [ + { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + { + "id": "3090ba82-3f5e-442f-8e49-f3eac9b7b0eb", + "info": { + "name": "Lightmode off", + "icon": "scenes_disco_ball" + }, + "type": "userScene", + "triggers": [ + { + "id": "6fff3797-c798-4d0c-abe0-ce46cc389104", + "type": "app", + "triggeredAt": "2024-10-16T00:38:16.257Z", + "disabled": false + } + ], + "actions": [ + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + }, + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 359.83392, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": false, + "lightLevel": 100, + "colorHue": 89.681786, + "colorSaturation": 0.99900264 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T23:14:12.399Z", + "lastCompleted": "2024-10-16T00:38:16.267Z", + "lastTriggered": "2024-10-16T00:38:16.267Z", + "undoAllowedDuration": 30 + } + ], + "rooms": [ + { + "id": "921edb07-e344-4c70-a09e-11935c727f58", + "name": "Esszimmer", + "color": "ikea_yellow_no_31", + "icon": "rooms_cutlery" + }, + { + "id": "2abdfb7b-d080-4b00-b576-0c6fb73e9b3b", + "name": "Lobby HWR", + "color": "ikea_pink_no_4", + "icon": "rooms_drill" + }, + { + "id": "7bc86420-cf4a-4a58-beff-7dcf462258da", + "name": "Badezimmer", + "color": "ikea_blue_no_58", + "icon": "rooms_bathtub" + }, + { + "id": "88f27266-14a8-4d66-8e4f-e10a3318868e", + "name": "Küche", + "color": "ikea_red_no_39", + "icon": "rooms_kitchen" + }, + { + "id": "893d50e0-f4bb-46ba-978d-e9280c6e299d", + "name": "Loft", + "color": "ikea_green_no_65", + "icon": "rooms_sofa" + }, + { + "id": "4328471f-03cd-4372-aa1e-e3e9f3be3da4", + "name": "Schlafzimmer ", + "color": "ikea_beige_no_3", + "icon": "rooms_bed" + }, + { + "id": "c9e407e5-5bfa-4e73-aaf3-c0258d9ccb5f", + "name": "Gäste WC", + "color": "ikea_blue_no_58", + "icon": "rooms_sink" + } + ], + "deviceSets": [], + "adaptiveProfiles": [] +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/scene-deleted.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/scene-deleted.json new file mode 100644 index 00000000000..bd1d540e6db --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-deleted/scene-deleted.json @@ -0,0 +1,79 @@ +{ + "data": { + "id": "086f4a37-ebe8-4fd4-9a25-a0220a1e5f58", + "info": { + "name": "Lightmode Christmas", + "icon": "scenes_tree" + }, + "type": "userScene", + "triggers": [ + { + "id": "987ec615-24d8-411c-9aa9-d03c447b246b", + "type": "app", + "triggeredAt": "2024-10-16T00:21:15.942Z", + "disabled": false + } + ], + "actions": [ + { + "id": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "type": "device", + "deviceId": "c27faa27-4c18-464f-81a0-a31ce57d83d5_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 0, + "colorSaturation": 1 + } + }, + { + "id": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "type": "device", + "deviceId": "891790db-8c17-483a-a1a6-c85bffd3a373_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "type": "device", + "deviceId": "d791dc18-9180-45c7-92fc-604481a34f15_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + }, + { + "id": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "type": "device", + "deviceId": "3c8b0049-eb5c-4ea1-9da3-cdedc50366ef_1", + "enabled": true, + "attributes": { + "isOn": true, + "lightLevel": 100, + "colorHue": 90, + "colorSaturation": 1 + } + } + ], + "commands": [], + "createdAt": "2024-10-15T11:13:42.273Z", + "lastCompleted": "2024-10-16T00:21:15.977Z", + "lastTriggered": "2024-10-16T00:21:15.977Z", + "undoAllowedDuration": 30, + "lastUndo": "2024-10-15T19:36:59.273Z" + }, + "specversion": "3.163.0", + "id": "382f5a61-0b3a-4c55-b601-d89c003ac27c", + "time": "2024-10-17T21:17:48.000Z", + "source": "urn:com:ikea:homesmart:iotc:zigbee", + "type": "sceneDeleted" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-pressed/scene-trigger-sequence.json b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-pressed/scene-trigger-sequence.json new file mode 100644 index 00000000000..4a04a8b93ab --- /dev/null +++ b/bundles/org.openhab.binding.dirigera/src/test/resources/websocket/scene-pressed/scene-trigger-sequence.json @@ -0,0 +1,98 @@ +[ + { + "data": { + "createdAt": "2024-10-27T19:47:59.155Z", + "undoAllowedDuration": 30, + "id": "3090ba82-aaaa-442f-8e49-f3eac9b7b0eb", + "type": "userScene", + "triggers": [ + { + "disabled": false, + "id": "5ec0d314-bbbb-470e-a92c-93beb7fbe09e", + "trigger": { + "controllerType": "shortcutController", + "buttonIndex": 0, + "days": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ], + "clickPattern": "singlePress", + "deviceId": "854bdf30-86b8-48f5-b070-16ff5ab12be4_1" + }, + "type": "controller" + }, + { + "disabled": false, + "id": "e615f61f-cccc-490c-98cf-1ad804f6b52d", + "triggeredAt": "2024-10-27T19:48:21.063Z", + "type": "app" + } + ], + "actions": [], + "commands": [], + "info": { + "name": "OHButtonConnector", + "icon": "scenes_disco_ball" + } + }, + "specversion": "3.163.0", + "id": "24c50fa3-035e-4d77-a9d4-b121c17e08a0", + "time": "2024-10-27T19:48:21.063Z", + "source": "urn:com:ikea:homesmart:iotc:rulesengine", + "type": "sceneUpdated" + }, + { + "data": { + "createdAt": "2024-10-27T19:47:59.155Z", + "lastTriggered": "2024-10-27T19:48:21.075Z", + "undoAllowedDuration": 30, + "lastCompleted": "2024-10-27T19:48:21.075Z", + "id": "3090ba82-aaaa-442f-8e49-f3eac9b7b0eb", + "type": "userScene", + "triggers": [ + { + "disabled": false, + "id": "5ec0d314-bbbb-470e-a92c-93beb7fbe09e", + "trigger": { + "controllerType": "shortcutController", + "buttonIndex": 0, + "days": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ], + "clickPattern": "singlePress", + "deviceId": "854bdf30-86b8-48f5-b070-16ff5ab12be4_1" + }, + "type": "controller" + }, + { + "disabled": false, + "id": "e615f61f-cccc-490c-98cf-1ad804f6b52d", + "triggeredAt": "2024-10-27T19:48:21.063Z", + "type": "app" + } + ], + "actions": [], + "commands": [], + "info": { + "name": "OHButtonConnector", + "icon": "scenes_disco_ball" + } + }, + "specversion": "3.163.0", + "id": "d4843df1-8c69-4c82-8a15-f611361455e4", + "time": "2024-10-27T19:48:21.084Z", + "source": "urn:com:ikea:homesmart:iotc:rulesengine", + "type": "sceneUpdated" + } +] \ No newline at end of file diff --git a/bundles/pom.xml b/bundles/pom.xml index 3d133fc04cb..2e149a1e213 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -117,6 +117,7 @@ org.openhab.binding.deutschebahn org.openhab.binding.digiplex org.openhab.binding.digitalstrom + org.openhab.binding.dirigera org.openhab.binding.dlinksmarthome org.openhab.binding.dmx org.openhab.binding.dolbycp