[dirigera] Initial contribution (#17719)
* feature-squash Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>pull/18733/merge
parent
dc3f53c0d5
commit
78c4962b6a
|
@ -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
|
||||
|
|
|
@ -406,6 +406,11 @@
|
|||
<artifactId>org.openhab.binding.digitalstrom</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.dirigera</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.dlinksmarthome</artifactId>
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
<img align="right" height="150" src="doc/follow-sun.png">
|
||||
|
||||
[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`.
|
||||
|
||||
<img align="center" width="500" src="doc/thing-properties.png">
|
||||
|
||||
### 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 |
|
||||
|
||||
<img align="right" width="300" src="doc/link-candidates.png">
|
||||
|
||||
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 |
|
||||
|
||||
<img align="right" width="150" src="doc/light-presets.png">
|
||||
|
||||
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 [<deviceId> | all] - Print JSON data
|
||||
Usage: openhab:dirigera debug [<deviceId> | 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!
|
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.dirigera</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Dirigera Binding</name>
|
||||
<dependencies>
|
||||
<!-- version needs to match with other projects like org.openhab.io.openhabcloud.pom.xml -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20231013</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.dirigera-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-dirigera" description="Dirigera Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dirigera/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
|
@ -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<ThingTypeUID> 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<ThingTypeUID> IGNORE_THING_TYPES_UIDS = Set.of(THING_TYPE_LIGHT_SENSOR, THING_TYPE_IGNORE);
|
||||
|
||||
public static final List<String> 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<String, Integer> OTA_STATUS_MAP = Map.of("upToDate", 0, "updateAvailable", 1);
|
||||
public static final Map<String, Integer> OTA_STATE_MAP = new HashMap<String, Integer>() {
|
||||
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<String, Integer> STARTUP_BEHAVIOR_MAPPING = Map.of("startPrevious", 0, "startOn", 1,
|
||||
"startOff", 2, "startToggle", 3);
|
||||
public static final Map<Integer, String> STARTUP_BEHAVIOR_REVERSE_MAPPING = reverseStateMapping(
|
||||
STARTUP_BEHAVIOR_MAPPING);
|
||||
|
||||
/**
|
||||
* DIRIGERA property to openHAB channel mappings
|
||||
*/
|
||||
public static final Map<String, String> AIR_PURIFIER_MAP = new HashMap<String, String>() {
|
||||
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<String, String> 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<String, String> BLIND_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL);
|
||||
|
||||
public static final Map<String, String> 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<String, String> 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<String, String> CONTACT_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL, "isOpen", CHANNEL_CONTACT);
|
||||
|
||||
public static final Map<String, String> LIGHT_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL, "circadianPresets", CHANNEL_LIGHT_PRESET);
|
||||
|
||||
public static final Map<String, String> LIGHT_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"illuminance", CHANNEL_ILLUMINANCE);
|
||||
|
||||
public static final Map<String, String> 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<String, String> 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<String, String> REPEATER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME);
|
||||
|
||||
public static final Map<String, String> SCENE_MAP = Map.of("lastTriggered", CHANNEL_TRIGGER);
|
||||
|
||||
public static final Map<String, String> SHORTCUT_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL);
|
||||
|
||||
public static final Map<String, String> SMART_PLUG_MAP = new HashMap<String, String>() {
|
||||
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<String, String> SOUND_CONTROLLER_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL);
|
||||
|
||||
public static final Map<String, String> 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<String, String> 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<String, String> WATER_SENSOR_MAP = Map.of(PROPERTY_CUSTOM_NAME, CHANNEL_CUSTOM_NAME,
|
||||
"batteryPercentage", CHANNEL_BATTERY_LEVEL, "waterLeakDetected", CHANNEL_LEAK_DETECTION);
|
||||
|
||||
public static Map<Integer, String> reverseStateMapping(Map<String, Integer> mapping) {
|
||||
Map<Integer, String> reverseMap = new HashMap<>();
|
||||
for (Map.Entry<String, Integer> entry : mapping.entrySet()) {
|
||||
reverseMap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
return reverseMap;
|
||||
}
|
||||
|
||||
public static Map<String, String> reverse(Map<String, String> mapping) {
|
||||
Map<String, String> reverseMap = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : mapping.entrySet()) {
|
||||
reverseMap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
return reverseMap;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ChannelUID, StateDescriptionFragment> 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<String> linkedItems = null;
|
||||
ItemChannelLinkRegistry compareRegistry = itemChannelLinkRegistry;
|
||||
if (compareRegistry != null) {
|
||||
linkedItems = compareRegistry.getLinkedItemNames(channelUid);
|
||||
}
|
||||
postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUid,
|
||||
linkedItems != null ? linkedItems : Set.of(), stateDescriptionFragment, old));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = "";
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<String> 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<String> 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<String> 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<DirigeraHandler> getHubs() {
|
||||
return thingRegistry.getAll().stream().map(thing -> thing.getHandler())
|
||||
.filter(DirigeraHandler.class::isInstance).map(DirigeraHandler.class::cast).toList();
|
||||
}
|
||||
|
||||
private List<DebugHandler> getDevices() {
|
||||
return thingRegistry.getAll().stream().map(thing -> thing.getHandler()).filter(DebugHandler.class::isInstance)
|
||||
.map(DebugHandler.class::cast).toList();
|
||||
}
|
||||
|
||||
private List<String> getDeviceIds() {
|
||||
return getDevices().stream().map(debugHandler -> debugHandler.getDeviceId()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(buildCommandUsage(CMD_TOKEN, "Get token from DIRIGERA hub"),
|
||||
buildCommandUsage(CMD_JSON + " [<deviceId> | all]", "Print JSON data"),
|
||||
buildCommandUsage(CMD_DEBUG + " [<deviceId> | all] [true | false] ",
|
||||
"Enable / disable detailed debugging for specific / all devices"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new DirigeraConsoleCommandCompleter();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<ThingTypeUID> 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<String, Object> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<PowerListener> 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<String, String> property2ChannelMap;
|
||||
protected Map<String, String> channel2PropertyMap;
|
||||
|
||||
// cache to handle each refresh command properly
|
||||
protected Map<String, State> 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<String> hardLinks = new ArrayList<>(Arrays.asList("undef"));
|
||||
protected List<String> softLinks = new ArrayList<>();
|
||||
protected List<String> linkCandidateTypes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Lists for canReceive and can Send capabilities
|
||||
*/
|
||||
protected List<String> receiveCapabilities = new ArrayList<>();
|
||||
protected List<String> 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<String, String> 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<String, Object> 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<String, String> 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<String> 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<String, State> initializeCache(Map<String, String> mapping) {
|
||||
final Map<String, State> 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<String> getLinks() {
|
||||
return new ArrayList<String>(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<String> 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<String> display = new ArrayList<>();
|
||||
List<CommandOption> linkCommandOptions = new ArrayList<>();
|
||||
List<String> 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<String> possibleCandidates = gateway().model().getDevicesForTypes(linkCandidateTypes);
|
||||
List<String> candidates = new ArrayList<>();
|
||||
possibleCandidates.forEach(entry -> {
|
||||
if (!hardLinks.contains(entry) && !softLinks.contains(entry)) {
|
||||
candidates.add(entry);
|
||||
}
|
||||
});
|
||||
|
||||
List<String> display = new ArrayList<>();
|
||||
List<CommandOption> 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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<String, Integer> 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<Integer, String> fanModeToState = reverseStateMapping(FAN_MODES);
|
||||
|
||||
public AirPurifierHandler(Thing thing, Map<String, String> 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<String> 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Integer> BLIND_STATES = Map.of("stopped", 0, "up", 1, "down", 2);
|
||||
public static Map<Integer, String> blindNumberToState = reverseStateMapping(BLIND_STATES);
|
||||
|
||||
public BlindHandler(Thing thing, Map<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> storage;
|
||||
public Map<String, String> sceneMapping = new HashMap<>();
|
||||
private Map<String, Instant> 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<String> CLICK_PATTERNS = List.of(SINGLE_PRESS, DOUBLE_PRESS, LONG_PRESS);
|
||||
|
||||
public BaseShortcutController(Thing thing, Map<String, String> mapping, Storage<String> 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
|
@ -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<String, String> relations = new TreeMap<>();
|
||||
|
||||
public DoubleShortcutControllerHandler(Thing thing, Map<String, String> mapping, Storage<String> 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<String, String> 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<String, String> 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();
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> mapping, Storage<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<LightCommand.Action, LightCommand> lastUserMode = new HashMap<>();
|
||||
|
||||
private List<LightCommand> lightRequestQueue = new ArrayList<>();
|
||||
private Instant readyForNextCommand = Instant.now();
|
||||
private JSONObject placeHolder = new JSONObject();
|
||||
private boolean executingCommand = false;
|
||||
|
||||
public BaseLight(Thing thing, Map<String, String> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String, String> properties = new TreeMap<>(editProperties());
|
||||
Iterator<String> 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<String> 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))));
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> relations = new TreeMap<>();
|
||||
|
||||
public MotionLightSensorHandler(Thing thing, Map<String, String> 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<String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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<String> getDevicesForTypes(List<String> types);
|
||||
|
||||
/**
|
||||
* Returns a list of all device id's.
|
||||
*
|
||||
* @return list of all connected devices
|
||||
*/
|
||||
List<String> 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<String> 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<String, String> 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<String, Object> 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<Integer, Integer> MAPPING_RGB_TEMPERETATURE = new TreeMap<>();
|
||||
private static final TreeMap<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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));
|
||||
}
|
||||
}
|
|
@ -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<String, DiscoveryResult> resultMap = new HashMap<>();
|
||||
private Map<String, String> templates = new HashMap<>();
|
||||
private List<String> 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<String> previousDevices = new ArrayList<>();
|
||||
previousDevices.addAll(devices);
|
||||
|
||||
// first get devices
|
||||
List<String> 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<String> getResolvedDeviceList() {
|
||||
List<String> deviceList = new ArrayList<>();
|
||||
if (!model.isNull(PROPERTY_DEVICES)) {
|
||||
JSONArray devices = model.getJSONArray(PROPERTY_DEVICES);
|
||||
Iterator<Object> 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<String, String> 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<String> getAllDeviceIds() {
|
||||
List<String> deviceList = new ArrayList<>();
|
||||
if (!model.isNull(PROPERTY_DEVICES)) {
|
||||
JSONArray devices = model.getJSONArray(PROPERTY_DEVICES);
|
||||
Iterator<Object> entries = devices.iterator();
|
||||
while (entries.hasNext()) {
|
||||
JSONObject entry = (JSONObject) entries.next();
|
||||
deviceList.add(entry.getString(PROPERTY_DEVICE_ID));
|
||||
}
|
||||
}
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
private List<String> getAllSceneIds() {
|
||||
List<String> sceneList = new ArrayList<>();
|
||||
if (!model.isNull(SCENES)) {
|
||||
JSONArray scenes = model.getJSONArray(SCENES);
|
||||
Iterator<Object> 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<String> getDevicesForTypes(List<String> types) {
|
||||
List<String> 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<Object> 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<Object> 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<String, Object> getPropertiesFor(String id) {
|
||||
final Map<String, Object> 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<String, String> getRelations(String relationId) {
|
||||
final TreeMap<String, String> relationsMap = new TreeMap<>();
|
||||
List<String> 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<String, String> 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<String, Object> 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<String> 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<String, String> 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<String> 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 "";
|
||||
}
|
||||
}
|
|
@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Instant> 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> websocketClient = Optional.empty();
|
||||
private Optional<Session> 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<String, Instant> getPingPongMap() {
|
||||
return pingPongMap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="dirigera" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>DIRIGERA Binding</name>
|
||||
<description>IKEA Smarthome binding for DIRIGERA Gateway</description>
|
||||
<connection>local</connection>
|
||||
<discovery-methods>
|
||||
<discovery-method>
|
||||
<service-type>mdns</service-type>
|
||||
<discovery-parameters>
|
||||
<discovery-parameter>
|
||||
<name>mdnsServiceType</name>
|
||||
<value>_ihsp._tcp.local.</value>
|
||||
</discovery-parameter>
|
||||
</discovery-parameters>
|
||||
</discovery-method>
|
||||
</discovery-methods>
|
||||
</addon:addon>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:dirigera:base-device">
|
||||
<parameter name="id" type="text">
|
||||
<label>Device Id</label>
|
||||
<description>Unique id of this device</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:dirigera:color-light">
|
||||
<parameter name="id" type="text">
|
||||
<label>Device Id</label>
|
||||
<description>Unique id of this device</description>
|
||||
</parameter>
|
||||
<parameter name="fadeTime" type="integer" unit="ms">
|
||||
<label>Fade Time</label>
|
||||
<description>Required time for fade sequnce to color or brightness</description>
|
||||
<default>750</default>
|
||||
</parameter>
|
||||
<parameter name="fadeSequence" type="integer">
|
||||
<label>Fade Sequence</label>
|
||||
<description>Define sequence if several light parameters are changed at once</description>
|
||||
<options>
|
||||
<option value="0">First brightness, then color</option>
|
||||
<option value="1">First color, then brightness</option>
|
||||
</options>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:dirigera:gateway">
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<label>IP Address</label>
|
||||
<description>Gateway IP Address</description>
|
||||
</parameter>
|
||||
<parameter name="id" type="text">
|
||||
<label>Device Id</label>
|
||||
<description>Unique id of this gateway</description>
|
||||
</parameter>
|
||||
<parameter name="discovery" type="boolean">
|
||||
<label>Discovery</label>
|
||||
<description>Configure if paired devices shall be detected by discovery</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:dirigera:light-device">
|
||||
<parameter name="id" type="text">
|
||||
<label>Device Id</label>
|
||||
<description>Unique id of this device</description>
|
||||
</parameter>
|
||||
<parameter name="fadeTime" type="integer" unit="ms">
|
||||
<label>Fade Time</label>
|
||||
<description>Required time for fade sequnce to color or brightness</description>
|
||||
<default>750</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
|
@ -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
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="air-purifier">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Air Purifier</label>
|
||||
<description>Air cleaning device with particle filter</description>
|
||||
|
||||
<channels>
|
||||
<channel id="fan-mode" typeId="fan-mode">
|
||||
<label>Fan Mode</label>
|
||||
<description>Fan on, off, speed or automatic behavior</description>
|
||||
</channel>
|
||||
<channel id="fan-speed" typeId="dimmer">
|
||||
<label>Fan Speed</label>
|
||||
<description>Manual regulation of fan speed</description>
|
||||
</channel>
|
||||
<channel id="fan-runtime" typeId="time">
|
||||
<label>Fan Runtime</label>
|
||||
<description>Fan runtime in minutes</description>
|
||||
</channel>
|
||||
<channel id="filter-elapsed" typeId="time">
|
||||
<label>Filter Elapsed</label>
|
||||
<description>Filter elapsed time in minutes</description>
|
||||
</channel>
|
||||
<channel id="filter-remain" typeId="time">
|
||||
<label>Filter Remain</label>
|
||||
<description>Remaining filter time in minutes</description>
|
||||
</channel>
|
||||
<channel id="filter-lifetime" typeId="time">
|
||||
<label>Filter Lifetime</label>
|
||||
<description>Filter lifetime in minutes</description>
|
||||
</channel>
|
||||
<channel id="filter-alarm" typeId="switch-ro">
|
||||
<label>Filter Alarm</label>
|
||||
<description>Filter alarm signal</description>
|
||||
</channel>
|
||||
<channel id="particulate-matter" typeId="pm25">
|
||||
<label>Particulate Matter</label>
|
||||
<description>Category 2.5 particulate matter</description>
|
||||
</channel>
|
||||
<channel id="disable-status-light" typeId="disable-status-light"/>
|
||||
<channel id="child-lock" typeId="child-lock"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="air-quality">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Air Quality</label>
|
||||
<description>Air measure for temperature, humidity and particles</description>
|
||||
|
||||
<channels>
|
||||
<channel id="temperature" typeId="system.indoor-temperature">
|
||||
<label>Temperature</label>
|
||||
<description>Current indoor temperature</description>
|
||||
</channel>
|
||||
<channel id="humidity" typeId="system.atmospheric-humidity">
|
||||
<label>Humidity</label>
|
||||
<description>Atmospheric humidity in percent</description>
|
||||
</channel>
|
||||
<channel id="particulate-matter" typeId="pm25">
|
||||
<label>Particulate Matter</label>
|
||||
<description>Category 2.5 particulate matter</description>
|
||||
</channel>
|
||||
<channel id="voc-index" typeId="voc">
|
||||
<label>VOC Index</label>
|
||||
<description>Relative VOC intensity compared to recent history</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="blind-controller">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Blinds Controller</label>
|
||||
<description>Controller to open and close blinds</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="blind">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Blind</label>
|
||||
<description>Window or door blind</description>
|
||||
|
||||
<channels>
|
||||
<channel id="blind-state" typeId="blind-state"/>
|
||||
<channel id="blind-level" typeId="blind-dimmer"/>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,347 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="text">
|
||||
<item-type>String</item-type>
|
||||
<label>Simple Text</label>
|
||||
</channel-type>
|
||||
<channel-type id="custom-name">
|
||||
<item-type>String</item-type>
|
||||
<label>Custom Name</label>
|
||||
<description>Name given from IKEA home smart</description>
|
||||
</channel-type>
|
||||
<channel-type id="light-preset">
|
||||
<item-type>String</item-type>
|
||||
<label>Light Preset</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="Off">Off</option>
|
||||
<option value="Warm">Warm</option>
|
||||
<option value="Slowdown">Slowdown</option>
|
||||
<option value="Smooth">Smooth</option>
|
||||
<option value="Bright">Bright</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
<channel-type id="sensor-schedule">
|
||||
<item-type>Number</item-type>
|
||||
<label>Sensor Schedule</label>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Always</option>
|
||||
<option value="1">Follow Sun</option>
|
||||
<option value="2">Time schedule</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Always</option>
|
||||
<option value="1">Follow Sun</option>
|
||||
<option value="2">Time schedule</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="repeat">
|
||||
<item-type>Number</item-type>
|
||||
<label>Repeat Options</label>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Title</option>
|
||||
<option value="2">Playlist</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Title</option>
|
||||
<option value="2">Playlist</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="startup">
|
||||
<item-type>Number</item-type>
|
||||
<label>Startup Behavior</label>
|
||||
<description>Startup behavior after power cutoff</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Previous</option>
|
||||
<option value="1">On</option>
|
||||
<option value="2">Off</option>
|
||||
<option value="3">Toggle</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Previous</option>
|
||||
<option value="1">On</option>
|
||||
<option value="2">Off</option>
|
||||
<option value="3">Toggle</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="disable-status-light">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Disable Status Light</label>
|
||||
<description>Disable status light on device</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="child-lock">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Child Lock</label>
|
||||
<description>Child lock for button on device</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>On Off Switch</label>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="switch-ro">
|
||||
<item-type>Switch</item-type>
|
||||
<label>On Off Switch</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Alarm Switch</label>
|
||||
<category>Alarm</category>
|
||||
<tags>
|
||||
<tag>Alarm</tag>
|
||||
<tag>Water</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="illuminance">
|
||||
<item-type>Number:Illuminance</item-type>
|
||||
<label>Illuminance</label>
|
||||
<description>Illuminance in Lux </description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Light</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="contact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact</label>
|
||||
<category>Contact</category>
|
||||
<tags>
|
||||
<tag>OpenState</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="dimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Dimmer</label>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="image">
|
||||
<item-type>Image</item-type>
|
||||
<label>Image</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="schedule-start-time">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Schedule Time</label>
|
||||
<state pattern="%1$tH:%1$tM"/>
|
||||
<command>
|
||||
<options>
|
||||
<option value="16:00">16:00</option>
|
||||
<option value="16:30">16:30</option>
|
||||
<option value="17:00">17:00</option>
|
||||
<option value="17:30">17:30</option>
|
||||
<option value="18:00">18:00</option>
|
||||
<option value="18:30">18:30</option>
|
||||
<option value="19:00">19:00</option>
|
||||
<option value="19:30">19:30</option>
|
||||
<option value="20:00">20:00</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
<channel-type id="schedule-end-time">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Schedule Time</label>
|
||||
<state pattern="%1$tH:%1$tM"/>
|
||||
<command>
|
||||
<options>
|
||||
<option value="04:00">4:00</option>
|
||||
<option value="04:30">4:30</option>
|
||||
<option value="05:00">5:00</option>
|
||||
<option value="05:30">5:30</option>
|
||||
<option value="06:00">6:00</option>
|
||||
<option value="06:30">6:30</option>
|
||||
<option value="07:00">7:00</option>
|
||||
<option value="07:30">7:30</option>
|
||||
<option value="08:00">8:00</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="datetime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Time</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM"/>
|
||||
</channel-type>
|
||||
<channel-type id="datetime-reset">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Date Time</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM"/>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Reset now</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="scene-trigger">
|
||||
<item-type>Number</item-type>
|
||||
<label>Scene Trigger</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Trigger</option>
|
||||
<option value="1">Undo</option>
|
||||
</options>
|
||||
</command>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="pm25">
|
||||
<item-type unitHint="µg/m³">Number:Density</item-type>
|
||||
<label>Particulate Matter category 2.5</label>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="voc">
|
||||
<item-type>Number</item-type>
|
||||
<label>VOC Index</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="time">
|
||||
<item-type unitHint="min">Number:Time</item-type>
|
||||
<label>Time</label>
|
||||
</channel-type>
|
||||
<channel-type id="duration">
|
||||
<item-type unitHint="min">Number:Time</item-type>
|
||||
<label>Time</label>
|
||||
<command>
|
||||
<options>
|
||||
<option value="1 min">1 minute</option>
|
||||
<option value="3 min">3 minutes</option>
|
||||
<option value="5 min">5 minutes</option>
|
||||
<option value="10 min">10 minutes</option>
|
||||
<option value="15 min">15 minutes</option>
|
||||
<option value="20 min">20 minutes</option>
|
||||
<option value="30 min">30 minutes</option>
|
||||
<option value="40 min">40 minutes</option>
|
||||
<option value="60 min">60 minutes</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
<channel-type id="fan-mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fan Mode</label>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Auto</option>
|
||||
<option value="1">Low</option>
|
||||
<option value="2">Medium</option>
|
||||
<option value="3">High</option>
|
||||
<option value="4">On</option>
|
||||
<option value="5">Off</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Auto</option>
|
||||
<option value="1">Low</option>
|
||||
<option value="2">Medium</option>
|
||||
<option value="3">High</option>
|
||||
<option value="4">On</option>
|
||||
<option value="5">Off</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
<channel-type id="blind-state">
|
||||
<item-type>Number</item-type>
|
||||
<label>Blind State</label>
|
||||
<description>State if blind is moving up, down or stopped</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Stopped</option>
|
||||
<option value="1">Up</option>
|
||||
<option value="2">Down</option>
|
||||
</options>
|
||||
</state>
|
||||
<command>
|
||||
<options>
|
||||
<option value="0">Stopped</option>
|
||||
<option value="1">Up</option>
|
||||
<option value="2">Down</option>
|
||||
</options>
|
||||
</command>
|
||||
</channel-type>
|
||||
<channel-type id="blind-dimmer">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Blind Level</label>
|
||||
<description>Current blind level</description>
|
||||
<category>Rollershutter</category>
|
||||
<tags>
|
||||
<tag>OpenLevel</tag>
|
||||
</tags>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="ota-status">
|
||||
<item-type>Number</item-type>
|
||||
<label>OTA Status</label>
|
||||
<description>Over-the-air overall status</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Up to date</option>
|
||||
<option value="1">Update available</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="ota-state" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>OTA State</label>
|
||||
<description>Over-the-air current state</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Ready to check</option>
|
||||
<option value="1">Check in progress</option>
|
||||
<option value="2">Ready to download</option>
|
||||
<option value="3">Download in progress</option>
|
||||
<option value="4">Update in progress</option>
|
||||
<option value="5">Update failed</option>
|
||||
<option value="6">Ready to update</option>
|
||||
<option value="7">Check failed</option>
|
||||
<option value="8">Download failed</option>
|
||||
<option value="9">Update complete</option>
|
||||
<option value="10">Battery check failed</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="ota-percent" advanced="true">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>OTA Progress</label>
|
||||
<description>Over-the-air update progress</description>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="links" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Links</label>
|
||||
<description>Linked controllers and sensors</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
<channel-type id="link-candidates" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Link Candidates</label>
|
||||
<description>Candidates which can be linked</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="color-light">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Color Light</label>
|
||||
<description>Light with color support</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Light Powered</label>
|
||||
<description>Power state of light</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="brightness" typeId="system.brightness">
|
||||
<label>Brightness</label>
|
||||
<description>Brightness of light in percent</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="color-temperature" typeId="system.color-temperature">
|
||||
<label>Color Temperature</label>
|
||||
<description>Color temperature from cold (0 %) to warm (100 %)</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="color-temperature-abs" typeId="system.color-temperature-abs">
|
||||
<label>Color Temperature Kelvin</label>
|
||||
<description>Color temperature of a bulb in Kelvin</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="color" typeId="system.color">
|
||||
<label>Color</label>
|
||||
<description>Color of light with hue, saturation and brightness</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:color-light"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="contact-sensor">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Contact Sensor</label>
|
||||
<description>Sensor tracking if windows or doors are open</description>
|
||||
|
||||
<channels>
|
||||
<channel id="contact" typeId="contact">
|
||||
<label>Contact State</label>
|
||||
<description>State if door or window is open or closed</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="dimmable-light">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Dimmable Light</label>
|
||||
<description>Light with brightness support</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Light Powered</label>
|
||||
<description>Power state of light</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="brightness" typeId="system.brightness">
|
||||
<label>Brightness</label>
|
||||
<description>Brightness of light in percent</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:light-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="double-shortcut">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Two Button Shortcut</label>
|
||||
<description>Shortcut controller with two buttons</description>
|
||||
|
||||
<channels>
|
||||
<channel id="button1" typeId="system.button">
|
||||
<label>Button 1 Trigger</label>
|
||||
<description>Trigger of first button</description>
|
||||
</channel>
|
||||
<channel id="button2" typeId="system.button">
|
||||
<label>Button 2 Trigger</label>
|
||||
<description>Trigger of second button</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="gateway">
|
||||
<label>DIRIGERA Gateway</label>
|
||||
<description>IKEA Gateway for smart products</description>
|
||||
|
||||
<channels>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
<channel id="location" typeId="text">
|
||||
<label>Home Location</label>
|
||||
<description>Location in latitude, longitude coordinates</description>
|
||||
</channel>
|
||||
<channel id="sunrise" typeId="datetime">
|
||||
<label>Sunrise</label>
|
||||
<description>Date and time of next sunrise</description>
|
||||
</channel>
|
||||
<channel id="sunset" typeId="datetime">
|
||||
<label>Sunset</label>
|
||||
<description>Date and time of next sunset</description>
|
||||
</channel>
|
||||
<channel id="pairing" typeId="switch">
|
||||
<label>Pairing</label>
|
||||
<description>Sets DIRIGERA hub into pairing mode</description>
|
||||
</channel>
|
||||
<channel id="ota-status" typeId="ota-status">
|
||||
<label>OTA Status</label>
|
||||
<description>Over-the-air overall status</description>
|
||||
</channel>
|
||||
<channel id="ota-state" typeId="ota-state">
|
||||
<label>OTA State</label>
|
||||
<description>Over-the-air current state</description>
|
||||
</channel>
|
||||
<channel id="ota-progress" typeId="ota-percent">
|
||||
<label>OTA Progress</label>
|
||||
<description>Over-the-air update progress</description>
|
||||
</channel>
|
||||
<channel id="statistics" typeId="text">
|
||||
<label>Gateway Statistics</label>
|
||||
<description>Several statistics about gateway activities</description>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:gateway"/>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="light-controller">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Light Controller</label>
|
||||
<description>Controller to handle light attributes</description>
|
||||
|
||||
<channels>
|
||||
<channel id="light-preset" typeId="light-preset">
|
||||
<label>Light Preset</label>
|
||||
<description>Light presets for different times of the day</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="light-sensor">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Light Sensor</label>
|
||||
<description>Sensor measuring illuminance in your room</description>
|
||||
|
||||
<channels>
|
||||
<channel id="illuminance" typeId="illuminance"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="motion-light-sensor">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Motion Light Sensor</label>
|
||||
<description>Sensor detecting motion events and measures light level</description>
|
||||
|
||||
<channels>
|
||||
<channel id="motion" typeId="system.motion">
|
||||
<label>Motion Detected</label>
|
||||
<description>Motion detected by the device</description>
|
||||
</channel>
|
||||
<channel id="active-duration" typeId="duration">
|
||||
<label>Active Duration</label>
|
||||
<description>Keep connected devices active for this duration</description>
|
||||
</channel>
|
||||
<channel id="illuminance" typeId="illuminance">
|
||||
<label>Illuminance</label>
|
||||
<description>Illuminance in Lux</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="schedule" typeId="sensor-schedule">
|
||||
<label>Activity Schedule</label>
|
||||
<description>Schedule when the sensor shall be active</description>
|
||||
</channel>
|
||||
<channel id="schedule-start" typeId="schedule-start-time">
|
||||
<label>Activity Schedule Start</label>
|
||||
<description>Start time of sensor activity</description>
|
||||
</channel>
|
||||
<channel id="schedule-end" typeId="schedule-end-time">
|
||||
<label>Activity Schedule End</label>
|
||||
<description>End time of sensor activity</description>
|
||||
</channel>
|
||||
<channel id="light-preset" typeId="light-preset">
|
||||
<label>Light Preset</label>
|
||||
<description>Light presets for different times of the day</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="motion-sensor">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Motion Sensor</label>
|
||||
<description>Sensor detecting motion events</description>
|
||||
|
||||
<channels>
|
||||
<channel id="motion" typeId="system.motion">
|
||||
<label>Detection Flag</label>
|
||||
<description>Flag if detection happened</description>
|
||||
</channel>
|
||||
<channel id="active-duration" typeId="duration">
|
||||
<label>Active Duration</label>
|
||||
<description>Keep connected devices active for this duration</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="schedule" typeId="sensor-schedule">
|
||||
<label>Activity Schedule</label>
|
||||
<description>Schedule when the sensor shall be active</description>
|
||||
</channel>
|
||||
<channel id="schedule-start" typeId="schedule-start-time">
|
||||
<label>Activity Schedule Start</label>
|
||||
<description>Start time of sensor activity</description>
|
||||
</channel>
|
||||
<channel id="schedule-end" typeId="schedule-end-time">
|
||||
<label>Activity Schedule End</label>
|
||||
<description>End time of sensor activity</description>
|
||||
</channel>
|
||||
<channel id="light-preset" typeId="light-preset">
|
||||
<label>Light Preset</label>
|
||||
<description>Light presets for different times of the day</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="power-plug">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Power Plug</label>
|
||||
<description>Power plug with control of power state, startup behavior, hardware on/off button and status light</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Plug Powered</label>
|
||||
<description>Power state of plug </description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="disable-status-light" typeId="disable-status-light"/>
|
||||
<channel id="child-lock" typeId="child-lock"/>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="repeater">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Repeater</label>
|
||||
<description>Repeater to strengthen signal</description>
|
||||
|
||||
<channels>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="scene">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Scene</label>
|
||||
<description>Scene from IKEA home smart App which can be triggered</description>
|
||||
|
||||
<channels>
|
||||
<channel id="trigger" typeId="scene-trigger">
|
||||
<label>Scene Trigger</label>
|
||||
<description>Perform / undo scene execution </description>
|
||||
</channel>
|
||||
<channel id="last-trigger" typeId="datetime">
|
||||
<label>Last Trigger</label>
|
||||
<description>Date and time when last trigger occurred</description>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="simple-plug">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Simple Plug</label>
|
||||
<description>Simple plug with control of power state and startup behavior</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Plug Powered</label>
|
||||
<description>Power state of plug </description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="single-shortcut">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Single Button Shortcut</label>
|
||||
<description>Shortcut controller with one button</description>
|
||||
|
||||
<channels>
|
||||
<channel id="button1" typeId="system.button">
|
||||
<label>Button 1 Trigger</label>
|
||||
<description>Trigger of first button</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="smart-plug">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Smart Power Plug</label>
|
||||
<description>Power plug with electricity measurements</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Plug Powered</label>
|
||||
<description>Power state of plug </description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="disable-status-light" typeId="disable-status-light"/>
|
||||
<channel id="child-lock" typeId="child-lock"/>
|
||||
<channel id="electric-power" typeId="system.electric-power">
|
||||
<label>Electric Power</label>
|
||||
<description>Electric power delivered by plug</description>
|
||||
</channel>
|
||||
<channel id="energy-total" typeId="system.electric-energy">
|
||||
<label>Total Energy</label>
|
||||
<description>Total energy consumption</description>
|
||||
</channel>
|
||||
<channel id="energy-reset" typeId="system.electric-energy">
|
||||
<label>Energy since Reset</label>
|
||||
<description>Energy consumption since last reset</description>
|
||||
</channel>
|
||||
<channel id="reset-date" typeId="datetime-reset">
|
||||
<label>Reset Date Time</label>
|
||||
<description>Date and time of last reset</description>
|
||||
</channel>
|
||||
<channel id="electric-current" typeId="system.electric-current">
|
||||
<label>Plug Current</label>
|
||||
<description>Electric current measured by plug</description>
|
||||
</channel>
|
||||
<channel id="electric-voltage" typeId="system.electric-voltage">
|
||||
<label>Plug Voltage</label>
|
||||
<description>Electric potential of plug</description>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sound-controller">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Sound Controller</label>
|
||||
<description>Controller for speakers</description>
|
||||
|
||||
<channels>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="speaker">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Speaker</label>
|
||||
<description>Speaker with player activities</description>
|
||||
|
||||
<channels>
|
||||
<channel id="media-control" typeId="system.media-control">
|
||||
<label>Media Control</label>
|
||||
<description>Media control play, pause, next, previous</description>
|
||||
</channel>
|
||||
<channel id="volume" typeId="system.volume">
|
||||
<label>Volume Control</label>
|
||||
<description>Control volume in percent</description>
|
||||
</channel>
|
||||
<channel id="mute" typeId="system.mute">
|
||||
<label>Mute Control</label>
|
||||
<description>Mute current audio without stop playing</description>
|
||||
</channel>
|
||||
<channel id="shuffle" typeId="switch">
|
||||
<label>Shuffle</label>
|
||||
<description>Control shuffle mode</description>
|
||||
</channel>
|
||||
<channel id="crossfade" typeId="switch">
|
||||
<label>Cross Fade</label>
|
||||
<description>Cross fading between tracks</description>
|
||||
</channel>
|
||||
<channel id="repeat" typeId="repeat">
|
||||
<label>Repeat</label>
|
||||
<description>Repeat Mode</description>
|
||||
</channel>
|
||||
<channel id="media-title" typeId="system.media-title">
|
||||
<label>Media Title</label>
|
||||
<description>Title of a played media file</description>
|
||||
</channel>
|
||||
<channel id="image" typeId="image">
|
||||
<label>Image</label>
|
||||
<description>Current playing track image</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="switch-light">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Switch Light</label>
|
||||
<description>Light with switch ON, OFF capability</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Light Powered</label>
|
||||
<description>Power state of light</description>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="temperature-light">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Temperature Light</label>
|
||||
<description>Light with color temperature support</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power">
|
||||
<label>Light Powered</label>
|
||||
<description>Power state of light</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="brightness" typeId="system.brightness">
|
||||
<label>Light Brightness</label>
|
||||
<description>Brightness of light in percent</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="color-temperature" typeId="system.color-temperature">
|
||||
<label>Color Temperature</label>
|
||||
<description>Color temperature from cold (0 %) to warm (100 %)</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="color-temperature-abs" typeId="system.color-temperature-abs">
|
||||
<label>Color Temperature Kelvin</label>
|
||||
<description>Color temperature of a bulb in Kelvin</description>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel>
|
||||
<channel id="startup" typeId="startup"/>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:color-light"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dirigera"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="water-sensor">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="gateway"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Water Sensor</label>
|
||||
<description>Sensor to detect water leaks</description>
|
||||
|
||||
<channels>
|
||||
<channel id="leak" typeId="alarm">
|
||||
<label>Leak Detection</label>
|
||||
<description>Water leak detection</description>
|
||||
</channel>
|
||||
<channel id="battery-level" typeId="system.battery-level">
|
||||
<label>Battery Charge Level</label>
|
||||
<description>Battery charge level in percent</description>
|
||||
</channel>
|
||||
<channel id="custom-name" typeId="custom-name"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:dirigera:base-device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"attributes": {
|
||||
"coordinates": {
|
||||
"latitude": %s,
|
||||
"longitude": %s,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"attributes": {
|
||||
"coordinates": {}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
]
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"attributes": {
|
||||
"sensorConfig": {
|
||||
"scheduleOn": false
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue