[shelly] Improved documentation, support for UNI, 1L, Color Bulb (#9508)

* This PR merges the 2.5 code base with new features, a bunch of bug fixes
and improved documentation. Parts of the code has been re-factored and
vaious issues are fixed/optimized along the way.

Signed-off-by: Markus Michels <markus7017@gmail.com>

* Minor fixed, support for Shelly Color Bulb, Shelly Motion

Signed-off-by: Markus Michels <markus7017@gmail.com>

* Review changes applied

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review change applied

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review changes

Signed-off-by: Markus Michels <markus7017@gmail.com>

* Review changes

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review changes

Signed-off-by: Markus Michels <markus7017@gmail.com>

* README updated

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review change

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review change

Signed-off-by: Markus Michels <markus7017@gmail.com>
pull/10046/head
Markus Michels 2021-02-03 22:25:19 +01:00 committed by GitHub
parent bc5ffb26e2
commit c4a3b1e6ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1939 additions and 1048 deletions

View File

@ -7,19 +7,20 @@ Allterco provides a rich set of smart home devices. All of them are WiFi enabled
The binding is officially acknowledged by Allterco and openHAB is listed as a reference and directly supports the openHAB community.
The binding controls the devices independently from the Allterco Shelly Cloud (in fact it can be disabled).
The binding co-exists with Shelly App for Smartphones, Shelly Web App, Shelly Cloud, mqqt and other 3rd party Apps.
The binding co-exists with Shelly App for Smartphones, Shelly Device Web UI, Shelly Cloud, MQTT and other 3rd party Apps.
The binding focuses on reporting the device status and device control.
Initial setup and device configuration has to be performed using the Shelly Apps (Web or Smartphone App).
Initial setup and device configuration has to be performed using the Shelly Apps (Web UI or Smartphone App).
The binding gets in sync with the next status refresh.
Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB Shelly integration, e.g. firmware update, network communication or log filtering.
## Supported Devices
| Thing Type | Model | Vendor ID |
| thing-type | Model | Vendor ID |
|--------------------|--------------------------------------------------------|-----------|
| shelly1 | Shelly 1 Single Relay Switch | SHSW-1 |
| shelly1l | Shelly 1L Single Relay Switch | SHSW-L |
| shelly1pm | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM |
| shelly2-relay | Shelly Double Relay Switch in relay mode | SHSW-21 |
| shelly2-roller | Shelly2 in Roller Mode | SHSW-21 |
@ -35,11 +36,14 @@ Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB
| shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 |
| shellyrgbw2 | Shelly RGB Controller | SHRGBW2 |
| shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 |
| shellybulbduo | Shelly Duo (White Mode) | SHBDUO-1 |
| shellybulbduo | Shelly Duo White | SHBDUO-1 |
| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 |
| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 |
| shellyvintage | Shelly Vintage (White Mode) | SHVIN-1 |
| shellyht | Shelly Sensor (temp+humidity) | SHHT-1 |
| shellyflood | Shelly Flood Sensor | SHWT-1 |
| shellysmoke | Shelly Smoke Sensor | SHSM-1 |
| shellymotion | Shelly Motion Sensor | SHMOS-01 |
| shellygas | Shelly Gas Sensor | SHGS-1 |
| shellydw | Shelly Door/Window | SHDW-1 |
| shellydw2 | Shelly Door/Window 2 | SHDW-2 |
@ -115,6 +119,14 @@ For those open the case, press that button and the LED starts flashing.
Wait a moment and then start the discovery. The device should show up in the Inbox and can be added.
Sometimes you need to run the discovery multiple times.
### Roller Favorites
Firmware 1.9.2 for Shelly 2.5 in roller mode supports so called favorites for positions.
You could use the Shelly App to setup 4 different positions (percentage) and assign id 1-4.
The channel `roller#rollerFav` allows to select those from openHAB and the roller moves to the desired position.
In the Thing configuration you could also configure an id when the `roller#control` channel receives UP or DOWN.
Values 1-4 are selecting the corresponding favorite id in the Shelly App, 0 means no favorite.
### Thing Status
The binding sets the following Thing status depending on the device status:
@ -168,6 +180,8 @@ You could also create a rule to catch those status changes or device alarms (see
|eventsSensorReport|true: register event "posted updated sensor data" | no |true for sensor devices |
|eventsCoIoT |true: Listen for CoIoT/COAP events | no |true for battery devices, false for others |
|eventsRoller |true: register event "trigger" when the roller updates status | no |true for roller devices |
|favoriteUP |0-4: Favorite id for UP (see Roller Favorites) | no |0 = no favorite id |
|favoriteDOWN |0-4: Favorite id for DOWN (see Roller Favorites) | no |0 = no favorite id |
### General Notes
@ -189,6 +203,7 @@ Every device has a channel group `device` with the following channels:
| |updateAvailable |Switch |yes |ON: A firmware update is available |
| |statusLed |Switch |r/w |ON: Status LED is disabled, OFF: LED enabled |
| |powerLed |Switch |r/w |ON: Power LED is disabled, OFF: LED enabled |
| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. |
Availability of channels is depending on the device type.
The binding detects many of those channels on-the-fly (when Thing changes to ONLINE state) and adjusts the Thing's channel structure.
@ -296,6 +311,8 @@ Depending on the device type and firmware release channels might be not availabl
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
| |input |Switch |yes |ON: Input/Button is powered, see general notes on channels |
| |button |Trigger |yes |Event trigger with payload, see SHORT_PRESSED or LONG_PRESSED |
| |lastEvent |String |yes |Last event type (S/SS/SSS/L) |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
@ -304,6 +321,34 @@ Depending on the device type and firmware release channels might be not availabl
| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) |
| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) |
### Shelly 1L (thing-type: shelly1l)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|relay |output |Switch |r/w |Controls the relay's output channel (on/off) |
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
| |input1 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels |
| |button1 |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent1 |String |yes |Last event type (S/SS/SSS/L) for input 1 |
| |eventCount1 |Number |yes |Counter gets incremented every time the device issues a button event. |
| |input2 |Switch |yes |ON: Input/Button for channel 2 is powered, see general notes on channels |
| |button2 |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent2 |String |yes |Last event type (S/SS/SSS/L) for input 2 |
| |eventCount2 |Number |yes |Counter gets incremented every time the device issues a button event. |
| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
|meter |currentWatts |Number |yes |Current power consumption in Watts |
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|sensors |temperature1 |Number |yes |Temperature value of external sensor #1 (if connected to temp/hum addon) |
| |temperature2 |Number |yes |Temperature value of external sensor #2 (if connected to temp/hum addon) |
| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) |
| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) |
Note: The `meter`for the Shelly 1L is kind of fake.
It doesn't have a real power meter, but you could setup an estimated consumption in the Shelly App, e.g. 60W if you have attached a good old light bulb to the output channel.
In this case the is no real measurement based on power consumption, but the Shelly reports the configured value when the relay is ON.
### Shelly 1PM (thing-type: shelly1pm)
|Group |Channel |Type |read-only|Description |
@ -330,6 +375,8 @@ Depending on the device type and firmware release channels might be not availabl
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
| |input |Switch |yes |ON: Input/Button is powered, see general notes on channels |
| |button |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent |String |yes |Last event type (S/SS/SSS/L) |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
@ -357,6 +404,8 @@ The Thing id is derived from the service name, so that's the reason why the Thin
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
| |input |Switch |yes |ON: Input/Button is powered, see general notes on channels |
| |button |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent |String |yes |Last event type (S/SS/SSS/L) |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
@ -419,6 +468,7 @@ The Thing id is derived from the service name, so that's the reason why the Thin
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
| |event |Trigger |yes |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP |
| |rollerpos |Number |r/w |Roller position: 100%=open...0%=closed; gets updated when the roller stops, see Notes |
| |rollerFav |Number |r/w |Select roller position favorite (1-4, 0=no), see Notes |
| |state |String |yes |Roller state: open/close/stop |
| |stopReason |String |yes |Last stop reasons: normal, safety_switch or obstacle |
| |safety |Switch |yes |Indicates status of the Safety Switch, ON=problem detected, powered off |
@ -495,8 +545,13 @@ The Shelly 4Pro provides 4 relays and 4 power meters.
|relay |brightness |Dimmer |r/w |Currently selected brightness. |
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
| |input1 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels |
| |input2 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels |
| |button |Trigger |yes |Event trigger, see section Button Events |
| |button1 |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent1 |String |yes |Last event type (S/SS/SSS/L) for input 1 |
| |eventCount1 |Number |yes |Counter gets incremented every time the device issues a button event. |
| |input2 |Switch |yes |ON: Input/Button for channel 2 is powered, see general notes on channels |
| |button2 |Trigger |yes |Event trigger, see section Button Events |
| |lastEvent2 |String |yes |Last event type (S/SS/SSS/L) for input 2 |
| |eventCount2 |Number |yes |Counter gets incremented every time the device issues a button event. |
| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
@ -521,7 +576,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
| |input3 |Switch |yes |State of Input 3 |
| |button |Trigger |yes |Event trigger: Event trigger, see section Button Events |
| |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
| |eventCount |Number |yes |Number of button events |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
### Shelly Bulb (thing-type: shellybulb)
@ -533,7 +588,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|color | | | |Color settings: only valid in COLOR mode |
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, bight not white |
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white |
| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode |
| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" |
| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) |
@ -547,9 +602,16 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
|white | | | |Color settings: only valid in WHITE mode |
| |temperature |Number |r/w |color temperature (K): 0..100% or 3000..6500 |
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
Note: The openHAB color picker has only values for red/green/blue (RGB), not for white as supported by the RGBW2.
Beside channel `hsb` the binding also offers the `white` channel (hsb as only RGB values).
Or control each color separately with channels `red`, `blue`, `green` (those are advanced channels).
#### Shelly Duo (thing-type: shellybulbduo)
This information applies to the Shelly Duo-1 as well as the Duo White for the G10 socket.
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
@ -569,16 +631,44 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|white | | | |Color settings: only valid in WHITE mode |
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
|meter |currentWatts |Number |yes |Current power consumption in Watts |
| |lastPower1 |Number |yes |Energy consumption for a round minute, 1 minute ago |
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
| |totalKWH |Number |yes |Total energy consumption in kWh since the device powered up (resets on restart)|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
## Shelly Duo Color (thing-type: shellyduocolor-color)
## Shelly RGBW2 in Color Mode (thing-type: shellyrgbw2-color)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|control |power |Switch |r/w |Switch light ON/OFF |
| |button |Trigger |yes |Event trigger, see section Button Events |
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|color | | | |Color settings: only valid in COLOR mode |
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white |
| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode |
| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" |
| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) |
| |green |Dimmer |r/w |Green brightness: 0..100% or 0..255 (control only the green channel) |
| |blue |Dimmer |r/w |Blue brightness: 0..100% or 0..255 (control only the blue channel) |
| |white |Dimmer |r/w |White brightness: 0..100% or 0..255 (control only the white channel) |
| |gain |Dimmer |r/w |Gain setting: 0..100% or 0..100 |
| |effect |Number |r/w |Puts the light into effect mode: 0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash |
|white | | | |Color settings: only valid in WHITE mode |
| |temperature |Number |r/w |color temperature (K): 0..100% or 3000..6500 |
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
|meter |currentWatts |Number |yes |Current power consumption in Watts |
Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set.
`true`: Brightness will be set and device output is powered = light turns on with the new brightness
`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
## Shelly Duo RGBW Color Bulb (thing-type: shellycolorbulb)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
@ -600,6 +690,8 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
| | | | |0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash |
|meter |currentWatts |Number |yes |Current power consumption in Watts |
Channels in group `color`or `white`apply depending on the selected mode - they are not active at the same time.
Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set.
`true`: Brightness will be set and device output is powered = light turns on with the new brightness
`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
@ -624,7 +716,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|channel4 |brightness |Dimmer |r/w |Channel 5: Brightness: 0..100, control power state with ON/OFF |
|channel4 |brightness |Dimmer |r/w |Channel 4: Brightness: 0..100, control power state with ON/OFF |
| |button |Trigger |yes |Event trigger, see section Button Events |
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
@ -651,12 +743,13 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit |
| |humidity |Number |yes |Relative humidity in % |
| |charger |Number |yes |ON: USB charging cable is |
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly Flood (thing type: shellyflood)
`Please Note:` If you have connected an USB cable to the H&T, but channel charger is off make sure that "Use external power supply" settings is activated in the Shelly App's device settings.
### Shelly Flood (thing-type: shellyflood)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
@ -666,7 +759,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly Door/Window (thing type: shellydw)
### Shelly Door/Window (thing-type: shellydw, shellydw2)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
@ -680,12 +773,26 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly Button 1 (thing type: shellybutton1)
### Shelly Motion (thing-type: shellymotion)
|Group |Channel |Type |read-only|Description |
|----------|---------------|---------|---------|---------------------------------------------------------------------|
|sensors |motion |Switch |yes |ON: Motion was detected |
| |motionTimestamp|DateTime |yes |Time when motion started/was detected |
| |lux |Number |yes |Brightness in Lux |
| |illumination |String |yes |Current illumination: dark/twilight/bright |
| |vibration |Switch |yes |ON: Vibration detected |
| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. |
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly Button 1 (thing-type: shellybutton1)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|status |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
| |eventCount |Number |yes |Number of button events |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED, DOUBLE_PRESSED... |
| |lastUpdate |DateTime |yes |Timestamp of the last update (any value changed) |
@ -694,7 +801,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
You should calibrate the sensor using the Shelly App to get information on the tilt status.
### Shelly Smoke(thing type: shellysmoke)
### Shelly Smoke (thing-type: shellysmoke)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
@ -705,7 +812,7 @@ You should calibrate the sensor using the Shelly App to get information on the t
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly Smoke(thing type: shellygas)
### Shelly Smoke(thing-type: shellysmoke)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
@ -866,6 +973,7 @@ end
#### Control CCT LED stripes
Usage & Requirements:
- 4 Items per Thing required. Example:
```

View File

@ -2,7 +2,7 @@
This section provides information for advanced use cases.
## Additiona Resources
## Additional Resources
There are additional resources available providing more information on Shelly devices and how to integrate those into openHAB:
@ -29,18 +29,33 @@ However, if this doesn't work (sometimes there are issues) you could use the [Sh
There are 3 options available to perform the upgrade
- The Shelly App usually detects when a new version becomes available and offers to do the upgrade within the UI (Web or App)
- Alterco provides the [Shelly Firmware Archive Link Generator](http://archive.shelly-faq.de).
This can be used to generate the upgrade link, which could be easily used to perform the upgrade on the cli-level having an Internet connection on that terminal (Shelly device doesn't require an Internet access).
### Using Shelly Web UI or Smartphone App
The Apps usually detect when a new version becomes available and offers to do the upgrade to the latest release or beta version.
### Trigger device update
The [Shelly Firmware Archive Link Generator](http://archive.shelly-faq.de) is provided by the community (not official, but works like charm).
This can be used to generate the update link, which could be easily used to perform the upgrade on the cli-level having an Internet connection on that terminal (Shelly device doesn't require an Internet access).
You specify the device's IP and device model SHSW-25 and the page will generate you the link for the firmware download using the OTA of the device.
Then you run "curl -s [-u user:password] &gt;generated link&gt;" from the terminal.
Then you run
```
curl -s [-u user:password] <generated link>
```
from the command line.
This should show a JSON result, make sure that it shows "status:updating".
Wait 15sec and access the device's Web UI, go to Settings:Firmware Upgrade and make sure than the new version was installed successful.
- Manual download and installation of the firmware
Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link.
Once you downloaded the file you need to copy it to an http server.
Open the following url http://&lt;shelly ip&gt;/ota?url=http://&lt;web server&gt;/&lt;path&gt;/&lt;zip-file&gt;
Again, make sure that the file is downloaded and installed properly.
### Manual download and installation of the firmware
- Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link.
- Once you downloaded the file you need to copy it to an http server.
- Open the following url http://&lt;shelly ip&gt;/ota?url=http://&lt;web server&gt;/&lt;path&gt;/&lt;zip-file&gt;
- Again, make sure that the file is downloaded and installed properly.
## Trouble Shooting
@ -86,10 +101,23 @@ Use a list of items to reduce logging.
`Please note:` Once events are filtered they are not show anymore in the logfile, you cant find them later.
The configuration format of openHAB 3 is in xml format.
- openHAB 2.5.x
A configuration is added as a new section to `openhab2-userdata/etc/org.ops4j.pax.logging.cfg`
```
# custom filtering rules
log4j2.appender.event.filter.uselessevents.type = RegexFilter
log4j2.appender.event.filter.uselessevents.regex = .*(heartBeat|LastUpdate|lastUpdate|LetzteAktualisierung|Uptime|Laufzeit|ZuletztGesehen).*
log4j2.appender.event.filter.uselessevents.onMatch = DENY
log4j2.appender.event.filter.uselessevents.onMisMatch = NEUTRAL
```
- openHAB 3.0
The configuration format of openHAB 3.0 is in xml format.
- Open the file `userdata/etc/log4j2.xml`
- Search for tag 'RollingFile'
- and add a tag `<RegexFilter>...</RegExFilter>`
- Search for tag RollingFile
- and add a tag `<RegexF,ilter>...</RegExFilter>`
The attribute `regex` of this tag defines the regular expression, `onMatch="DENY"` the the logger to discard those lines

View File

@ -31,7 +31,7 @@ To implement this use case you need
For this how-to a roller with an electrical motor is required and will be controlled using A/C and a momentary switch.
The same solutions works with other switch types (DC/AC, 2 button switch etc.).
In this case you need to look into details and user a different Shelly configuration or adapt technical installation.
Refer to the Alterco Shelly documentation and make sure to use proper technical installation and wiring.
Refer to the Allterco Shelly documentation and make sure to use proper technical installation and wiring.
Important: Electrical installation should be performed only be people knowing what they do - failures could harm you!!
- Shelly 2.5 to control the roller with firmware 1.9.2+
@ -63,7 +63,7 @@ Latest version is always available [here](https://github.com/markus7017/myfiles/
Ideally the upgrade could be performed on a Raspberry with
```
openhab-cli stop
openhab-clu clean-cache
openhab-cli clean-cache
apt-get update
apt-get upgrade
openhab-cli start
@ -133,7 +133,7 @@ It simplifies to identify the roller when you have a ton of Shelly things (belie
### Shelly setup - Positioning Favorites
With version 1.9 Alterco introduced an interesting feature called Favorites.
With version 1.9 Allterco introduced an interesting feature called Favorites.
Those allow to store up to 4 pre-defined positions in the device, e.g. 15%, 50%, 65%, 98%.
Once defined you have kind of a short-cut to bring the roller to that position and those favorites are also supported by the binding (see below).
@ -171,7 +171,7 @@ There is also a dedicated channel (roller#rollerFav), which accepts this ids and
### Device events
As you might know the binding supports the Alterco Shelly CoIoT protocol.
As you might know the binding supports the Allterco Shelly CoIoT protocol.
The device supports so called I/O URL Actions, which are kind of a callback to an application for certain events.
Whenever possible you should prefer CoIoT events, because they are triggered near realtime and provide way more information compared to the Action URLs.
The binding uses those CoIoT updates as triggers, but also to update the channel data.

View File

@ -35,6 +35,7 @@ public class ShellyBindingConstants {
// Type names
public static final String THING_TYPE_SHELLY1_STR = "shelly1";
public static final String THING_TYPE_SHELLY1L_STR = "shelly1l";
public static final String THING_TYPE_SHELLY1PM_STR = "shelly1pm";
public static final String THING_TYPE_SHELLYEM_STR = "shellyem";
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
@ -55,8 +56,9 @@ public class ShellyBindingConstants {
public static final String THING_TYPE_SHELLYDUO_STR = "shellybulbduo";
public static final String THING_TYPE_SHELLYVINTAGE_STR = "shellyvintage";
public static final String THING_TYPE_SHELLYRGBW2_PREFIX = "shellyrgbw2";
public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = "shellyrgbw2-color";
public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = "shellyrgbw2-white";
public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-color";
public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-white";
public static final String THING_TYPE_SHELLYDUORGBW_STR = "shellycolorbulb";
public static final String THING_TYPE_SHELLYHT_STR = "shellyht";
public static final String THING_TYPE_SHELLYSMOKE_STR = "shellysmoke";
public static final String THING_TYPE_SHELLYGAS_STR = "shellygas";
@ -65,13 +67,16 @@ public class ShellyBindingConstants {
public static final String THING_TYPE_SHELLYDOORWIN2_STR = "shellydw2";
public static final String THING_TYPE_SHELLYEYE_STR = "shellyseye";
public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
public static final String THING_TYPE_SHELLYMOTION_STR = "shellymotion";
public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1";
public static final String THING_TYPE_SHELLYUNI_STR = "shellyuni";
public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
// Device Types
public static final String SHELLYDT_1 = "SHSW-1";
public static final String SHELLYDT_1PM = "SHSW-PM";
public static final String SHELLYDT_1L = "SHSW-L";
public static final String SHELLYDT_SHPLG = "SHPLG-1";
public static final String SHELLYDT_SHPLG_S = "SHPLG-S";
public static final String SHELLYDT_SHPLG_U1 = "SHPLG-U1";
@ -84,18 +89,22 @@ public class ShellyBindingConstants {
public static final String SHELLYDT_DW = "SHDW-1";
public static final String SHELLYDT_DW2 = "SHDW-2";
public static final String SHELLYDT_SENSE = "SHSEN-1";
public static final String SHELLYDT_MOTION = "SHMOS-01";
public static final String SHELLYDT_GAS = "SHGS-1";
public static final String SHELLYDT_DIMMER = "SHDM-1";
public static final String SHELLYDT_DIMMER2 = "SHDM-2";
public static final String SHELLYDT_IX3 = "SHIX3-1";
public static final String SHELLYDT_BULB = "SHBLB-1";
public static final String SHELLYDT_DUO = "SHBDUO-1";
public static final String SHELLYDT_DUORGBW = "SHCB-1";
public static final String SHELLYDT_VINTAGE = "SHVIN-1";
public static final String SHELLYDT_RGBW2 = "SHRGBW2";
public static final String SHELLYDT_BUTTON1 = "SHBTN-1";
public static final String SHELLYDT_UNI = "SHUNI-1";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
public static final ThingTypeUID THING_TYPE_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR);
public static final ThingTypeUID THING_TYPE_SHELLY1PM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1PM_STR);
public static final ThingTypeUID THING_TYPE_SHELLYEM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEM_STR);
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
@ -112,6 +121,7 @@ public class ShellyBindingConstants {
public static final ThingTypeUID THING_TYPE_SHELLYPLUGS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUGS_STR);
public static final ThingTypeUID THING_TYPE_SHELLYPLUGU1 = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYPLUGU1_STR);
public static final ThingTypeUID THING_TYPE_SHELLYUNI = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYUNI_STR);
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYDIMMER_STR);
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER2 = new ThingTypeUID(BINDING_ID,
@ -121,6 +131,8 @@ public class ShellyBindingConstants {
public static final ThingTypeUID THING_TYPE_SHELLYDUO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYDUO_STR);
public static final ThingTypeUID THING_TYPE_SHELLYVINTAGE = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYVINTAGE_STR);
public static final ThingTypeUID THING_TYPE_SHELLYDUORGBW = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYDUORGBW_STR);
public static final ThingTypeUID THING_TYPE_SHELLYHT = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYHT_STR);
public static final ThingTypeUID THING_TYPE_SHELLYSENSE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSENSE_STR);
public static final ThingTypeUID THING_TYPE_SHELLYSMOKE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSMOKE_STR);
@ -133,6 +145,7 @@ public class ShellyBindingConstants {
public static final ThingTypeUID THING_TYPE_SHELLYBUTTON1 = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYBUTTON1_STR);
public static final ThingTypeUID THING_TYPE_SHELLYEYE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEYE_STR);
public static final ThingTypeUID THING_TYPE_SHELLMOTION = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYMOTION_STR);
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_COLOR = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYRGBW2_COLOR_STR);
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_WHITE = new ThingTypeUID(BINDING_ID,
@ -142,16 +155,17 @@ public class ShellyBindingConstants {
public static final ThingTypeUID THING_TYPE_SHELLYUNKNOWN = new ThingTypeUID(BINDING_ID,
THING_TYPE_SHELLYUNKNOWN_STR);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM,
THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER, THING_TYPE_SHELLY25_RELAY,
THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG, THING_TYPE_SHELLYPLUGS,
THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3,
THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYRGBW2_COLOR,
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM,
THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER,
THING_TYPE_SHELLY25_RELAY, THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER,
THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO,
THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR,
THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT, THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE,
THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN,
THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLYPROTECTED,
THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, /* THING_TYPE_SHELLMOTION, */
THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
// Thing Configuration Properties
public static final String CONFIG_DEVICEIP = "deviceIp";
@ -199,9 +213,11 @@ public class ShellyBindingConstants {
public static final String CHANNEL_GROUP_ROL_CONTROL = "roller";
public static final String CHANNEL_ROL_CONTROL_CONTROL = "control";
public static final String CHANNEL_ROL_CONTROL_POS = "rollerpos";
public static final String CHANNEL_ROL_CONTROL_FAV = "rollerFav";
public static final String CHANNEL_ROL_CONTROL_TIMER = "timer";
public static final String CHANNEL_ROL_CONTROL_STATE = "state";
public static final String CHANNEL_ROL_CONTROL_STOPR = "stopReason";
public static final String CHANNEL_ROL_CONTROL_SAFETY = "safety";
// Dimmer
public static final String CHANNEL_GROUP_DIMMER_CONTROL = CHANNEL_GROUP_RELAY_CONTROL;
@ -223,6 +239,7 @@ public class ShellyBindingConstants {
public static final String CHANNEL_SENSOR_HUM = "humidity";
public static final String CHANNEL_SENSOR_LUX = "lux";
public static final String CHANNEL_SENSOR_PPM = "ppm";
public static final String CHANNEL_SENSOR_VOLTAGE = "voltage";
public static final String CHANNEL_SENSOR_ILLUM = "illumination";
public static final String CHANNEL_SENSOR_VIBRATION = "vibration";
public static final String CHANNEL_SENSOR_TILT = "tilt";
@ -233,6 +250,7 @@ public class ShellyBindingConstants {
public static final String CHANNEL_SENSOR_SSTATE = "status"; // Shelly Gas
public static final String CHANNEL_SENSOR_ALARM_STATE = "alarmState";
public static final String CHANNEL_SENSOR_MOTION = "motion";
public static final String CHANNEL_SENSOR_MOTION_TS = "motionTimestamp";
public static final String CHANNEL_SENSOR_ERROR = "lastError";
// External sensors for Shelly1/1PM
@ -290,12 +308,18 @@ public class ShellyBindingConstants {
// Button/xi3
public static final String CHANNEL_GROUP_STATUS = "status";
public static final String CHANNEL_STATUS_EVENTTYPE = "lastEvent";
public static final String CHANNEL_STATUS_EVENTTYPE1 = CHANNEL_STATUS_EVENTTYPE + "1";
public static final String CHANNEL_STATUS_EVENTTYPE2 = CHANNEL_STATUS_EVENTTYPE + "2";
public static final String CHANNEL_STATUS_EVENTCOUNT = "eventCount";
public static final String CHANNEL_STATUS_EVENTCOUNT1 = CHANNEL_STATUS_EVENTCOUNT + "1";
public static final String CHANNEL_STATUS_EVENTCOUNT2 = CHANNEL_STATUS_EVENTCOUNT + "2";
// General
public static final String CHANNEL_LAST_UPDATE = "lastUpdate";
public static final String CHANNEL_EVENT_TRIGGER = "event";
public static final String CHANNEL_BUTTON_TRIGGER = "button";
public static final String CHANNEL_BUTTON_TRIGGER1 = CHANNEL_BUTTON_TRIGGER + "1";
public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2";
public static final String SERVICE_TYPE = "_http._tcp.local.";
public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+
@ -324,11 +348,11 @@ public class ShellyBindingConstants {
// Formatting: Number of scaling digits
public static final int DIGITS_NONE = 0;
public static final int DIGITS_WATT = 1;
public static final int DIGITS_WATT = 2;
public static final int DIGITS_KWH = 3;
public static final int DIGITS_VOLT = 1;
public static final int DIGITS_TEMP = 1;
public static final int DIGITS_LUX = 1;
public static final int DIGITS_LUX = 0;
public static final int DIGITS_PERCENT = 1;
public static final int SHELLY_API_TIMEOUT_MS = 5000;

View File

@ -16,21 +16,19 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyUtils;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
@ -60,7 +58,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
private final ShellyTranslationProvider messages;
private final ShellyCoapServer coapServer;
private final Set<ShellyBaseHandler> deviceListeners = new ConcurrentHashSet<>();
private final Set<ShellyBaseHandler> deviceListeners = ConcurrentHashMap.newKeySet();
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
private String localIP = "";
@ -75,14 +73,18 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
*/
@Activate
public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService,
@Reference LocaleProvider localeProvider, @Reference TranslationProvider i18nProvider,
@Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
Map<String, Object> configProperties) {
@Reference ShellyTranslationProvider translationProvider, @Reference HttpClientFactory httpClientFactory,
ComponentContext componentContext, Map<String, Object> configProperties) {
logger.debug("Activate Shelly HandlerFactory");
super.activate(componentContext);
messages = translationProvider;
// Save bindingConfig & pass it to all registered listeners
bindingConfig.updateFromProperties(configProperties);
messages = new ShellyTranslationProvider(bundleContext.getBundle(), i18nProvider, localeProvider);
localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
localIP = bindingConfig.localIP;
if (localIP.isEmpty()) {
localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
}
if (localIP.isEmpty()) {
logger.warn("{}", messages.get("message.init.noipaddress"));
}
@ -95,9 +97,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
logger.debug("Using OH HTTP port {}", httpPort);
this.coapServer = new ShellyCoapServer();
// Save bindingConfig & pass it to all registered listeners
bindingConfig.updateFromProperties(configProperties);
}
@Override
@ -116,9 +115,10 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
thingTypeUID.toString());
handler = new ShellyProtectedHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort,
httpClient);
} else if (thingType.equals(THING_TYPE_SHELLYBULB.getId()) || thingType.equals(THING_TYPE_SHELLYDUO.getId())
|| thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR.getId())
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE.getId())) {
} else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR)
|| thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR_STR)
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR)
|| thingType.equals(THING_TYPE_SHELLYDUORGBW_STR)) {
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
thingTypeUID.toString());
handler = new ShellyLightHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);

View File

@ -92,7 +92,8 @@ public class ShellyApiException extends Exception {
public boolean isTimeout() {
Class<?> extype = !isEmpty() ? getCauseClass() : null;
return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
|| (extype == InterruptedException.class) || getMessage().toLowerCase().contains("timeout"));
|| (extype == InterruptedException.class)
|| nonNullString(getMessage()).toLowerCase().contains("timeout"));
}
public boolean isHttpAccessUnauthorized() {
@ -125,7 +126,7 @@ public class ShellyApiException extends Exception {
private Class<?> getCauseClass() {
Throwable cause = getCause();
if (getCause() != null) {
if (cause != null) {
return cause.getClass();
}
return ShellyApiException.class;

View File

@ -110,6 +110,7 @@ public class ShellyApiJsonDTO {
public static final String SHELLY_BTNT_MOMENTARY = "momentary";
public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release";
public static final String SHELLY_BTNT_ONE_BUTTON = "one_button";
public static final String SHELLY_BTNT_TWO_BUTTON = "dual_button";
public static final String SHELLY_BTNT_TOGGLE = "toggle";
public static final String SHELLY_BTNT_EDGE = "edge";
public static final String SHELLY_BTNT_DETACHED = "detached";
@ -334,6 +335,10 @@ public class ShellyApiJsonDTO {
public String defaultState; // Accepted values: off, on, last, switch
@SerializedName("btn_type")
public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("btn1_type") // Shelly 1L
public String btnType1;
@SerializedName("btn2_type") // Shelly 1L
public String btnType2;
@SerializedName("has_timer")
public Boolean hasTimer; // Whether a timer is currently armed for this channel
@SerializedName("auto_on")
@ -390,6 +395,10 @@ public class ShellyApiJsonDTO {
public String pushShortUrl; // short push button event
@SerializedName("btn_type")
public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("btn1_type")
public String btnType1; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("btn2_type")
public String btnType2; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("swap_inputs")
public Integer swapInputs; // 0=no
}
@ -438,6 +447,32 @@ public class ShellyApiJsonDTO {
public Boolean positioning;
}
public static class ShellySettingsRgbwLight {
public String name;
public Boolean ison; // true: output is ON
public Integer brightness;
public Integer transition;
public String default_state;
@SerializedName("auto_on")
public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF.
@SerializedName("auto_off")
public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON.
public Boolean schedule;
@SerializedName("btn_type")
public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("btn_reverse")
public Integer btnReverse; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
@SerializedName("out_on_url")
public String outOnUrl; // output is activated
@SerializedName("out_off_url")
public String outOffUrl; // output is deactivated
}
public static class ShellyFavPos { // FW 1.9.2+ in roller mode
public String name;
public Integer pos;
}
public static class ShellyInputState {
public Integer input;
@ -506,11 +541,6 @@ public class ShellyApiJsonDTO {
@SerializedName("sleep_mode")
public ShellySensorSleepMode sleepMode; // FW 1.6
// @SerializedName("ext_temperature")
// public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
// @SerializedName("ext_humidity")
// public ShellyStatusSensor.ShellyExtHumidity extHumidity; // Shelly 1/1PM: sensor values
public String timezone;
public Double lat;
public Double lng;
@ -531,6 +561,7 @@ public class ShellyApiJsonDTO {
public ArrayList<ShellySettingsRelay> relays;
public ArrayList<ShellySettingsDimmer> dimmers;
public ArrayList<ShellySettingsRgbwLight> lights;
public ArrayList<ShellySettingsEMeter> emeters;
public ArrayList<ShellySettingsInput> inputs; // ix3
@ -580,6 +611,11 @@ public class ShellyApiJsonDTO {
public String alarmMidUrl; // URL reports middle alarm
@SerializedName("alarm_heavy_url")
public String alarmHeavyfUrl; // URL reports heavy alarm
// Roller with FW 1.9.2+
@SerializedName("favorites_enabled")
public Boolean favoritesEnabled;
public ArrayList<ShellyFavPos> favorites;
}
public static class ShellySettingsAttributes {
@ -651,7 +687,7 @@ public class ShellyApiJsonDTO {
@SerializedName("btn_type")
public String btnType;
// included attributes not yet processed
// attributes not yet processed
// public String name;
// @SerializedName("btn_reverse")
// public Integer btnReverse;
@ -727,6 +763,7 @@ public class ShellyApiJsonDTO {
public String mac; // MAC
public ArrayList<ShellyShortStatusRelay> relays; // relay status
public ArrayList<ShellySettingsMeter> meters; // current meter value
public ArrayList<ShellyInputState> inputs; // Firmware 1.5.6+
@SerializedName("ext_temperature")
public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
@ -838,6 +875,14 @@ public class ShellyApiJsonDTO {
@SerializedName("is_valid")
public Boolean isValid; // whether the internal sensor is operating properly
public String state; // Shelly Door/Window
// Shelly Motion
public Boolean motion;
public Boolean vibration;
@SerializedName("timestamp")
public Long motionTimestamp;
@SerializedName("active")
public Boolean motionActive;
}
public static class ShellySensorLux {
@ -880,13 +925,17 @@ public class ShellyApiJsonDTO {
public ShellyShortHum sensor1;
}
public static class ShellyADC {
public Double voltage;
}
public ShellySensorTmp tmp;
public ShellySensorHum hum;
public ShellySensorLux lux;
public ShellySensorAccel accel;
public ShellySensorBat bat;
@SerializedName("sensor")
public ShellySensorState contact;
public ShellySensorState sensor;
public Boolean smoke; // SHelly Smoke
public Boolean flood; // Shelly Flood: true = flood condition detected
@SerializedName("rain_sensor")
@ -914,6 +963,9 @@ public class ShellyApiJsonDTO {
@SerializedName("connect_retries")
public Integer connectRetries;
public ArrayList<ShellyInputState> inputs; // Firmware 1.5.6+
// Shelly UNI FW 1.9+
public ArrayList<ShellyADC> adcs;
}
public static class ShellySettingsSmoke {
@ -985,10 +1037,14 @@ public class ShellyApiJsonDTO {
public Boolean ison;
public Double power;
public Boolean overpower;
@SerializedName("auto_on")
public Double autoOn; // see above
@SerializedName("auto_off")
public Double autoOff; // see above
@SerializedName("has_timer")
public Boolean hasTimer;
@SerializedName("timer_started")
public Integer timerStarted;
@SerializedName("timer_duration")
public Integer timerDuration;
@SerializedName("timer_remaining")
public Integer timerRemaining;
public Integer red; // red brightness, 0..255, applies in mode="color"
public Integer green; // green brightness, 0..255, applies in mode="color"
@ -1003,18 +1059,10 @@ public class ShellyApiJsonDTO {
public static class ShellyStatusLight {
public Boolean ison; // Whether output channel is on or off
public ArrayList<ShellyStatusLightChannel> lights;
public ArrayList<ShellySettingsMeter> meters;
public Integer input;
// not yet used:
// public String mode; // COLOR or WHITE
// public Boolean has_update;
// public ShellySettingsUpdate update;
// public ShellySettingsWiFiNetwork wifi_sta; // WiFi client configuration. See
// /settings/sta for details
// public ShellyStatusCloud cloud;
// public ShellyStatusMqtt mqtt;
public ArrayList<ShellyStatusLightChannel> lights;
public ArrayList<ShellySettingsMeter> meters;
}
public static class ShellySenseKeyCode {

View File

@ -18,19 +18,18 @@ import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
@ -68,6 +67,7 @@ public class ShellyDeviceProfile {
public int numRollers = 0; // number of Rollers, usually 1
public boolean isRoller = false; // true for Shelly2 in roller mode
public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
public int numInputs = 0; // number of inputs
public int numMeters = 0;
public boolean isEMeter = false; // true for ShellyEM/3EM
@ -101,14 +101,10 @@ public class ShellyDeviceProfile {
initialized = false;
try {
initFromThingType(thingType);
settingsJson = json;
settings = Objects.requireNonNull(gson.fromJson(json, ShellySettingsGlobal.class));
} catch (IllegalArgumentException | JsonSyntaxException e) {
throw new ShellyApiException(
thingName + ": Unable to transform settings JSON " + e.toString() + ", json='" + json + "'", e);
}
initFromThingType(thingType);
settingsJson = json;
ShellySettingsGlobal gs = fromJson(gson, json, ShellySettingsGlobal.class);
settings = gs; // only update when no exception
// General settings
deviceType = getString(settings.device.type);
@ -131,8 +127,10 @@ public class ShellyDeviceProfile {
numRelays = 0;
}
isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
hasRelays = (numRelays > 0) || isDimmer;
numRollers = getInteger(settings.device.numRollers);
numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0;
isEMeter = settings.emeters != null;
numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
@ -140,18 +138,17 @@ public class ShellyDeviceProfile {
// RGBW2 doesn't report, but has one
numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
}
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
if (settings.sleepMode != null) {
// Sensor, usally 12h
// Sensor, usually 12h, H&T in USB mode 10min
updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
: settings.sleepMode.period * 3600; // hours
updatePeriod += 600; // give 10min extra
updatePeriod += 60; // give 1min extra
} else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
// Derive from CoAP update interval, usually 2*15+5s=50sec -> 70sec
updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 3 * getInteger(settings.coiot.updatePeriod)) + 10;
// Derive from CoAP update interval, usually 2*15+10s=40sec -> 70sec
updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 2 * getInteger(settings.coiot.updatePeriod)) + 10;
} else {
updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
}
initialized = true;
@ -178,7 +175,8 @@ public class ShellyDeviceProfile {
}
isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR);
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)
|| thingType.equals(THING_TYPE_SHELLYDUORGBW_STR);
isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
isLight = isBulb || isDuo || isRGBW2;
if (isLight) {
@ -189,14 +187,29 @@ public class ShellyDeviceProfile {
boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
boolean isMotion = thingType.equals(THING_TYPE_SHELLYMOTION_STR);
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isSense;
hasBattery = isHT || isFlood || isDW || isSmoke || isButton; // we assume that Sense is connected to // the
// charger
isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isSense;
hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion; // we assume that Sense is connected to
// the charger
}
public void updateFromStatus(ShellySettingsStatus status) {
if (hasRelays) {
// Dimmer-2 doesn't report inputs under /settings, only on /status, we need to update that info after
// initialization
if (status.inputs != null) {
numInputs = status.inputs.size();
}
} else if (status.input != null) {
// RGBW2
numInputs = 1;
}
}
public String getControlGroup(int i) {
@ -208,11 +221,13 @@ public class ShellyDeviceProfile {
if (isDimmer) {
return CHANNEL_GROUP_DIMMER_CONTROL;
} else if (isRoller) {
return numRollers == 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
return numRollers <= 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
} else if (isDimmer) {
return CHANNEL_GROUP_RELAY_CONTROL;
} else if (hasRelays) {
return numRelays == 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
} else if (isLight) {
return numRelays == 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
return numRelays <= 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
} else if (isButton) {
return CHANNEL_GROUP_STATUS;
} else if (isSensor) {
@ -239,14 +254,17 @@ public class ShellyDeviceProfile {
}
}
public String getInputChannel(int i) {
public String getInputSuffix(int i) {
int idx = i + 1; // channel names are 1-based
if (isRGBW2 || isIX3) {
return CHANNEL_INPUT; // RGBW2 has only 1 channel
return ""; // RGBW2 has only 1 channel
} else if (isRoller || isDimmer) {
// Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs
return String.valueOf(idx);
} else if (hasRelays) {
return CHANNEL_INPUT + idx;
return (numRelays) == 1 && (numInputs >= 2) ? String.valueOf(idx) : "";
}
return CHANNEL_INPUT;
return "";
}
public boolean inButtonMode(int idx) {
@ -257,25 +275,43 @@ public class ShellyDeviceProfile {
String btnType = "";
if (isButton) {
return true;
} else if (isIX3) {
if ((settings.inputs != null) && (idx >= 0) && (idx < settings.inputs.size())) {
ShellySettingsInput input = settings.inputs.get(idx);
btnType = input.btnType;
}
} else if (isIX3 && (settings.inputs != null) && (idx < settings.inputs.size())) {
ShellySettingsInput input = settings.inputs.get(idx);
btnType = getString(input.btnType);
} else if (isDimmer) {
if ((settings.dimmers != null) && (idx >= 0) && (idx < settings.dimmers.size())) {
ShellySettingsDimmer dimmer = settings.dimmers.get(idx);
if (settings.dimmers != null) {
ShellySettingsDimmer dimmer = settings.dimmers.get(0);
btnType = dimmer.btnType;
}
} else if ((settings.relays != null) && (idx >= 0) && (idx < settings.relays.size())) {
ShellySettingsRelay relay = settings.relays.get(idx);
btnType = relay.btnType;
} else if (settings.relays != null) {
if (numRelays == 1) {
ShellySettingsRelay relay = settings.relays.get(0);
if (relay.btnType != null) {
btnType = getString(relay.btnType);
} else {
// Shelly 1L has 2 inputs
btnType = idx == 0 ? getString(relay.btnType1) : getString(relay.btnType2);
}
} else if (idx < settings.relays.size()) {
// only one input channel
ShellySettingsRelay relay = settings.relays.get(idx);
btnType = getString(relay.btnType);
}
} else if (isRGBW2 && (settings.lights != null) && (idx < settings.lights.size())) {
ShellySettingsRgbwLight light = settings.lights.get(idx);
btnType = light.btnType;
}
if (btnType.equals(SHELLY_BTNT_MOMENTARY) || btnType.equals(SHELLY_BTNT_MOM_ON_RELEASE)
|| btnType.equals(SHELLY_BTNT_DETACHED) || btnType.equals(SHELLY_BTNT_ONE_BUTTON)) {
return true;
logger.trace("{}: Checking for trigger, button-type[{}] is {}", thingName, idx, btnType);
return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE)
|| btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON);
}
public int getRollerFav(int id) {
if ((id >= 0) && getBool(settings.favoritesEnabled) && (settings.favorites != null)
&& (id < settings.favorites.size())) {
return settings.favorites.get(id).pos;
}
return false;
return -1;
}
}

View File

@ -87,7 +87,7 @@ public class ShellyEventServlet extends HttpServlet {
}
try {
path = request.getRequestURI().toLowerCase();
path = getString(request.getRequestURI()).toLowerCase();
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();

View File

@ -135,9 +135,9 @@ public class ShellyHttpApi {
try {
json = request(SHELLY_URL_STATUS);
// Dimmer2 returns invalid json type for loaderror :-(
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,");
json = json.replace("\"loaderror\":1,", "\"loaderror\":true,");
ShellySettingsStatus status = gson.fromJson(json, ShellySettingsStatus.class);
json = getString(json.replace("\"loaderror\":0,", "\"loaderror\":false,"));
json = getString(json.replace("\"loaderror\":1,", "\"loaderror\":true,"));
ShellySettingsStatus status = fromJson(gson, json, ShellySettingsStatus.class);
status.json = json;
return status;
} catch (JsonSyntaxException e) {
@ -188,9 +188,9 @@ public class ShellyHttpApi {
status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value
: ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(getDouble(status.tmp.value))
.doubleValue();
status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value
: SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT).convert(getDouble(status.tmp.value))
.doubleValue();
double f = (double) SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT)
.convert(getDouble(status.tmp.value));
status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value : f;
}
if ((status.charger == null) && (status.externalPower != null)) {
// SHelly H&T uses external_power, Sense uses charger
@ -286,11 +286,12 @@ public class ShellyHttpApi {
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":");
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} ");
String json = "{\"key_codes\" : [" + keyList + "] }";
ShellySendKeyList codes = gson.fromJson(json, ShellySendKeyList.class);
ShellySendKeyList codes = fromJson(gson, json, ShellySendKeyList.class);
Map<String, String> list = new HashMap<>();
for (ShellySenseKeyCode key : codes.keyCodes) {
list.put(key.id, key.name);
if (key != null) {
list.put(key.id, key.name);
}
}
return list;
}
@ -318,9 +319,6 @@ public class ShellyHttpApi {
url = url + "&" + "id=" + keyCode;
} else if (type.equals(SHELLY_IR_CODET_PRONTO)) {
String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8));
if (code == null) {
throw new IllegalArgumentException("Unable to BASE64 encode the pronto code: " + keyCode);
}
url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code;
} else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) {
url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode;
@ -470,12 +468,8 @@ public class ShellyHttpApi {
* @param uri: URI (e.g. "/settings")
*/
public <T> T callApi(String uri, Class<T> classOfT) throws ShellyApiException {
try {
String json = request(uri);
return gson.fromJson(json, classOfT);
} catch (JsonSyntaxException e) {
throw new ShellyApiException("Unable to convert JSON", e);
}
String json = request(uri);
return fromJson(gson, json, classOfT);
}
private String request(String uri) throws ShellyApiException {
@ -503,7 +497,7 @@ public class ShellyHttpApi {
logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString());
}
}
throw new ShellyApiException("Inconsistent API result or Timeout"); // successful
throw new ShellyApiException("API Timeout or inconsistent result"); // successful
}
private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException {
@ -533,7 +527,7 @@ public class ShellyHttpApi {
if (contentResponse.getStatus() != HttpStatus.OK_200) {
throw new ShellyApiException(apiResult);
}
if (response == null || response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
if (response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
throw new ShellyApiException("Unexpected response: " + response);
}
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {

View File

@ -16,9 +16,11 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.core.types.State;
/**
@ -30,8 +32,12 @@ import org.openhab.core.types.State;
public interface ShellyCoIoTInterface {
public int getVersion();
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
Map<String, State> updates);
public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap);
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
Map<String, State> updates, ShellyColorUtils col);
public String getLastWakeup();
}

View File

@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
@ -34,6 +35,10 @@ import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* The {@link ShellyCoIoTProtocol} implements common functions for the CoIoT implementations
*
@ -47,12 +52,14 @@ public class ShellyCoIoTProtocol {
protected final ShellyDeviceProfile profile;
protected final Map<String, CoIotDescrBlk> blkMap;
protected final Map<String, CoIotDescrSen> sensorMap;
private final Gson gson = new GsonBuilder().create();
// Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish
// between a real update or just a repeated status on periodic updates
protected int lastCfgCount = -1;
protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine
protected String[] inputEvent = { "", "", "", "", "", "", "", "" };
protected String lastWakeup = "";
public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
Map<String, CoIotDescrSen> sensorMap) {
@ -64,7 +71,7 @@ public class ShellyCoIoTProtocol {
}
protected boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
Map<String, State> updates) {
Map<String, State> updates, ShellyColorUtils col) {
// Process status information and convert into channel updates
// Integer rIndex = Integer.parseInt(sen.links) + 1;
// String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
@ -72,6 +79,7 @@ public class ShellyCoIoTProtocol {
int rIndex = getIdFromBlk(sen);
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
switch (sen.type.toLowerCase()) {
case "b": // BatteryLevel +
updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
@ -131,22 +139,27 @@ public class ShellyCoIoTProtocol {
break;
// RGBW2/Bulb
case "red":
col.setRed((int) s.value);
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_RED,
ShellyColorUtils.toPercent((int) s.value));
break;
case "green":
col.setGreen((int) s.value);
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GREEN,
ShellyColorUtils.toPercent((int) s.value));
break;
case "blue":
col.setBlue((int) s.value);
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_BLUE,
ShellyColorUtils.toPercent((int) s.value));
break;
case "white":
col.setWhite((int) s.value);
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_WHITE,
ShellyColorUtils.toPercent((int) s.value));
break;
case "gain":
col.setGain((int) s.value);
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GAIN,
ShellyColorUtils.toPercent((int) s.value, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
break;
@ -167,7 +180,7 @@ public class ShellyCoIoTProtocol {
return true;
}
protected boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
public static boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
updates.put(mkChannelId(group, channel), value);
return true;
}
@ -175,26 +188,32 @@ public class ShellyCoIoTProtocol {
protected void handleInput(CoIotDescrSen sen, CoIotSensor s, String rGroup, Map<String, State> updates) {
int idx = getSensorNumber(sen.desc, sen.id) - 1;
String iGroup = profile.getInputGroup(idx);
String iChannel = profile.getInputChannel(idx);
String iChannel = CHANNEL_INPUT + profile.getInputSuffix(idx);
updateChannel(updates, iGroup, iChannel, s.value == 0 ? OnOffType.OFF : OnOffType.ON);
}
protected void handleInputEvent(CoIotDescrSen sen, String type, Integer count, Map<String, State> updates) {
protected void handleInputEvent(CoIotDescrSen sen, String type, int count, int serial, Map<String, State> updates) {
int idx = getSensorNumber(sen.desc, sen.id) - 1;
String group = profile.getInputGroup(idx);
if (count == -1) {
// event type
updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE, new StringType(type));
updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(idx), new StringType(type));
inputEvent[idx] = type;
} else {
// event count
updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(count));
if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1)) || (count != lastEventCount[idx]))) {
if (profile.isButton || (lastEventCount[idx] != -1)) { // skip the first one if binding was restarted
thingHandler.triggerButton(group, inputEvent[idx]);
updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(idx), getDecimal(count));
logger.trace(
"{}: Check button[{}] for event trigger (isButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}",
thingName, idx, profile.inButtonMode(idx), profile.isButton, profile.hasBattery, serial, count, idx,
lastEventCount[idx]);
if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1))
|| ((lastEventCount[idx] != -1) && (count != lastEventCount[idx])))) {
if (!profile.isButton || (profile.isButton && (serial != 0x200))) { // skip duplicate on wake-up
logger.debug("{}: Trigger event {}", thingName, inputEvent[idx]);
thingHandler.triggerButton(group, idx, inputEvent[idx]);
}
lastEventCount[idx] = count;
}
lastEventCount[idx] = count;
}
}
@ -225,25 +244,25 @@ public class ShellyCoIoTProtocol {
} else if (profile.isDimmer) {
group = CHANNEL_GROUP_RELAY_CONTROL;
} else if (profile.isRGBW2) {
checkL = String.valueOf(id); // String.valueOf(id - 1); // id is 1-based, L is 0-based
group = CHANNEL_GROUP_LIGHT_CHANNEL + id;
checkL = String.valueOf(id - 1); // id is 1-based, L is 0-based
logger.trace("{}: updatePower() for L={}", thingName, checkL);
}
// We need to update brigthtess and on/off state at the same time to avoid "flipping brightness slider" in
// We need to update brightness and on/off state at the same time to avoid "flipping brightness slider" in
// the UI
Double brightness = -1.0;
Double power = -1.0;
double brightness = -1.0;
double power = -1.0;
for (CoIotSensor update : allUpdates) {
CoIotDescrSen d = fixDescription(sensorMap.getOrDefault(update.id, new CoIotDescrSen()), blkMap);
CoIotDescrSen d = fixDescription(sensorMap.get(update.id), blkMap);
if (!checkL.isEmpty() && !d.links.equals(checkL)) {
// continue until we find the correct one
continue;
}
if (d.desc.equalsIgnoreCase("brightness")) {
brightness = new Double(update.value);
brightness = update.value;
} else if (d.desc.equalsIgnoreCase("output") || d.desc.equalsIgnoreCase("state")) {
power = new Double(update.value);
power = update.value;
}
}
if (power != -1) {
@ -298,11 +317,11 @@ public class ShellyCoIoTProtocol {
protected int getIdFromBlk(CoIotDescrSen sen) {
int idx = -1;
if (blkMap.containsKey(sen.links)) {
CoIotDescrBlk blk = blkMap.get(sen.links);
CoIotDescrBlk blk = blkMap.get(sen.links);
if (blk != null) {
String desc = blk.desc.toLowerCase();
if (desc.startsWith(SHELLY_CLASS_RELAY) || desc.startsWith(SHELLY_CLASS_ROLLER)
|| desc.startsWith(SHELLY_CLASS_EMETER)) {
|| desc.startsWith(SHELLY_CLASS_LIGHT) || desc.startsWith(SHELLY_CLASS_EMETER)) {
if (desc.contains("_")) { // CoAP v2
idx = Integer.parseInt(substringAfter(desc, "_"));
} else { // CoAP v1
@ -349,7 +368,28 @@ public class ShellyCoIoTProtocol {
return profile;
}
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
return sen;
public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
return sen != null ? sen : new CoIotDescrSen();
}
public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap) {
}
protected void addSensor(Map<String, CoIotDescrSen> sensorMap, String key, String json) {
try {
if (!sensorMap.containsKey(key)) {
CoIotDescrSen sen = gson.fromJson(json, CoIotDescrSen.class);
if (sen != null) {
sensorMap.put(key, sen);
}
}
} catch (JsonSyntaxException e) {
// should never happen
logger.trace("Unable to parse sensor definition: {}", json, e);
}
}
public String getLastWakeup() {
return lastWakeup;
}
}

View File

@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
@ -63,10 +64,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
* ignored.
*/
@Override
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
Map<String, State> updates) {
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
Map<String, State> updates, ShellyColorUtils col) {
// first check the base implementation
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) {
// process by the base class
return true;
}
@ -162,10 +163,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
toQuantityType(pos, Units.PERCENT));
break;
case "input event": // Shelly Button 1
handleInputEvent(sen, getString(s.valueStr), -1, updates);
handleInputEvent(sen, getString(s.valueStr), -1, serial, updates);
break;
case "input event counter": // Shelly Button 1/ix3
handleInputEvent(sen, "", getInteger((int) s.value), updates);
handleInputEvent(sen, "", getInteger((int) s.value), serial, updates);
break;
case "flood":
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
@ -227,7 +228,7 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
* @return fixed Sensor description (sen)
*/
@Override
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
// Shelly1: reports null descr+type "Switch" -> map to S
// Shelly1PM: reports null descr+type "Overtemp" -> map to O
// Shelly1PM: reports null descr+type "W" -> add description
@ -238,6 +239,9 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
// Shelly Sense: Motion is reported with Desc "battery", but type "H" instead of "B"
// Shelly Bulb: Colors are coded with Type="Red" etc. rather than Type="S" and color as Descr
// Shelly RGBW2 is reporting Brightness, Power, VSwitch for each channel, but all with L=0
if (sen == null) {
throw new IllegalArgumentException("sen should not be null!");
}
if (sen.desc == null) {
sen.desc = "";
}
@ -255,8 +259,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
CoIotDescrBlk blk = new CoIotDescrBlk();
CoIotDescrBlk blk0 = blkMap.get("0"); // blk 0 is always there
blk.id = sen.links;
blk.desc = blk0.desc + "_" + blk.id;
blkMap.put(blk.id, blk);
if (blk0 != null) {
blk.desc = blk0.desc + "_" + blk.id;
blkMap.put(blk.id, blk);
}
}
}

View File

@ -25,10 +25,12 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.unit.SIUnits;
@ -60,17 +62,15 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
* Process CoIoT status update message. If a status update is received, but the device description has not been
* received yet a GET is send to query device description.
*
* @param devId device id included in the status packet
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
* @param serial Serial for this request. If this the the same as last serial
* the update was already sent and processed so this one gets
* ignored.
* @param sensorUpdates Complete list of sensor updates
* @param sen The specific sensor update to handle
* @param updates Resulting updates (new updates will be added to input list)
*/
@Override
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
Map<String, State> updates) {
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
Map<String, State> updates, ShellyColorUtils col) {
// first check the base implementation
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) {
// process by the base class
return true;
}
@ -80,7 +80,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
int rIndex = getIdFromBlk(sen);
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
String mGroup = profile.numMeters <= 1 ? CHANNEL_GROUP_METER
: CHANNEL_GROUP_METER + (profile.isEMeter ? getIdFromBlk(sen) : rIndex);
boolean processed = true;
@ -89,14 +89,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
switch (sen.id) {
case "3103": // H, humidity, 0-100 percent, unknown 999
case "3106": // L, luminosity, lux, U32, -1
case "3109": // S, tilt, 0-180deg, -1
case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown
case "3111": // B, battery, 0-100%, unknown -1
case "3112": // S, charger, 0/1
case "3115": // S, sensorError, 0/1
case "5101": // S, brightness, 1-100%
// processed by base handler
break;
case "6109": // P, overpowerValue, W, U32
case "9101":
// Relay: S, mode, relay/roller or
@ -113,7 +112,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
case "1103": // roller_0: S, rollerPos, 0-100, unknown -1
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min((int) value, SHELLY_MAX_ROLLER_POS));
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
break;
case "1105": // S, valvle, closed/opened/not_connected/failure/closing/opening/checking or unbknown
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr));
@ -129,13 +128,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
case "2202": // Input_1: EV, inputEvent
case "2302": // Input_2: EV, inputEvent
case "2402": // Input_3: EV, inputEvent
handleInputEvent(sen, getString(s.valueStr), -1, updates);
handleInputEvent(sen, getString(s.valueStr), -1, serial, updates);
break;
case "2103": // EVC, inputEventCnt, U16
case "2203": // EVC, inputEventCnt, U16
case "2303": // EVC, inputEventCnt, U16
case "2403": // EVC, inputEventCnt, U16
handleInputEvent(sen, "", getInteger((int) s.value), updates);
handleInputEvent(sen, "", getInteger((int) s.value), serial, updates);
break;
case "3101": // sensor_0: T, extTemp, C, -55/125; unknown 999
case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999
@ -172,6 +171,10 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
logger.debug("{}: Sensor error reported, check device, battery and installation", thingName);
}
break;
case "3109": // S, tilt, 0-180deg, -1
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT,
toQuantityType(s.value, DIGITS_NONE, Units.DEGREE_ANGLE));
break;
case "3113": // S, sensorOp, warmup/normal/fault
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
break;
@ -181,19 +184,27 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
case "3117": // S, extInput, 0/1
handleInput(sen, s, rGroup, updates);
break;
case "3118":
updateChannel(updates, mGroup, CHANNEL_SENSOR_VOLTAGE,
toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.VOLT));
break;
case "4101": // relay_0: P, power, W
case "4201": // relay_1: P, power, W
case "4301": // relay_2: P, power, W
case "4401": // relay_3: P, power, W
case "4101": // relay_0/light_0: P, power, W
case "4201": // relay_1/light_1: P, power, W
case "4301": // relay_2/light_2: P, power, W
case "4401": // relay_3/light_3: P, power, W
case "4105": // emeter_0: P, power, W
case "4205": // emeter_1: P, power, W
case "4305": // emeter_2: P, power, W
case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1
case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1
logger.debug("{}: Updating {}:currentWatts with {}", thingName, mGroup, s.value);
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
toQuantityType(s.value, DIGITS_WATT, Units.WATT));
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
if (!profile.isRGBW2 && !profile.isRoller) {
// only for regular, not-aggregated meters
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
}
break;
case "4103": // relay_0: E, energy, Wmin, U32
@ -237,6 +248,16 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
break;
case "5101": // {"I":5101,"T":"S","D":"brightness","R":"0/100","L":1},
case "5102": // {"I":5102,"T":"S","D":"gain","R":"0/100","L":1},
case "5103": // {"I":5103,"T":"S","D":"colorTemp","U":"K","R":"3000/6500","L":1},
case "5105": // {"I":5105,"T":"S","D":"red","R":"0/255","L":1},
case "5106": // {"I":5106,"T":"S","D":"green","R":"0/255","L":1},
case "5107": // {"I":5107,"T":"S","D":"blue","R":"0/255","L":1},
case "5108": // {"I":5108,"T":"S","D":"white","R":"0/255","L":1},
// already covered by base handler
break;
case "6101": // A, overtemp, 0/1
if (s.value == 1) {
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
@ -268,6 +289,21 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
value == 1 ? OnOffType.ON : OnOffType.OFF);
break;
case "6107": // A, motion, 0/1, -1
// {"I":6107,"T":"A","D":"motion","R":["0/1","-1"],"L":1},
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
value == 1 ? OnOffType.ON : OnOffType.OFF);
break;
case "3119": // Motion timestamp
// {"I":3119,"T":"S","D":"timestamp","U":"s","R":["U32","-1"],"L":1},
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
getTimestamp(getString(profile.settings.timezone), (long) s.value));
break;
case "3120": // motionActive
// {"I":3120,"T":"S","D":"motionActive","R":["0/1","-1"],"L":1},
break;
case "6108": // A, gas, none/mild/heavy/test or unknown
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, getStringType(s.valueStr));
break;
@ -276,7 +312,10 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
value == 1 ? OnOffType.ON : OnOffType.OFF);
break;
case "9102": // EV, wakeupEvent, battery/button/periodic/poweron/sensor/ext_power, "unknown"=unknown
thingHandler.updateWakeupReason(s.valueArray);
if (s.valueArray.size() > 0) {
thingHandler.updateWakeupReason(s.valueArray);
lastWakeup = (String) s.valueArray.get(0);
}
break;
case "9103": // EVC, cfgChanged, U16
if ((lastCfgCount != -1) && (lastCfgCount != s.value)) {
@ -292,7 +331,19 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
}
@Override
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
return sen;
public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
return super.fixDescription(sen, blkMap);
}
private static final String ID_4101_DESCR = "{ \"I\":4101, \"T\":\"P\", \"D\":\"power\", \"U\": \"W\", \"R\":\"0/3500\", \"L\": 1}";
private static final String ID_4103_DESCR = "{ \"I\":4103, \"T\":\"E\", \"D\":\"energy\", \"U\": \"Wmin\", \"R\":\"U32\", \"L\": 1}";
@Override
public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap) {
if (profile.isDuo && profile.inColor) {
addSensor(sensorMap, "4101", ID_4101_DESCR);
addSensor(sensorMap, "4103", ID_4103_DESCR);
}
super.completeMissingSensorDefinition(sensorMap);
}
}

View File

@ -16,8 +16,12 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.coap.CoAP.Code;
@ -28,6 +32,7 @@ import org.eclipse.californium.core.coap.Option;
import org.eclipse.californium.core.coap.OptionNumberRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyApiException;
@ -41,6 +46,8 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -79,18 +86,18 @@ public class ShellyCoapHandler implements ShellyCoapListener {
private String lastPayload = "";
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>();
private final ShellyDeviceProfile profile;
private ShellyDeviceProfile profile;
public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) {
this.thingHandler = thingHandler;
this.thingName = thingHandler.thingName;
this.profile = thingHandler.getProfile();
this.coapServer = coapServer;
this.coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); // Default
this.coiot = new ShellyCoIoTVersion2(thingName, thingHandler, blkMap, sensorMap); // Default: V2
gsonBuilder.registerTypeAdapter(CoIotDevDescription.class, new CoIotDevDescrTypeAdapter());
gsonBuilder.registerTypeAdapter(CoIotGenericSensorList.class, new CoIotSensorTypeAdapter());
gson = gsonBuilder.create();
profile = thingHandler.getProfile();
}
/**
@ -104,6 +111,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
try {
this.thingName = thingName;
this.config = config;
this.profile = thingHandler.getProfile();
if (isStarted()) {
logger.trace("{}: CoAP Listener was already started", thingName);
stop();
@ -113,9 +121,21 @@ public class ShellyCoapHandler implements ShellyCoapListener {
coapServer.start(config.localIp, this);
statusClient = new CoapClient(completeUrl(config.deviceIp, COLOIT_URI_DEVSTATUS))
.setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint());
@Nullable
Endpoint endpoint = null;
if (statusClient != null) {
endpoint = statusClient.getEndpoint();
}
if ((endpoint == null) || !endpoint.isStarted()) {
logger.warn("{}: Unable to initialize CoAP access (network error)", thingName);
throw new ShellyApiException("Network initialization failed");
}
discover();
} catch (SocketException e) {
logger.warn("{}: Unable to initialize CoAP access (socket exception) - {}", thingName, e.getMessage());
throw new ShellyApiException("Network error", e);
} catch (UnknownHostException e) {
logger.debug("{}: CoAP Exception", thingName, e);
logger.info("{}: CoAP Exception (Unknown Host)", thingName, e);
throw new ShellyApiException("Unknown Host: " + config.deviceIp, e);
}
}
@ -142,7 +162,6 @@ public class ShellyCoapHandler implements ShellyCoapListener {
String payload = "";
String devId = "";
String uri = "";
// int validity = 0;
int serial = -1;
try {
if (logger.isDebugEnabled()) {
@ -208,8 +227,8 @@ public class ShellyCoapHandler implements ShellyCoapListener {
// The device changes the serial on every update, receiving a message with the same serial is a
// duplicate, excep for battery devices! Those reset the serial every time when they wake-up
if ((serial == lastSerial) && payload.equals(lastPayload)
&& (!profile.hasBattery || ((serial & 0xFF) != 0))) {
if ((serial == lastSerial) && payload.equals(lastPayload) && (!profile.hasBattery
|| coiot.getLastWakeup().equalsIgnoreCase("ext_power") || ((serial & 0xFF) != 0))) {
logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
return;
}
@ -217,11 +236,16 @@ public class ShellyCoapHandler implements ShellyCoapListener {
// fixed malformed JSON :-(
payload = fixJSON(payload);
if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC) || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
handleDeviceDescription(devId, payload);
} else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
|| (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
handleStatusUpdate(devId, payload, serial);
try {
if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC)
|| (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
handleDeviceDescription(devId, payload);
} else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
|| (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
handleStatusUpdate(devId, payload, serial);
}
} catch (ShellyApiException e) {
logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
}
} else {
// error handling
@ -249,14 +273,14 @@ public class ShellyCoapHandler implements ShellyCoapListener {
* @param payload Device desciption in JSon format, example:
* {"blk":[{"I":0,"D":"Relay0"}],"sen":[{"I":112,"T":"Switch","R":"0/1","L":0}],"act":[{"I":211,"D":"Switch","L":0,"P":[{"I":2011,"D":"ToState","R":"0/1"}]}]}
*/
private void handleDeviceDescription(String devId, String payload) {
private void handleDeviceDescription(String devId, String payload) throws ShellyApiException {
logger.debug("{}: CoIoT Device Description for {}: {}", thingName, devId, payload);
try {
boolean valid = true;
// Decode Json
CoIotDevDescription descr = gson.fromJson(payload, CoIotDevDescription.class);
CoIotDevDescription descr = fromJson(gson, payload, CoIotDevDescription.class);
for (int i = 0; i < descr.blk.size(); i++) {
CoIotDescrBlk blk = descr.blk.get(i);
logger.debug("{}: id={}: {}", thingName, blk.id, blk.desc);
@ -288,12 +312,13 @@ public class ShellyCoapHandler implements ShellyCoapListener {
valid &= addSensor(descr.sen.get(i));
}
}
coiot.completeMissingSensorDefinition(sensorMap);
if (!valid) {
logger.debug(
"{}: Incompatible device description detected for CoIoT version {} (id length mismatch), discarding!",
thingName, coiot.getVersion());
thingHandler.updateProperties(PROPERTY_COAP_DESCR, "");
discover();
return;
}
@ -319,6 +344,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
int vers = coiot.getVersion();
if (((vers == COIOT_VERSION_1) && (sen.id.length() > 3))
|| ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4))) {
logger.debug("{}: Invalid format for sensor defition detected, id={}", thingName, sen.id);
return false;
}
@ -346,8 +372,9 @@ public class ShellyCoapHandler implements ShellyCoapListener {
* @param serial Serial for this request. If this the the same as last serial
* the update was already sent and processed so this one gets
* ignored.
* @throws ShellyApiException
*/
private void handleStatusUpdate(String devId, String payload, int serial) {
private void handleStatusUpdate(String devId, String payload, int serial) throws ShellyApiException {
logger.debug("{}: CoIoT Sensor data {} (serial={})", thingName, payload, serial);
if (blkMap.isEmpty()) {
// send discovery packet
@ -367,7 +394,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
}
// Parse Json,
CoIotGenericSensorList list = gson.fromJson(fixJSON(payload), CoIotGenericSensorList.class);
CoIotGenericSensorList list = fromJson(gson, fixJSON(payload), CoIotGenericSensorList.class);
if (list.generic == null) {
logger.debug("{}: Sensor list has invalid format! Payload: {}", devId, payload);
return;
@ -377,33 +404,29 @@ public class ShellyCoapHandler implements ShellyCoapListener {
Map<String, State> updates = new TreeMap<String, State>();
logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size());
int failed = 0;
ShellyColorUtils col = new ShellyColorUtils();
for (int i = 0; i < sensorUpdates.size(); i++) {
try {
CoIotSensor s = sensorUpdates.get(i);
if (!sensorMap.containsKey(s.id)) {
logger.debug("{}: Invalid id in sensor description: {}, index {}", thingName, s.id, i);
failed++;
continue;
}
CoIotDescrSen sen = sensorMap.get(s.id);
Objects.requireNonNull(sen);
if (sen == null) {
logger.debug("{}: Unable to sensor definition for id={}, payload={}", thingName, s.id, payload);
continue;
}
// find matching sensor definition from device description, use the Link ID as index
CoIotDescrBlk element = null;
sen = coiot.fixDescription(sen, blkMap);
if (!blkMap.containsKey(sen.links)) {
logger.debug("{}: Invalid CoAP description: sen.links({}", thingName, getString(sen.links));
element = blkMap.get(sen.links);
if (element == null) {
logger.debug("{}: Unable to find BLK for link {} from sen.id={}, payload={}", thingName, sen.links,
sen.id, payload);
continue;
}
if (!blkMap.containsKey(sen.links)) {
logger.debug("{}: Unable to find BLK for link {} from sen.id={}", thingName, sen.links, sen.id);
continue;
}
CoIotDescrBlk element = blkMap.get(sen.links);
logger.trace("{}: Sensor value[{}]: id={}, Value={} ({}, Type={}, Range={}, Link={}: {})", thingName,
i, s.id, getString(s.valueStr).isEmpty() ? s.value : s.valueStr, sen.desc, sen.type, sen.range,
sen.links, element.desc);
if (!coiot.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
if (!coiot.handleStatusUpdate(sensorUpdates, sen, serial, s, updates, col)) {
logger.debug("{}: CoIoT data for id {}, type {}/{} not processed, value={}; payload={}", thingName,
sen.id, sen.type, sen.desc, s.value, payload);
}
@ -418,7 +441,8 @@ public class ShellyCoapHandler implements ShellyCoapListener {
if (!updates.isEmpty()) {
int updated = 0;
for (Map.Entry<String, State> u : updates.entrySet()) {
updated += thingHandler.updateChannel(u.getKey(), u.getValue(), false) ? 1 : 0;
String key = u.getKey();
updated += thingHandler.updateChannel(key, u.getValue(), false) ? 1 : 0;
}
if (updated > 0) {
logger.debug("{}: {} channels updated from CoIoT status, serial={}", thingName, updated, serial);
@ -428,6 +452,40 @@ public class ShellyCoapHandler implements ShellyCoapListener {
}
}
if (profile.isLight && profile.inColor && col.isRgbValid()) {
// Update color picker from single values
if (col.isRgbValid()) {
thingHandler.updateChannel(mkChannelId(CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_PICKER),
col.toHSB(), false);
}
}
if ((profile.isRGBW2 && !profile.inColor) || profile.isRoller) {
// Aggregate Meter Data from different Coap updates
int i = 1;
double totalCurrent = 0.0;
double totalKWH = 0.0;
boolean updateMeter = false;
while (i <= thingHandler.getProfile().numMeters) {
String meter = CHANNEL_GROUP_METER + i;
double current = thingHandler.getChannelDouble(meter, CHANNEL_METER_CURRENTWATTS);
double total = thingHandler.getChannelDouble(meter, CHANNEL_METER_TOTALKWH);
logger.debug("{}: {}#{}={}, total={}", thingName, meter, CHANNEL_METER_CURRENTWATTS, current,
totalCurrent);
totalCurrent += current >= 0 ? current : 0;
totalKWH += total >= 0 ? total : 0;
updateMeter |= current >= 0 | total >= 0;
i++;
}
logger.debug("{}: totalCurrent={}, totalKWH={}, update={}", thingName, totalCurrent, totalKWH,
updateMeter);
if (updateMeter) {
thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_METER_CURRENTWATTS,
toQuantityType(totalCurrent, DIGITS_WATT, Units.WATT));
thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_LAST_UPDATE, getTimestamp());
}
}
// Old firmware release are lacking various status values, which are not updated using CoIoT.
// In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most
// of the values are available

View File

@ -67,7 +67,7 @@ public class ShellyCoapJSonDTO {
@SerializedName("I")
String id; // ID
@SerializedName("D")
String desc; // Description
String desc = ""; // Description
@SerializedName("T")
public String type; // Type
@SerializedName("R")

View File

@ -16,8 +16,10 @@ import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.COIOT_P
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer;
@ -32,7 +34,6 @@ import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.elements.UdpMulticastConnector;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,7 +50,7 @@ public class ShellyCoapServer {
private CoapEndpoint statusEndpoint = new CoapEndpoint.Builder().build();
private @Nullable UdpMulticastConnector statusConnector;
private final CoapServer server = new CoapServer(NetworkConfig.getStandard(), COIOT_PORT);;
private final Set<ShellyCoapListener> coapListeners = new ConcurrentHashSet<>();
private final Set<ShellyCoapListener> coapListeners = ConcurrentHashMap.newKeySet();
protected class ShellyStatusListener extends CoapResource {
private ShellyCoapServer listener;
@ -76,7 +77,8 @@ public class ShellyCoapServer {
}
}
public synchronized void start(String localIp, ShellyCoapListener listener) throws UnknownHostException {
public synchronized void start(String localIp, ShellyCoapListener listener)
throws UnknownHostException, SocketException {
if (!started) {
logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, COIOT_PORT);
NetworkConfig nc = NetworkConfig.getStandard();

View File

@ -31,17 +31,16 @@ public class ShellyBindingConfiguration {
// Binding Configuration Properties
public static final String CONFIG_DEF_HTTP_USER = "defaultUserId";
public static final String CONFIG_DEF_HTTP_PWD = "defaultPassword";
public static final String CONFIG_LOCAL_IP = "localIP";
public static final String CONFIG_AUTOCOIOT = "autoCoIoT";
public String defaultUserId = ""; // default for http basic user id
public String defaultPassword = ""; // default for http basic auth password
public String localIP = ""; // default:use OH network config
public boolean autoCoIoT = true;
public void updateFromProperties(Map<String, Object> properties) {
for (Map.Entry<String, Object> e : properties.entrySet()) {
if (e.getValue() == null) {
continue;
}
switch (e.getKey()) {
case CONFIG_DEF_HTTP_USER:
defaultUserId = (String) e.getValue();
@ -49,6 +48,9 @@ public class ShellyBindingConfiguration {
case CONFIG_DEF_HTTP_PWD:
defaultPassword = (String) e.getValue();
break;
case CONFIG_LOCAL_IP:
localIP = (String) e.getValue();
break;
case CONFIG_AUTOCOIOT:
autoCoIoT = (boolean) e.getValue();
break;

View File

@ -29,6 +29,9 @@ public class ShellyThingConfiguration {
public int lowBattery = 15; // threshold for battery value
public boolean brightnessAutoOn = true; // true: turn on device if brightness > 0 is set
public int favoriteUP = 0; // Roller position favorite when control channel receives ON, 0=none
public int favoriteDOWN = 0; // Roller position favorite when control channel receives ON, 0=none
public boolean eventsButton = false; // true: register for Relay btn_xxx events
public boolean eventsSwitch = true; // true: register for device out_xxx events
public boolean eventsPush = true; // true: register for short/long push events

View File

@ -33,12 +33,11 @@ import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
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.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
@ -66,20 +65,13 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
private final HttpClient httpClient;
private final ConfigurationAdmin configurationAdmin;
/**
* OSGI Service Activation
*
* @param componentContext
* @param localeProvider
*/
@Activate
public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
@Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
@Reference TranslationProvider i18nProvider, ComponentContext componentContext) {
@Reference ShellyTranslationProvider translationProvider, ComponentContext componentContext) {
logger.debug("Activating ShellyDiscovery service");
this.configurationAdmin = configurationAdmin;
this.messages = new ShellyTranslationProvider(componentContext.getBundleContext().getBundle(), i18nProvider,
localeProvider);
this.messages = translationProvider;
this.httpClient = httpClientFactory.getCommonHttpClient();
bindingConfig.updateFromProperties(componentContext.getProperties());
}
@ -168,7 +160,6 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
} else {
logger.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
logger.debug("{}: Discovery failed", name, e);
}
} catch (IllegalArgumentException e) { // maybe some format description was buggy
logger.debug("{}: Discovery failed!", name, e);

View File

@ -34,6 +34,7 @@ public class ShellyThingCreator {
static {
// mapping by device type id
THING_TYPE_MAPPING.put(SHELLYDT_1PM, THING_TYPE_SHELLY1PM_STR);
THING_TYPE_MAPPING.put(SHELLYDT_1L, THING_TYPE_SHELLY1L_STR);
THING_TYPE_MAPPING.put(SHELLYDT_1, THING_TYPE_SHELLY1_STR);
THING_TYPE_MAPPING.put(SHELLYDT_3EM, THING_TYPE_SHELLY3EM_STR);
THING_TYPE_MAPPING.put(SHELLYDT_EM, THING_TYPE_SHELLYEM_STR);
@ -43,22 +44,27 @@ public class ShellyThingCreator {
THING_TYPE_MAPPING.put(SHELLYDT_DW, THING_TYPE_SHELLYDOORWIN_STR);
THING_TYPE_MAPPING.put(SHELLYDT_DW2, THING_TYPE_SHELLYDOORWIN2_STR);
THING_TYPE_MAPPING.put(SHELLYDT_DUO, THING_TYPE_SHELLYDUO_STR);
THING_TYPE_MAPPING.put(SHELLYDT_DUORGBW, THING_TYPE_SHELLYDUORGBW_STR);
THING_TYPE_MAPPING.put(SHELLYDT_BULB, THING_TYPE_SHELLYBULB_STR);
THING_TYPE_MAPPING.put(SHELLYDT_VINTAGE, THING_TYPE_SHELLYVINTAGE_STR);
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER, THING_TYPE_SHELLYDIMMER_STR);
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER2, THING_TYPE_SHELLYDIMMER2_STR);
THING_TYPE_MAPPING.put(SHELLYDT_IX3, THING_TYPE_SHELLYIX3_STR);
THING_TYPE_MAPPING.put(SHELLYDT_BUTTON1, THING_TYPE_SHELLYBUTTON1_STR);
THING_TYPE_MAPPING.put(SHELLYDT_UNI, THING_TYPE_SHELLYUNI_STR);
THING_TYPE_MAPPING.put(SHELLYDT_HT, THING_TYPE_SHELLYHT_STR);
// mapping by thing type
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1_STR, THING_TYPE_SHELLY1_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1PM_STR, THING_TYPE_SHELLY1PM_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1L_STR, THING_TYPE_SHELLY1L_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY4PRO_STR, THING_TYPE_SHELLY4PRO_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER2_STR, THING_TYPE_SHELLYDIMMER2_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER_STR, THING_TYPE_SHELLYDIMMER_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYIX3_STR, THING_TYPE_SHELLYIX3_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY3EM_STR, THING_TYPE_SHELLY3EM_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEM_STR, THING_TYPE_SHELLYEM_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUORGBW_STR, THING_TYPE_SHELLYDUORGBW_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYVINTAGE_STR, THING_TYPE_SHELLYVINTAGE_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBULB_STR, THING_TYPE_SHELLYBULB_STR);
@ -72,6 +78,7 @@ public class ShellyThingCreator {
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSENSE_STR, THING_TYPE_SHELLYSENSE_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEYE_STR, THING_TYPE_SHELLYEYE_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYUNI_STR, THING_TYPE_SHELLYUNI_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR);
}
@ -97,7 +104,7 @@ public class ShellyThingCreator {
String name = hostname.toLowerCase();
String type = substringBefore(name, "-").toLowerCase();
String devid = substringAfterLast(name, "-");
if ((devid == null) || (type == null)) {
if (devid.isEmpty() || type.isEmpty()) {
throw new IllegalArgumentException("Invalid device name format: " + hostname);
}
@ -125,12 +132,15 @@ public class ShellyThingCreator {
// Check general mapping
if (!deviceType.isEmpty()) {
String str = THING_TYPE_MAPPING.get(deviceType);
if (str != null) {
return str;
String res = THING_TYPE_MAPPING.get(deviceType);
if (res != null) {
return res;
}
}
return THING_TYPE_MAPPING.getOrDefault(type, THING_TYPE_SHELLYUNKNOWN_STR);
String res = THING_TYPE_MAPPING.get(type);
if (res != null) {
return res;
}
return THING_TYPE_SHELLYUNKNOWN_STR;
}
}

View File

@ -26,7 +26,6 @@ import java.util.TreeMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.time.StopWatch;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
@ -43,11 +42,14 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@ -72,7 +74,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener {
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
protected final ShellyChannelDefinitionsDTO channelDefinitions;
protected final ShellyChannelDefinitions channelDefinitions;
public String thingName = "";
public String thingType = "";
@ -84,14 +86,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
private final ShellyCoapHandler coap;
public boolean autoCoIoT = false;
private final ShellyTranslationProvider messages;
public final ShellyTranslationProvider messages;
protected boolean stopping = false;
private boolean channelsCreated = false;
private long lastUptime = 0;
private long lastAlarmTs = 0;
private long lastTimeoutErros = -1;
private final StopWatch watchdog = new StopWatch();
private long watchdog = now();
private @Nullable ScheduledFuture<?> statusJob;
public int scheduledUpdates = 0;
@ -127,7 +129,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
this.messages = translationProvider;
this.cache = new ShellyChannelCache(this);
this.channelDefinitions = new ShellyChannelDefinitionsDTO(messages);
this.channelDefinitions = new ShellyChannelDefinitions(messages);
this.bindingConfig = bindingConfig;
this.localIP = localIP;
@ -154,7 +156,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
"{}: Configured Events: Button: {}, Switch (on/off): {}, Push: {}, Roller: {}, Sensor: {}, CoIoT: {}, Enable AutoCoIoT: {}",
thingName, config.eventsButton, config.eventsSwitch, config.eventsPush, config.eventsRoller,
config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT);
updateStatus(ThingStatus.UNKNOWN);
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
messages.get("status.unknown.initializing"));
start = initializeThing();
} catch (ShellyApiException e) {
ShellyApiResult res = e.getApiResult();
@ -253,6 +256,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
// update thing properties
ShellySettingsStatus status = api.getStatus();
tmpPrf.updateFromStatus(status);
updateProperties(tmpPrf, status);
checkVersion(tmpPrf, status);
if (autoCoIoT) {
@ -289,11 +293,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
public void handleCommand(ChannelUID channelUID, Command command) {
try {
if (command instanceof RefreshType) {
String channelId = channelUID.getId();
State value = cache.getValue(channelId);
if (value != UnDefType.NULL) {
updateState(channelId, value);
}
return;
}
if (!profile.isInitialized()) {
logger.debug("{}: {}", thingName, messages.get("message.command.init", command));
logger.debug("{}: {}", thingName, messages.get("command.init", command));
initializeThing();
} else {
profile = getProfile(false);
@ -350,7 +359,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
skipUpdate++;
ThingStatus thingStatus = getThing().getStatus();
if (refreshSettings || (scheduledUpdates > 0) || (skipUpdate % skipCount == 0)) {
if (!profile.isInitialized() || ((thingStatus == ThingStatus.OFFLINE))
|| (thingStatus == ThingStatus.UNKNOWN)) {
@ -362,6 +370,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
logger.trace("{}: Updating status", thingName);
ShellySettingsStatus status = api.getStatus();
profile.updateFromStatus(status);
// If status update was successful the thing must be online
setThingOnline();
@ -370,14 +379,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME, getStringType(profile.settings.name));
updated |= this.updateDeviceStatus(status);
updated |= ShellyComponents.updateDeviceStatus(this, status);
// if (!channelsCreated || !cache.isEnabled() || (coap.getVersion() <
// ShellyCoapJSonDTO.COIOT_VERSION_2)) {
updated |= updateInputs(status);
updated |= updateMeters(this, status);
updated |= updateSensors(this, status);
updated |= updateInputs(status);
// } else {
// logger.debug("Skipping Meter/Sensor/Input updates, because device is running CoIoT version 2");
// }
// All channels must be created after the first cycle
channelsCreated = true;
@ -397,8 +401,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (isWatchdogStarted()) {
if (!isWatchdogExpired()) {
logger.debug("{}: Ignore API Timeout, retry later", thingName);
} else if (profile.hasBattery) {
logger.debug("{}: Ignore API Timeout for battery powered device", thingName);
} else {
logger.debug("{}: Watchdog expired after {}sec,", thingName, profile.updatePeriod);
if (isThingOnline()) {
@ -410,6 +412,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} else if (e.isJSONException()) {
status = "offline.status-error-unexpected-api-result";
logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e);
} else if (res.isHttpTimeout()) {
// Watchdog not started, e.g. device in sleep mode
if (isThingOnline()) { // ignore when already offline
status = "offline.status-error-watchdog";
}
} else {
status = "offline.status-error-unexpected-api-result";
logger.debug("{}: Unexpected API result: {}", thingName, res.response, e);
@ -454,30 +461,30 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (!isThingOffline()) {
logger.info("{}: Thing goes OFFLINE: {}", thingName, messages.get(messageKey));
updateStatus(ThingStatus.OFFLINE, detail, "@text/" + messageKey);
watchdog.reset();
watchdog = 0;
channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
}
}
public synchronized void restartWatchdog() {
watchdog = now();
updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT, getTimestamp());
watchdog.reset();
watchdog.start();
logger.trace("{}: Watchdog restarted (expires in {} sec)", thingName, profile.updatePeriod);
}
private boolean isWatchdogExpired() {
return watchdog.getTime() > profile.updatePeriod;
long timeout = profile.hasBattery ? profile.updatePeriod : profile.updatePeriod;
long delta = now() - watchdog;
if ((watchdog > 0) && (delta > timeout)) {
logger.trace("{}: Watchdog expired after {}sec (started={}, now={}", thingName, delta, watchdog, now());
return true;
}
return false;
}
private boolean isWatchdogStarted() {
try {
if (isThingOnline()) {
watchdog.getStartTime();
}
return true;
} catch (IllegalStateException e) {
return false;
}
logger.trace("{}: Watchdog is {}", thingName, watchdog > 0 ? "started" : "inactive");
return watchdog > 0;
}
public void reinitializeThing() {
@ -494,8 +501,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
ShellyComponents.updateDeviceStatus(this, status);
if (api.isInitialized() && (lastTimeoutErros != api.getTimeoutErrors())) {
propertyUpdates.put(PROPERTY_STATS_TIMEOUTS, new Integer(api.getTimeoutErrors()).toString());
propertyUpdates.put(PROPERTY_STATS_TRECOVERED, new Integer(api.getTimeoutsRecovered()).toString());
propertyUpdates.put(PROPERTY_STATS_TIMEOUTS, String.valueOf(api.getTimeoutErrors()));
propertyUpdates.put(PROPERTY_STATS_TRECOVERED, String.valueOf(api.getTimeoutsRecovered()));
lastTimeoutErros = api.getTimeoutErrors();
}
@ -585,8 +592,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
case SHELLY_EVENT_TRIPLE_SHORTPUSH:
case SHELLY_EVENT_LONGPUSH:
if (isButton) {
triggerButton(group, mapButtonEvent(event));
channel = CHANNEL_BUTTON_TRIGGER;
triggerButton(group, idx, mapButtonEvent(event));
channel = CHANNEL_BUTTON_TRIGGER + profile.getInputSuffix(idx);
payload = ShellyApiJsonDTO.mapButtonEvent(event);
} else {
logger.debug("{}: Relay button is not in memontary or detached mode, ignore SHORT/LONGPUSH",
@ -705,7 +712,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
config.deviceIp = saddr;
}
} catch (UnknownHostException e) {
logger.debug("{}: Unable to resolehostname {}", thingName, config.deviceIp);
logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp);
}
config.localIp = localIP;
@ -722,7 +729,20 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
config.updateInterval = UPDATE_MIN_DELAY;
}
// Try to get updatePeriod from properties
// For battery devinities the REST call to get the settings will most likely fail, because the device is in
// sleep mode. Therefore we use the last saved property value as default. Will be overwritten, when device is
// initialized successfully by the REST call.
String lastPeriod = getString(properties.get(PROPERTY_UPDATE_PERIOD));
if (!lastPeriod.isEmpty()) {
int period = Integer.parseInt(lastPeriod);
if (period > 0) {
profile.updatePeriod = period;
}
}
skipCount = config.updateInterval / UPDATE_STATUS_INTERVAL_SECONDS;
logger.trace("{}: updateInterval = {}s -> skipCount = {}", thingName, config.updateInterval, skipCount);
}
private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) {
@ -743,13 +763,13 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
}
autoCoIoT = true;
}
if (status.update.hasUpdate && !version.checkBeta(getString(prf.fwVersion))) {
logger.info("{}: {}", thingName,
messages.get("versioncheck.update", status.update.oldVersion, status.update.newVersion));
}
} catch (NullPointerException e) { // could be inconsistant format of beta version
logger.debug("{}: {}", thingName, messages.get("versioncheck.failed", prf.fwVersion));
}
if (status.update.hasUpdate) {
logger.info("{}: {}", thingName,
messages.get("versioncheck.update", status.update.oldVersion, status.update.newVersion));
}
}
/**
@ -841,57 +861,34 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* @param status Shelly device status
* @return true: one or more inputs were updated
*/
public boolean updateInputs(String groupName, ShellySettingsStatus status, int index) {
if ((status.input == null)) {
return false;
}
boolean updated = false;
if (index == 0) {
// RGBW2: a single int rather than an array
updated |= updateChannel(groupName, CHANNEL_INPUT,
getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
} else {
if (profile.isDimmer || profile.isRoller) {
ShellyInputState state1 = status.inputs.get(0);
ShellyInputState state2 = status.inputs.get(1);
logger.trace("{}: Updating {}#input1 with {}, input2 with {}", thingName, groupName,
getOnOff(state1.input), getOnOff(state2.input));
updated |= updateChannel(groupName, CHANNEL_INPUT + "1", getOnOff(state1.input));
updated |= updateChannel(groupName, CHANNEL_INPUT + "2", getOnOff(state2.input));
} else {
if (index < status.inputs.size()) {
ShellyInputState state = status.inputs.get(index);
updated |= updateChannel(groupName, CHANNEL_INPUT, getOnOff(state.input));
} else {
logger.debug("{}: Unable to update input, index is out of range ({}/{}", thingName, index,
status.inputs.size());
}
}
}
return updated;
}
public boolean updateInputs(ShellySettingsStatus status) {
boolean updated = false;
String groupName = "";
if (status.input != null) {
// RGBW2: a single int rather than an array
return updateChannel(groupName, CHANNEL_INPUT,
getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
}
if (status.inputs != null) {
int idx = 0;
boolean multiInput = status.inputs.size() >= 2; // device has multiple SW (inputs)
for (ShellyInputState input : status.inputs) {
String group = profile.getControlGroup(idx);
updated |= updateChannel(group, CHANNEL_INPUT, getOnOff(input.input));
String suffix = multiInput ? profile.getInputSuffix(idx) : "";
if (!areChannelsCreated()) {
updateChannelDefinitions(
ShellyChannelDefinitions.createInputChannels(thing, profile, status, group));
}
updated |= updateChannel(group, CHANNEL_INPUT + suffix, getOnOff(input.input));
if (input.event != null) {
updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE, getStringType(input.event));
updated |= updateChannel(group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(input.eventCount));
updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
updated |= updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + suffix, getDecimal(input.eventCount));
}
idx++;
}
} else {
if (status.input != null) {
// RGBW2: a single int rather than an array
return updateChannel(profile.getControlGroup(0), CHANNEL_INPUT,
getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
}
}
return updated;
}
@ -911,14 +908,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return changed;
}
public void triggerButton(String group, String value) {
public void triggerButton(String group, int idx, String value) {
String trigger = mapButtonEvent(value);
if (trigger.isEmpty()) {
return;
}
logger.debug("{}: Update button state with {}/{}", thingName, value, trigger);
triggerChannel(group, CHANNEL_BUTTON_TRIGGER, trigger);
triggerChannel(group,
profile.isRoller ? CHANNEL_EVENT_TRIGGER : CHANNEL_BUTTON_TRIGGER + profile.getInputSuffix(idx),
trigger);
updateChannel(group, CHANNEL_LAST_UPDATE, getTimestamp());
if (!profile.hasBattery) {
// refresh status of the input channel
@ -927,8 +926,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
}
public void publishState(String channelId, State value) {
if (!stopping) {
updateState(channelId.contains("$") ? substringBefore(channelId, "$") : channelId, value);
String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId;
if (!stopping && isLinked(id)) {
updateState(id, value);
}
}
@ -937,14 +937,26 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
}
public boolean updateChannel(String channelId, State value, boolean force) {
return !stopping && (channelId.contains("$") || isLinked(channelId))
&& cache.updateChannel(channelId, value, force);
return !stopping && cache.updateChannel(channelId, value, force);
}
public State getChannelValue(String group, String channel) {
return cache.getValue(group, channel);
}
public double getChannelDouble(String group, String channel) {
State value = getChannelValue(group, channel);
if (value != UnDefType.NULL) {
if (value instanceof QuantityType) {
return ((QuantityType<?>) value).toBigDecimal().doubleValue();
}
if (value instanceof DecimalType) {
return ((DecimalType) value).doubleValue();
}
}
return -1;
}
/**
* Update Thing's channels according to available status information from the API
*
@ -1078,14 +1090,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
properties.put(PROPERTY_NUM_RELAYS, String.valueOf(profile.numRelays));
properties.put(PROPERTY_NUM_ROLLERS, String.valueOf(profile.numRollers));
properties.put(PROPERTY_NUM_METER, String.valueOf(profile.numMeters));
properties.put(PROPERTY_UPDATE_PERIOD, String.valueOf(profile.updatePeriod));
if (!profile.hwRev.isEmpty()) {
properties.put(PROPERTY_HWREV, profile.hwRev);
properties.put(PROPERTY_HWBATCH, profile.hwBatchId);
}
if (profile.updatePeriod >= 0) {
properties.put(PROPERTY_UPDATE_PERIOD, String.valueOf(profile.updatePeriod));
}
}
return properties;
}

View File

@ -1,393 +0,0 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.handler;
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
* added on the first thing status update
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
public class ShellyChannelDefinitionsDTO {
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
// shortcuts to avoid line breaks (make code more readable)
private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
private static final String CHGR_METER = CHANNEL_GROUP_METER;
private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
public static final String ITEM_TYPE_NUMBER = "Number";
public static final String ITEM_TYPE_STRING = "String";
public static final String ITEM_TYPE_SWITCH = "Switch";
public static final String ITEM_TYPE_CONTACT = "Contact";
public static final String ITEM_TYPE_DATETIME = "DateTime";
public static final String ITEM_TYPE_TEMP = "Number:Temperature";
public static final String ITEM_TYPE_LUX = "Number:Illuminance";
public static final String ITEM_TYPE_POWER = "Number:Power";
public static final String ITEM_TYPE_ENERGY = "Number:Energy";
public static final String ITEM_TYPE_VOLT = "Number:ElectricPotential";
public static final String ITEM_TYPE_AMP = "Number:ElectricPotential";
public static final String ITEM_TYPE_PERCENT = "Number:Dimensionless";
public static final String ITEM_TYPE_ANGLE = "Number:Angle";
public static final String PREFIX_GROUP = "definitions.shelly.group.";
public static final String PREFIX_CHANNEL = "channel-type.shelly.";
public static final String SUFFIX_LABEL = ".label";
public static final String SUFFIX_DESCR = ".description";
public ShellyChannelDefinitionsDTO(ShellyTranslationProvider m) {
// Device: Internal Temp
CHANNEL_DEFINITIONS
// Device
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEM_TYPE_TEMP))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEM_TYPE_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEM_TYPE_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEM_TYPE_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEM_TYPE_NUMBER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEM_TYPE_DATETIME))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEM_TYPE_SWITCH))
// Relay
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEM_TYPE_STRING))
// Roller
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEM_TYPE_STRING))
// RGBW2
.add(new ShellyChannel(m, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
// Power Meter
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEM_TYPE_POWER))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEM_TYPE_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEM_TYPE_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
// EMeter
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEM_TYPE_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEM_TYPE_POWER))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEM_TYPE_VOLT))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEM_TYPE_AMP))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEM_TYPE_NUMBER))
// Sensors
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEM_TYPE_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEM_TYPE_PERCENT))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEM_TYPE_LUX))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEM_TYPE_CONTACT))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEM_TYPE_ANGLE))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEM_TYPE_NUMBER))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
// Button/ix3
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "eventType", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEM_TYPE_NUMBER))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system.button", ITEM_TYPE_STRING))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
// Addon with external sensors
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEM_TYPE_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEM_TYPE_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEM_TYPE_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEM_TYPE_PERCENT))
// Battery
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level",
ITEM_TYPE_PERCENT))
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEM_TYPE_SWITCH));
}
public static ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
String group = substringBefore(channelName, "#");
String channel = substringAfter(channelName, "#");
if (group.contains(CHANNEL_GROUP_METER)) {
group = CHANNEL_GROUP_METER; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
group = CHANNEL_GROUP_LIGHT_CHANNEL;
} else if (group.contains(CHANNEL_GROUP_STATUS)) {
group = CHANNEL_GROUP_STATUS; // map status1..n to meter
}
String channelId = group + "#" + channel;
return CHANNEL_DEFINITIONS.get(channelId);
}
/**
* Auto-create relay channels depending on relay type/mode
*
* @return ArrayList<Channel> of channels to be added to the thing
*/
public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellySettingsStatus status) {
Map<String, Channel> add = new LinkedHashMap<>();
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
if (!profile.isSensor) {
// Only some devices report the internal device temp
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
CHANNEL_DEVST_ITEMP);
}
// RGBW2
addChannel(thing, add, status.input != null, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT);
// If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
&& !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
if (profile.settings.ledPowerDisable != null) {
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
}
if (profile.settings.ledStatusDisable != null) {
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
}
return add;
}
/**
* Auto-create relay channels depending on relay type/mode
*
* @return ArrayList<Channel> of channels to be added to the thing
*/
public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusRelay relay, int idx) {
Map<String, Channel> add = new LinkedHashMap<>();
String group = profile.getControlGroup(idx);
ShellySettingsRelay rs = profile.settings.relays.get(idx);
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
// Shelly 1/1PM Addon
if (relay.extTemperature != null) {
addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
}
if (relay.extHumidity != null) {
addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
}
return add;
}
public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
Map<String, Channel> add = new LinkedHashMap<>();
addChannel(thing, add, roller.state != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
return add;
}
public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
if (meter.counters != null) {
addChannel(thing, newChannels, meter.counters[0] != null, group, CHANNEL_METER_LASTMIN1);
}
addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
return newChannels;
}
public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
String group) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
return newChannels;
}
public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusSensor sdata) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
// Sensor data
addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
if (sdata.accel != null) {
addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_VIBRATION);
addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
}
addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_ILLUM);
addChannel(thing, newChannels, sdata.contact != null && sdata.contact.state != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_CONTACT);
addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
// Gas
if (sdata.gasSensor != null) {
addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_SSTATE);
addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_ALARM_STATE);
}
// Battery
if (sdata.bat != null) {
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
}
addChannel(thing, newChannels, true, profile.getControlGroup(0), CHANNEL_LAST_UPDATE);
return newChannels;
}
private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
String channelName) throws IllegalArgumentException {
if (supported) {
final String channelId = group + "#" + channelName;
final ShellyChannel channelDef = getDefinition(channelId);
final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
final ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
? new ChannelTypeUID(channelDef.typeId)
: new ChannelTypeUID(BINDING_ID, channelDef.typeId);
Channel channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
newChannels.put(channelId, channel);
}
}
public class ShellyChannel {
private final ShellyTranslationProvider messages;
public String group = "";
public String groupLabel = "";
public String groupDescription = "";
public String channel = "";
public String label = "";
public String description = "";
public String itemType = "";
public String typeId = "";
public String category = "";
public Set<String> tags = new HashSet<>();
public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
String itemType, String... category) {
this.messages = messages;
this.group = group;
this.channel = channel;
this.itemType = itemType;
this.typeId = typeId;
groupLabel = getText(PREFIX_GROUP + group + SUFFIX_LABEL);
groupDescription = getText(PREFIX_GROUP + group + SUFFIX_DESCR);
label = getText(PREFIX_CHANNEL + channel + SUFFIX_LABEL);
description = getText(PREFIX_CHANNEL + channel + SUFFIX_DESCR);
}
public String getChanneId() {
return group + "#" + channel;
}
private String getText(String key) {
String text = messages.get(key);
return text != null ? text : "";
}
}
public static class ChannelMap {
private final Map<String, ShellyChannel> map = new LinkedHashMap<>();
private ChannelMap add(ShellyChannel def) {
map.put(def.getChanneId(), def);
return this;
}
public ShellyChannel get(String channelName) throws IllegalArgumentException {
ShellyChannel def = null;
if (channelName.contains("#")) {
def = map.get(channelName);
}
for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
if (entry.getValue().channel.contains("#" + channelName)) {
def = entry.getValue();
break;
}
}
if (def == null) {
throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
}
return def;
}
}
}

View File

@ -65,16 +65,16 @@ public class ShellyColorUtils {
setTemp(col.temp);
}
void setMode(String mode) {
public void setMode(String mode) {
this.mode = mode;
}
void setMinMaxTemp(int min, int max) {
public void setMinMaxTemp(int min, int max) {
minTemp = min;
maxTemp = max;
}
boolean setRGBW(int red, int green, int blue, int white) {
public boolean setRGBW(int red, int green, int blue, int white) {
setRed(red);
setGreen(green);
setBlue(blue);
@ -82,56 +82,56 @@ public class ShellyColorUtils {
return true;
}
boolean setRed(int value) {
public boolean setRed(int value) {
boolean changed = red != value;
red = value;
percentRed = toPercent(red);
return changed;
}
boolean setGreen(int value) {
public boolean setGreen(int value) {
boolean changed = green != value;
green = value;
percentGreen = toPercent(green);
return changed;
}
boolean setBlue(int value) {
public boolean setBlue(int value) {
boolean changed = blue != value;
blue = value;
percentBlue = toPercent(blue);
return changed;
}
boolean setWhite(int value) {
public boolean setWhite(int value) {
boolean changed = white != value;
white = value;
percentWhite = toPercent(white);
return changed;
}
boolean setBrightness(int value) {
public boolean setBrightness(int value) {
boolean changed = brightness != value;
brightness = value;
percentBrightness = toPercent(brightness, SHELLY_MIN_BRIGHTNESS, SHELLY_MAX_BRIGHTNESS);
return changed;
}
boolean setGain(int value) {
public boolean setGain(int value) {
boolean changed = gain != value;
gain = value;
percentGain = toPercent(gain, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN);
return changed;
}
boolean setTemp(int value) {
public boolean setTemp(int value) {
boolean changed = temp != value;
temp = value;
percentTemp = toPercent(temp, minTemp, maxTemp);
return changed;
}
boolean setEffect(int value) {
public boolean setEffect(int value) {
boolean changed = effect != value;
effect = value;
return changed;
@ -168,18 +168,22 @@ public class ShellyColorUtils {
return values;
}
public boolean isRgbValid() {
return (red != -1) && (blue != -1) && (green != -1);
}
public static PercentType toPercent(Integer value) {
return toPercent(value, 0, SHELLY_MAX_COLOR);
}
public static PercentType toPercent(Integer _value, Integer min, Integer max) {
Double range = max.doubleValue() - min.doubleValue();
Double value = _value.doubleValue();
double range = max.doubleValue() - min.doubleValue();
double value = _value.doubleValue();
value = value < min ? min.doubleValue() : value;
value = value > max ? max.doubleValue() : value;
Double percent = 0.0;
double percent = 0.0;
if (range > 0) {
percent = new Double(Math.round((value - min) / range * 100));
percent = Math.round((value - min) / range * 100);
}
return new PercentType(new BigDecimal(percent));
}

View File

@ -16,15 +16,15 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor.ShellyADC;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.unit.ImperialUnits;
@ -48,13 +48,13 @@ public class ShellyComponents {
*/
public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
.createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status));
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
thingHandler.getProfile(), status));
}
Integer rssi = getInteger(status.wifiSta.rssi);
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
toQuantityType(new Double(getLong(status.uptime)), DIGITS_NONE, Units.SECOND));
toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND));
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
if (status.tmp != null) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
@ -109,7 +109,7 @@ public class ShellyComponents {
if (!thingHandler.areChannelsCreated()) {
// skip for Shelly Bulb: JSON has a meter, but values don't get updated
if (!profile.isBulb) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createMeterChannels(thingHandler.getThing(), meter, groupName));
}
}
@ -141,7 +141,7 @@ public class ShellyComponents {
String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString()
: CHANNEL_GROUP_METER;
if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createEMeterChannels(thingHandler.getThing(), emeter, groupName));
}
@ -197,7 +197,7 @@ public class ShellyComponents {
}
// Create channels for 1 Meter
if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
}
@ -237,7 +237,7 @@ public class ShellyComponents {
* @param profile ShellyDeviceProfile
* @param status Last ShellySettingsStatus
*
* @throws IOException
* @throws ShellyApiException
*/
public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
throws ShellyApiException {
@ -250,20 +250,20 @@ public class ShellyComponents {
if (!thingHandler.areChannelsCreated()) {
thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
thingHandler.updateChannelDefinitions(
ShellyChannelDefinitionsDTO.createSensorChannels(thingHandler.getThing(), profile, sdata));
ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata));
}
updated |= thingHandler.updateWakeupReason(sdata.actReasons);
if ((sdata.contact != null) && sdata.contact.isValid) {
if ((sdata.sensor != null) && sdata.sensor.isValid) {
// Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
getString(sdata.contact.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
getString(sdata.sensor.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
: OpenClosedType.CLOSED);
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
getStringType(sdata.sensorError));
if (changed) {
thingHandler.postEvent(sdata.sensorError, true);
thingHandler.postEvent(getString(sdata.sensorError), true);
}
updated |= changed;
}
@ -320,7 +320,11 @@ public class ShellyComponents {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
getDecimal(sdata.concentration.ppm));
}
if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) {
ShellyADC adc = sdata.adcs.get(0);
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE,
getDecimal(adc.voltage));
}
if (sdata.bat != null) { // no update for Sense
thingHandler.logger.trace("{}: Updating battery", thingHandler.thingName);
updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
@ -332,10 +336,18 @@ public class ShellyComponents {
thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
}
}
if (sdata.motion != null) {
if (sdata.motion != null) { // Shelly Sense
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
getOnOff(sdata.motion));
}
if (sdata.sensor != null) { // Shelly Motion
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
getOnOff(sdata.sensor.motion));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
getTimestamp(getString(profile.settings.timezone), sdata.sensor.motionTimestamp));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
getOnOff(sdata.sensor.vibration));
}
if (sdata.charger != null) {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
getOnOff(sdata.charger));

View File

@ -22,6 +22,7 @@ import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
@ -29,7 +30,8 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLigh
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
@ -146,9 +148,8 @@ public class ShellyLightHandler extends ShellyBaseHandler {
col.power = getOnOff(light.ison);
col.setBrightness(light.brightness);
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", col.power);
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType(new Double(col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE,
Units.PERCENT));
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
(double) (col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE, Units.PERCENT));
update = false;
break;
}
@ -264,17 +265,15 @@ public class ShellyLightHandler extends ShellyBaseHandler {
logger.debug("{}: {} brightness by {}", thingName, command, SHELLY_DIM_STEPSIZE);
PercentType percent = (PercentType) super.getChannelValue(CHANNEL_GROUP_COLOR_CONTROL,
CHANNEL_BRIGHTNESS);
if (percent != null) {
int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
int newBrightness = currentBrightness;
if (command == IncreaseDecreaseType.DECREASE) {
newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
} else {
newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
}
col.brightness = newBrightness;
updated = currentBrightness != newBrightness;
int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
int newBrightness = currentBrightness;
if (command == IncreaseDecreaseType.DECREASE) {
newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
} else {
newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
}
col.brightness = newBrightness;
updated = currentBrightness != newBrightness;
}
}
return updated;
@ -303,14 +302,13 @@ public class ShellyLightHandler extends ShellyBaseHandler {
}
private ShellyColorUtils getCurrentColors(int lightId) {
ShellyColorUtils col;
if (!channelColors.containsKey(lightId)) {
ShellyColorUtils col = channelColors.get(lightId);
if (col == null) {
col = new ShellyColorUtils(); // create a new entry
col.setMinMaxTemp(profile.minTemp, profile.maxTemp);
channelColors.put(lightId, col);
logger.trace("{}: Colors entry created for lightId {}", thingName, lightId);
} else {
col = channelColors.get(lightId);
logger.trace(
"{}: Colors loaded for lightId {}: power={}, RGBW={}/{}/{}/{}, gain={}, brightness={}, color temp={} (min={}, max={}",
thingName, lightId, col.power, col.red, col.green, col.blue, col.white, col.gain, col.brightness,
@ -339,6 +337,7 @@ public class ShellyLightHandler extends ShellyBaseHandler {
for (ShellyStatusLightChannel light : status.lights) {
Integer channelId = lightId + 1;
String controlGroup = buildControlGroupName(profile, channelId);
createLightChannels(light, lightId);
// The bulb has a combined channel set for color or white mode
// The RGBW2 uses 2 different thing types: color=1 channel, white=4 channel
if (profile.isBulb) {
@ -349,11 +348,12 @@ public class ShellyLightHandler extends ShellyBaseHandler {
col.power = getOnOff(light.ison);
// Channel control/timer
// ShellyStatusLightChannel light = status.lights.get(i);
ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId);
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn));
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff));
updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(light.autoOn));
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(light.autoOff));
updated |= updateInputs(controlGroup, genericStatus, lightId);
updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer));
if (getBool(light.overpower)) {
postEvent(ALARM_TYPE_OVERPOWER, false);
}
@ -386,8 +386,8 @@ public class ShellyLightHandler extends ShellyBaseHandler {
col.setBrightness(getInteger(light.brightness));
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Switch", col.power);
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : new Double(0),
DIGITS_NONE, Units.PERCENT));
toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : 0, DIGITS_NONE,
Units.PERCENT));
if ((profile.isBulb || profile.isDuo) && (light.temp != null)) {
col.setTemp(getInteger(light.temp));
@ -403,14 +403,20 @@ public class ShellyLightHandler extends ShellyBaseHandler {
return updated;
}
private void createLightChannels(ShellyStatusLightChannel status, int idx) {
if (!areChannelsCreated()) {
updateChannelDefinitions(ShellyChannelDefinitions.createLightChannels(getThing(), profile, status, idx));
}
}
private Integer setColor(Integer lightId, String colorName, Command command, Integer minValue, Integer maxValue)
throws ShellyApiException, IllegalArgumentException {
Integer value = -1;
logger.debug("{}: Set {} to {} ({})", thingName, colorName, command, command.getClass());
if (command instanceof PercentType) {
PercentType percent = (PercentType) command;
Double v = new Double(maxValue) * percent.doubleValue() / 100.0;
value = v.intValue();
double v = (double) maxValue * percent.doubleValue() / 100.0;
value = (int) v;
logger.debug("{}: Value for {} is in percent: {}%={}", thingName, colorName, percent, value);
} else if (command instanceof DecimalType) {
value = ((DecimalType) command).intValue();
@ -499,14 +505,13 @@ public class ShellyLightHandler extends ShellyBaseHandler {
lightId, col.red, col.green, col.blue, col.white, col.gain, col.brightness, col.temp);
}
private Integer getColorFromHSB(PercentType colorPercent) {
return getColorFromHSB(colorPercent, new Double(SATURATION_FACTOR));
private int getColorFromHSB(PercentType colorPercent) {
return getColorFromHSB(colorPercent, SATURATION_FACTOR);
}
private Integer getColorFromHSB(PercentType colorPercent, Double factor) {
Double value = new Double(Math.round(colorPercent.doubleValue() * factor));
logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, value.intValue(),
factor);
return value.intValue();
private int getColorFromHSB(PercentType colorPercent, double factor) {
double value = Math.round(colorPercent.doubleValue() * factor);
logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, (int) value, factor);
return (int) value;
}
}

View File

@ -16,10 +16,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ShellyProtectedHandler} implements a dummy handler for password protected devices.
@ -28,8 +26,6 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class ShellyProtectedHandler extends ShellyBaseHandler {
private final Logger logger = LoggerFactory.getLogger(ShellyProtectedHandler.class);
/**
* Constructor
*

View File

@ -30,7 +30,8 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortStatu
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
@ -118,6 +119,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
break;
case CHANNEL_ROL_CONTROL_FAV:
if (command instanceof Number) {
int id = ((Number) command).intValue() - 1;
int pos = profile.getRollerFav(id);
if (pos > 0) {
logger.debug("{}: Selecting favorite {}, position = {}", thingName, id, pos);
api.setRollerPos(rIndex, pos);
break;
}
}
logger.debug("{}: Invalid favorite index: {}", thingName, command);
break;
case CHANNEL_TIMER_AUTOON:
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command));
@ -152,7 +166,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
return;
} else if (command instanceof IncreaseDecreaseType) {
ShellyShortLightStatus light = api.getLightStatus(index);
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
if (command == IncreaseDecreaseType.INCREASE) {
value = Math.min(light.brightness + DIM_STEPSIZE, 100);
} else {
value = Math.max(light.brightness - DIM_STEPSIZE, 0);
@ -172,14 +186,16 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
}
private void updateBrightnessChannel(int lightId, OnOffType power, int brightness) throws ShellyApiException {
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
if (brightness > 0) {
api.setBrightness(lightId, brightness, config.brightnessAutoOn);
} else {
api.setRelayTurn(lightId, power == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
if (brightness >= 0) { // ignore -1
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType((double) (power == OnOffType.ON ? brightness : 0), DIGITS_NONE, Units.PERCENT));
}
}
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType(new Double(power == OnOffType.ON ? brightness : 0), DIGITS_NONE, Units.PERCENT));
}
@Override
@ -209,10 +225,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
ShellyControlRoller rstatus = api.getRollerStatus(index);
if (!getString(rstatus.state).isEmpty() && !getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_STOP)) {
boolean up = command instanceof UpDownType && (UpDownType) command == UpDownType.UP;
boolean down = command instanceof UpDownType && (UpDownType) command == UpDownType.DOWN;
if ((up && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
|| (down && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
if ((command == UpDownType.UP && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
|| (command == UpDownType.DOWN
&& getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
logger.debug("{}: Roller is already moving ({}), ignore command {}", thingName,
getString(rstatus.state), command);
requestUpdates(1, false);
@ -220,20 +235,31 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
}
}
if (((command instanceof UpDownType) && UpDownType.UP.equals(command))
|| ((command instanceof OnOffType) && OnOffType.ON.equals(command))) {
if ((command == UpDownType.UP) || (command == OnOffType.ON)) {
logger.debug("{}: Open roller", thingName);
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
position = SHELLY_MAX_ROLLER_POS;
}
if (((command instanceof UpDownType) && UpDownType.DOWN.equals(command))
|| ((command instanceof OnOffType) && OnOffType.OFF.equals(command))) {
int pos = profile.getRollerFav(config.favoriteUP - 1);
position = pos > 0 ? pos : SHELLY_MAX_ROLLER_POS;
if (pos > 0) {
logger.debug("{}: Use favoriteUP id {} for positioning roller({}%)", thingName, config.favoriteUP,
pos);
}
} else if ((command == UpDownType.DOWN) || (command == OnOffType.OFF)) {
logger.debug("{}: Closing roller", thingName);
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
position = SHELLY_MIN_ROLLER_POS;
int pos = profile.getRollerFav(config.favoriteDOWN - 1);
if (pos > 0) {
// use favorite position
if (pos > 0) {
logger.debug("{}: Use favoriteDOWN id {} for positioning roller ({}%)", thingName,
config.favoriteDOWN, pos);
}
api.setRollerPos(index, pos);
} else {
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
}
position = SHELLY_MAX_ROLLER_POS - pos;
}
} else if ((command instanceof StopMoveType) && StopMoveType.STOP.equals(command)) {
} else if (command == StopMoveType.STOP) {
logger.debug("{}: Stop roller", thingName);
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_STOP);
} else {
@ -246,7 +272,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
position = d.intValue();
} else {
throw new IllegalArgumentException(
"Invalid value type for roller control/posiution" + command.getClass().toString());
"Invalid value type for roller control/position" + command.getClass().toString());
}
// take position from RollerShutter control and map to Shelly positon (OH:
@ -274,13 +300,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
*/
private void createRelayChannels(ShellyStatusRelay relay, int idx) {
if (!areChannelsCreated()) {
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRelayChannels(getThing(), profile, relay, idx));
updateChannelDefinitions(ShellyChannelDefinitions.createRelayChannels(getThing(), profile, relay, idx));
}
}
private void createDimmerChannels(ShellySettingsStatus dstatus, int idx) {
if (!areChannelsCreated()) {
updateChannelDefinitions(ShellyChannelDefinitions.createDimmerChannels(getThing(), profile, dstatus, idx));
}
}
private void createRollerChannels(ShellyControlRoller roller) {
if (!areChannelsCreated()) {
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRollerChannels(getThing(), roller));
updateChannelDefinitions(ShellyChannelDefinitions.createRollerChannels(getThing(), roller));
}
}
@ -343,16 +375,11 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
toQuantityType(getDouble(rsettings.autoOff), Units.SECOND));
}
// Update input(s) state
updated |= updateInputs(groupName, status, i);
}
i++;
}
}
// Check for Relay in Roller Mode
if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
} else if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
// Check for Relay in Roller Mode
logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
int i = 0;
@ -373,15 +400,15 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
if (state.equals(SHELLY_ALWD_ROLLER_TURN_STOP)) { // only valid in stop state
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
toQuantityType(new Double(pos), Units.PERCENT));
toQuantityType((double) pos, Units.PERCENT));
scheduledUpdates = 1; // one more poll and then stop
}
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR, getStringType(control.stopReason));
updated |= updateInputs(groupName, status, i);
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_SAFETY, getOnOff(control.safetySwitch));
i++;
}
@ -406,7 +433,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
// the same structure as lights[] from Bulb,RGBW2 and Duo. The tag gets replaced by dimmers[] so that Gson
// maps to a different structure (ShellyShortLight).
Gson gson = new Gson();
ShellySettingsStatus dstatus = gson.fromJson(ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
ShellySettingsStatus dstatus = fromJson(gson, ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
ShellySettingsStatus.class);
logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
@ -416,17 +443,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
String groupName = profile.numRelays <= 1 ? CHANNEL_GROUP_DIMMER_CONTROL
: CHANNEL_GROUP_DIMMER_CONTROL + r.toString();
createDimmerChannels(dstatus, l);
// On a status update we map a dimmer.ison = false to brightness 0 rather than the device's brightness
// and send a OFF status to the same channel.
// When the device's brightness is > 0 we send the new value to the channel and a ON command
if (dimmer.ison) {
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.ON);
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType(new Double(getInteger(dimmer.brightness)), DIGITS_NONE, Units.PERCENT));
toQuantityType((double) getInteger(dimmer.brightness), DIGITS_NONE, Units.PERCENT));
} else {
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.OFF);
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
toQuantityType(new Double(0), DIGITS_NONE, Units.PERCENT));
toQuantityType(0.0, DIGITS_NONE, Units.PERCENT));
}
ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
@ -437,7 +466,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
toQuantityType(getDouble(dsettings.autoOff), Units.SECOND));
}
updated |= updateInputs(groupName, orgStatus, l);
l++;
}
}

