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 |
| 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 |
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:
|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
| |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.
| |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 |
| |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 |
| |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 |
| |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 |
| |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 |
|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 |
| |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)
| |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) |
|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 |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|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)
+
+|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 RGBW2 in Color Mode (thing-type: shellyrgbw2-color)
+## Shelly Duo RGBW Color Bulb (thing-type: shellycolorbulb)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
| | | | |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.
| |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|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|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 |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|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 |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|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) |
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 |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|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 |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
#### Control CCT LED stripes
Usage & Requirements:
+
- 4 Items per Thing required. Example:
```
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:
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] >generated link>" 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://<shelly ip>/ota?url=http://<web server>/<path>/<zip-file>
-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://<shelly ip>/ota?url=http://<web server>/<path>/<zip-file>
+- Again, make sure that the file is downloaded and installed properly.
## Trouble Shooting
`Please note:` Once events are filtered they are not show anymore in the logfile, you can’t 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
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+
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
### 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).
### 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.
// 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
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";
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";
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);
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,
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);
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,
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";
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;
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";
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
// 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+
// 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;
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;
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 = "";
*/
@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"));
}
logger.debug("Using OH HTTP port {}", httpPort);
this.coapServer = new ShellyCoapServer();
-
- // Save bindingConfig & pass it to all registered listeners
- bindingConfig.updateFromProperties(configProperties);
}
@Override
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);
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() {
private Class<?> getCauseClass() {
Throwable cause = getCause();
- if (getCause() != null) {
+ if (cause != null) {
return cause.getClass();
}
return ShellyApiException.class;
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";
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")
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
}
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;
@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;
public ArrayList<ShellySettingsRelay> relays;
public ArrayList<ShellySettingsDimmer> dimmers;
+ public ArrayList<ShellySettingsRgbwLight> lights;
public ArrayList<ShellySettingsEMeter> emeters;
public ArrayList<ShellySettingsInput> inputs; // ix3
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 {
@SerializedName("btn_type")
public String btnType;
- // included attributes not yet processed
+ // attributes not yet processed
// public String name;
// @SerializedName("btn_reverse")
// public Integer btnReverse;
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
@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 {
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")
@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 {
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"
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 {
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
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
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);
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);
// 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;
}
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) {
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) {
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) {
}
}
- 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) {
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;
}
}
}
try {
- path = request.getRequestURI().toLowerCase();
+ path = getString(request.getRequestURI()).toLowerCase();
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
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) {
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
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;
}
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;
* @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 {
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 {
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) {
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;
/**
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();
}
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;
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
*
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) {
}
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
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,
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;
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;
}
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;
}
}
} 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) {
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
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;
}
}
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;
* 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;
}
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,
* @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
// 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 = "";
}
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);
+ }
}
}
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;
* 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;
}
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;
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
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));
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
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;
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
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);
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;
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)) {
}
@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);
}
}
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;
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;
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;
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();
}
/**
try {
this.thingName = thingName;
this.config = config;
+ this.profile = thingHandler.getProfile();
if (isStarted()) {
logger.trace("{}: CoAP Listener was already started", thingName);
stop();
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);
}
}
String payload = "";
String devId = "";
String uri = "";
- // int validity = 0;
int serial = -1;
try {
if (logger.isDebugEnabled()) {
// 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;
}
// 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
* @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);
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;
}
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;
}
* @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
}
// 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;
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++;
+ CoIotDescrSen sen = sensorMap.get(s.id);
+ if (sen == null) {
+ logger.debug("{}: Unable to sensor definition for id={}, payload={}", thingName, s.id, payload);
continue;
}
- CoIotDescrSen sen = sensorMap.get(s.id);
- Objects.requireNonNull(sen);
// 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));
- continue;
- }
-
- if (!blkMap.containsKey(sen.links)) {
- logger.debug("{}: Unable to find BLK for link {} from sen.id={}", thingName, sen.links, sen.id);
+ 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;
}
- 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);
}
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);
}
}
+ 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
@SerializedName("I")
String id; // ID
@SerializedName("D")
- String desc; // Description
+ String desc = ""; // Description
@SerializedName("T")
public String type; // Type
@SerializedName("R")
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;
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;
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;
}
}
- 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();
// 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();
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;
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
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;
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());
}
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);
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);
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);
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);
}
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);
}
// 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;
}
}
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;
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;
@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 = "";
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;
this.messages = translationProvider;
this.cache = new ShellyChannelCache(this);
- this.channelDefinitions = new ShellyChannelDefinitionsDTO(messages);
+ this.channelDefinitions = new ShellyChannelDefinitions(messages);
this.bindingConfig = bindingConfig;
this.localIP = localIP;
"{}: 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();
// update thing properties
ShellySettingsStatus status = api.getStatus();
+ tmpPrf.updateFromStatus(status);
updateProperties(tmpPrf, status);
checkVersion(tmpPrf, status);
if (autoCoIoT) {
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);
skipUpdate++;
ThingStatus thingStatus = getThing().getStatus();
-
if (refreshSettings || (scheduledUpdates > 0) || (skipUpdate % skipCount == 0)) {
if (!profile.isInitialized() || ((thingStatus == ThingStatus.OFFLINE))
|| (thingStatus == ThingStatus.UNKNOWN)) {
logger.trace("{}: Updating status", thingName);
ShellySettingsStatus status = api.getStatus();
+ profile.updateFromStatus(status);
// If status update was successful the thing must be online
setThingOnline();
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;
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()) {
} 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);
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() {
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();
}
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",
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;
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) {
}
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));
- }
}
/**
* @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;
}
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
}
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);
}
}
}
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
*
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;
}
+++ /dev/null
-/**
- * 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;
- }
- }
-}
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);
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;
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));
}
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;
*/
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,
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));
}
}
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));
}
}
// Create channels for 1 Meter
if (!thingHandler.areChannelsCreated()) {
- thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
+ thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
}
* @param profile ShellyDeviceProfile
* @param status Last ShellySettingsStatus
*
- * @throws IOException
+ * @throws ShellyApiException
*/
public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
throws ShellyApiException {
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;
}
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,
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));
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;
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;
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;
}
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;
}
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,
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) {
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);
}
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));
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();
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;
}
}
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.
*/
@NonNullByDefault
public class ShellyProtectedHandler extends ShellyBaseHandler {
- private final Logger logger = LoggerFactory.getLogger(ShellyProtectedHandler.class);
-
/**
* Constructor
*
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;
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));
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);
}
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
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);
}
}
- 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 {
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:
*/
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));
}
}
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;
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++;
}
// 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());
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);
toQuantityType(getDouble(dsettings.autoOff), Units.SECOND));
}
- updated |= updateInputs(groupName, orgStatus, l);
l++;
}
}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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 java.util.Locale;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+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
+ *
+ * @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;
+
+ @Activate
+ public ShellyTranslationProvider(@Reference TranslationProvider i18nProvider,
+ @Reference LocaleProvider localeProvider) {
+ this.bundle = FrameworkUtil.getBundle(this.getClass());
+ this.i18nProvider = i18nProvider;
+ this.localeProvider = localeProvider;
+ }
+
+ public @Nullable String get(String key, @Nullable Object... arguments) {
+ return getText(key.contains("@text/") || key.contains(".shelly.") ? key : "message." + key, arguments);
+ }
+
+ public @Nullable String getText(String key, @Nullable Object... arguments) {
+ try {
+ Locale locale = localeProvider.getLocale();
+ return i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
+ } catch (IllegalArgumentException e) {
+ return "Unable to load message for key " + key;
+ }
+ }
+
+ public @Nullable String getDefaultText(String key) {
+ return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
+ }
+}
}
} 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;
}
}
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) {
+++ /dev/null
-/**
- * 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.util;
-
-import java.util.Locale;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
-import org.osgi.framework.Bundle;
-
-/**
- * {@link ShellyTranslationProvider} provides i18n message lookup
- *
- * @author Markus Michels - Initial contribution
- */
-@NonNullByDefault
-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;
- 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);
- }
-
- public @Nullable String getText(String key, @Nullable Object... arguments) {
- try {
- Locale locale = localeProvider.getLocale();
- return i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
- } catch (IllegalArgumentException e) {
- return "Unable to load message for key " + key;
- }
- }
-
- public @Nullable String getDefaultText(String key) {
- return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
- }
-}
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;
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
*
*/
@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;
}
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) {
}
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();
}
<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>
<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>
<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>
<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>
<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>
</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">
<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>
<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>
<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>
<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>
<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>
+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.
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
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
# 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.
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}
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)
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)
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)
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
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
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
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
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
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
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
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
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
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
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
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
<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">
<tags>
<tag>CurrentTemperature</tag>
</tags>
- <state readOnly="true" pattern="%f %unit%">
+ <state readOnly="true" pattern="%.0f %unit%">
</state>
</channel-type>
<channel-type id="selfTest">
<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>
<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>
<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>
<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>
<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"/>
<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>
<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>
<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>
<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>
<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>
</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>
<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">
<description>Shelly H&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>
<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>
<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>
<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>
<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>
<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>
<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>
<config-description-ref uri="thing-type:shelly:battery"/>
</thing-type>
- <channel-group-type id="htSensor">
- <label>Sensor Data</label>
- <description>Data from the HT Sensor</description>
- </channel-group-type>
+ <thing-type id="shellymotion">
+ <label>Shelly Motion</label>
+ <description>Shelly Motion Sensor (battery powered)</description>
- <channel-group-type id="floodSensor">
- <label>Sensor Data</label>
- <description>Data from the Flood Sensor</description>
- </channel-group-type>
+ <channel-groups>
+ <channel-group id="sensors" typeId="sensorData"/>
+ <channel-group id="battery" typeId="batteryStatus"/>
+ <channel-group id="device" typeId="deviceStatus"/>
+ </channel-groups>
- <channel-group-type id="smokeSensor">
- <label>Sensor Data</label>
- <description>Data from the Flood Sensor</description>
- </channel-group-type>
- <channel-group-type id="gasSensor">
+ <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 Gas Sensor</description>
- </channel-group-type>
- <channel-group-type id="doorWinSensors">
- <label>Sensors</label>
- <description>Data from the sensors</description>
+ <description>Data from the various 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>
<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>
<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>
<tags>
<tag>CurrentTemperature</tag>
</tags>
- <state readOnly="true" pattern="%f %unit%">
+ <state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorExtTemp">
<tags>
<tag>CurrentTemperature</tag>
</tags>
- <state readOnly="true" pattern="%f %unit%">
+ <state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorExtHum">
<tags>
<tag>CurrentHumidity</tag>
</tags>
- <state readOnly="true" pattern="%f %unit%">
+ <state readOnly="true" pattern="%.1f %unit%">
</state>
</channel-type>
<channel-type id="sensorHumidity">
<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>
<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">
</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>
<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">