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 @@
+
+
+
+
+
+ Device Id
+ 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 @@
+
+
+
+
+
+ Device Id
+ Unique id of this device
+
+
+ Fade Time
+ Required time for fade sequnce to color or brightness
+ 750
+
+
+ Fade Sequence
+ Define sequence if several light parameters are changed at once
+
+ First brightness, then color
+ First color, then brightness
+
+ 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 @@
+
+
+
+
+
+ IP Address
+ Gateway IP Address
+
+
+ Device Id
+ Unique id of this gateway
+
+
+ Discovery
+ 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 @@
+
+
+
+
+
+ Device Id
+ Unique id of this device
+
+
+ Fade Time
+ 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 Purifier
+ Air cleaning device with particle filter
+
+
+
+ Fan Mode
+ Fan on, off, speed or automatic behavior
+
+
+ Fan Speed
+ Manual regulation of fan speed
+
+
+ Fan Runtime
+ Fan runtime in minutes
+
+
+ Filter Elapsed
+ Filter elapsed time in minutes
+
+
+ Filter Remain
+ Remaining filter time in minutes
+
+
+ Filter Lifetime
+ Filter lifetime in minutes
+
+
+ Filter Alarm
+ Filter alarm signal
+
+
+ Particulate Matter
+ 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 Quality
+ Air measure for temperature, humidity and particles
+
+
+
+ Temperature
+ Current indoor temperature
+
+
+ Humidity
+ Atmospheric humidity in percent
+
+
+ Particulate Matter
+ Category 2.5 particulate matter
+
+
+ VOC Index
+ 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 @@
+
+
+
+
+
+
+
+
+ Blinds Controller
+ Controller to open and close blinds
+
+
+
+ Battery Charge Level
+ 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 @@
+
+
+
+
+
+
+
+
+ Blind
+ Window or door blind
+
+
+
+
+
+ Battery Charge Level
+ 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
+ Simple Text
+
+
+ String
+ Custom Name
+ Name given from IKEA home smart
+
+
+ String
+ Light Preset
+
+
+ Off
+ Warm
+ Slowdown
+ Smooth
+ Bright
+
+
+
+
+ Number
+ Sensor Schedule
+
+
+ Always
+ Follow Sun
+ Time schedule
+
+
+
+
+ Always
+ Follow Sun
+ Time schedule
+
+
+ veto
+
+
+ Number
+ Repeat Options
+
+
+ Off
+ Title
+ Playlist
+
+
+
+
+ Off
+ Title
+ Playlist
+
+
+ veto
+
+
+ Number
+ Startup Behavior
+ Startup behavior after power cutoff
+
+
+ Previous
+ On
+ Off
+ Toggle
+
+
+
+
+ Previous
+ On
+ Off
+ Toggle
+
+
+ veto
+
+
+ Switch
+ Disable Status Light
+ Disable status light on device
+ veto
+
+
+ Switch
+ Child Lock
+ Child lock for button on device
+ veto
+
+
+ Switch
+ On Off Switch
+ veto
+
+
+ Switch
+ On Off Switch
+
+
+
+ Switch
+ Alarm Switch
+ Alarm
+
+ Alarm
+ Water
+
+
+
+
+ Number:Illuminance
+ Illuminance
+ Illuminance in Lux
+
+ Measurement
+ Light
+
+
+
+
+ Contact
+ Contact
+ Contact
+
+ OpenState
+
+
+
+
+ Dimmer
+ Dimmer
+ veto
+
+
+ Image
+ Image
+
+
+
+ DateTime
+ Schedule Time
+
+
+
+ 16:00
+ 16:30
+ 17:00
+ 17:30
+ 18:00
+ 18:30
+ 19:00
+ 19:30
+ 20:00
+
+
+
+
+ DateTime
+ Schedule Time
+
+
+
+ 4:00
+ 4:30
+ 5:00
+ 5:30
+ 6:00
+ 6:30
+ 7:00
+ 7:30
+ 8:00
+
+
+ veto
+
+
+ DateTime
+ Date Time
+
+
+
+ DateTime
+ Date Time
+
+
+
+ Reset now
+
+
+ veto
+
+
+ Number
+ Scene Trigger
+
+
+ Trigger
+ Undo
+
+
+ veto
+
+
+ Number:Density
+ Particulate Matter category 2.5
+
+
+
+ Number
+ VOC Index
+
+
+
+ Number:Time
+ Time
+
+
+ Number:Time
+ Time
+
+
+ 1 minute
+ 3 minutes
+ 5 minutes
+ 10 minutes
+ 15 minutes
+ 20 minutes
+ 30 minutes
+ 40 minutes
+ 60 minutes
+
+
+
+
+ Number
+ Fan Mode
+
+
+ Auto
+ Low
+ Medium
+ High
+ On
+ Off
+
+
+
+
+ Auto
+ Low
+ Medium
+ High
+ On
+ Off
+
+
+
+
+ Number
+ Blind State
+ State if blind is moving up, down or stopped
+
+
+ Stopped
+ Up
+ Down
+
+
+
+
+ Stopped
+ Up
+ Down
+
+
+
+
+ Dimmer
+ Blind Level
+ Current blind level
+ Rollershutter
+
+ OpenLevel
+
+ veto
+
+
+ Number
+ OTA Status
+ Over-the-air overall status
+
+
+ Up to date
+ Update available
+
+
+
+
+ Number
+ OTA State
+ Over-the-air current state
+
+
+ Ready to check
+ Check in progress
+ Ready to download
+ Download in progress
+ Update in progress
+ Update failed
+ Ready to update
+ Check failed
+ Download failed
+ Update complete
+ Battery check failed
+
+
+
+
+ Number:Dimensionless
+ OTA Progress
+ Over-the-air update progress
+
+
+
+ String
+ Links
+ Linked controllers and sensors
+ veto
+
+
+ String
+ Link Candidates
+ 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 @@
+
+
+
+
+
+
+
+
+ Color Light
+ Light with color support
+
+
+
+ Light Powered
+ Power state of light
+ veto
+
+
+ Brightness
+ Brightness of light in percent
+ veto
+
+
+ Color Temperature
+ Color temperature from cold (0 %) to warm (100 %)
+ veto
+
+
+ Color Temperature Kelvin
+ Color temperature of a bulb in Kelvin
+ veto
+
+
+ Color
+ 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 @@
+
+
+
+
+
+
+
+
+ Contact Sensor
+ Sensor tracking if windows or doors are open
+
+
+
+ Contact State
+ State if door or window is open or closed
+
+
+ Battery Charge Level
+ 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 @@
+
+
+
+
+
+
+
+
+ Dimmable Light
+ Light with brightness support
+
+
+
+ Light Powered
+ Power state of light
+ veto
+
+
+ Brightness
+ 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 @@
+
+
+
+
+
+
+
+
+ Two Button Shortcut
+ Shortcut controller with two buttons
+
+
+
+ Button 1 Trigger
+ Trigger of first button
+
+
+ Button 2 Trigger
+ Trigger of second button
+
+
+ Battery Charge Level
+ 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 @@
+
+
+
+
+ DIRIGERA Gateway
+ IKEA Gateway for smart products
+
+
+
+
+ Home Location
+ Location in latitude, longitude coordinates
+
+
+ Sunrise
+ Date and time of next sunrise
+
+
+ Sunset
+ Date and time of next sunset
+
+
+ Pairing
+ Sets DIRIGERA hub into pairing mode
+
+
+ OTA Status
+ Over-the-air overall status
+
+
+ OTA State
+ Over-the-air current state
+
+
+ OTA Progress
+ Over-the-air update progress
+
+
+ Gateway Statistics
+ 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 @@
+
+
+
+
+
+
+
+
+ Light Controller
+ Controller to handle light attributes
+
+
+
+ Light Preset
+ Light presets for different times of the day
+
+
+ Battery Charge Level
+ 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 @@
+
+
+
+
+
+
+
+
+ Light Sensor
+ 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 @@
+
+
+
+
+
+
+
+
+ Motion Light Sensor
+ Sensor detecting motion events and measures light level
+
+
+
+ Motion Detected
+ Motion detected by the device
+
+
+ Active Duration
+ Keep connected devices active for this duration
+
+
+ Illuminance
+ Illuminance in Lux
+
+
+ Battery Charge Level
+ Battery charge level in percent
+
+
+ Activity Schedule
+ Schedule when the sensor shall be active
+
+
+ Activity Schedule Start
+ Start time of sensor activity
+
+
+ Activity Schedule End
+ End time of sensor activity
+
+
+ Light Preset
+ 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 @@
+
+
+
+
+
+
+
+
+ Motion Sensor
+ Sensor detecting motion events
+
+
+
+ Detection Flag
+ Flag if detection happened
+
+
+ Active Duration
+ Keep connected devices active for this duration
+
+
+ Battery Charge Level
+ Battery charge level in percent
+
+
+ Activity Schedule
+ Schedule when the sensor shall be active
+
+
+ Activity Schedule Start
+ Start time of sensor activity
+
+
+ Activity Schedule End
+ End time of sensor activity
+
+
+ Light Preset
+ 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
+ Power plug with control of power state, startup behavior, hardware on/off button and status light
+
+
+
+ Plug Powered
+ 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
+ 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
+ Scene from IKEA home smart App which can be triggered
+
+
+
+ Scene Trigger
+ Perform / undo scene execution
+
+
+ Last Trigger
+ 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
+ Simple plug with control of power state and startup behavior
+
+
+
+ Plug Powered
+ 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 @@
+
+
+
+
+
+
+
+
+ Single Button Shortcut
+ Shortcut controller with one button
+
+
+
+ Button 1 Trigger
+ Trigger of first button
+
+
+ Battery Charge Level
+ 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 @@
+
+
+
+
+
+
+
+
+ Smart Power Plug
+ Power plug with electricity measurements
+
+
+
+ Plug Powered
+ Power state of plug
+ veto
+
+
+
+
+ Electric Power
+ Electric power delivered by plug
+
+
+ Total Energy
+ Total energy consumption
+
+
+ Energy since Reset
+ Energy consumption since last reset
+
+
+ Reset Date Time
+ Date and time of last reset
+
+
+ Plug Current
+ Electric current measured by plug
+
+
+ Plug Voltage
+ 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 @@
+
+
+
+
+
+
+
+
+ Sound Controller
+ Controller for speakers
+
+
+
+ Battery Charge Level
+ 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
+ Speaker with player activities
+
+
+
+ Media Control
+ Media control play, pause, next, previous
+
+
+ Volume Control
+ Control volume in percent
+
+
+ Mute Control
+ Mute current audio without stop playing
+
+
+ Shuffle
+ Control shuffle mode
+
+
+ Cross Fade
+ Cross fading between tracks
+
+
+ Repeat
+ Repeat Mode
+
+
+ Media Title
+ Title of a played media file
+
+
+ Image
+ 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 @@
+
+
+
+
+
+
+
+
+ Switch Light
+ Light with switch ON, OFF capability
+
+
+
+ Light Powered
+ 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 @@
+
+
+
+
+
+
+
+
+ Temperature Light
+ Light with color temperature support
+
+
+
+ Light Powered
+ Power state of light
+ veto
+
+
+ Light Brightness
+ Brightness of light in percent
+ veto
+
+
+ Color Temperature
+ Color temperature from cold (0 %) to warm (100 %)
+ veto
+
+
+ Color Temperature Kelvin
+ 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 @@
+
+
+
+
+
+
+
+
+ Water Sensor
+ Sensor to detect water leaks
+
+
+
+ Leak Detection
+ Water leak detection
+
+
+ Battery Charge Level
+ 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