View File

@ -0,0 +1,608 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.provider;
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyInputState;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLightChannel;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
* added on the first thing status update
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
@Component(service = ShellyChannelDefinitions.class)
public class ShellyChannelDefinitions {
public static final String ITEMT_STRING = "String";
public static final String ITEMT_NUMBER = "Number";
public static final String ITEMT_SWITCH = "Switch";
public static final String ITEMT_CONTACT = "Contact";
public static final String ITEMT_ROLLER = "Rollershutter";
public static final String ITEMT_DIMMER = "Dimmer";
public static final String ITEMT_LOCATION = "Location";
public static final String ITEMT_DATETIME = "DateTime";
public static final String ITEMT_TEMP = "Number:Temperature";
public static final String ITEMT_LUX = "Number:Illuminance";
public static final String ITEMT_POWER = "Number:Power";
public static final String ITEMT_ENERGY = "Number:Energy";
public static final String ITEMT_VOLT = "Number:ElectricPotential";
public static final String ITEMT_AMP = "Number:ElectricPotential";
public static final String ITEMT_ANGLE = "Number:Angle";
public static final String ITEMT_DISTANCE = "Number:Length";
public static final String ITEMT_SPEED = "Number:Speed";
public static final String ITEMT_VOLUME = "Number:Volume";
public static final String ITEMT_TIME = "Number:Time";
public static final String ITEMT_PERCENT = "Number:Dimensionless";
// shortcuts to avoid line breaks (make code more readable)
private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
private static final String CHGR_LIGHT = CHANNEL_GROUP_LIGHT_CONTROL;
private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
private static final String CHGR_METER = CHANNEL_GROUP_METER;
private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
@Activate
public ShellyChannelDefinitions(@Reference ShellyTranslationProvider translationProvider) {
ShellyTranslationProvider m = translationProvider;
// Device: Internal Temp
CHANNEL_DEFINITIONS
// Device
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEMT_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEMT_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEMT_POWER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEMT_DATETIME))
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEMT_SWITCH))
// Relay
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT, "system:power", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
// Dimmer
.add(new ShellyChannel(m, CHANNEL_GROUP_DIMMER_CONTROL, CHANNEL_BRIGHTNESS, "dimmerBrightness",
ITEMT_DIMMER))
// Roller
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL, "rollerShutter", ITEMT_ROLLER))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS, "rollerPosition", ITEMT_DIMMER))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV, "rollerFavorite", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR, "rollerStop", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY, "rollerSafety", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER, "system:button", "system:button"))
// RGBW2
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_LIGHT_POWER, "system:power", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
// Power Meter
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEMT_POWER))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEMT_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEMT_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
// EMeter
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEMT_ENERGY))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEMT_POWER))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEMT_VOLT))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
// Sensors
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEMT_PERCENT))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEMT_LUX))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VOLTAGE, "sensorADC", ITEMT_VOLT))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEMT_CONTACT))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEMT_ANGLE))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_TS, "motionTimestamp", ITEMT_DATETIME))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
// Button/ix3
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
// Addon with external sensors
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEMT_PERCENT))
// Battery
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level", ITEMT_PERCENT))
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEMT_SWITCH));
}
public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
String group = substringBefore(channelName, "#");
String channel = substringAfter(channelName, "#");
if (group.contains(CHANNEL_GROUP_METER)) {
group = CHANNEL_GROUP_METER; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
group = CHANNEL_GROUP_LIGHT_CHANNEL;
} else if (group.contains(CHANNEL_GROUP_STATUS)) {
group = CHANNEL_GROUP_STATUS; // map status1..n to meter
}
if (channel.startsWith(CHANNEL_INPUT)) {
channel = CHANNEL_INPUT;
} else if (channel.startsWith(CHANNEL_BUTTON_TRIGGER)) {
channel = CHANNEL_BUTTON_TRIGGER;
} else if (channel.startsWith(CHANNEL_STATUS_EVENTTYPE)) {
channel = CHANNEL_STATUS_EVENTTYPE;
} else if (channel.startsWith(CHANNEL_STATUS_EVENTCOUNT)) {
channel = CHANNEL_STATUS_EVENTCOUNT;
}
String channelId = group + "#" + channel;
return CHANNEL_DEFINITIONS.get(channelId);
}
/**
* Auto-create relay channels depending on relay type/mode
*
* @return ArrayList<Channel> of channels to be added to the thing
*/
public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellySettingsStatus status) {
Map<String, Channel> add = new LinkedHashMap<>();
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
if (!profile.isSensor) {
// Only some devices report the internal device temp
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
CHANNEL_DEVST_ITEMP);
}
// If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
&& !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
if (profile.settings.ledPowerDisable != null) {
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
}
if (profile.settings.ledStatusDisable != null) {
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
}
return add;
}
/**
* Auto-create relay channels depending on relay type/mode
*
* @return ArrayList<Channel> of channels to be added to the thing
*/
public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusRelay relay, int idx) {
Map<String, Channel> add = new LinkedHashMap<>();
String group = profile.getControlGroup(idx);
ShellySettingsRelay rs = profile.settings.relays.get(idx);
addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
addChannel(thing, add, rs.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
// Shelly 1/1PM Addon
if (relay.extTemperature != null) {
addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
}
if (relay.extHumidity != null) {
addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
}
return add;
}
public static Map<String, Channel> createDimmerChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellySettingsStatus dstatus, int idx) {
Map<String, Channel> add = new LinkedHashMap<>();
String group = profile.getControlGroup(idx);
// Shelly Dimmer has an additional brightness channel
addChannel(thing, add, profile.isDimmer, group, CHANNEL_BRIGHTNESS);
ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
return add;
}
public static Map<String, Channel> createLightChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusLightChannel status, int idx) {
Map<String, Channel> add = new LinkedHashMap<>();
String group = profile.getControlGroup(idx);
ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
// The is no brightness channel in color mode, so we need a power channel
addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
addChannel(thing, add, light.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
addChannel(thing, add, status.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
return add;
}
public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellySettingsStatus status, String group) {
Map<String, Channel> add = new LinkedHashMap<>();
if (status.inputs != null) {
// Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
// created by adding the index to the channel name
boolean multi = ((profile.numRelays == 1) || profile.isDimmer || profile.isRoller)
&& (profile.numInputs >= 2);
for (int i = 0; i < profile.numInputs; i++) {
String suffix = multi ? String.valueOf(i + 1) : "";
ShellyInputState input = status.inputs.get(i);
addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
if (profile.inButtonMode(i)) {
addChannel(thing, add, input.event != null, group, CHANNEL_STATUS_EVENTTYPE + suffix);
addChannel(thing, add, input.eventCount != null, group, CHANNEL_STATUS_EVENTCOUNT + suffix);
}
addChannel(thing, add, true, group,
(!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
}
} else if (status.input != null) {
// old RGBW2 firmware
addChannel(thing, add, true, group, CHANNEL_INPUT);
addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
}
return add;
}
public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
Map<String, Channel> add = new LinkedHashMap<>();
addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL);
addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER);
addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS);
addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR);
addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY);
ShellyBaseHandler handler = (ShellyBaseHandler) thing.getHandler();
if (handler != null) {
ShellySettingsGlobal settings = handler.getProfile().settings;
if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) {
addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
}
}
return add;
}
public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
addChannel(thing, newChannels, (meter.counters != null) && (meter.counters[0] != null), group,
CHANNEL_METER_LASTMIN1);
addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
return newChannels;
}
public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
String group) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
return newChannels;
}
public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusSensor sdata) {
Map<String, Channel> newChannels = new LinkedHashMap<>();
// Sensor data
addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_ILLUM);
addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
if (sdata.sensor != null) { // DW2 or Motion
addChannel(thing, newChannels, sdata.sensor.state != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT); // DW/DW2
addChannel(thing, newChannels, sdata.sensor.motionTimestamp != null, CHANNEL_GROUP_SENSOR, // Motion
CHANNEL_SENSOR_MOTION_TS);
addChannel(thing, newChannels, sdata.sensor.vibration != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_VIBRATION);
}
if (sdata.accel != null) { // DW2
addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_VIBRATION);
addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
}
// Gas
if (sdata.gasSensor != null) {
addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_SSTATE);
addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
CHANNEL_SENSOR_ALARM_STATE);
}
addChannel(thing, newChannels, sdata.adcs != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE); // UNI
// Battery
if (sdata.bat != null) {
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
}
addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
addChannel(thing, newChannels, true, profile.isButton ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_SENSOR,
CHANNEL_LAST_UPDATE);
return newChannels;
}
private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
String channelName) throws IllegalArgumentException {
if (supported) {
String channelId = group + "#" + channelName;
ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
ShellyChannel channelDef = getDefinition(channelId);
if (channelDef != null) {
ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
? new ChannelTypeUID(channelDef.typeId)
: new ChannelTypeUID(BINDING_ID, channelDef.typeId);
Channel channel;
if (channelDef.typeId.equalsIgnoreCase("system:button")) {
channel = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER)
.withType(channelTypeUID).build();
} else {
channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
}
newChannels.put(channelId, channel);
}
}
}
public class ShellyChannel {
private final ShellyTranslationProvider messages;
public String group = "";
public String groupLabel = "";
public String groupDescription = "";
public String channel = "";
public String label = "";
public String description = "";
public String itemType = "";
public String typeId = "";
public String category = "";
public Set<String> tags = new HashSet<>();
public @Nullable Unit<?> unit;
public Optional<Integer> min = Optional.empty();
public Optional<Integer> max = Optional.empty();
public Optional<Integer> step = Optional.empty();
public Optional<String> pattern = Optional.empty();
public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
String itemType, String... category) {
this.messages = messages;
this.group = group;
this.channel = channel;
this.itemType = itemType;
this.typeId = typeId;
groupLabel = getText(PREFIX_GROUP + group + ".label");
groupDescription = getText(PREFIX_GROUP + group + ".description");
label = getText(PREFIX_CHANNEL + channel + ".label");
description = getText(PREFIX_CHANNEL + channel + ".description");
}
public String getChanneId() {
return group + "#" + channel;
}
public String getGroupLabel() {
return getGroupAttribute("group");
}
public String getGroupDescription() {
return getGroupAttribute("group");
}
public String getLabel() {
return getChannelAttribute("label");
}
public String getDescription() {
return getChannelAttribute("description");
}
public boolean getAdvanced() {
String attr = getChannelAttribute("advanced");
return attr.isEmpty() ? false : Boolean.valueOf(attr);
}
public boolean getReadyOnly() {
String attr = getChannelAttribute("advanced");
return attr.isEmpty() ? false : Boolean.valueOf(attr);
}
public String getCategory() {
return getChannelAttribute("category");
}
public String getMin() {
return getChannelAttribute("min");
}
public String getMax() {
return getChannelAttribute("max");
}
public String getStep() {
return getChannelAttribute("step");
}
public String getPattern() {
return getChannelAttribute("pattern");
}
public String getGroupAttribute(String attribute) {
String key = PREFIX_GROUP + group + "." + attribute;
String value = messages.getText(key);
return value != null && !value.equals(key) ? value : "";
}
public String getChannelAttribute(String attribute) {
String key = PREFIX_CHANNEL + channel + "." + attribute;
String value = messages.getText(key);
return value != null && !value.equals(key) ? value : "";
}
private String getText(String key) {
String text = messages.get(key);
return text != null ? text : "";
}
}
public static class ChannelMap {
private final Map<String, ShellyChannel> map = new HashMap<>();
private ChannelMap add(ShellyChannel def) {
map.put(def.getChanneId(), def);
return this;
}
public ShellyChannel get(String channelName) throws IllegalArgumentException {
ShellyChannel def = null;
if (channelName.contains("#")) {
def = map.get(channelName);
if (def != null) {
return def;
}
}
for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
if (entry.getValue().channel.contains("#" + channelName)) {
def = entry.getValue();
break;
}
}
if (def == null) {
throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
}
return def;
}
}
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.util;
package org.openhab.binding.shelly.internal.provider;
import java.util.Locale;
@ -19,6 +19,10 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* {@link ShellyTranslationProvider} provides i18n message lookup
@ -26,24 +30,20 @@ import org.osgi.framework.Bundle;
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
@Component(service = ShellyTranslationProvider.class)
public class ShellyTranslationProvider {
private final Bundle bundle;
private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider;
public ShellyTranslationProvider(Bundle bundle, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
this.bundle = bundle;
@Activate
public ShellyTranslationProvider(@Reference TranslationProvider i18nProvider,
@Reference LocaleProvider localeProvider) {
this.bundle = FrameworkUtil.getBundle(this.getClass());
this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider;
}
public ShellyTranslationProvider(final ShellyTranslationProvider other) {
this.bundle = other.bundle;
this.i18nProvider = other.i18nProvider;
this.localeProvider = other.localeProvider;
}
public @Nullable String get(String key, @Nullable Object... arguments) {
return getText(key.contains("@text/") || key.contains(".shelly.") ? key : "message." + key, arguments);
}

View File

@ -93,7 +93,7 @@ public class ShellyChannelCache {
}
} catch (IllegalArgumentException e) {
logger.debug("{}: Unable to update channel {} with {} (type {}): {} ({})", thingName, channelId, newValue,
newValue.getClass(), ShellyUtils.getMessage(e), e.getClass());
newValue.getClass(), ShellyUtils.getMessage(e), e.getClass(), e);
}
return false;
}
@ -119,7 +119,8 @@ public class ShellyChannelCache {
}
public State getValue(String channelId) {
return channelData.getOrDefault(channelId, UnDefType.NULL);
State st = channelData.get(channelId);
return st != null ? st : UnDefType.NULL;
}
public void resetChannel(String channelId) {

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
@ -40,6 +41,10 @@ import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.internal.Primitives;
/**
* {@link ShellyUtils} provides general utility functions
*
@ -47,6 +52,49 @@ import org.openhab.core.types.UnDefType;
*/
@NonNullByDefault
public class ShellyUtils {
private final static String PRE = "Unable to create object of type ";
public static <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT) throws ShellyApiException {
@Nullable
T o = fromJson(gson, json, classOfT, true);
if (o == null) {
throw new ShellyApiException("Unable to create JSON object");
}
return o;
}
public static @Nullable <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT, boolean exceptionOnNull)
throws ShellyApiException {
String className = substringAfter(classOfT.getName(), "$");
if (json == null) {
if (exceptionOnNull) {
throw new IllegalArgumentException(PRE + className + ": json is null!");
} else {
return null;
}
}
if (classOfT.isInstance(json)) {
return Primitives.wrap(classOfT).cast(json);
} else if (json.isEmpty()) { // update GSON might return null
throw new ShellyApiException(PRE + className + "from empty JSON");
} else {
try {
@Nullable
T obj = gson.fromJson(json, classOfT);
if ((obj == null) && exceptionOnNull) { // new in OH3: fromJson may return null
throw new ShellyApiException(PRE + className + "from JSON: " + json);
}
return obj;
} catch (JsonSyntaxException e) {
throw new ShellyApiException(PRE + className + "from JSON (syntax/format error): " + json, e);
} catch (RuntimeException e) {
throw new ShellyApiException(PRE + className + "from JSON: " + json, e);
}
}
}
public static String mkChannelId(String group, String channel) {
return group + "#" + channel;
}
@ -171,7 +219,7 @@ public class ShellyUtils {
return UnDefType.NULL;
}
BigDecimal bd = new BigDecimal(value.doubleValue());
return toQuantityType(bd.setScale(digits, BigDecimal.ROUND_HALF_UP), unit);
return toQuantityType(bd.setScale(digits, RoundingMode.HALF_UP), unit);
}
public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
@ -233,7 +281,7 @@ public class ShellyUtils {
}
public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
return profile.isBulb || profile.isDuo && !profile.inColor ? CHANNEL_GROUP_WHITE_CONTROL
return profile.isBulb || profile.isDuo ? CHANNEL_GROUP_WHITE_CONTROL
: CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
}

View File

@ -5,22 +5,28 @@
<name>Shelly Binding</name>
<description>This binding supports the Shelly series of devices.</description>
<author>Markus Michels</author>
<config-description>
<config-description uri="binding:shelly">
<parameter name="defaultUserId" type="text">
<default>admin</default>
<label>Default User</label>
<description>Default userId to access protected Shelly devices.</description>
<label>@text/binding.shelly.config.defaultUserId.label</label>
<description>@text/binding.shelly.config.defaultUserId.description</description>
</parameter>
<parameter name="defaultPassword" type="text">
<default>admin</default>
<label>Default Password</label>
<description>Default password to access protected Shelly devices.</description>
<label>@text/binding.shelly.config.defaultPassword.label</label>
<description>@text/binding.shelly.config.defaultPassword.description</description>
</parameter>
<parameter name="localIP" type="text">
<label>@text/binding.shelly.config.localIP.label</label>
<description>@text/binding.shelly.config.localIP.description</description>
<default></default>
</parameter>
<parameter name="autoCoIoT" type="boolean">
<default>true</default>
<label>Auto-enable CoIoT</label>
<description>True: Enable CoIoT events by default when firmware 1.6+ is detected</description>
<label>@text/binding.shelly.config.autoCoIoT.label</label>
<description>@text/binding.shelly.config.autoCoIoT.description</description>
</parameter>
</config-description>

View File

@ -43,8 +43,8 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>60</default>
<unitLabel>seconds</unitLabel>
@ -62,6 +62,16 @@
<description>Password for HTTP API access.</description>
<context>password</context>
</parameter>
<parameter name="favoriteUP" type="integer" min="0" max="4" required="false">
<label>Favorite Id for UP</label>
<description>Which position favorite should be used for UP (0=none, favorites are defined in the Shelly App)</description>
<default>0</default>
</parameter>
<parameter name="favoriteDOWN" type="integer" min="0" max="4" required="false">
<label>Favorite Id for DOWN</label>
<description>Which position favorite should be used for DOWN (0=none, favorites are defined in the Shelly App)</description>
<default>0</default>
</parameter>
<parameter name="deviceIp" type="text" required="true">
<label>Device IP Address</label>
<description>IP-Address of the Shelly device.</description>
@ -71,7 +81,7 @@
<label>Enable Roller Events (Roller only)</label>
<description>True if the binding should register to get Roller Events.</description>
<advanced>true</advanced>
<default>true</default>
<default>false</default>
</parameter>
<parameter name="eventsCoIoT" type="boolean" required="false">
<label>Enable CoIoT Events</label>
@ -79,8 +89,8 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>60</default>
<unitLabel>seconds</unitLabel>
@ -104,7 +114,8 @@
</parameter>
<parameter name="brightnessAutoOn" type="boolean" required="false">
<label>Brightness Auto-ON</label>
<description>true: Turn device ON if brightness>0 is set; false: don't touch power status when brightness is set.</description>
<description>true: Turn device ON if brightness above 0 is set; false: don't touch power status when brightness is
set.</description>
<default>true</default>
</parameter>
<parameter name="eventsButton" type="boolean" required="false">
@ -131,8 +142,8 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>60</default>
<unitLabel>seconds</unitLabel>
@ -172,8 +183,8 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>60</default>
<unitLabel>seconds</unitLabel>
@ -207,8 +218,8 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>60</default>
<unitLabel>seconds</unitLabel>
@ -250,10 +261,10 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<label>Update Interval</label>
<parameter name="updateInterval" type="integer" min="60" required="true" unit="s">
<label>General Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>3600</default>
<default>900</default>
<unitLabel>seconds</unitLabel>
<advanced>true</advanced>
</parameter>
@ -286,10 +297,10 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="updateInterval" type="integer" required="true" unit="s">
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
<label>Update Interval</label>
<description>Interval to query an update from the device.</description>
<default>3600</default>
<default>60</default>
<unitLabel>seconds</unitLabel>
<advanced>true</advanced>
</parameter>

View File

@ -1,3 +1,12 @@
binding.shelly.config.defaultUserId.label = Default UserId
binding.shelly.config.defaultUserId.description = This default user id will be used for device access if no one is specified in the thing configuration.
binding.shelly.config.defaultPassword.label = Default Password
binding.shelly.config.defaultPassword.description = Default password for device access if none is specified in the thing configuration.
binding.shelly.config.localIP.label = Host Interface IP
binding.shelly.config.localIP.description = This interface will be used to setup CoIoT listen and build Action URLs. openHAB's network configuration will be used if this is not set (recommended)
binding.shelly.config.autoCoIoT.label = Auto-CoIoT
binding.shelly.config.autoCoIoT.description = If enabled CoIoT will be automatically used when the devices runs a firmware version 1.6 or newer; false: Use thing configuration to enabled/disable CoIoT events.
discovery.failed# Config status messages
config-status.error.missing-device-ip=IP address of the Shelly device is missing.
@ -20,12 +29,14 @@ message.init.noipaddress = Unable to detect local IP address. Please make sure t
message.init.protected = Device is password protected, enter correct credentials in thing configuration.
message.command.failed = ERROR: Unable to process command {0} for channel {1}
message.command.init = Thing not yet initialized, command {0} triggers initialization
message.status.unknown.initializing = Initializing or device in sleep mode.
message.statusupdate.failed = Unable to update status
message.event.triggered = Event triggered: {0}
message.coap.init.failed = Unable to start CoIoT: {0}
message.discovery.disabled = Device is marked as non-discoverable -> skip
message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
message.discovery.failed = Device discovery of device with IP address {0} failed: {1}
message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App
# Device
channel-type.shelly.deviceName.label = Device Name
@ -42,6 +53,8 @@ channel-type.shelly.temperature3.label = Temperature 3
channel-type.shelly.temperature3.description = Temperature of external Sensor #3
channel-type.shelly.humidity.label = Humidity
channel-type.shelly.humidity.description = Relative humidity (0..100%)
channel-type.shelly.motionTimestamp.label = Last Motion
channel-type.shelly.motionTimestamp.description = Timestamp when last motion was detected.
# Roller
channel-type.shelly.rollerState.label = State

View File

@ -1,6 +1,14 @@
# binding
binding.shelly.name = Shelly Binding
binding.shelly.description = Dieses Binding integriert Shelly-Komponenten, die über WiFi gesteuert werden können.
binding.shelly.config.defaultUserId.label = Standardbenutzerkennung
binding.shelly.config.defaultUserId.description = Sofern konfiguriert, wird diese für den Zugriff auf das Gerät verwendet, wenn in der Thing-Konfiguration keine angegeben ist.
binding.shelly.config.defaultPassword.label = Standardpasswort
binding.shelly.config.defaultPassword.description = Dieses Passwort wird für den Gerätezugriff verwendet, wenn in der Thing-Konfiguration keines gesetzt ist.
binding.shelly.config.localIP.label = Host Interface IP
binding.shelly.config.localIP.description = Lokale IP-Adresse der Netzwerk-Schnittstelle, welche für Verbindungen genutzt wird (CoIoT Listen und http-Callback). Default: Voreingestelltes Interface aus der openHAB Netzwerkkonfiguration.
binding.shelly.config.autoCoIoT.label = Auto-CoIoT
binding.shelly.config.autoCoIoT.description = Bei aktiviertem Auto-CoIoT wird das Protokoll aktiviert, sobald das Gerät eine Firmwareversion 1.6 oder neuer verwendet. Andernfalls wird dies über die Thing-Konfiguration gesteuert.
# Config status messages
config-status.error.missing-deviceip=Die IP-Adresse des Shelly Gerätes ist nicht konfiguriert.
@ -27,6 +35,7 @@ message.init.noipaddress = Es konnte keine lokale IP-Adresse ermittelt werden. B
message.init.protected = Das Gerät ist passwortgeschützt, die Zugangsdaten müssen in der Thing Konfiguration hinterlegt werden.
message.command.failed = FEHLER: Der Befehl {0} für Kanal {1} kann nicht verarbeitet werden
message.command.init = Thing aktuell nicht initialisiert, der Befehl {0} führt zur Initialisierung
message.status.unknown.initializing = Initialisierung oder Gerät im Schlafmodus.
message.statusupdate.failed = Status konnte nicht aktualisiert werden
message.event.triggered = Event erzeugt: {0}
message.coap.init.failed = CoAP/CoIoT konnte nicht gestartet werden: {0}
@ -34,10 +43,13 @@ message.discovery.disabled = Das Ger
message.discovery.protected = Das Gerät mit der IP-Adresse {0} ist zugriffsgeschützt und keine Zugangsdaten konfiguriert.
message.discovery.failed = Erkennung des Gerätes mit der IP-Adresse {0} ist fehlgeschlagen
message.roller.calibrating = Das Gerät ist nicht kalibriert. Es ist eine Kalibrierung mit der Shelly App erforderlich.
message.roller.favmissing = Positions-Favoriten werden von der installierten Firmwareversion nicht unterstützt (ab 1.9.2), oder sind nicht in der Shelly App konfiguriert.
# thing types
thing-type.shelly.shelly1.label = Shelly1 (SHSW-1)
thing-type.shelly.shelly1.label = Shelly 1 (SHSW-1)
thing-type.shelly.shelly1.description = Shelly 1 (1 Relay)
thing-type.shelly.shelly1l.label = Shelly 1L (SHSW-L)
thing-type.shelly.shelly1l.description = Shelly 1L (1 Relay)
thing-type.shelly.shelly1pm.label = Shelly 1PM (SHSW-PM)
thing-type.shelly.shelly1pm.description = Shelly 1PM mit 1xRelais und Strommesser
thing-type.shelly.shellyem.label = Shelly EM (SHEM)
@ -56,8 +68,14 @@ thing-type.shelly.shelly4pro.label = Shelly4 Pro Relay (SHSW-4)
thing-type.shelly.shelly4pro.description = Shelly 4 Pro mit 4 Relais und Strommessern
thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
thing-type.shelly.shellyplugu1.label = Shelly Plug US (SHPLG-U1)
thing-type.shelly.shellyplugu1.description = Shelly Plug-US als schaltbare Steckdose (110V)
thing-type.shelly.shellyplugs.label = Shelly Plug-S (SHPLG-S)
thing-type.shelly.shellyplugs.description = Shelly Plug-S als schaltbare Steckdose
thing-type.shelly.shellyuni.label = Shelly UNI (SHUNI-1)
thing-type.shelly.shellyuni.description = Shelly UNI
thing-type.shelly.shellydimmer.label = Shelly Dimmer (SHDM-1)
thing-type.shelly.shellydimmer.description = Shelly mit Dimmer-Funktion
thing-type.shelly.shellydimmer2.label = Shelly Dimmer (SHDM-2)
@ -80,6 +98,8 @@ thing-type.shelly.shellybulb.label = Shelly Bulb (SHBLB-1)
thing-type.shelly.shellybulb.description = Shelly Glühbirne weiß/Farbe
thing-type.shelly.shellybulbduo.label = Shelly Duo (SHBDUO-1)
thing-type.shelly.shellybulbduo.description = Shelly Duo Glühbirne
thing-type.shelly.shellycolorbulb.label = Shelly Duo Color (SHCB-1)
thing-type.shelly.shellycolorbulb.description = Farbige Shelly Duo Glühbirne (Farb- und Weißmodus)
thing-type.shelly.shellyvintage.label = Shelly Vintage (SHVIN-1)
thing-type.shelly.shellyvintage.description = Shelly Vintage Glühbirne
thing-type.shelly.shellyrgbw2-color.label = Shelly RGBW2 Color Mode (SHRGBW2)
@ -87,25 +107,61 @@ thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW-Controller im Farb
thing-type.shelly.shellyrgbw2-white.label = Shelly RGBW2 White Mode (SHRGBW2)
thing-type.shelly.shellyrgbw2-white.description = Shelly RGBW-Controller im Weiß-Modus, 4 Streifen
# thing config - generic
thing-type.config.shelly.generic.userId.label = Benutzer
thing-type.config.shelly.generic.userId.description = Benutzerkennung für API-Zugriff
thing-type.config.shelly.generic.password.label = Passwort
thing-type.config.shelly.generic.password.description = Passwort für API-Zugriff
thing-type.config.shelly.generic.deviceIp.label = IP Adresse
thing-type.config.shelly.generic.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.generic.weakSignal.label = Schwaches Signal (dBm)
thing-type.config.shelly.generic.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
thing-type.config.shelly.generic.eventsButton.label = Button Events
thing-type.config.shelly.generic.eventsButton.description = Aktiviert die Button Action URLS
thing-type.config.shelly.generic.eventsPush.label = Push Events
thing-type.config.shelly.generic.eventsPush.description = Aktiviert die Push Button Action URLS
thing-type.config.shelly.generic.eventsSwitch.label = Output Events
thing-type.config.shelly.generic.eventsSwitch.description = Aktiviert die Output Action URLS
thing-type.config.shelly.generic.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.generic.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.generic.updateInterval.label = Status-Intervall
thing-type.config.shelly.generic.updateInterval.description = Intervall für die Hintergundaktualisierung
# thing config - relay
thing-type.config.shelly.relay.userId.label = Benutzer
thing-type.config.shelly.relay.userId.description = Benutzerkennung für API-Zugriff
thing-type.config.shelly.relay.password.label = Passwort
thing-type.config.shelly.relay.password.description = Passwort für API-Zugriff
thing-type.config.shelly.relay.deviceIp.label = IP Adresse
thing-type.config.shelly.relay.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.relay.eventsButton.label = Button Events
thing-type.config.shelly.relay.eventsButton.description = Aktiviert die Button Action URLS
thing-type.config.shelly.relay.eventsPush.label = Push Events
thing-type.config.shelly.relay.eventsPush.description = Aktiviert die Push Button Action URLS
thing-type.config.shelly.relay.eventsSwitch.label = Output Events
thing-type.config.shelly.relay.eventsSwitch.description = Aktiviert die Output Action URLS
thing-type.config.shelly.relay.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.relay.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.relay.updateInterval.label = Status-Intervall
thing-type.config.shelly.relay.updateInterval.description = Intervall für die Hintergundaktualisierung
# thing config - roller
thing-type.config.shelly.roller.userId.label = Benutzer
thing-type.config.shelly.roller.userId.description = Benutzerkennung für API-Zugriff
thing-type.config.shelly.roller.password.label = Passwort
thing-type.config.shelly.roller.password.description = Passwort für API-Zugriff
thing-type.config.shelly.roller.favoriteUP.label = Favoriten ID für UP
thing-type.config.shelly.roller.favoriteUP.description = Gibt die Favoriten ID an, die beim Command UP auf den roller#control channel verwendet wird (Konfiguration in der Shelly App)
thing-type.config.shelly.roller.favoriteDOWN.label = Favoriten ID für DOWN
thing-type.config.shelly.roller.favoriteDOWN.description = Gibt die Favoriten ID an, die beim Command DOWN auf den roller#control channel verwendet wird (Konfiguration in der Shelly App)
thing-type.config.shelly.roller.deviceIp.label = IP Adresse
thing-type.config.shelly.roller.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.roller.eventsRoller.label = Rollladen-Events
thing-type.config.shelly.roller.eventsRoller.description = Aktiviert die Roller Action URLS
thing-type.config.shelly.roller.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.roller.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.roller.updateInterval.label = Status-Intervall
thing-type.config.shelly.roller.updateInterval.description = Intervall für die Hintergundaktualisierung
# thing config - dimmer
thing-type.config.shelly.dimmer.userId.label = Benutzer
thing-type.config.shelly.dimmer.userId.description = Benutzerkennung für API-Zugriff
thing-type.config.shelly.dimmer.password.label = Passwort
thing-type.config.shelly.dimmer.password.description = Passwort für API-Zugriff
thing-type.config.shelly.dimmer.deviceIp.label = IP Adresse
thing-type.config.shelly.dimmer.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.dimmer.brightnessAutoOn.label = Helligkeit Auto-EIN
thing-type.config.shelly.dimmer.brightnessAutoOn.description = an: Licht wird eingeschaltet, wenn ein Helligkeitswert größer 0 gesetzt wird; aus: Helligkeit wird genetzt, aber das Gerät nicht eingeschaltet
thing-type.config.shelly.dimmer.eventsButton.label = Button Events
thing-type.config.shelly.dimmer.eventsButton.description = Aktiviert die Button Action URLS
thing-type.config.shelly.dimmer.eventsPush.label = Push Events
thing-type.config.shelly.dimmer.eventsPush.description = Aktiviert die Push Button Action URLS
thing-type.config.shelly.dimmer.eventsSwitch.label = Output Events
thing-type.config.shelly.dimmer.eventsSwitch.description = Aktiviert die Output Action URLS
thing-type.config.shelly.dimmer.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.dimmer.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.dimmer.updateInterval.label = Status-Intervall
thing-type.config.shelly.dimmer.updateInterval.description = Intervall für die Hintergundaktualisierung
# thing config - light
thing-type.config.shelly.light.userId.label = Benutzer
@ -114,8 +170,6 @@ thing-type.config.shelly.light.password.label = Passwort
thing-type.config.shelly.light.password.description = Passwort für API-Zugriff
thing-type.config.shelly.light.deviceIp.label = IP Adresse
thing-type.config.shelly.light.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.light.weakSignal.label = Schwaches Signal (dBm)
thing-type.config.shelly.light.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
thing-type.config.shelly.light.brightnessAutoOn.label = Helligkeit Auto-EIN
thing-type.config.shelly.light.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
thing-type.config.shelly.light.eventsButton.label = Button Events
@ -129,6 +183,20 @@ thing-type.config.shelly.light.eventsCoIoT.description = Aktiviert CoIoT-Protoko
thing-type.config.shelly.light.updateInterval.label = Status-Intervall
thing-type.config.shelly.light.updateInterval.description = Intervall für die Hintergrundaktualisierung
# thing config - RGBW2
thing-type.config.shelly.rgbw2.userId.label = Benutzer
thing-type.config.shelly.rgbw2.userId.description = Benutzerkennung für API-Zugriff
thing-type.config.shelly.rgbw2.password.label = Passwort
thing-type.config.shelly.rgbw2.password.description = Passwort für API-Zugriff
thing-type.config.shelly.rgbw2.deviceIp.label = IP Adresse
thing-type.config.shelly.rgbw2.deviceIp.description = IP Adresse der Shelly-Komponente
thing-type.config.shelly.rgbw2.brightnessAutoOn.label = Helligkeit Auto-EIN
thing-type.config.shelly.rgbw2.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
thing-type.config.shelly.rgbw2.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.rgbw2.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.rgbw2.updateInterval.label = Status-Intervall
thing-type.config.shelly.rgbw2.updateInterval.description = Intervall für die Hintergrundaktualisierung
# thing config - battery
thing-type.config.shelly.battery.userId.label = Benutzer
thing-type.config.shelly.battery.userId.description = Benutzerkennung für API-Zugriff
@ -140,8 +208,6 @@ thing-type.config.shelly.battery.eventsSensorReport.label = Sensor Events
thing-type.config.shelly.battery.eventsSensorReport.description = Aktiviert die Sensor Action URLS
thing-type.config.shelly.battery.eventsCoIoT.label = CoIoT aktivieren
thing-type.config.shelly.battery.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
thing-type.config.shelly.battery.weakSignal.label = Schwaches Signal (dBm)
thing-type.config.shelly.battery.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
thing-type.config.shelly.battery.lowBattery.label = Batterieladung niedrig (%)
thing-type.config.shelly.battery.lowBattery.description = Ein Alarm wird ausgelöst, wenn das Gerät eine Batterieladung kleiner diesem Schwellwert meldet. Default: 20%
thing-type.config.shelly.battery.updateInterval.label = Status-Intervall
@ -170,6 +236,14 @@ thing-type.shelly.shelly1pm.group.sensors.label = Externe Sensoren
thing-type.shelly.shelly1pm.group.sensors.description = Werte der externen Sensoren (nur wenn angeschlossen)
thing-type.shelly.shelly1pm.group.device.label = Gerätestatus
thing-type.shelly.shelly1pm.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shelly1l.group.relay.label = Relais
thing-type.shelly.shelly1l.group.relay.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shelly1l.group.meter.label = Verbrauch
thing-type.shelly.shelly1l.group.meter.description = Verbrauchswerte und andere Informationen
thing-type.shelly.shelly1l.group.sensors.label = Externe Sensoren
thing-type.shelly.shelly1l.group.sensors.description = Temperaturwerte der externen Sensoren (nur wenn angeschlossen)
thing-type.shelly.shelly1l.group.device.label = Gerätestatus
thing-type.shelly.shelly1l.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellyem.group.relay.label = Relais
thing-type.shelly.shellyem.group.relay.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shellyem.group.meter1.label = Verbrauch 1
@ -248,6 +322,20 @@ thing-type.shelly.shellyplugs.group.meter.label = Verbrauch
thing-type.shelly.shellyplugs.group.meter.description = Verbrauchswerte und andere Informationen
thing-type.shelly.shellyplugs.group.device.label = Gerätestatus
thing-type.shelly.shellyplugs.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellyplugu1.group.relay.label = Relais
thing-type.shelly.shellyplugu1.group.relay.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shellyplugu1.group.meter.label = Verbrauch
thing-type.shelly.shellyplugu1.group.meter.description = Verbrauchswerte und andere Informationen
thing-type.shelly.shellyplugu1.group.device.label = Gerätestatus
thing-type.shelly.shellyplugu1.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellyuni.group.relay1.label = Relais 1
thing-type.shelly.shellyuni.group.relay1.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shellyuni.group.relay2.label = Relais 2
thing-type.shelly.shellyuni.group.relay2.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shellyuni.group.sensors.label = Sensordaten
thing-type.shelly.shellyuni.group.sensors.description = Daten der angeschlossenen Sensoren
thing-type.shelly.shellyuni.group.device.label = Gerätestatus
thing-type.shelly.shellyuni.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellydimmer.group.relay.label = Relais
thing-type.shelly.shellydimmer.group.relay.description = Relais Ein-/Ausgänge und Status
thing-type.shelly.shellydimmer.group.meter.label = Verbrauch
@ -260,11 +348,11 @@ thing-type.shelly.shellydimmer2.group.meter.label = Verbrauch
thing-type.shelly.shellydimmer2.group.meter.description = Verbrauchswerte und andere Informationen
thing-type.shelly.shellydimmer2.group.device.label = Gerätestatus
thing-type.shelly.shellydimmer2.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellyix3.group.status1.label = Eingang #1
thing-type.shelly.shellyix3.group.status1.label = Eingang 1
thing-type.shelly.shellyix3.group.status1.description = Status Informationen zum Eingang 1
thing-type.shelly.shellyix3.group.status2.label = Eingang #2
thing-type.shelly.shellyix3.group.status2.label = Eingang 2
thing-type.shelly.shellyix3.group.status2.description = Status Informationen zum Eingang 2
thing-type.shelly.shellyix3.group.status3.label = Eingang #3
thing-type.shelly.shellyix3.group.status3.label = Eingang 3
thing-type.shelly.shellyix3.group.status3.description = Status Informationen zum Eingang 3
thing-type.shelly.shellyix3.group.device.label = Gerätestatus
thing-type.shelly.shellyix3.group.device.description = Informationen zum Gerätestatus
@ -290,10 +378,18 @@ thing-type.shelly.shellybulbduo.group.meter.label = Verbrauch
thing-type.shelly.shellybulbduo.group.meter.description = Verbrauchswerte
thing-type.shelly.shellybulbduo.group.device.label = Gerätestatus
thing-type.shelly.shellybulbduo.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellycolorbulb.group.control.label = Steuerung
thing-type.shelly.shellycolorbulb.group.control.description = Steuerung des Lichts
thing-type.shelly.shellycolorbulb.group.white.label = Weißwerte
thing-type.shelly.shellycolorbulb.group.white.description = Einstellungen für den Weiß-Modus
thing-type.shelly.shellycolorbulb.group.meter.label = Verbrauch
thing-type.shelly.shellycolorbulb.group.meter.description = Verbrauchswerte
thing-type.shelly.shellycolorbulb.group.device.label = Gerätestatus
thing-type.shelly.shellycolorbulb.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellyvintage.group.control.label = Steuerung
thing-type.shelly.shellyvintage.group.control.description = Steuerung des Lichts
thing-type.shelly.shellyvintage.group.white.label = Weißwerte
thing-type.shelly.shellyvintage.group.white.description = Einstellungen für den Weiß-Modus
thing-type.shelly.shellyvintage.group.white.label = Steuerung
thing-type.shelly.shellyvintage.group.white.description = Geräteeinstellungen
thing-type.shelly.shellyvintage.group.meter.label = Verbrauch
thing-type.shelly.shellyvintage.group.meter.description = Verbrauchswerte
thing-type.shelly.shellyvintage.group.device.label = Gerätestatus
@ -358,6 +454,12 @@ thing-type.shelly.shellygas.group.sensors.label = Sensordaten
thing-type.shelly.shellygas.group.sensors.description = Messwerte und Status des Sensors
thing-type.shelly.shellygas.group.device.label = Gerätestatus
thing-type.shelly.shellygas.group.device.description = Informationen zum Gerätestatus
thing-type.shelly.shellymotion.group.sensors.label = Sensordaten
thing-type.shelly.shellymotion.group.sensors.description = Messwerte und Status des Sensors
thing-type.shelly.shellymotion.group.battery.label = Batteriestatus
thing-type.shelly.shellymotion.group.battery.description = Informationen zum Akku
thing-type.shelly.shellymotion.group.device.label = Gerätestatus
thing-type.shelly.shellymotion.group.device.description = Informationen zum Gerätestatus
# channels
@ -370,17 +472,19 @@ channel-type.shelly.timerAutoOff.description = Wenn das Relais eingeschaltet wir
channel-type.shelly.timerActive.label = Timer aktiv
channel-type.shelly.timerActive.description = ON: Auto-On/Off Timer ist aktiv
channel-type.shelly.temperature1.label = Temperatur 1
channel-type.shelly.temperature1.description = Temperatur des externen Sensors #1
channel-type.shelly.temperature1.description = Temperatur des externen Sensors 1
channel-type.shelly.temperature2.label = Temperatur 2
channel-type.shelly.temperature2.description = Temperatur des externen Sensors #2
channel-type.shelly.temperature2.description = Temperatur des externen Sensors 2
channel-type.shelly.temperature3.label = Temperatur 3
channel-type.shelly.temperature3.description = Temperatur des externen Sensors #3
channel-type.shelly.temperature3.description = Temperatur des externen Sensors 3
channel-type.shelly.humidity.label = Luftfeuchtigkeit
channel-type.shelly.humidity.description = Relative Luftfeuchtigkeit (0..100%)
channel-type.shelly.rollerShutter.label = Steuerung (0=offen, 100=geschlossen)
channel-type.shelly.rollerShutter.description = Steuerung für den Rollladen: UP, DOWN, STOP, Position (0=offen, 100=geschlossen)
channel-type.shelly.rollerPosition.label = Position (100=offen, 0=zu)
channel-type.shelly.rollerPosition.description = Invertierte Position des Rollladen: 100=offen, 0=zu
channel-type.shelly.rollerFavorite.label = Positionsfavorit
channel-type.shelly.rollerFavorite.description = Wählt den Positionsfavoriten 1-4, Positionen werden in der Shelly App konfiguriert; 0=undefiniert (kein Favorit gewählt)
channel-type.shelly.rollerState.label = Status
channel-type.shelly.rollerState.description = Zustand des Rollladen (open/closed/stopped).
channel-type.shelly.rollerState.state.option.open = öffnet
@ -402,12 +506,12 @@ channel-type.shelly.whiteBrightness.label = Helligkeit
channel-type.shelly.whiteBrightness.description = Helligkeit (0-100%, 0=aus)
channel-type.shelly.meterWatts.label = Leistung
channel-type.shelly.meterWatts.description = Aktueller Stromverbrauch in Watt
channel-type.shelly.meterAccuWatts.label = Kumulierter Verbrauch
channel-type.shelly.meterAccuWatts.description = Kumulierter Verbrauch in Watt
channel-type.shelly.meterAccuWatts.label = Kumulierte Verbrauch
channel-type.shelly.meterAccuWatts.description = Kumulierterr Verbrauch in Watt
channel-type.shelly.meterAccuTotal.label = Kumulierter Gesamtverbrauch
channel-type.shelly.meterAccuTotal.description = Kumulierter Gesamtverbrauch in kW/h
channel-type.shelly.meterAccuReturned.label = Kumulierter Einspeisung
channel-type.shelly.meterAccuReturned.description = Kumulierter Einspeisung in kW/h
channel-type.shelly.meterAccuReturned.label = Kumulierte Einspeisung
channel-type.shelly.meterAccuReturned.description = Kumulierte Einspeisung in kW/h
channel-type.shelly.meterCurrent.label = Stromstärke
channel-type.shelly.meterCurrent.description = Aktuelle gemessene Stromstärke
channel-type.shelly.meterTotal.label = Gesamtverbrauch
@ -487,10 +591,16 @@ channel-type.shelly.sensorIllumination.state.option.unknown = Unbekannt
channel-type.shelly.sensorIllumination.description = Angabe zum erkannten Tageslichtwert
channel-type.shelly.sensorPPM.label = Gas-Konzentration
channel-type.shelly.sensorPPM.description = Gemessene Konzentration in PPM
channel-type.shelly.sensorADC.label = Spannung (ADC)
channel-type.shelly.sensorADC.description = Gemessene Spannung
channel-type.shelly.sensorTilt.label = Öffnungswinkel
channel-type.shelly.sensorTilt.description = Öffnungswinkel in Grad (erfordert Kalibrierung in der App)
channel-type.shelly.sensorVibration.label = Vibration
channel-type.shelly.sensorVibration.description = ON: Sensor hat eine Vibration erkannt
channel-type.shelly.sensorMotion.label = Bewegung
channel-type.shelly.sensorMotion.description = ON: Es wurde eine Bewegung erkannt
channel-type.shelly.motionTimestamp.label = Letzte Bewegung
channel-type.shelly.motionTimestamp.description = Datum/Uhrzeit, wann die letzte Bewegung erkannt wurde.
channel-type.shelly.sensorValve.label = Ventil
channel-type.shelly.sensorValve.description = Gibt den Status des Ventils an, sofern eines angeschlossen ist.
channel-type.shelly.sensorValve.state.option.closed = geschlossen

View File

@ -69,14 +69,14 @@
<item-type>Number:ElectricPotential</item-type>
<label>Battery Voltage</label>
<description>Battery voltage in V</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="uptime" advanced="true">
<item-type>Number:Time</item-type>
<label>Uptime</label>
<description>Number of seconds since the device was powered up</description>
<state readOnly="true" pattern="%d %unit%">
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="heartBeat" advanced="true">
@ -102,7 +102,7 @@
<tags>
<tag>CurrentTemperature</tag>
</tags>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="selfTest">

View File

@ -32,6 +32,21 @@
<config-description-ref uri="thing-type:shelly:light"/>
</thing-type>
<thing-type id="shellycolorbulb">
<label>Shelly Duo Color Bulb (SHSCB-1)</label>
<description>Shelly Duo Color Bulb in Color or White Mode</description>
<channel-groups>
<channel-group id="control" typeId="duoControl"/>
<channel-group id="color" typeId="colorSettingsBulb"/>
<channel-group id="white" typeId="whiteSettings"/>
<channel-group id="meter" typeId="meter"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
<representation-property>deviceName</representation-property>
<config-description-ref uri="thing-type:shelly:light"/>
</thing-type>
<thing-type id="shellyvintage">
<label>Shelly Vintage (SHVIN-1)</label>
<description>Shelly Vintage Light Bulb</description>
@ -109,12 +124,6 @@
<channel-group-type id="rgbw2ColorControl">
<label>Light Control</label>
<description>Control your light channels</description>
<channels>
<channel id="power" typeId="system.power"/>
<channel id="autoOn" typeId="timerAutoOn"/>
<channel id="autoOff" typeId="timerAutoOff"/>
<channel id="timerActive" typeId="timerActive"/>
</channels>
</channel-group-type>
<channel-group-type id="rgbw2WhiteControl">
<label>White Control</label>
@ -211,7 +220,7 @@
<description>blue, 0..255, only in Color Mode</description>
<state min="0" max="255" step="1" readOnly="false"></state>
</channel-type>
<channel-type id="colorWhite" advanced="true">
<channel-type id="colorWhite">
<item-type>Dimmer</item-type>
<label>White</label>
<description>white, 0..255, applies in Color Mode</description>
@ -234,6 +243,7 @@
<label>Brightness</label>
<description>Brightness: 0..100%</description>
<category>DimmableLight</category>
<state min="0" max="100" step="1" readOnly="false"></state>
</channel-type>
<channel-type id="colorEffectBulb">
<item-type>Number</item-type>

View File

@ -6,7 +6,7 @@
<thing-type id="shelly1">
<label>Shelly1 (SHSW-1)</label>
<description>Shelly1 device with single relay</description>
<description>Shelly1 device with a single relay</description>
<channel-groups>
<channel-group id="relay" typeId="relayChannel"/>
<channel-group id="sensors" typeId="externalSensors"/>
@ -17,6 +17,20 @@
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<thing-type id="shelly1l">
<label>Shelly 1L (SHSW-L)</label>
<description>Shelly 1L device with a single relay</description>
<channel-groups>
<channel-group id="relay" typeId="relayChannel"/>
<channel-group id="meter" typeId="meter"/>
<channel-group id="sensors" typeId="externalSensors"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
<representation-property>deviceName</representation-property>
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<thing-type id="shelly1pm">
<label>Shelly1PM (SHSW-PM)</label>
<description>Shelly1PM device with single relay and power meter</description>
@ -218,6 +232,25 @@
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<thing-type id="shellyuni">
<label>Shelly UNI (SHUNI-2)</label>
<description>Embedded Shelly device</description>
<channel-groups>
<channel-group id="relay1" typeId="relayChannel">
<label>Relay 1</label>
</channel-group>
<channel-group id="relay2" typeId="relayChannel">
<label>Relay 2</label>
</channel-group>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
<representation-property>deviceName</representation-property>
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<thing-type id="shellydimmer">
<label>Shelly Dimmer (SHDM-1)</label>
<description>Shelly Dimmer</description>
@ -260,62 +293,38 @@
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<thing-type id="shellyuni">
<label>Shelly UNI (SHUNI-1)</label>
<description>Shelly UNI device</description>
<channel-groups>
<channel-group id="relay1" typeId="relayChannel"/>
<channel-group id="relay2" typeId="relayChannel"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
<representation-property>deviceName</representation-property>
<config-description-ref uri="thing-type:shelly:relay"/>
</thing-type>
<channel-group-type id="relayChannel">
<label>Relay</label>
<description>A Shelly relay channel</description>
<channels>
<channel id="output" typeId="system.power"/>
<channel id="input" typeId="inputState"/>
<channel id="button" typeId="system.button"/>
<channel id="autoOn" typeId="timerAutoOn"/>
<channel id="autoOff" typeId="timerAutoOff"/>
<channel id="timerActive" typeId="timerActive"/>
</channels>
</channel-group-type>
<channel-group-type id="relayChannelPlug">
<label>Relay</label>
<description>A Shelly relay channel</description>
<channels>
<channel id="output" typeId="system.power"/>
<channel id="autoOn" typeId="timerAutoOn"/>
<channel id="autoOff" typeId="timerAutoOff"/>
<channel id="timerActive" typeId="timerActive"/>
</channels>
</channel-group-type>
<channel-group-type id="dimmerChannel">
<label>Dimmer</label>
<description>A Shelly Dimmer channel</description>
<channels>
<channel id="brightness" typeId="dimmerBrightness"/>
<channel id="input1" typeId="inputState1"/>
<channel id="input2" typeId="inputState2"/>
<channel id="button" typeId="system.button"/>
<channel id="autoOn" typeId="timerAutoOn"/>
<channel id="autoOff" typeId="timerAutoOff"/>
</channels>
</channel-group-type>
<channel-group-type id="ix3Channel">
<label>Input</label>
<description>Input Status</description>
<channels>
<channel id="input" typeId="inputState"/>
<channel id="button" typeId="system.button"/>
<channel id="lastEvent" typeId="lastEvent"/>
<channel id="eventCount" typeId="eventCount"/>
</channels>
</channel-group-type>
<channel-group-type id="rollerControl">
<label>Roller Control</label>
<description>Controlling the roller mode</description>
<channels>
<channel id="control" typeId="rollerShutter"/>
<channel id="rollerpos" typeId="rollerPosition"/>
<channel id="state" typeId="rollerState"/>
<channel id="stopReason" typeId="rollerStop"/>
<channel id="input1" typeId="inputState1"/>
<channel id="input2" typeId="inputState2"/>
<channel id="event" typeId="eventTrigger"/>
</channels>
</channel-group-type>
<channel-group-type id="meter">
<label>Power Meter</label>
@ -337,13 +346,13 @@
<item-type>Number:Time</item-type>
<label>Auto-ON Timer</label>
<description>ON: After the output was turned off it turns on automatically after xx seconds; 0 disables the timer</description>
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
<state min="0" step="1" pattern="%.0f %unit%" readOnly="false"></state>
</channel-type>
<channel-type id="timerAutoOff" advanced="true">
<item-type>Number:Time</item-type>
<label>Auto-OFF Timer</label>
<description>ON: After the output was turned on it turns off automatically after xx seconds; 0 disables the timer</description>
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
<state min="0" step="1" pattern="%.0f %unit%" readOnly="false"></state>
</channel-type>
<channel-type id="timerActive" advanced="true">
<item-type>Switch</item-type>
@ -362,7 +371,13 @@
<item-type>Dimmer</item-type>
<label>Roller Position (100=open, 0=closed)</label>
<description>Position the roller (100..0 in %, where 100%=open, 0%=closed)</description>
<state readOnly="false" min="0" max="100"/>
<state readOnly="false" min="0" max="100" pattern="%.0f %%"/>
</channel-type>
<channel-type id="rollerFavorite">
<item-type>Number</item-type>
<label>Position Favorite</label>
<description>Set roller position by selecting favorite 1-4 (needs to be defined in the Shelly App, 0=n/a)</description>
<state readOnly="false" min="0" max="4"/>
</channel-type>
<channel-type id="rollerState">
<item-type>String</item-type>
@ -400,6 +415,13 @@
</options>
</state>
</channel-type>
<channel-type id="rollerSafety" advanced="true">
<item-type>Switch</item-type>
<label>Safety Switch</label>
<description>Status of the safety switch</description>
<state readOnly="true">
</state>
</channel-type>
<channel-type id="inputState">
<item-type>Switch</item-type>
<label>Input</label>
@ -431,91 +453,77 @@
<item-type>Number:Power</item-type>
<label>Watt</label>
<description>Current power consumption in Watt</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.2f %unit%">
</state>
</channel-type>
<channel-type id="meterAccuWatts" advanced="true">
<item-type>Number:Power</item-type>
<label>Accumulated Watt</label>
<description>Accumulated current power consumption in Watt from all meters</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.2f %unit%">
</state>
</channel-type>
<channel-type id="meterAccuTotal" advanced="true">
<item-type>Number:Power</item-type>
<label>Accumulated Total</label>
<description>Accumulated total power consumption in kw/h from all meters</description>
<state readOnly="true" pattern="%f %unit%">
<description>Accumulated total power consumption from all meters</description>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterAccuReturned" advanced="true">
<item-type>Number:Power</item-type>
<label>Accumulated Returned</label>
<description>Accumulated returned power consumption in kw/h from all meters</description>
<state readOnly="true" pattern="%f %unit%">
<description>Accumulated returned power consumption from all meters</description>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterReactive">
<item-type>Number:Power</item-type>
<label>Reactive Watt</label>
<description>Instantaneous reactive power in Watts (W)</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="lastPower1" advanced="true">
<item-type>Number:Energy</item-type>
<label>Last Power #1</label>
<description>Last power consumption #1 - one rounded minute</description>
<state readOnly="true" pattern="%f %unit%">
</state>
</channel-type>
<channel-type id="lastPower2" advanced="true">
<item-type>Number:Energy</item-type>
<label>Last Power #2</label>
<description>Last power consumption #2 - one rounded minute</description>
<state readOnly="true" pattern="%f %unit%">
</state>
</channel-type>
<channel-type id="lastPower3" advanced="true">
<item-type>Number:Energy</item-type>
<label>Last Power #3</label>
<description>Last power consumption #3 - one rounded minute</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterTotal">
<item-type>Number:Energy</item-type>
<label>Total Energy</label>
<description>Total power consumption in kw/h</description>
<state readOnly="true" pattern="%f %unit%">
<description>Total power consumption</description>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterReturned">
<item-type>Number:Energy</item-type>
<label>Total Returned Energy (kw/h)</label>
<description>Total returned energy in kw/h</description>
<state readOnly="true" pattern="%f %unit%">
<label>Total Returned Energy</label>
<description>Total returned energy</description>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterVoltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>RMS voltage, Volts </description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterCurrent">
<item-type>Number:ElectricPotential</item-type>
<label>Current</label>
<description>Current in A </description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="meterPowerFactor">
<item-type>Number</item-type>
<label>Power Factor</label>
<description></description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="timestamp">

View File

@ -9,7 +9,7 @@
<description>Shelly H&amp;T Sensor</description>
<channel-groups>
<channel-group id="sensors" typeId="htSensor"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -23,7 +23,7 @@
<description>Shelly Smoke Sensor (battery powered)</description>
<channel-groups>
<channel-group id="sensors" typeId="smokeSensor"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -37,7 +37,7 @@
<description>Shelly Gas Sensor</description>
<channel-groups>
<channel-group id="sensors" typeId="gasSensor"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -49,7 +49,7 @@
<label>Shelly Flood (SHWT-1)</label>
<description>Shelly Flood Sensor (battery powered)</description>
<channel-groups>
<channel-group id="sensors" typeId="floodSensor"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -63,7 +63,7 @@
<description>Shelly Door/Window Sensor (battery powered)</description>
<channel-groups>
<channel-group id="sensors" typeId="doorWinSensors"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -77,7 +77,7 @@
<description>Shelly Door/Window 2 Sensor (battery powered)</description>
<channel-groups>
<channel-group id="sensors" typeId="doorWinSensors"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -91,7 +91,7 @@
<description>Shelly Sense Remote IR Controller</description>
<channel-groups>
<channel-group id="control" typeId="senseControl"/>
<channel-group id="sensors" typeId="senseSensors"/>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -114,33 +114,25 @@
<config-description-ref uri="thing-type:shelly:battery"/>
</thing-type>
<channel-group-type id="htSensor">
<thing-type id="shellymotion">
<label>Shelly Motion</label>
<description>Shelly Motion Sensor (battery powered)</description>
<channel-groups>
<channel-group id="sensors" typeId="sensorData"/>
<channel-group id="battery" typeId="batteryStatus"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
<representation-property>deviceName</representation-property>
<config-description-ref uri="thing-type:shelly:battery"/>
</thing-type>
<channel-group-type id="sensorData">
<label>Sensor Data</label>
<description>Data from the HT Sensor</description>
<description>Data from the various sensors</description>
</channel-group-type>
<channel-group-type id="floodSensor">
<label>Sensor Data</label>
<description>Data from the Flood Sensor</description>
</channel-group-type>
<channel-group-type id="smokeSensor">
<label>Sensor Data</label>
<description>Data from the Flood Sensor</description>
</channel-group-type>
<channel-group-type id="gasSensor">
<label>Sensor Data</label>
<description>Data from the Gas Sensor</description>
</channel-group-type>
<channel-group-type id="doorWinSensors">
<label>Sensors</label>
<description>Data from the sensors</description>
</channel-group-type>
<channel-group-type id="senseSensors">
<label>Sensors</label>
<description>Data from the Sense sensors</description>
</channel-group-type>
<channel-group-type id="batteryStatus">
<label>Battery Status</label>
</channel-group-type>
@ -154,12 +146,6 @@
<channel-group-type id="buttonState">
<label>Button State</label>
<description>Status of the Button</description>
<channels>
<channel id="input" typeId="inputState"/>
<channel id="button" typeId="system.button"/>
<channel id="lastEvent" typeId="lastEvent"/>
<channel id="eventCount" typeId="eventCount"/>
</channels>
</channel-group-type>
@ -221,7 +207,7 @@
<item-type>Number</item-type>
<label>Event Count</label>
<description>Event Count</description>
<state pattern="%d" readOnly="true">
<state pattern="%.0f" readOnly="true">
</state>
</channel-type>
@ -233,7 +219,7 @@
<tags>
<tag>CurrentTemperature</tag>
</tags>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorExtTemp">
@ -244,7 +230,7 @@
<tags>
<tag>CurrentTemperature</tag>
</tags>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorExtHum">
@ -255,7 +241,7 @@
<tags>
<tag>CurrentHumidity</tag>
</tags>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorHumidity">
@ -265,7 +251,7 @@
<tags>
<tag>CurrentHumidity</tag>
</tags>
<state readOnly="true" min="0" max="100" pattern="%f %unit%"/>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="sensorFlood">
<item-type>Switch</item-type>
@ -285,7 +271,7 @@
<item-type>Number:Illuminance</item-type>
<label>Lux</label>
<description>Brightness from the sensor (Lux)</description>
<state readOnly="true" pattern="%f %unit%">
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="sensorIllumination">
@ -300,6 +286,20 @@
</options>
</state>
</channel-type>
<channel-type id="motionTimestamp">
<item-type>DateTime</item-type>
<label>Last motion</label>
<description>Timestamp of last detected motion</description>
<state readOnly="true">
</state>
</channel-type>
<channel-type id="sensorMotion">
<item-type>Switch</item-type>
<label>Motion</label>
<description>ON: Motion detected</description>
<state readOnly="true">
</state>
</channel-type>
<channel-type id="sensorVibration">
<item-type>Switch</item-type>
<label>Vibration</label>
@ -311,14 +311,21 @@
<item-type>Number:Angle</item-type>
<label>Tilt</label>
<description>Tilt in degrees (requires calibration)</description>
<state readOnly="true">
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="sensorPPM">
<item-type>Number:Density</item-type>
<label>Concentration</label>
<description>Gas concentration in ppm</description>
<state readOnly="true" pattern="%d %unit%">
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="sensorADC">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage (ADC)</label>
<description>ADC voltage in V</description>
<state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="sensorValve">