]> git.basschouten.com Git - openhab-addons.git/commitdiff
[shelly] Improved documentation, support for UNI, 1L, Color Bulb (#9508)
authorMarkus Michels <markus7017@gmail.com>
Wed, 3 Feb 2021 21:25:19 +0000 (22:25 +0100)
committerGitHub <noreply@github.com>
Wed, 3 Feb 2021 21:25:19 +0000 (22:25 +0100)
* This PR merges the 2.5 code base with new features, a bunch of bug fixes
and improved documentation. Parts of the code has been re-factored and
vaious issues are fixed/optimized along the way.

Signed-off-by: Markus Michels <markus7017@gmail.com>
* Minor fixed, support for Shelly Color Bulb, Shelly Motion

Signed-off-by: Markus Michels <markus7017@gmail.com>
* Review changes applied

Signed-off-by: Markus Michels <markus7017@gmail.com>
* review change applied

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

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

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

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

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

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

Signed-off-by: Markus Michels <markus7017@gmail.com>
41 files changed:
bundles/org.openhab.binding.shelly/README.md
bundles/org.openhab.binding.shelly/doc/AdvancedUsers.md
bundles/org.openhab.binding.shelly/doc/UseCaseSmartRoller.md
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyEventServlet.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTInterface.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapJSonDTO.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapServer.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyChannelDefinitionsDTO.java [deleted file]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyColorUtils.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyProtectedHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java [new file with mode: 0644]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyTranslationProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyTranslationProvider.java [deleted file]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/binding/binding.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/lights.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml

index 92fd1a71a83a9109fb021992bd8e4be0c941b8a3..9006b94dd3759eaee9d10e1169c7095ff11ca5d8 100644 (file)
@@ -7,19 +7,20 @@ Allterco provides a rich set of smart home devices. All of them are WiFi enabled
 The binding is officially acknowledged by Allterco and openHAB is listed as a reference and directly supports the openHAB community.
 
 The binding controls the devices independently from the Allterco Shelly Cloud (in fact it can be disabled).
-The binding co-exists with Shelly App for Smartphones, Shelly Web App, Shelly Cloud, mqqt and other 3rd party Apps.
+The binding co-exists with Shelly App for Smartphones, Shelly Device Web UI, Shelly Cloud, MQTT and other 3rd party Apps.
 
 The binding focuses on reporting the device status and device control.
-Initial setup and device configuration has to be performed using the Shelly Apps (Web or Smartphone App).
+Initial setup and device configuration has to be performed using the Shelly Apps (Web UI or Smartphone App).
 The binding gets in sync with the next status refresh.
 
 Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB Shelly integration, e.g. firmware update, network communication or log filtering.
 
 ## Supported Devices
 
-| Thing Type         | Model                                                  | Vendor ID |
+| thing-type         | Model                                                  | Vendor ID |
 |--------------------|--------------------------------------------------------|-----------|
 | shelly1            | Shelly 1 Single Relay Switch                           | SHSW-1    |
+| shelly1l           | Shelly 1L Single Relay Switch                          | SHSW-L    |
 | shelly1pm          | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM   |
 | shelly2-relay      | Shelly Double Relay Switch in relay mode               | SHSW-21   |
 | shelly2-roller     | Shelly2 in Roller Mode                                 | SHSW-21   |
@@ -35,11 +36,14 @@ Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB
 | shellyem3          | Shelly 3EM with 3 integrated Power Meter               | SHEM-3    |
 | shellyrgbw2        | Shelly RGB Controller                                  | SHRGBW2   |
 | shellybulb         | Shelly Bulb in Color or White Mode                     | SHBLB-1   |
-| shellybulbduo      | Shelly Duo (White Mode)                                | SHBDUO-1  |
+| shellybulbduo      | Shelly Duo White                                       | SHBDUO-1  |
+| shellybulbduo      | Shelly Duo White G10                                   | SHBDUO-1  |
+| shellycolorbulb    | Shelly Duo Color G10                                   | SHCB-1    |
 | shellyvintage      | Shelly Vintage (White Mode)                            | SHVIN-1   |
 | shellyht           | Shelly Sensor (temp+humidity)                          | SHHT-1    |
 | shellyflood        | Shelly Flood Sensor                                    | SHWT-1    |
 | shellysmoke        | Shelly Smoke Sensor                                    | SHSM-1    |
+| shellymotion       | Shelly Motion Sensor                                   | SHMOS-01  |
 | shellygas          | Shelly Gas Sensor                                      | SHGS-1    |
 | shellydw           | Shelly Door/Window                                     | SHDW-1    |
 | shellydw2          | Shelly Door/Window 2                                   | SHDW-2    |
@@ -115,6 +119,14 @@ For those open the case, press that button and the LED starts flashing.
 Wait a moment and then start the discovery. The device should show up in the Inbox and can be added.
 Sometimes you need to run the discovery multiple times.
 
+### Roller Favorites
+
+Firmware 1.9.2 for Shelly 2.5 in roller mode supports so called favorites for positions.
+You could use the Shelly App to setup 4 different positions (percentage) and assign id 1-4.
+The channel `roller#rollerFav` allows to select those from openHAB and the roller moves to the desired position.
+In the Thing configuration you could also configure an id when the `roller#control` channel receives UP or DOWN.
+Values 1-4 are selecting the corresponding favorite id in the Shelly App, 0 means no favorite.
+
 ### Thing Status
 
 The binding sets the following Thing status depending on the device status:
@@ -168,6 +180,8 @@ You could also create a rule to catch those status changes or device alarms (see
 |eventsSensorReport|true: register event "posted updated sensor data"             |    no   |true for sensor devices                           |
 |eventsCoIoT       |true: Listen for CoIoT/COAP events                            |    no   |true for battery devices, false for others        |
 |eventsRoller      |true: register event "trigger" when the roller updates status |    no   |true for roller devices                           |
+|favoriteUP        |0-4: Favorite id for UP (see Roller Favorites)                |    no   |0 = no favorite id                                |
+|favoriteDOWN      |0-4: Favorite id for DOWN (see Roller Favorites)              |    no   |0 = no favorite id                                |
 
 
 ### General Notes
@@ -189,6 +203,7 @@ Every device has a channel group `device` with the following channels:
 |          |updateAvailable    |Switch  |yes      |ON: A firmware update is available                                               |
 |          |statusLed          |Switch  |r/w      |ON: Status LED is disabled, OFF: LED enabled                                     |
 |          |powerLed           |Switch  |r/w      |ON: Power LED is disabled, OFF: LED enabled                                      |
+|          |charger            |Switch  |yes      |ON: USB charging cable is connected external power supply activated.             |
 
 Availability of channels is depending on the device type.
 The binding detects many of those channels on-the-fly (when Thing changes to ONLINE state) and adjusts the Thing's channel structure.
@@ -296,6 +311,8 @@ Depending on the device type and firmware release channels might be not availabl
 |          |outputName   |String   |yes      |Logical name of this relay output as configured in the Shelly App                |
 |          |input        |Switch   |yes      |ON: Input/Button is powered, see general notes on channels                       |
 |          |button       |Trigger  |yes      |Event trigger with payload, see SHORT_PRESSED or LONG_PRESSED                    |
+|          |lastEvent    |String   |yes      |Last event type (S/SS/SSS/L)                                                     |
+|          |eventCount   |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
 |          |autoOn       |Number   |r/w      |Relay #1: Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Relay #1: Sets a  timer to turn the device OFF after every ON command; in seconds|
 |          |timerActive  |Switch   |yes      |Relay #1: ON: An auto-on/off timer is active                                     |
@@ -304,6 +321,34 @@ Depending on the device type and firmware release channels might be not availabl
 |          |temperature3 |Number   |yes      |Temperature value of external sensor #3 (if connected to temp/hum addon)         |
 |          |humidity     |Number   |yes      |Humidity in percent (if connected to temp/hum addon)                             |
 
+### Shelly 1L (thing-type: shelly1l)
+
+|Group     |Channel      |Type     |read-only|Description                                                                      |
+|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
+|relay     |output       |Switch   |r/w      |Controls the relay's output channel (on/off)                                     |
+|          |outputName   |String   |yes      |Logical name of this relay output as configured in the Shelly App                |
+|          |input1       |Switch   |yes      |ON: Input/Button for input 1 is powered, see general notes on channels           |
+|          |button1      |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent1   |String   |yes      |Last event type (S/SS/SSS/L) for input 1                                         |
+|          |eventCount1  |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
+|          |input2       |Switch   |yes      |ON: Input/Button for channel 2 is powered, see general notes on channels         |
+|          |button2      |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent2   |String   |yes      |Last event type (S/SS/SSS/L) for input 2                                         |
+|          |eventCount2  |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
+|          |autoOn       |Number   |r/w      |Relay #1: Sets a  timer to turn the device ON after every OFF command; in seconds|
+|          |autoOff      |Number   |r/w      |Relay #1: Sets a  timer to turn the device OFF after every ON command; in seconds|
+|          |timerActive  |Switch   |yes      |Relay #1: ON: An auto-on/off timer is active                                     |
+|meter     |currentWatts |Number   |yes      |Current power consumption in Watts                                               |
+|          |lastUpdate   |DateTime |yes      |Timestamp of the last measurement                                                |
+|sensors   |temperature1 |Number   |yes      |Temperature value of external sensor #1 (if connected to temp/hum addon)         |
+|          |temperature2 |Number   |yes      |Temperature value of external sensor #2 (if connected to temp/hum addon)         |
+|          |temperature3 |Number   |yes      |Temperature value of external sensor #3 (if connected to temp/hum addon)         |
+|          |humidity     |Number   |yes      |Humidity in percent (if connected to temp/hum addon)                             |
+
+Note: The `meter`for the Shelly 1L is kind of fake.
+It doesn't have a real power meter, but you could setup an estimated consumption in the Shelly App, e.g. 60W if you have attached a good old light bulb to the output channel.
+In this case the is no real measurement based on power consumption, but the Shelly reports the configured value when the relay is ON.
+
 ### Shelly 1PM (thing-type: shelly1pm)
 
 |Group     |Channel      |Type     |read-only|Description                                                                      |
@@ -330,6 +375,8 @@ Depending on the device type and firmware release channels might be not availabl
 |          |outputName   |String   |yes      |Logical name of this relay output as configured in the Shelly App                |
 |          |input        |Switch   |yes      |ON: Input/Button is powered, see general notes on channels                       |
 |          |button       |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent    |String   |yes      |Last event type (S/SS/SSS/L)                                                     |
+|          |eventCount   |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
 |          |autoOn       |Number   |r/w      |Relay #1: Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Relay #1: Sets a  timer to turn the device OFF after every ON command; in seconds|
 |          |timerActive  |Switch   |yes      |Relay #1: ON: An auto-on/off timer is active                                     |
@@ -357,6 +404,8 @@ The Thing id is derived from the service name, so that's the reason why the Thin
 |          |outputName   |String   |yes      |Logical name of this relay output as configured in the Shelly App                |
 |          |input        |Switch   |yes      |ON: Input/Button is powered, see general notes on channels                       |
 |          |button       |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent    |String   |yes      |Last event type (S/SS/SSS/L)                                                     |
+|          |eventCount   |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
 |          |autoOn       |Number   |r/w      |Relay #1: Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Relay #1: Sets a  timer to turn the device OFF after every ON command; in seconds|
 |          |timerActive  |Switch   |yes      |Relay #1: ON: An auto-on/off timer is active                                     |
@@ -419,6 +468,7 @@ The Thing id is derived from the service name, so that's the reason why the Thin
 |          |input        |Switch   |yes      |ON: Input/Button is powered, see General Notes on Channels                            |
 |          |event        |Trigger  |yes      |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP            |
 |          |rollerpos    |Number   |r/w      |Roller position: 100%=open...0%=closed; gets updated when the roller stops, see Notes |
+|          |rollerFav    |Number   |r/w      |Select roller position favorite (1-4, 0=no), see Notes                                |
 |          |state        |String   |yes      |Roller state: open/close/stop                                                         |
 |          |stopReason   |String   |yes      |Last stop reasons: normal, safety_switch or obstacle                                  |
 |          |safety       |Switch   |yes      |Indicates status of the Safety Switch, ON=problem detected, powered off               |
@@ -495,8 +545,13 @@ The Shelly 4Pro provides 4 relays and 4 power meters.
 |relay     |brightness   |Dimmer   |r/w      |Currently selected brightness.                                                   |
 |          |outputName   |String   |yes      |Logical name of this relay output as configured in the Shelly App                |
 |          |input1       |Switch   |yes      |ON: Input/Button for input 1 is powered, see general notes on channels           |
-|          |input2       |Switch   |yes      |ON: Input/Button for input 1 is powered, see general notes on channels           |
-|          |button       |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |button1      |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent1   |String   |yes      |Last event type (S/SS/SSS/L) for input 1                                         |
+|          |eventCount1  |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
+|          |input2       |Switch   |yes      |ON: Input/Button for channel 2 is powered, see general notes on channels         |
+|          |button2      |Trigger  |yes      |Event trigger, see section Button Events                                         |
+|          |lastEvent2   |String   |yes      |Last event type (S/SS/SSS/L) for input 2                                         |
+|          |eventCount2  |Number   |yes      |Counter gets incremented every time the device issues a button event.            |
 |          |autoOn       |Number   |r/w      |Relay #1: Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Relay #1: Sets a  timer to turn the device OFF after every ON command; in seconds|
 |          |timerActive  |Switch   |yes      |Relay #1: ON: An auto-on/off timer is active                                     |
@@ -521,7 +576,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |          |input3       |Switch   |yes      |State of Input 3                                                       |
 |          |button       |Trigger  |yes      |Event trigger: Event trigger, see section Button Events                |
 |          |lastEvent    |String   |yes      |S/SS/SSS for 1/2/3x Shortpush or L for Longpush                        |
-|          |eventCount   |Number   |yes      |Number of button events                                                |
+|          |eventCount   |Number   |yes      |Counter gets incremented every time the device issues a button event.  |
 
 ### Shelly Bulb (thing-type: shellybulb)
 
@@ -533,7 +588,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |          |autoOff      |Number   |r/w      |Sets a  timer to turn the device OFF after every ON: in sec            |
 |          |timerActive  |Switch   |yes      |ON: An auto-on/off timer is active                                     |
 |color     |             |         |         |Color settings: only valid in COLOR mode                               |
-|          |hsb          |HSB      |r/w      |Represents the color picker (HSBType), control r/g/b, bight not white  |
+|          |hsb          |HSB      |r/w      |Represents the color picker (HSBType), control r/g/b, but not white    |
 |          |full         |String   |r/w      |Set Red / Green / Blue / Yellow / White mode and switch mode           |
 |          |             |         |r/w      |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" | 
 |          |red          |Dimmer   |r/w      |Red brightness: 0..100% or 0..255 (control only the red channel)       |
@@ -547,9 +602,16 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |white     |             |         |         |Color settings: only valid in WHITE mode                               |
 |          |temperature  |Number   |r/w      |color temperature (K): 0..100% or 3000..6500                           |
 |          |brightness   |Dimmer   |         |Brightness: 0..100% or 0..100                                          |
+
+Note: The openHAB color picker has only values for red/green/blue (RGB), not for white as supported by the RGBW2.
+Beside channel `hsb` the binding also offers the `white` channel (hsb as only RGB values).
+Or control each color separately with channels `red`, `blue`, `green` (those are advanced channels).
+
+
 #### Shelly Duo (thing-type: shellybulbduo)
 
+This information applies to the Shelly Duo-1 as well as the Duo White for the G10 socket.
+
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
 |control   |autoOn       |Number   |r/w      |Sets a  timer to turn the device ON after every OFF; in sec            |
@@ -569,16 +631,44 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
 |control   |autoOn       |Number   |r/w      |Sets a  timer to turn the device ON after every OFF; in sec            |
 |          |autoOff      |Number   |r/w      |Sets a  timer to turn the device OFF after every ON: in sec            |
-|          |timerActive  |Switch   |yes      |ON: An auto-on/off timer is active                                          |
+|          |timerActive  |Switch   |yes      |ON: An auto-on/off timer is active                                     |
 |white     |             |         |         |Color settings: only valid in WHITE mode                               |
 |          |brightness   |Dimmer   |         |Brightness: 0..100% or 0..100                                          |
 |meter     |currentWatts |Number   |yes      |Current power consumption in Watts                                     |
 |          |lastPower1   |Number   |yes      |Energy consumption for a round minute, 1 minute  ago                   |
-|          |totalKWH     |Number   |yes      |Total energy consumption in Watts since the device powered up (resets on restart)|
+|          |totalKWH     |Number   |yes      |Total energy consumption in kWh since the device powered up (resets on restart)|
 |          |lastUpdate   |DateTime |yes      |Timestamp of the last measurement                                      |
  
+## Shelly Duo Color (thing-type: shellyduocolor-color)
+
+|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                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
@@ -600,6 +690,8 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |          |             |         |         |0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash                |
 |meter     |currentWatts |Number   |yes      |Current power consumption in Watts                                     |
 
+Channels in group `color`or `white`apply depending on the selected mode - they are not active at the same time. 
+
 Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set.
 `true`:  Brightness will be set and device output is powered = light turns on with the new brightness
 `false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
@@ -624,7 +716,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
 |          |autoOn       |Number   |r/w      |Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Sets a  timer to turn the device OFF after every ON command; in seconds|
 |          |timerActive  |Switch   |yes      |ON: An auto-on/off timer is active                                     |
-|channel4  |brightness   |Dimmer   |r/w      |Channel 5: Brightness: 0..100, control power state with ON/OFF         |
+|channel4  |brightness   |Dimmer   |r/w      |Channel 4: Brightness: 0..100, control power state with ON/OFF         |
 |          |button       |Trigger  |yes      |Event trigger, see section Button Events                               |
 |          |autoOn       |Number   |r/w      |Sets a  timer to turn the device ON after every OFF command; in seconds|
 |          |autoOff      |Number   |r/w      |Sets a  timer to turn the device OFF after every ON command; in seconds|
@@ -651,12 +743,13 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
 |sensors   |temperature  |Number   |yes      |Temperature, unit is reported by tempUnit                              |
 |          |humidity     |Number   |yes      |Relative humidity in %                                                 |
-|          |charger      |Number   |yes      |ON: USB charging cable is                                              |
 |          |lastUpdate   |DateTime |yes      |Timestamp of the last update (any sensor value changed)                |
 |battery   |batteryLevel |Number   |yes      |Battery Level in %                                                     |
 |          |lowBattery   |Switch   |yes      |Low battery alert (< 20%)                                              |
 
-### Shelly Flood (thing type: shellyflood)
+`Please Note:` If you have connected an USB cable to the H&T, but channel charger is off make sure that "Use external power supply" settings is activated in the Shelly App's device settings.
+
+### Shelly Flood (thing-type: shellyflood)
 
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
@@ -666,7 +759,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
 |battery   |batteryLevel |Number   |yes      |Battery Level in %                                                     |
 |          |lowBattery   |Switch   |yes      |Low battery alert (< 20%)                                              |
 
-### Shelly Door/Window (thing type: shellydw)
+### Shelly Door/Window (thing-type: shellydw, shellydw2)
 
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
@@ -680,12 +773,26 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
 |battery   |batteryLevel |Number   |yes      |Battery Level in %                                                     |
 |          |lowBattery   |Switch   |yes      |Low battery alert (< 20%)                                              |
 
-### Shelly Button 1 (thing type: shellybutton1)
+### Shelly Motion (thing-type: shellymotion)
+
+|Group     |Channel        |Type     |read-only|Description                                                          |
+|----------|---------------|---------|---------|---------------------------------------------------------------------|
+|sensors   |motion         |Switch   |yes      |ON: Motion was detected                                              |
+|          |motionTimestamp|DateTime |yes      |Time when motion started/was detected                                |
+|          |lux            |Number   |yes      |Brightness in Lux                                                    |
+|          |illumination   |String   |yes      |Current illumination: dark/twilight/bright                           |
+|          |vibration      |Switch   |yes      |ON: Vibration detected                                               |
+|          |charger        |Switch   |yes      |ON: USB charging cable is connected external power supply activated. |
+|          |lastUpdate     |DateTime |yes      |Timestamp of the last update (any sensor value changed)              |
+|battery   |batteryLevel   |Number   |yes      |Battery Level in %                                                   |
+|          |lowBattery     |Switch   |yes      |Low battery alert (< 20%)                                            |
+
+### Shelly Button 1 (thing-type: shellybutton1)
 
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
 |status    |lastEvent    |String   |yes      |S/SS/SSS for 1/2/3x Shortpush or L for Longpush                        |
-|          |eventCount   |Number   |yes      |Number of button events                                                |
+|          |eventCount   |Number   |yes      |Counter gets incremented every time the device issues a button event.  |
 |          |input        |Switch   |yes      |ON: Input/Button is powered, see General Notes on Channels             |
 |          |button       |Trigger  |yes      |Event trigger with payload SHORT_PRESSED, DOUBLE_PRESSED...            |
 |          |lastUpdate   |DateTime |yes      |Timestamp of the last update (any value changed)                       |
@@ -694,7 +801,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
 
 You should calibrate the sensor using the Shelly App to get information on the tilt status.
 
-### Shelly Smoke(thing type: shellysmoke)
+### Shelly Smoke (thing-type: shellysmoke)
 
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
@@ -705,7 +812,7 @@ You should calibrate the sensor using the Shelly App to get information on the t
 |battery   |batteryLevel |Number   |yes      |Battery Level in %                                                     |
 |          |lowBattery   |Switch   |yes      |Low battery alert (< 20%)                                              |
 
-### Shelly Smoke(thing type: shellygas)
+### Shelly Smoke(thing-type: shellysmoke)
 
 |Group     |Channel      |Type     |read-only|Description                                                            |
 |----------|-------------|---------|---------|-----------------------------------------------------------------------|
@@ -866,6 +973,7 @@ end
 #### Control CCT LED stripes
 
 Usage & Requirements:
+
 - 4 Items per Thing required. Example:
 
 ```
index 069e03378ac33f57d342b85fb73b6cb35ee048fa..be86a015fd627592aa560aa4f8e910d1f2225fd2 100644 (file)
@@ -2,7 +2,7 @@
 
 This section provides information for advanced use cases.
 
-## Additiona Resources
+## Additional Resources
 
 There are additional resources available providing more information on Shelly devices and how to integrate those into openHAB:
 
@@ -29,18 +29,33 @@ However, if this doesn't work (sometimes there are issues) you could use the [Sh
 
 
 There are 3 options available to perform the upgrade
-- The Shelly App usually detects when a new version becomes available and offers to do the upgrade within the UI (Web or App)
-- Alterco provides the [Shelly Firmware Archive Link Generator](http://archive.shelly-faq.de).
-This can be used to generate the upgrade link, which could be easily used to perform the upgrade on the cli-level having an Internet connection on that terminal (Shelly device doesn't require an Internet access).
+
+### Using Shelly Web UI or Smartphone App
+
+The Apps usually detect when a new version becomes available and offers to do the upgrade to the latest release or beta version.
+
+### Trigger device update
+The [Shelly Firmware Archive Link Generator](http://archive.shelly-faq.de) is provided by the community (not official, but works like charm).
+This can be used to generate the update link, which could be easily used to perform the upgrade on the cli-level having an Internet connection on that terminal (Shelly device doesn't require an Internet access).
+
 You specify the device's IP and device model SHSW-25 and the page will generate you the link for the firmware download using the OTA of the device.
-Then you run "curl -s [-u user:password] &gt;generated link&gt;" from the terminal.
+
+Then you run 
+```
+curl -s [-u user:password] <generated link>
+```
+from the command line.
+
 This should show a JSON result, make sure that it shows "status:updating".
 Wait 15sec and access the device's Web UI, go to Settings:Firmware Upgrade and make sure than the new version was installed successful.
-- Manual download and installation of the firmware
-Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link.
-Once you downloaded the file you need to copy it to an http server. 
-Open the following url http://&lt;shelly ip&gt;/ota?url=http://&lt;web server&gt;/&lt;path&gt;/&lt;zip-file&gt;
-Again, make sure that the file is downloaded and installed properly.
+
+### Manual download and installation of the firmware
+
+- Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link.
+- Once you downloaded the file you need to copy it to an http server. 
+- Open the following url http://&lt;shelly ip&gt;/ota?url=http://&lt;web server&gt;/&lt;path&gt;/&lt;zip-file&gt;
+- Again, make sure that the file is downloaded and installed properly.
 
 ## Trouble Shooting
 
@@ -86,10 +101,23 @@ Use a list of items to reduce logging.
 `Please note:` Once events are filtered they are not show anymore in the logfile, you 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
 
index 3f904b487693df012dd831ad9d2786f3c3d0e224..9060e1c67704f6b10d64b41c81016609582d68ce 100644 (file)
@@ -31,7 +31,7 @@ To implement this use case you need
 For this how-to a roller with an electrical motor is required and will be controlled using A/C and a momentary switch.
 The same solutions works with other switch types (DC/AC, 2 button switch etc.).
 In this case you need to look into details and user a different Shelly configuration or adapt technical installation.
-Refer to the Alterco Shelly documentation and make sure to use proper technical installation and wiring.
+Refer to the Allterco Shelly documentation and make sure to use proper technical installation and wiring.
 Important: Electrical installation should be performed only be people knowing what they do - failures could harm you!!
 
 - Shelly 2.5 to control the roller with firmware 1.9.2+
@@ -63,7 +63,7 @@ Latest version is always available [here](https://github.com/markus7017/myfiles/
 Ideally the upgrade could be performed on a Raspberry with
 ```
 openhab-cli stop
-openhab-clu clean-cache
+openhab-cli clean-cache
 apt-get update
 apt-get upgrade
 openhab-cli start
@@ -133,7 +133,7 @@ It simplifies to identify the roller when you have a ton of Shelly things (belie
 
 ### Shelly setup - Positioning Favorites
 
-With version 1.9 Alterco introduced an interesting feature called Favorites.
+With version 1.9 Allterco introduced an interesting feature called Favorites.
 Those allow to store up to 4 pre-defined positions in the device, e.g. 15%, 50%, 65%, 98%.
 Once defined you have kind of a short-cut to bring the roller to that position and those favorites are also supported by the binding (see below).
 
@@ -171,7 +171,7 @@ There is also a dedicated channel (roller#rollerFav), which accepts this ids and
 
 ### Device events
 
-As you might know the binding supports the Alterco Shelly CoIoT protocol. 
+As you might know the binding supports the Allterco Shelly CoIoT protocol. 
 The device supports so called I/O URL Actions, which are kind of a callback to an application for certain events.
 Whenever possible you should prefer CoIoT events, because they are triggered near realtime and provide way more information compared to the Action URLs.
 The binding uses those CoIoT updates as triggers, but also to update the channel data.
index 5e4b5a813a60e5fcf8bbf13ff2f76e852c25cb84..c3bf59eaa0a13a8a6a34cf5a5410ed1c2fb29529 100755 (executable)
@@ -35,6 +35,7 @@ public class ShellyBindingConstants {
 
     // Type names
     public static final String THING_TYPE_SHELLY1_STR = "shelly1";
+    public static final String THING_TYPE_SHELLY1L_STR = "shelly1l";
     public static final String THING_TYPE_SHELLY1PM_STR = "shelly1pm";
     public static final String THING_TYPE_SHELLYEM_STR = "shellyem";
     public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
@@ -55,8 +56,9 @@ public class ShellyBindingConstants {
     public static final String THING_TYPE_SHELLYDUO_STR = "shellybulbduo";
     public static final String THING_TYPE_SHELLYVINTAGE_STR = "shellyvintage";
     public static final String THING_TYPE_SHELLYRGBW2_PREFIX = "shellyrgbw2";
-    public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = "shellyrgbw2-color";
-    public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = "shellyrgbw2-white";
+    public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-color";
+    public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-white";
+    public static final String THING_TYPE_SHELLYDUORGBW_STR = "shellycolorbulb";
     public static final String THING_TYPE_SHELLYHT_STR = "shellyht";
     public static final String THING_TYPE_SHELLYSMOKE_STR = "shellysmoke";
     public static final String THING_TYPE_SHELLYGAS_STR = "shellygas";
@@ -65,13 +67,16 @@ public class ShellyBindingConstants {
     public static final String THING_TYPE_SHELLYDOORWIN2_STR = "shellydw2";
     public static final String THING_TYPE_SHELLYEYE_STR = "shellyseye";
     public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
+    public static final String THING_TYPE_SHELLYMOTION_STR = "shellymotion";
     public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1";
+    public static final String THING_TYPE_SHELLYUNI_STR = "shellyuni";
     public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
     public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
 
     // Device Types
     public static final String SHELLYDT_1 = "SHSW-1";
     public static final String SHELLYDT_1PM = "SHSW-PM";
+    public static final String SHELLYDT_1L = "SHSW-L";
     public static final String SHELLYDT_SHPLG = "SHPLG-1";
     public static final String SHELLYDT_SHPLG_S = "SHPLG-S";
     public static final String SHELLYDT_SHPLG_U1 = "SHPLG-U1";
@@ -84,18 +89,22 @@ public class ShellyBindingConstants {
     public static final String SHELLYDT_DW = "SHDW-1";
     public static final String SHELLYDT_DW2 = "SHDW-2";
     public static final String SHELLYDT_SENSE = "SHSEN-1";
+    public static final String SHELLYDT_MOTION = "SHMOS-01";
     public static final String SHELLYDT_GAS = "SHGS-1";
     public static final String SHELLYDT_DIMMER = "SHDM-1";
     public static final String SHELLYDT_DIMMER2 = "SHDM-2";
     public static final String SHELLYDT_IX3 = "SHIX3-1";
     public static final String SHELLYDT_BULB = "SHBLB-1";
     public static final String SHELLYDT_DUO = "SHBDUO-1";
+    public static final String SHELLYDT_DUORGBW = "SHCB-1";
     public static final String SHELLYDT_VINTAGE = "SHVIN-1";
     public static final String SHELLYDT_RGBW2 = "SHRGBW2";
     public static final String SHELLYDT_BUTTON1 = "SHBTN-1";
+    public static final String SHELLYDT_UNI = "SHUNI-1";
 
     // List of all Thing Type UIDs
     public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
+    public static final ThingTypeUID THING_TYPE_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR);
     public static final ThingTypeUID THING_TYPE_SHELLY1PM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1PM_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYEM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEM_STR);
     public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
@@ -112,6 +121,7 @@ public class ShellyBindingConstants {
     public static final ThingTypeUID THING_TYPE_SHELLYPLUGS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUGS_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYPLUGU1 = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYPLUGU1_STR);
+    public static final ThingTypeUID THING_TYPE_SHELLYUNI = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYUNI_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYDIMMER = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYDIMMER_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYDIMMER2 = new ThingTypeUID(BINDING_ID,
@@ -121,6 +131,8 @@ public class ShellyBindingConstants {
     public static final ThingTypeUID THING_TYPE_SHELLYDUO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYDUO_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYVINTAGE = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYVINTAGE_STR);
+    public static final ThingTypeUID THING_TYPE_SHELLYDUORGBW = new ThingTypeUID(BINDING_ID,
+            THING_TYPE_SHELLYDUORGBW_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYHT = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYHT_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYSENSE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSENSE_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYSMOKE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSMOKE_STR);
@@ -133,6 +145,7 @@ public class ShellyBindingConstants {
     public static final ThingTypeUID THING_TYPE_SHELLYBUTTON1 = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYBUTTON1_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYEYE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEYE_STR);
+    public static final ThingTypeUID THING_TYPE_SHELLMOTION = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYMOTION_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_COLOR = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYRGBW2_COLOR_STR);
     public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_WHITE = new ThingTypeUID(BINDING_ID,
@@ -142,16 +155,17 @@ public class ShellyBindingConstants {
     public static final ThingTypeUID THING_TYPE_SHELLYUNKNOWN = new ThingTypeUID(BINDING_ID,
             THING_TYPE_SHELLYUNKNOWN_STR);
 
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
-            Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM,
-                    THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER, THING_TYPE_SHELLY25_RELAY,
-                    THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG, THING_TYPE_SHELLYPLUGS,
-                    THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3,
-                    THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYRGBW2_COLOR,
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
+            .unmodifiableSet(Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM,
+                    THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER,
+                    THING_TYPE_SHELLY25_RELAY, THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
+                    THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER,
+                    THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO,
+                    THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR,
                     THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT, THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE,
                     THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN,
-                    THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLYPROTECTED,
-                    THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
+                    THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, /* THING_TYPE_SHELLMOTION, */
+                    THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
 
     // Thing Configuration Properties
     public static final String CONFIG_DEVICEIP = "deviceIp";
@@ -199,9 +213,11 @@ public class ShellyBindingConstants {
     public static final String CHANNEL_GROUP_ROL_CONTROL = "roller";
     public static final String CHANNEL_ROL_CONTROL_CONTROL = "control";
     public static final String CHANNEL_ROL_CONTROL_POS = "rollerpos";
+    public static final String CHANNEL_ROL_CONTROL_FAV = "rollerFav";
     public static final String CHANNEL_ROL_CONTROL_TIMER = "timer";
     public static final String CHANNEL_ROL_CONTROL_STATE = "state";
     public static final String CHANNEL_ROL_CONTROL_STOPR = "stopReason";
+    public static final String CHANNEL_ROL_CONTROL_SAFETY = "safety";
 
     // Dimmer
     public static final String CHANNEL_GROUP_DIMMER_CONTROL = CHANNEL_GROUP_RELAY_CONTROL;
@@ -223,6 +239,7 @@ public class ShellyBindingConstants {
     public static final String CHANNEL_SENSOR_HUM = "humidity";
     public static final String CHANNEL_SENSOR_LUX = "lux";
     public static final String CHANNEL_SENSOR_PPM = "ppm";
+    public static final String CHANNEL_SENSOR_VOLTAGE = "voltage";
     public static final String CHANNEL_SENSOR_ILLUM = "illumination";
     public static final String CHANNEL_SENSOR_VIBRATION = "vibration";
     public static final String CHANNEL_SENSOR_TILT = "tilt";
@@ -233,6 +250,7 @@ public class ShellyBindingConstants {
     public static final String CHANNEL_SENSOR_SSTATE = "status"; // Shelly Gas
     public static final String CHANNEL_SENSOR_ALARM_STATE = "alarmState";
     public static final String CHANNEL_SENSOR_MOTION = "motion";
+    public static final String CHANNEL_SENSOR_MOTION_TS = "motionTimestamp";
     public static final String CHANNEL_SENSOR_ERROR = "lastError";
 
     // External sensors for Shelly1/1PM
@@ -290,12 +308,18 @@ public class ShellyBindingConstants {
     // Button/xi3
     public static final String CHANNEL_GROUP_STATUS = "status";
     public static final String CHANNEL_STATUS_EVENTTYPE = "lastEvent";
+    public static final String CHANNEL_STATUS_EVENTTYPE1 = CHANNEL_STATUS_EVENTTYPE + "1";
+    public static final String CHANNEL_STATUS_EVENTTYPE2 = CHANNEL_STATUS_EVENTTYPE + "2";
     public static final String CHANNEL_STATUS_EVENTCOUNT = "eventCount";
+    public static final String CHANNEL_STATUS_EVENTCOUNT1 = CHANNEL_STATUS_EVENTCOUNT + "1";
+    public static final String CHANNEL_STATUS_EVENTCOUNT2 = CHANNEL_STATUS_EVENTCOUNT + "2";
 
     // General
     public static final String CHANNEL_LAST_UPDATE = "lastUpdate";
     public static final String CHANNEL_EVENT_TRIGGER = "event";
     public static final String CHANNEL_BUTTON_TRIGGER = "button";
+    public static final String CHANNEL_BUTTON_TRIGGER1 = CHANNEL_BUTTON_TRIGGER + "1";
+    public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2";
 
     public static final String SERVICE_TYPE = "_http._tcp.local.";
     public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+
@@ -324,11 +348,11 @@ public class ShellyBindingConstants {
 
     // Formatting: Number of scaling digits
     public static final int DIGITS_NONE = 0;
-    public static final int DIGITS_WATT = 1;
+    public static final int DIGITS_WATT = 2;
     public static final int DIGITS_KWH = 3;
     public static final int DIGITS_VOLT = 1;
     public static final int DIGITS_TEMP = 1;
-    public static final int DIGITS_LUX = 1;
+    public static final int DIGITS_LUX = 0;
     public static final int DIGITS_PERCENT = 1;
 
     public static final int SHELLY_API_TIMEOUT_MS = 5000;
index e5ed391a6ebfc3cf06dce0c13b2e0c46702dda62..9e0d3c02b0e6cce617713f9b149b11a614d0b101 100755 (executable)
@@ -16,21 +16,19 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
 
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
 import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
 import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
 import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.binding.shelly.internal.util.ShellyUtils;
-import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
 import org.openhab.core.io.net.http.HttpClientFactory;
 import org.openhab.core.net.HttpServiceUtil;
 import org.openhab.core.net.NetworkAddressService;
@@ -60,7 +58,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
     private final HttpClient httpClient;
     private final ShellyTranslationProvider messages;
     private final ShellyCoapServer coapServer;
-    private final Set<ShellyBaseHandler> deviceListeners = new ConcurrentHashSet<>();
+    private final Set<ShellyBaseHandler> deviceListeners = ConcurrentHashMap.newKeySet();
     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
     private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
     private String localIP = "";
@@ -75,14 +73,18 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
      */
     @Activate
     public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService,
-            @Reference LocaleProvider localeProvider, @Reference TranslationProvider i18nProvider,
-            @Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
-            Map<String, Object> configProperties) {
+            @Reference ShellyTranslationProvider translationProvider, @Reference HttpClientFactory httpClientFactory,
+            ComponentContext componentContext, Map<String, Object> configProperties) {
         logger.debug("Activate Shelly HandlerFactory");
         super.activate(componentContext);
+        messages = translationProvider;
+        // Save bindingConfig & pass it to all registered listeners
+        bindingConfig.updateFromProperties(configProperties);
 
-        messages = new ShellyTranslationProvider(bundleContext.getBundle(), i18nProvider, localeProvider);
-        localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
+        localIP = bindingConfig.localIP;
+        if (localIP.isEmpty()) {
+            localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
+        }
         if (localIP.isEmpty()) {
             logger.warn("{}", messages.get("message.init.noipaddress"));
         }
@@ -95,9 +97,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
         logger.debug("Using OH HTTP port {}", httpPort);
 
         this.coapServer = new ShellyCoapServer();
-
-        // Save bindingConfig & pass it to all registered listeners
-        bindingConfig.updateFromProperties(configProperties);
     }
 
     @Override
@@ -116,9 +115,10 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
                     thingTypeUID.toString());
             handler = new ShellyProtectedHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort,
                     httpClient);
-        } else if (thingType.equals(THING_TYPE_SHELLYBULB.getId()) || thingType.equals(THING_TYPE_SHELLYDUO.getId())
-                || thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR.getId())
-                || thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE.getId())) {
+        } else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR)
+                || thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR_STR)
+                || thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR)
+                || thingType.equals(THING_TYPE_SHELLYDUORGBW_STR)) {
             logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
                     thingTypeUID.toString());
             handler = new ShellyLightHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);
index 5b018c8e5b237ca675a8d6422fbddc9bde9cf835..0e7df9d5a3829db4b5940125172209d5d1202472 100644 (file)
@@ -92,7 +92,8 @@ public class ShellyApiException extends Exception {
     public boolean isTimeout() {
         Class<?> extype = !isEmpty() ? getCauseClass() : null;
         return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
-                || (extype == InterruptedException.class) || getMessage().toLowerCase().contains("timeout"));
+                || (extype == InterruptedException.class)
+                || nonNullString(getMessage()).toLowerCase().contains("timeout"));
     }
 
     public boolean isHttpAccessUnauthorized() {
@@ -125,7 +126,7 @@ public class ShellyApiException extends Exception {
 
     private Class<?> getCauseClass() {
         Throwable cause = getCause();
-        if (getCause() != null) {
+        if (cause != null) {
             return cause.getClass();
         }
         return ShellyApiException.class;
index 003f1555107d0423daf633c14eccaf20ee28b3fd..739861d62eaa713aadbf8e100abcd6a39f70529b 100644 (file)
@@ -110,6 +110,7 @@ public class ShellyApiJsonDTO {
     public static final String SHELLY_BTNT_MOMENTARY = "momentary";
     public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release";
     public static final String SHELLY_BTNT_ONE_BUTTON = "one_button";
+    public static final String SHELLY_BTNT_TWO_BUTTON = "dual_button";
     public static final String SHELLY_BTNT_TOGGLE = "toggle";
     public static final String SHELLY_BTNT_EDGE = "edge";
     public static final String SHELLY_BTNT_DETACHED = "detached";
@@ -334,6 +335,10 @@ public class ShellyApiJsonDTO {
         public String defaultState; // Accepted values: off, on, last, switch
         @SerializedName("btn_type")
         public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
+        @SerializedName("btn1_type") // Shelly 1L
+        public String btnType1;
+        @SerializedName("btn2_type") // Shelly 1L
+        public String btnType2;
         @SerializedName("has_timer")
         public Boolean hasTimer; // Whether a timer is currently armed for this channel
         @SerializedName("auto_on")
@@ -390,6 +395,10 @@ public class ShellyApiJsonDTO {
         public String pushShortUrl; // short push button event
         @SerializedName("btn_type")
         public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
+        @SerializedName("btn1_type")
+        public String btnType1; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
+        @SerializedName("btn2_type")
+        public String btnType2; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
         @SerializedName("swap_inputs")
         public Integer swapInputs; // 0=no
     }
@@ -438,6 +447,32 @@ public class ShellyApiJsonDTO {
         public Boolean positioning;
     }
 
+    public static class ShellySettingsRgbwLight {
+        public String name;
+        public Boolean ison; // true: output is ON
+        public Integer brightness;
+        public Integer transition;
+        public String default_state;
+        @SerializedName("auto_on")
+        public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF.
+        @SerializedName("auto_off")
+        public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON.
+        public Boolean schedule;
+        @SerializedName("btn_type")
+        public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
+        @SerializedName("btn_reverse")
+        public Integer btnReverse; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx
+        @SerializedName("out_on_url")
+        public String outOnUrl; // output is activated
+        @SerializedName("out_off_url")
+        public String outOffUrl; // output is deactivated
+    }
+
+    public static class ShellyFavPos { // FW 1.9.2+ in roller mode
+        public String name;
+        public Integer pos;
+    }
+
     public static class ShellyInputState {
         public Integer input;
 
@@ -506,11 +541,6 @@ public class ShellyApiJsonDTO {
         @SerializedName("sleep_mode")
         public ShellySensorSleepMode sleepMode; // FW 1.6
 
-        // @SerializedName("ext_temperature")
-        // public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
-        // @SerializedName("ext_humidity")
-        // public ShellyStatusSensor.ShellyExtHumidity extHumidity; // Shelly 1/1PM: sensor values
-
         public String timezone;
         public Double lat;
         public Double lng;
@@ -531,6 +561,7 @@ public class ShellyApiJsonDTO {
 
         public ArrayList<ShellySettingsRelay> relays;
         public ArrayList<ShellySettingsDimmer> dimmers;
+        public ArrayList<ShellySettingsRgbwLight> lights;
         public ArrayList<ShellySettingsEMeter> emeters;
         public ArrayList<ShellySettingsInput> inputs; // ix3
 
@@ -580,6 +611,11 @@ public class ShellyApiJsonDTO {
         public String alarmMidUrl; // URL reports middle alarm
         @SerializedName("alarm_heavy_url")
         public String alarmHeavyfUrl; // URL reports heavy alarm
+
+        // Roller with FW 1.9.2+
+        @SerializedName("favorites_enabled")
+        public Boolean favoritesEnabled;
+        public ArrayList<ShellyFavPos> favorites;
     }
 
     public static class ShellySettingsAttributes {
@@ -651,7 +687,7 @@ public class ShellyApiJsonDTO {
         @SerializedName("btn_type")
         public String btnType;
 
-        // included attributes not yet processed
+        // attributes not yet processed
         // public String name;
         // @SerializedName("btn_reverse")
         // public Integer btnReverse;
@@ -727,6 +763,7 @@ public class ShellyApiJsonDTO {
         public String mac; // MAC
         public ArrayList<ShellyShortStatusRelay> relays; // relay status
         public ArrayList<ShellySettingsMeter> meters; // current meter value
+        public ArrayList<ShellyInputState> inputs; // Firmware 1.5.6+
 
         @SerializedName("ext_temperature")
         public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
@@ -838,6 +875,14 @@ public class ShellyApiJsonDTO {
             @SerializedName("is_valid")
             public Boolean isValid; // whether the internal sensor is operating properly
             public String state; // Shelly Door/Window
+
+            // Shelly Motion
+            public Boolean motion;
+            public Boolean vibration;
+            @SerializedName("timestamp")
+            public Long motionTimestamp;
+            @SerializedName("active")
+            public Boolean motionActive;
         }
 
         public static class ShellySensorLux {
@@ -880,13 +925,17 @@ public class ShellyApiJsonDTO {
             public ShellyShortHum sensor1;
         }
 
+        public static class ShellyADC {
+            public Double voltage;
+        }
+
         public ShellySensorTmp tmp;
         public ShellySensorHum hum;
         public ShellySensorLux lux;
         public ShellySensorAccel accel;
         public ShellySensorBat bat;
         @SerializedName("sensor")
-        public ShellySensorState contact;
+        public ShellySensorState sensor;
         public Boolean smoke; // SHelly Smoke
         public Boolean flood; // Shelly Flood: true = flood condition detected
         @SerializedName("rain_sensor")
@@ -914,6 +963,9 @@ public class ShellyApiJsonDTO {
         @SerializedName("connect_retries")
         public Integer connectRetries;
         public ArrayList<ShellyInputState> inputs; // Firmware 1.5.6+
+
+        // Shelly UNI FW 1.9+
+        public ArrayList<ShellyADC> adcs;
     }
 
     public static class ShellySettingsSmoke {
@@ -985,10 +1037,14 @@ public class ShellyApiJsonDTO {
         public Boolean ison;
         public Double power;
         public Boolean overpower;
-        @SerializedName("auto_on")
-        public Double autoOn; // see above
-        @SerializedName("auto_off")
-        public Double autoOff; // see above
+        @SerializedName("has_timer")
+        public Boolean hasTimer;
+        @SerializedName("timer_started")
+        public Integer timerStarted;
+        @SerializedName("timer_duration")
+        public Integer timerDuration;
+        @SerializedName("timer_remaining")
+        public Integer timerRemaining;
 
         public Integer red; // red brightness, 0..255, applies in mode="color"
         public Integer green; // green brightness, 0..255, applies in mode="color"
@@ -1003,18 +1059,10 @@ public class ShellyApiJsonDTO {
 
     public static class ShellyStatusLight {
         public Boolean ison; // Whether output channel is on or off
-        public ArrayList<ShellyStatusLightChannel> lights;
-        public ArrayList<ShellySettingsMeter> meters;
         public Integer input;
 
-        // not yet used:
-        // public String mode; // COLOR or WHITE
-        // public Boolean has_update;
-        // public ShellySettingsUpdate update;
-        // public ShellySettingsWiFiNetwork wifi_sta; // WiFi client configuration. See
-        // /settings/sta for details
-        // public ShellyStatusCloud cloud;
-        // public ShellyStatusMqtt mqtt;
+        public ArrayList<ShellyStatusLightChannel> lights;
+        public ArrayList<ShellySettingsMeter> meters;
     }
 
     public static class ShellySenseKeyCode {
index 127c72a9048972ab687f9ae5cd510beaec4a1820..00cdb38fa12f9ccd4b1568578206b4fcb55d7e45 100644 (file)
@@ -18,19 +18,18 @@ import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.gson.Gson;
-import com.google.gson.JsonSyntaxException;
 
 /**
  * The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
@@ -68,6 +67,7 @@ public class ShellyDeviceProfile {
     public int numRollers = 0; // number of Rollers, usually 1
     public boolean isRoller = false; // true for Shelly2 in roller mode
     public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
+    public int numInputs = 0; // number of inputs
 
     public int numMeters = 0;
     public boolean isEMeter = false; // true for ShellyEM/3EM
@@ -101,14 +101,10 @@ public class ShellyDeviceProfile {
 
         initialized = false;
 
-        try {
-            initFromThingType(thingType);
-            settingsJson = json;
-            settings = Objects.requireNonNull(gson.fromJson(json, ShellySettingsGlobal.class));
-        } catch (IllegalArgumentException | JsonSyntaxException e) {
-            throw new ShellyApiException(
-                    thingName + ": Unable to transform settings JSON " + e.toString() + ", json='" + json + "'", e);
-        }
+        initFromThingType(thingType);
+        settingsJson = json;
+        ShellySettingsGlobal gs = fromJson(gson, json, ShellySettingsGlobal.class);
+        settings = gs; // only update when no exception
 
         // General settings
         deviceType = getString(settings.device.type);
@@ -131,8 +127,10 @@ public class ShellyDeviceProfile {
             numRelays = 0;
         }
         isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
+        isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
         hasRelays = (numRelays > 0) || isDimmer;
         numRollers = getInteger(settings.device.numRollers);
+        numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0;
 
         isEMeter = settings.emeters != null;
         numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
@@ -140,18 +138,17 @@ public class ShellyDeviceProfile {
             // RGBW2 doesn't report, but has one
             numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
         }
-        isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
 
         if (settings.sleepMode != null) {
-            // Sensor, usally 12h
+            // Sensor, usually 12h, H&T in USB mode 10min
             updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
                     : settings.sleepMode.period * 3600; // hours
-            updatePeriod += 600; // give 10min extra
+            updatePeriod += 60; // give 1min extra
         } else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
-            // Derive from CoAP update interval, usually 2*15+5s=50sec -> 70sec
-            updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 3 * getInteger(settings.coiot.updatePeriod)) + 10;
+            // Derive from CoAP update interval, usually 2*15+10s=40sec -> 70sec
+            updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 2 * getInteger(settings.coiot.updatePeriod)) + 10;
         } else {
-            updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
+            updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
         }
 
         initialized = true;
@@ -178,7 +175,8 @@ public class ShellyDeviceProfile {
         }
 
         isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
-        isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR);
+        isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)
+                || thingType.equals(THING_TYPE_SHELLYDUORGBW_STR);
         isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
         isLight = isBulb || isDuo || isRGBW2;
         if (isLight) {
@@ -189,14 +187,29 @@ public class ShellyDeviceProfile {
         boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
         boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
         boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
+        boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
+        boolean isMotion = thingType.equals(THING_TYPE_SHELLYMOTION_STR);
         isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
         isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
         isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
         isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
         isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
-        isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isSense;
-        hasBattery = isHT || isFlood || isDW || isSmoke || isButton; // we assume that Sense is connected to // the
-                                                                     // charger
+        isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isSense;
+        hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion; // we assume that Sense is connected to
+                                                                                 // the charger
+    }
+
+    public void updateFromStatus(ShellySettingsStatus status) {
+        if (hasRelays) {
+            // Dimmer-2 doesn't report inputs under /settings, only on /status, we need to update that info after
+            // initialization
+            if (status.inputs != null) {
+                numInputs = status.inputs.size();
+            }
+        } else if (status.input != null) {
+            // RGBW2
+            numInputs = 1;
+        }
     }
 
     public String getControlGroup(int i) {
@@ -208,11 +221,13 @@ public class ShellyDeviceProfile {
         if (isDimmer) {
             return CHANNEL_GROUP_DIMMER_CONTROL;
         } else if (isRoller) {
-            return numRollers == 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
+            return numRollers <= 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
+        } else if (isDimmer) {
+            return CHANNEL_GROUP_RELAY_CONTROL;
         } else if (hasRelays) {
-            return numRelays == 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
+            return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
         } else if (isLight) {
-            return numRelays == 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
+            return numRelays <= 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
         } else if (isButton) {
             return CHANNEL_GROUP_STATUS;
         } else if (isSensor) {
@@ -239,14 +254,17 @@ public class ShellyDeviceProfile {
         }
     }
 
-    public String getInputChannel(int i) {
+    public String getInputSuffix(int i) {
         int idx = i + 1; // channel names are 1-based
         if (isRGBW2 || isIX3) {
-            return CHANNEL_INPUT; // RGBW2 has only 1 channel
+            return ""; // RGBW2 has only 1 channel
+        } else if (isRoller || isDimmer) {
+            // Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs
+            return String.valueOf(idx);
         } else if (hasRelays) {
-            return CHANNEL_INPUT + idx;
+            return (numRelays) == 1 && (numInputs >= 2) ? String.valueOf(idx) : "";
         }
-        return CHANNEL_INPUT;
+        return "";
     }
 
     public boolean inButtonMode(int idx) {
@@ -257,25 +275,43 @@ public class ShellyDeviceProfile {
         String btnType = "";
         if (isButton) {
             return true;
-        } else if (isIX3) {
-            if ((settings.inputs != null) && (idx >= 0) && (idx < settings.inputs.size())) {
-                ShellySettingsInput input = settings.inputs.get(idx);
-                btnType = input.btnType;
-            }
+        } else if (isIX3 && (settings.inputs != null) && (idx < settings.inputs.size())) {
+            ShellySettingsInput input = settings.inputs.get(idx);
+            btnType = getString(input.btnType);
         } else if (isDimmer) {
-            if ((settings.dimmers != null) && (idx >= 0) && (idx < settings.dimmers.size())) {
-                ShellySettingsDimmer dimmer = settings.dimmers.get(idx);
+            if (settings.dimmers != null) {
+                ShellySettingsDimmer dimmer = settings.dimmers.get(0);
                 btnType = dimmer.btnType;
             }
-        } else if ((settings.relays != null) && (idx >= 0) && (idx < settings.relays.size())) {
-            ShellySettingsRelay relay = settings.relays.get(idx);
-            btnType = relay.btnType;
+        } else if (settings.relays != null) {
+            if (numRelays == 1) {
+                ShellySettingsRelay relay = settings.relays.get(0);
+                if (relay.btnType != null) {
+                    btnType = getString(relay.btnType);
+                } else {
+                    // Shelly 1L has 2 inputs
+                    btnType = idx == 0 ? getString(relay.btnType1) : getString(relay.btnType2);
+                }
+            } else if (idx < settings.relays.size()) {
+                // only one input channel
+                ShellySettingsRelay relay = settings.relays.get(idx);
+                btnType = getString(relay.btnType);
+            }
+        } else if (isRGBW2 && (settings.lights != null) && (idx < settings.lights.size())) {
+            ShellySettingsRgbwLight light = settings.lights.get(idx);
+            btnType = light.btnType;
         }
 
-        if (btnType.equals(SHELLY_BTNT_MOMENTARY) || btnType.equals(SHELLY_BTNT_MOM_ON_RELEASE)
-                || btnType.equals(SHELLY_BTNT_DETACHED) || btnType.equals(SHELLY_BTNT_ONE_BUTTON)) {
-            return true;
+        logger.trace("{}: Checking for trigger, button-type[{}] is {}", thingName, idx, btnType);
+        return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE)
+                || btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON);
+    }
+
+    public int getRollerFav(int id) {
+        if ((id >= 0) && getBool(settings.favoritesEnabled) && (settings.favorites != null)
+                && (id < settings.favorites.size())) {
+            return settings.favorites.get(id).pos;
         }
-        return false;
+        return -1;
     }
 }
index 5578e95e760b613f14e8579582d10ab485f28f83..5d7863c14af5dabef4adf21585dfb0fe661e0b8d 100644 (file)
@@ -87,7 +87,7 @@ public class ShellyEventServlet extends HttpServlet {
         }
 
         try {
-            path = request.getRequestURI().toLowerCase();
+            path = getString(request.getRequestURI()).toLowerCase();
             String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
             if (ipAddress == null) {
                 ipAddress = request.getRemoteAddr();
index 0a34797b7d40ddc9cc8802bf24d3111beaf645d1..7a383f2a027cd0f880a4caf768e95017da3ac335 100644 (file)
@@ -135,9 +135,9 @@ public class ShellyHttpApi {
         try {
             json = request(SHELLY_URL_STATUS);
             // Dimmer2 returns invalid json type for loaderror :-(
-            json = json.replace("\"loaderror\":0,", "\"loaderror\":false,");
-            json = json.replace("\"loaderror\":1,", "\"loaderror\":true,");
-            ShellySettingsStatus status = gson.fromJson(json, ShellySettingsStatus.class);
+            json = getString(json.replace("\"loaderror\":0,", "\"loaderror\":false,"));
+            json = getString(json.replace("\"loaderror\":1,", "\"loaderror\":true,"));
+            ShellySettingsStatus status = fromJson(gson, json, ShellySettingsStatus.class);
             status.json = json;
             return status;
         } catch (JsonSyntaxException e) {
@@ -188,9 +188,9 @@ public class ShellyHttpApi {
             status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value
                     : ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(getDouble(status.tmp.value))
                             .doubleValue();
-            status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value
-                    : SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT).convert(getDouble(status.tmp.value))
-                            .doubleValue();
+            double f = (double) SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT)
+                    .convert(getDouble(status.tmp.value));
+            status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value : f;
         }
         if ((status.charger == null) && (status.externalPower != null)) {
             // SHelly H&T uses external_power, Sense uses charger
@@ -286,11 +286,12 @@ public class ShellyHttpApi {
         keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":");
         keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} ");
         String json = "{\"key_codes\" : [" + keyList + "] }";
-
-        ShellySendKeyList codes = gson.fromJson(json, ShellySendKeyList.class);
+        ShellySendKeyList codes = fromJson(gson, json, ShellySendKeyList.class);
         Map<String, String> list = new HashMap<>();
         for (ShellySenseKeyCode key : codes.keyCodes) {
-            list.put(key.id, key.name);
+            if (key != null) {
+                list.put(key.id, key.name);
+            }
         }
         return list;
     }
@@ -318,9 +319,6 @@ public class ShellyHttpApi {
             url = url + "&" + "id=" + keyCode;
         } else if (type.equals(SHELLY_IR_CODET_PRONTO)) {
             String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8));
-            if (code == null) {
-                throw new IllegalArgumentException("Unable to BASE64 encode the pronto code: " + keyCode);
-            }
             url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code;
         } else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) {
             url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode;
@@ -470,12 +468,8 @@ public class ShellyHttpApi {
      * @param uri: URI (e.g. "/settings")
      */
     public <T> T callApi(String uri, Class<T> classOfT) throws ShellyApiException {
-        try {
-            String json = request(uri);
-            return gson.fromJson(json, classOfT);
-        } catch (JsonSyntaxException e) {
-            throw new ShellyApiException("Unable to convert JSON", e);
-        }
+        String json = request(uri);
+        return fromJson(gson, json, classOfT);
     }
 
     private String request(String uri) throws ShellyApiException {
@@ -503,7 +497,7 @@ public class ShellyHttpApi {
                 logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString());
             }
         }
-        throw new ShellyApiException("Inconsistent API result or Timeout"); // successful
+        throw new ShellyApiException("API Timeout or inconsistent result"); // successful
     }
 
     private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException {
@@ -533,7 +527,7 @@ public class ShellyHttpApi {
             if (contentResponse.getStatus() != HttpStatus.OK_200) {
                 throw new ShellyApiException(apiResult);
             }
-            if (response == null || response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
+            if (response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
                 throw new ShellyApiException("Unexpected response: " + response);
             }
         } catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
index ca8d861dc11dc853db997897e606afefb6ca18a4..7073d71671d62c9a8caea7e6f1c6e75bff3d23b4 100644 (file)
@@ -16,9 +16,11 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
+import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
 import org.openhab.core.types.State;
 
 /**
@@ -30,8 +32,12 @@ import org.openhab.core.types.State;
 public interface ShellyCoIoTInterface {
     public int getVersion();
 
-    public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
+    public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
 
-    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
-            Map<String, State> updates);
+    public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap);
+
+    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
+            Map<String, State> updates, ShellyColorUtils col);
+
+    public String getLastWakeup();
 }
index 6d6f40ea82c4bc642e8ebdde976acdf715cf96f4..db9d7f614af1efeafcf4458c6175ad1f5f875d24 100644 (file)
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
@@ -34,6 +35,10 @@ import org.openhab.core.types.State;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
 /**
  * The {@link ShellyCoIoTProtocol} implements common functions for the CoIoT implementations
  *
@@ -47,12 +52,14 @@ public class ShellyCoIoTProtocol {
     protected final ShellyDeviceProfile profile;
     protected final Map<String, CoIotDescrBlk> blkMap;
     protected final Map<String, CoIotDescrSen> sensorMap;
+    private final Gson gson = new GsonBuilder().create();
 
     // Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish
     // between a real update or just a repeated status on periodic updates
     protected int lastCfgCount = -1;
     protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine
     protected String[] inputEvent = { "", "", "", "", "", "", "", "" };
+    protected String lastWakeup = "";
 
     public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
             Map<String, CoIotDescrSen> sensorMap) {
@@ -64,7 +71,7 @@ public class ShellyCoIoTProtocol {
     }
 
     protected boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
-            Map<String, State> updates) {
+            Map<String, State> updates, ShellyColorUtils col) {
         // Process status information and convert into channel updates
         // Integer rIndex = Integer.parseInt(sen.links) + 1;
         // String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
@@ -72,6 +79,7 @@ public class ShellyCoIoTProtocol {
         int rIndex = getIdFromBlk(sen);
         String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
                 : CHANNEL_GROUP_RELAY_CONTROL + rIndex;
+
         switch (sen.type.toLowerCase()) {
             case "b": // BatteryLevel +
                 updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
@@ -131,22 +139,27 @@ public class ShellyCoIoTProtocol {
                         break;
                     // RGBW2/Bulb
                     case "red":
+                        col.setRed((int) s.value);
                         updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_RED,
                                 ShellyColorUtils.toPercent((int) s.value));
                         break;
                     case "green":
+                        col.setGreen((int) s.value);
                         updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GREEN,
                                 ShellyColorUtils.toPercent((int) s.value));
                         break;
                     case "blue":
+                        col.setBlue((int) s.value);
                         updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_BLUE,
                                 ShellyColorUtils.toPercent((int) s.value));
                         break;
                     case "white":
+                        col.setWhite((int) s.value);
                         updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_WHITE,
                                 ShellyColorUtils.toPercent((int) s.value));
                         break;
                     case "gain":
+                        col.setGain((int) s.value);
                         updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GAIN,
                                 ShellyColorUtils.toPercent((int) s.value, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
                         break;
@@ -167,7 +180,7 @@ public class ShellyCoIoTProtocol {
         return true;
     }
 
-    protected boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
+    public static boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
         updates.put(mkChannelId(group, channel), value);
         return true;
     }
@@ -175,26 +188,32 @@ public class ShellyCoIoTProtocol {
     protected void handleInput(CoIotDescrSen sen, CoIotSensor s, String rGroup, Map<String, State> updates) {
         int idx = getSensorNumber(sen.desc, sen.id) - 1;
         String iGroup = profile.getInputGroup(idx);
-        String iChannel = profile.getInputChannel(idx);
+        String iChannel = CHANNEL_INPUT + profile.getInputSuffix(idx);
         updateChannel(updates, iGroup, iChannel, s.value == 0 ? OnOffType.OFF : OnOffType.ON);
     }
 
-    protected void handleInputEvent(CoIotDescrSen sen, String type, Integer count, Map<String, State> updates) {
+    protected void handleInputEvent(CoIotDescrSen sen, String type, int count, int serial, Map<String, State> updates) {
         int idx = getSensorNumber(sen.desc, sen.id) - 1;
         String group = profile.getInputGroup(idx);
         if (count == -1) {
             // event type
-            updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE, new StringType(type));
+            updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(idx), new StringType(type));
             inputEvent[idx] = type;
         } else {
             // event count
-            updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(count));
-            if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1)) || (count != lastEventCount[idx]))) {
-                if (profile.isButton || (lastEventCount[idx] != -1)) { // skip the first one if binding was restarted
-                    thingHandler.triggerButton(group, inputEvent[idx]);
+            updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(idx), getDecimal(count));
+            logger.trace(
+                    "{}: Check button[{}] for event trigger (isButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}",
+                    thingName, idx, profile.inButtonMode(idx), profile.isButton, profile.hasBattery, serial, count, idx,
+                    lastEventCount[idx]);
+            if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1))
+                    || ((lastEventCount[idx] != -1) && (count != lastEventCount[idx])))) {
+                if (!profile.isButton || (profile.isButton && (serial != 0x200))) { // skip duplicate on wake-up
+                    logger.debug("{}: Trigger event {}", thingName, inputEvent[idx]);
+                    thingHandler.triggerButton(group, idx, inputEvent[idx]);
                 }
-                lastEventCount[idx] = count;
             }
+            lastEventCount[idx] = count;
         }
     }
 
@@ -225,25 +244,25 @@ public class ShellyCoIoTProtocol {
             } else if (profile.isDimmer) {
                 group = CHANNEL_GROUP_RELAY_CONTROL;
             } else if (profile.isRGBW2) {
+                checkL = String.valueOf(id); // String.valueOf(id - 1); // id is 1-based, L is 0-based
                 group = CHANNEL_GROUP_LIGHT_CHANNEL + id;
-                checkL = String.valueOf(id - 1); // id is 1-based, L is 0-based
                 logger.trace("{}: updatePower() for L={}", thingName, checkL);
             }
 
-            // We need to update brigthtess and on/off state at the same time to avoid "flipping brightness slider" in
+            // We need to update brightness and on/off state at the same time to avoid "flipping brightness slider" in
             // the UI
-            Double brightness = -1.0;
-            Double power = -1.0;
+            double brightness = -1.0;
+            double power = -1.0;
             for (CoIotSensor update : allUpdates) {
-                CoIotDescrSen d = fixDescription(sensorMap.getOrDefault(update.id, new CoIotDescrSen()), blkMap);
+                CoIotDescrSen d = fixDescription(sensorMap.get(update.id), blkMap);
                 if (!checkL.isEmpty() && !d.links.equals(checkL)) {
                     // continue until we find the correct one
                     continue;
                 }
                 if (d.desc.equalsIgnoreCase("brightness")) {
-                    brightness = new Double(update.value);
+                    brightness = update.value;
                 } else if (d.desc.equalsIgnoreCase("output") || d.desc.equalsIgnoreCase("state")) {
-                    power = new Double(update.value);
+                    power = update.value;
                 }
             }
             if (power != -1) {
@@ -298,11 +317,11 @@ public class ShellyCoIoTProtocol {
 
     protected int getIdFromBlk(CoIotDescrSen sen) {
         int idx = -1;
-        if (blkMap.containsKey(sen.links)) {
-            CoIotDescrBlk blk = blkMap.get(sen.links);
+        CoIotDescrBlk blk = blkMap.get(sen.links);
+        if (blk != null) {
             String desc = blk.desc.toLowerCase();
             if (desc.startsWith(SHELLY_CLASS_RELAY) || desc.startsWith(SHELLY_CLASS_ROLLER)
-                    || desc.startsWith(SHELLY_CLASS_EMETER)) {
+                    || desc.startsWith(SHELLY_CLASS_LIGHT) || desc.startsWith(SHELLY_CLASS_EMETER)) {
                 if (desc.contains("_")) { // CoAP v2
                     idx = Integer.parseInt(substringAfter(desc, "_"));
                 } else { // CoAP v1
@@ -349,7 +368,28 @@ public class ShellyCoIoTProtocol {
         return profile;
     }
 
-    public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
-        return sen;
+    public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
+        return sen != null ? sen : new CoIotDescrSen();
+    }
+
+    public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap) {
+    }
+
+    protected void addSensor(Map<String, CoIotDescrSen> sensorMap, String key, String json) {
+        try {
+            if (!sensorMap.containsKey(key)) {
+                CoIotDescrSen sen = gson.fromJson(json, CoIotDescrSen.class);
+                if (sen != null) {
+                    sensorMap.put(key, sen);
+                }
+            }
+        } catch (JsonSyntaxException e) {
+            // should never happen
+            logger.trace("Unable to parse sensor definition: {}", json, e);
+        }
+    }
+
+    public String getLastWakeup() {
+        return lastWakeup;
     }
 }
index 4ef26e8829096b49dcc77eb1b63752a9783b360c..c650efeea7fdbf7def898f76012e94bd039c811f 100644 (file)
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
@@ -63,10 +64,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
      *            ignored.
      */
     @Override
-    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
-            Map<String, State> updates) {
+    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
+            Map<String, State> updates, ShellyColorUtils col) {
         // first check the base implementation
-        if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
+        if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) {
             // process by the base class
             return true;
         }
@@ -162,10 +163,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
                                 toQuantityType(pos, Units.PERCENT));
                         break;
                     case "input event": // Shelly Button 1
-                        handleInputEvent(sen, getString(s.valueStr), -1, updates);
+                        handleInputEvent(sen, getString(s.valueStr), -1, serial, updates);
                         break;
                     case "input event counter": // Shelly Button 1/ix3
-                        handleInputEvent(sen, "", getInteger((int) s.value), updates);
+                        handleInputEvent(sen, "", getInteger((int) s.value), serial, updates);
                         break;
                     case "flood":
                         updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
@@ -227,7 +228,7 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
      * @return fixed Sensor description (sen)
      */
     @Override
-    public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
+    public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
         // Shelly1: reports null descr+type "Switch" -> map to S
         // Shelly1PM: reports null descr+type "Overtemp" -> map to O
         // Shelly1PM: reports null descr+type "W" -> add description
@@ -238,6 +239,9 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
         // Shelly Sense: Motion is reported with Desc "battery", but type "H" instead of "B"
         // Shelly Bulb: Colors are coded with Type="Red" etc. rather than Type="S" and color as Descr
         // Shelly RGBW2 is reporting Brightness, Power, VSwitch for each channel, but all with L=0
+        if (sen == null) {
+            throw new IllegalArgumentException("sen should not be null!");
+        }
         if (sen.desc == null) {
             sen.desc = "";
         }
@@ -255,8 +259,10 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
                 CoIotDescrBlk blk = new CoIotDescrBlk();
                 CoIotDescrBlk blk0 = blkMap.get("0"); // blk 0 is always there
                 blk.id = sen.links;
-                blk.desc = blk0.desc + "_" + blk.id;
-                blkMap.put(blk.id, blk);
+                if (blk0 != null) {
+                    blk.desc = blk0.desc + "_" + blk.id;
+                    blkMap.put(blk.id, blk);
+                }
             }
         }
 
index ebc2207773561a7bd2debfbbc4ff163b17c3586d..78e2cc7f01e3baee2a6ebc3d8e67d7244b335ac6 100644 (file)
@@ -25,10 +25,12 @@ import java.util.List;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
+import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
 import org.openhab.core.library.unit.SIUnits;
@@ -60,17 +62,15 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
      * Process CoIoT status update message. If a status update is received, but the device description has not been
      * received yet a GET is send to query device description.
      *
-     * @param devId device id included in the status packet
-     * @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
-     * @param serial Serial for this request. If this the the same as last serial
-     *            the update was already sent and processed so this one gets
-     *            ignored.
+     * @param sensorUpdates Complete list of sensor updates
+     * @param sen The specific sensor update to handle
+     * @param updates Resulting updates (new updates will be added to input list)
      */
     @Override
-    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
-            Map<String, State> updates) {
+    public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s,
+            Map<String, State> updates, ShellyColorUtils col) {
         // first check the base implementation
-        if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
+        if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) {
             // process by the base class
             return true;
         }
@@ -80,7 +80,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
         int rIndex = getIdFromBlk(sen);
         String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
                 : CHANNEL_GROUP_RELAY_CONTROL + rIndex;
-        String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
+        String mGroup = profile.numMeters <= 1 ? CHANNEL_GROUP_METER
                 : CHANNEL_GROUP_METER + (profile.isEMeter ? getIdFromBlk(sen) : rIndex);
 
         boolean processed = true;
@@ -89,14 +89,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
         switch (sen.id) {
             case "3103": // H, humidity, 0-100 percent, unknown 999
             case "3106": // L, luminosity, lux, U32, -1
-            case "3109": // S, tilt, 0-180deg, -1
             case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown
             case "3111": // B, battery, 0-100%, unknown -1
             case "3112": // S, charger, 0/1
             case "3115": // S, sensorError, 0/1
-            case "5101": // S, brightness, 1-100%
                 // processed by base handler
                 break;
+
             case "6109": // P, overpowerValue, W, U32
             case "9101":
                 // Relay: S, mode, relay/roller or
@@ -113,7 +112,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
             case "1103": // roller_0: S, rollerPos, 0-100, unknown -1
                 int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min((int) value, SHELLY_MAX_ROLLER_POS));
                 updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
-                        toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
+                        toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
                 break;
             case "1105": // S, valvle, closed/opened/not_connected/failure/closing/opening/checking or unbknown
                 updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr));
@@ -129,13 +128,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
             case "2202": // Input_1: EV, inputEvent
             case "2302": // Input_2: EV, inputEvent
             case "2402": // Input_3: EV, inputEvent
-                handleInputEvent(sen, getString(s.valueStr), -1, updates);
+                handleInputEvent(sen, getString(s.valueStr), -1, serial, updates);
                 break;
             case "2103": // EVC, inputEventCnt, U16
             case "2203": // EVC, inputEventCnt, U16
             case "2303": // EVC, inputEventCnt, U16
             case "2403": // EVC, inputEventCnt, U16
-                handleInputEvent(sen, "", getInteger((int) s.value), updates);
+                handleInputEvent(sen, "", getInteger((int) s.value), serial, updates);
                 break;
             case "3101": // sensor_0: T, extTemp, C, -55/125; unknown 999
             case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999
@@ -172,6 +171,10 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
                     logger.debug("{}: Sensor error reported, check device, battery and installation", thingName);
                 }
                 break;
+            case "3109": // S, tilt, 0-180deg, -1
+                updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT,
+                        toQuantityType(s.value, DIGITS_NONE, Units.DEGREE_ANGLE));
+                break;
             case "3113": // S, sensorOp, warmup/normal/fault
                 updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
                 break;
@@ -181,19 +184,27 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
             case "3117": // S, extInput, 0/1
                 handleInput(sen, s, rGroup, updates);
                 break;
+            case "3118":
+                updateChannel(updates, mGroup, CHANNEL_SENSOR_VOLTAGE,
+                        toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.VOLT));
+                break;
 
-            case "4101": // relay_0: P, power, W
-            case "4201": // relay_1: P, power, W
-            case "4301": // relay_2: P, power, W
-            case "4401": // relay_3: P, power, W
+            case "4101": // relay_0/light_0: P, power, W
+            case "4201": // relay_1/light_1: P, power, W
+            case "4301": // relay_2/light_2: P, power, W
+            case "4401": // relay_3/light_3: P, power, W
             case "4105": // emeter_0: P, power, W
             case "4205": // emeter_1: P, power, W
             case "4305": // emeter_2: P, power, W
             case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1
             case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1
+                logger.debug("{}: Updating {}:currentWatts with {}", thingName, mGroup, s.value);
                 updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
                         toQuantityType(s.value, DIGITS_WATT, Units.WATT));
-                updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
+                if (!profile.isRGBW2 && !profile.isRoller) {
+                    // only for regular, not-aggregated meters
+                    updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
+                }
                 break;
 
             case "4103": // relay_0: E, energy, Wmin, U32
@@ -237,6 +248,16 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
                 updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
                 break;
 
+            case "5101": // {"I":5101,"T":"S","D":"brightness","R":"0/100","L":1},
+            case "5102": // {"I":5102,"T":"S","D":"gain","R":"0/100","L":1},
+            case "5103": // {"I":5103,"T":"S","D":"colorTemp","U":"K","R":"3000/6500","L":1},
+            case "5105": // {"I":5105,"T":"S","D":"red","R":"0/255","L":1},
+            case "5106": // {"I":5106,"T":"S","D":"green","R":"0/255","L":1},
+            case "5107": // {"I":5107,"T":"S","D":"blue","R":"0/255","L":1},
+            case "5108": // {"I":5108,"T":"S","D":"white","R":"0/255","L":1},
+                // already covered by base handler
+                break;
+
             case "6101": // A, overtemp, 0/1
                 if (s.value == 1) {
                     thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
@@ -268,6 +289,21 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
                 updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
                         value == 1 ? OnOffType.ON : OnOffType.OFF);
                 break;
+
+            case "6107": // A, motion, 0/1, -1
+                // {"I":6107,"T":"A","D":"motion","R":["0/1","-1"],"L":1},
+                updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
+                        value == 1 ? OnOffType.ON : OnOffType.OFF);
+                break;
+            case "3119": // Motion timestamp
+                // {"I":3119,"T":"S","D":"timestamp","U":"s","R":["U32","-1"],"L":1},
+                updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
+                        getTimestamp(getString(profile.settings.timezone), (long) s.value));
+                break;
+            case "3120": // motionActive
+                // {"I":3120,"T":"S","D":"motionActive","R":["0/1","-1"],"L":1},
+                break;
+
             case "6108": // A, gas, none/mild/heavy/test or unknown
                 updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, getStringType(s.valueStr));
                 break;
@@ -276,7 +312,10 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
                         value == 1 ? OnOffType.ON : OnOffType.OFF);
                 break;
             case "9102": // EV, wakeupEvent, battery/button/periodic/poweron/sensor/ext_power, "unknown"=unknown
-                thingHandler.updateWakeupReason(s.valueArray);
+                if (s.valueArray.size() > 0) {
+                    thingHandler.updateWakeupReason(s.valueArray);
+                    lastWakeup = (String) s.valueArray.get(0);
+                }
                 break;
             case "9103": // EVC, cfgChanged, U16
                 if ((lastCfgCount != -1) && (lastCfgCount != s.value)) {
@@ -292,7 +331,19 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
     }
 
     @Override
-    public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
-        return sen;
+    public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
+        return super.fixDescription(sen, blkMap);
+    }
+
+    private static final String ID_4101_DESCR = "{ \"I\":4101, \"T\":\"P\", \"D\":\"power\",  \"U\": \"W\",    \"R\":\"0/3500\", \"L\": 1}";
+    private static final String ID_4103_DESCR = "{ \"I\":4103, \"T\":\"E\", \"D\":\"energy\", \"U\": \"Wmin\", \"R\":\"U32\", \"L\": 1}";
+
+    @Override
+    public void completeMissingSensorDefinition(Map<String, CoIotDescrSen> sensorMap) {
+        if (profile.isDuo && profile.inColor) {
+            addSensor(sensorMap, "4101", ID_4101_DESCR);
+            addSensor(sensorMap, "4103", ID_4103_DESCR);
+        }
+        super.completeMissingSensorDefinition(sensorMap);
     }
 }
index d2ece5034ec12e5b18c948a001936838910177ec..8b2db84103249ca00e67bdfa35e64a4385c25597 100644 (file)
@@ -16,8 +16,12 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
 import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.*;
 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
 
+import java.net.SocketException;
 import java.net.UnknownHostException;
-import java.util.*;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.eclipse.californium.core.CoapClient;
 import org.eclipse.californium.core.coap.CoAP.Code;
@@ -28,6 +32,7 @@ import org.eclipse.californium.core.coap.Option;
 import org.eclipse.californium.core.coap.OptionNumberRegistry;
 import org.eclipse.californium.core.coap.Request;
 import org.eclipse.californium.core.coap.Response;
+import org.eclipse.californium.core.network.Endpoint;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.shelly.internal.api.ShellyApiException;
@@ -41,6 +46,8 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter;
 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
+import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.State;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -79,18 +86,18 @@ public class ShellyCoapHandler implements ShellyCoapListener {
     private String lastPayload = "";
     private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
     private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>();
-    private final ShellyDeviceProfile profile;
+    private ShellyDeviceProfile profile;
 
     public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) {
         this.thingHandler = thingHandler;
         this.thingName = thingHandler.thingName;
+        this.profile = thingHandler.getProfile();
         this.coapServer = coapServer;
-        this.coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); // Default
+        this.coiot = new ShellyCoIoTVersion2(thingName, thingHandler, blkMap, sensorMap); // Default: V2
 
         gsonBuilder.registerTypeAdapter(CoIotDevDescription.class, new CoIotDevDescrTypeAdapter());
         gsonBuilder.registerTypeAdapter(CoIotGenericSensorList.class, new CoIotSensorTypeAdapter());
         gson = gsonBuilder.create();
-        profile = thingHandler.getProfile();
     }
 
     /**
@@ -104,6 +111,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         try {
             this.thingName = thingName;
             this.config = config;
+            this.profile = thingHandler.getProfile();
             if (isStarted()) {
                 logger.trace("{}: CoAP Listener was already started", thingName);
                 stop();
@@ -113,9 +121,21 @@ public class ShellyCoapHandler implements ShellyCoapListener {
             coapServer.start(config.localIp, this);
             statusClient = new CoapClient(completeUrl(config.deviceIp, COLOIT_URI_DEVSTATUS))
                     .setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint());
+            @Nullable
+            Endpoint endpoint = null;
+            if (statusClient != null) {
+                endpoint = statusClient.getEndpoint();
+            }
+            if ((endpoint == null) || !endpoint.isStarted()) {
+                logger.warn("{}: Unable to initialize CoAP access (network error)", thingName);
+                throw new ShellyApiException("Network initialization failed");
+            }
             discover();
+        } catch (SocketException e) {
+            logger.warn("{}: Unable to initialize CoAP access (socket exception) - {}", thingName, e.getMessage());
+            throw new ShellyApiException("Network error", e);
         } catch (UnknownHostException e) {
-            logger.debug("{}: CoAP Exception", thingName, e);
+            logger.info("{}: CoAP Exception (Unknown Host)", thingName, e);
             throw new ShellyApiException("Unknown Host: " + config.deviceIp, e);
         }
     }
@@ -142,7 +162,6 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         String payload = "";
         String devId = "";
         String uri = "";
-        // int validity = 0;
         int serial = -1;
         try {
             if (logger.isDebugEnabled()) {
@@ -208,8 +227,8 @@ public class ShellyCoapHandler implements ShellyCoapListener {
 
                 // The device changes the serial on every update, receiving a message with the same serial is a
                 // duplicate, excep for battery devices! Those reset the serial every time when they wake-up
-                if ((serial == lastSerial) && payload.equals(lastPayload)
-                        && (!profile.hasBattery || ((serial & 0xFF) != 0))) {
+                if ((serial == lastSerial) && payload.equals(lastPayload) && (!profile.hasBattery
+                        || coiot.getLastWakeup().equalsIgnoreCase("ext_power") || ((serial & 0xFF) != 0))) {
                     logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
                     return;
                 }
@@ -217,11 +236,16 @@ public class ShellyCoapHandler implements ShellyCoapListener {
                 // fixed malformed JSON :-(
                 payload = fixJSON(payload);
 
-                if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC) || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
-                    handleDeviceDescription(devId, payload);
-                } else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
-                        || (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
-                    handleStatusUpdate(devId, payload, serial);
+                try {
+                    if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC)
+                            || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
+                        handleDeviceDescription(devId, payload);
+                    } else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
+                            || (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
+                        handleStatusUpdate(devId, payload, serial);
+                    }
+                } catch (ShellyApiException e) {
+                    logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
                 }
             } else {
                 // error handling
@@ -249,14 +273,14 @@ public class ShellyCoapHandler implements ShellyCoapListener {
      * @param payload Device desciption in JSon format, example:
      *            {"blk":[{"I":0,"D":"Relay0"}],"sen":[{"I":112,"T":"Switch","R":"0/1","L":0}],"act":[{"I":211,"D":"Switch","L":0,"P":[{"I":2011,"D":"ToState","R":"0/1"}]}]}
      */
-    private void handleDeviceDescription(String devId, String payload) {
+    private void handleDeviceDescription(String devId, String payload) throws ShellyApiException {
         logger.debug("{}: CoIoT Device Description for {}: {}", thingName, devId, payload);
 
         try {
             boolean valid = true;
 
             // Decode Json
-            CoIotDevDescription descr = gson.fromJson(payload, CoIotDevDescription.class);
+            CoIotDevDescription descr = fromJson(gson, payload, CoIotDevDescription.class);
             for (int i = 0; i < descr.blk.size(); i++) {
                 CoIotDescrBlk blk = descr.blk.get(i);
                 logger.debug("{}:    id={}: {}", thingName, blk.id, blk.desc);
@@ -288,12 +312,13 @@ public class ShellyCoapHandler implements ShellyCoapListener {
                     valid &= addSensor(descr.sen.get(i));
                 }
             }
+            coiot.completeMissingSensorDefinition(sensorMap);
 
             if (!valid) {
                 logger.debug(
                         "{}: Incompatible device description detected for CoIoT version {} (id length mismatch), discarding!",
                         thingName, coiot.getVersion());
-                thingHandler.updateProperties(PROPERTY_COAP_DESCR, "");
+
                 discover();
                 return;
             }
@@ -319,6 +344,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         int vers = coiot.getVersion();
         if (((vers == COIOT_VERSION_1) && (sen.id.length() > 3))
                 || ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4))) {
+            logger.debug("{}: Invalid format for sensor defition detected, id={}", thingName, sen.id);
             return false;
         }
 
@@ -346,8 +372,9 @@ public class ShellyCoapHandler implements ShellyCoapListener {
      * @param serial Serial for this request. If this the the same as last serial
      *            the update was already sent and processed so this one gets
      *            ignored.
+     * @throws ShellyApiException
      */
-    private void handleStatusUpdate(String devId, String payload, int serial) {
+    private void handleStatusUpdate(String devId, String payload, int serial) throws ShellyApiException {
         logger.debug("{}: CoIoT Sensor data {} (serial={})", thingName, payload, serial);
         if (blkMap.isEmpty()) {
             // send discovery packet
@@ -367,7 +394,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         }
 
         // Parse Json,
-        CoIotGenericSensorList list = gson.fromJson(fixJSON(payload), CoIotGenericSensorList.class);
+        CoIotGenericSensorList list = fromJson(gson, fixJSON(payload), CoIotGenericSensorList.class);
         if (list.generic == null) {
             logger.debug("{}: Sensor list has invalid format! Payload: {}", devId, payload);
             return;
@@ -377,33 +404,29 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         Map<String, State> updates = new TreeMap<String, State>();
         logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size());
         int failed = 0;
+        ShellyColorUtils col = new ShellyColorUtils();
         for (int i = 0; i < sensorUpdates.size(); i++) {
             try {
                 CoIotSensor s = sensorUpdates.get(i);
-                if (!sensorMap.containsKey(s.id)) {
-                    logger.debug("{}: Invalid id in sensor description: {}, index {}", thingName, s.id, i);
-                    failed++;
+                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);
                 }
@@ -418,7 +441,8 @@ public class ShellyCoapHandler implements ShellyCoapListener {
         if (!updates.isEmpty()) {
             int updated = 0;
             for (Map.Entry<String, State> u : updates.entrySet()) {
-                updated += thingHandler.updateChannel(u.getKey(), u.getValue(), false) ? 1 : 0;
+                String key = u.getKey();
+                updated += thingHandler.updateChannel(key, u.getValue(), false) ? 1 : 0;
             }
             if (updated > 0) {
                 logger.debug("{}: {} channels updated from CoIoT status, serial={}", thingName, updated, serial);
@@ -428,6 +452,40 @@ public class ShellyCoapHandler implements ShellyCoapListener {
                 }
             }
 
+            if (profile.isLight && profile.inColor && col.isRgbValid()) {
+                // Update color picker from single values
+                if (col.isRgbValid()) {
+                    thingHandler.updateChannel(mkChannelId(CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_PICKER),
+                            col.toHSB(), false);
+                }
+            }
+
+            if ((profile.isRGBW2 && !profile.inColor) || profile.isRoller) {
+                // Aggregate Meter Data from different Coap updates
+                int i = 1;
+                double totalCurrent = 0.0;
+                double totalKWH = 0.0;
+                boolean updateMeter = false;
+                while (i <= thingHandler.getProfile().numMeters) {
+                    String meter = CHANNEL_GROUP_METER + i;
+                    double current = thingHandler.getChannelDouble(meter, CHANNEL_METER_CURRENTWATTS);
+                    double total = thingHandler.getChannelDouble(meter, CHANNEL_METER_TOTALKWH);
+                    logger.debug("{}: {}#{}={}, total={}", thingName, meter, CHANNEL_METER_CURRENTWATTS, current,
+                            totalCurrent);
+                    totalCurrent += current >= 0 ? current : 0;
+                    totalKWH += total >= 0 ? total : 0;
+                    updateMeter |= current >= 0 | total >= 0;
+                    i++;
+                }
+                logger.debug("{}: totalCurrent={}, totalKWH={}, update={}", thingName, totalCurrent, totalKWH,
+                        updateMeter);
+                if (updateMeter) {
+                    thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_METER_CURRENTWATTS,
+                            toQuantityType(totalCurrent, DIGITS_WATT, Units.WATT));
+                    thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_LAST_UPDATE, getTimestamp());
+                }
+            }
+
             // Old firmware release are lacking various status values, which are not updated using CoIoT.
             // In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most
             // of the values are available
index 35612611ab09a9bf0d59d738161f54e42667074a..352e928a93876bca6fe17a617cc6701e5641c226 100644 (file)
@@ -67,7 +67,7 @@ public class ShellyCoapJSonDTO {
         @SerializedName("I")
         String id; // ID
         @SerializedName("D")
-        String desc; // Description
+        String desc = ""; // Description
         @SerializedName("T")
         public String type; // Type
         @SerializedName("R")
index 1cfc49f4a96b49c7b722ca15288808104e89a4de..c1cc0399d8cebba91a5f749a07dbbb3a20bca1f2 100644 (file)
@@ -16,8 +16,10 @@ import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.COIOT_P
 
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.californium.core.CoapResource;
 import org.eclipse.californium.core.CoapServer;
@@ -32,7 +34,6 @@ import org.eclipse.californium.core.network.config.NetworkConfig;
 import org.eclipse.californium.elements.UdpMulticastConnector;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.util.ConcurrentHashSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,7 +50,7 @@ public class ShellyCoapServer {
     private CoapEndpoint statusEndpoint = new CoapEndpoint.Builder().build();
     private @Nullable UdpMulticastConnector statusConnector;
     private final CoapServer server = new CoapServer(NetworkConfig.getStandard(), COIOT_PORT);;
-    private final Set<ShellyCoapListener> coapListeners = new ConcurrentHashSet<>();
+    private final Set<ShellyCoapListener> coapListeners = ConcurrentHashMap.newKeySet();
 
     protected class ShellyStatusListener extends CoapResource {
         private ShellyCoapServer listener;
@@ -76,7 +77,8 @@ public class ShellyCoapServer {
         }
     }
 
-    public synchronized void start(String localIp, ShellyCoapListener listener) throws UnknownHostException {
+    public synchronized void start(String localIp, ShellyCoapListener listener)
+            throws UnknownHostException, SocketException {
         if (!started) {
             logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, COIOT_PORT);
             NetworkConfig nc = NetworkConfig.getStandard();
index 27e7570c89a81a7eede418b37a1163fc5cf0ee59..1d53b441e6095844d8187dffc4c98f09bd016cd1 100644 (file)
@@ -31,17 +31,16 @@ public class ShellyBindingConfiguration {
     // Binding Configuration Properties
     public static final String CONFIG_DEF_HTTP_USER = "defaultUserId";
     public static final String CONFIG_DEF_HTTP_PWD = "defaultPassword";
+    public static final String CONFIG_LOCAL_IP = "localIP";
     public static final String CONFIG_AUTOCOIOT = "autoCoIoT";
 
     public String defaultUserId = ""; // default for http basic user id
     public String defaultPassword = ""; // default for http basic auth password
+    public String localIP = ""; // default:use OH network config
     public boolean autoCoIoT = true;
 
     public void updateFromProperties(Map<String, Object> properties) {
         for (Map.Entry<String, Object> e : properties.entrySet()) {
-            if (e.getValue() == null) {
-                continue;
-            }
             switch (e.getKey()) {
                 case CONFIG_DEF_HTTP_USER:
                     defaultUserId = (String) e.getValue();
@@ -49,6 +48,9 @@ public class ShellyBindingConfiguration {
                 case CONFIG_DEF_HTTP_PWD:
                     defaultPassword = (String) e.getValue();
                     break;
+                case CONFIG_LOCAL_IP:
+                    localIP = (String) e.getValue();
+                    break;
                 case CONFIG_AUTOCOIOT:
                     autoCoIoT = (boolean) e.getValue();
                     break;
index d2626a01834ff368fc080e48260e37cc188767cd..6e57a2c8e29e0aabcd6310dfd7e00fac01722bd2 100755 (executable)
@@ -29,6 +29,9 @@ public class ShellyThingConfiguration {
     public int lowBattery = 15; // threshold for battery value
     public boolean brightnessAutoOn = true; // true: turn on device if brightness > 0 is set
 
+    public int favoriteUP = 0; // Roller position favorite when control channel receives ON, 0=none
+    public int favoriteDOWN = 0; // Roller position favorite when control channel receives ON, 0=none
+
     public boolean eventsButton = false; // true: register for Relay btn_xxx events
     public boolean eventsSwitch = true; // true: register for device out_xxx events
     public boolean eventsPush = true; // true: register for short/long push events
index 843150f4631907f3a4663c0c3c84ee61bb537e15..81bc24158285abaa37ff01c5e9aa5800acb125eb 100755 (executable)
@@ -33,12 +33,11 @@ import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
 import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.i18n.TranslationProvider;
 import org.openhab.core.io.net.http.HttpClientFactory;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
@@ -66,20 +65,13 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
     private final HttpClient httpClient;
     private final ConfigurationAdmin configurationAdmin;
 
-    /**
-     * OSGI Service Activation
-     *
-     * @param componentContext
-     * @param localeProvider
-     */
     @Activate
     public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
             @Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
-            @Reference TranslationProvider i18nProvider, ComponentContext componentContext) {
+            @Reference ShellyTranslationProvider translationProvider, ComponentContext componentContext) {
         logger.debug("Activating ShellyDiscovery service");
         this.configurationAdmin = configurationAdmin;
-        this.messages = new ShellyTranslationProvider(componentContext.getBundleContext().getBundle(), i18nProvider,
-                localeProvider);
+        this.messages = translationProvider;
         this.httpClient = httpClientFactory.getCommonHttpClient();
         bindingConfig.updateFromProperties(componentContext.getProperties());
     }
@@ -168,7 +160,6 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
                     thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
                 } else {
                     logger.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
-                    logger.debug("{}: Discovery failed", name, e);
                 }
             } catch (IllegalArgumentException e) { // maybe some format description was buggy
                 logger.debug("{}: Discovery failed!", name, e);
index 4e58cce2cd502c4dacbdabbbbe3f76ba60f8b0b8..67336c2b7e7c5e135e66d7b311cdc6b0302dc556 100644 (file)
@@ -34,6 +34,7 @@ public class ShellyThingCreator {
     static {
         // mapping by device type id
         THING_TYPE_MAPPING.put(SHELLYDT_1PM, THING_TYPE_SHELLY1PM_STR);
+        THING_TYPE_MAPPING.put(SHELLYDT_1L, THING_TYPE_SHELLY1L_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_1, THING_TYPE_SHELLY1_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_3EM, THING_TYPE_SHELLY3EM_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_EM, THING_TYPE_SHELLYEM_STR);
@@ -43,22 +44,27 @@ public class ShellyThingCreator {
         THING_TYPE_MAPPING.put(SHELLYDT_DW, THING_TYPE_SHELLYDOORWIN_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_DW2, THING_TYPE_SHELLYDOORWIN2_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_DUO, THING_TYPE_SHELLYDUO_STR);
+        THING_TYPE_MAPPING.put(SHELLYDT_DUORGBW, THING_TYPE_SHELLYDUORGBW_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_BULB, THING_TYPE_SHELLYBULB_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_VINTAGE, THING_TYPE_SHELLYVINTAGE_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_DIMMER, THING_TYPE_SHELLYDIMMER_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_DIMMER2, THING_TYPE_SHELLYDIMMER2_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_IX3, THING_TYPE_SHELLYIX3_STR);
         THING_TYPE_MAPPING.put(SHELLYDT_BUTTON1, THING_TYPE_SHELLYBUTTON1_STR);
+        THING_TYPE_MAPPING.put(SHELLYDT_UNI, THING_TYPE_SHELLYUNI_STR);
+        THING_TYPE_MAPPING.put(SHELLYDT_HT, THING_TYPE_SHELLYHT_STR);
 
         // mapping by thing type
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1_STR, THING_TYPE_SHELLY1_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1PM_STR, THING_TYPE_SHELLY1PM_STR);
+        THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1L_STR, THING_TYPE_SHELLY1L_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLY4PRO_STR, THING_TYPE_SHELLY4PRO_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER2_STR, THING_TYPE_SHELLYDIMMER2_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER_STR, THING_TYPE_SHELLYDIMMER_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYIX3_STR, THING_TYPE_SHELLYIX3_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLY3EM_STR, THING_TYPE_SHELLY3EM_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEM_STR, THING_TYPE_SHELLYEM_STR);
+        THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUORGBW_STR, THING_TYPE_SHELLYDUORGBW_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYVINTAGE_STR, THING_TYPE_SHELLYVINTAGE_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBULB_STR, THING_TYPE_SHELLYBULB_STR);
@@ -72,6 +78,7 @@ public class ShellyThingCreator {
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSENSE_STR, THING_TYPE_SHELLYSENSE_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEYE_STR, THING_TYPE_SHELLYEYE_STR);
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR);
+        THING_TYPE_MAPPING.put(THING_TYPE_SHELLYUNI_STR, THING_TYPE_SHELLYUNI_STR);
 
         THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR);
     }
@@ -97,7 +104,7 @@ public class ShellyThingCreator {
         String name = hostname.toLowerCase();
         String type = substringBefore(name, "-").toLowerCase();
         String devid = substringAfterLast(name, "-");
-        if ((devid == null) || (type == null)) {
+        if (devid.isEmpty() || type.isEmpty()) {
             throw new IllegalArgumentException("Invalid device name format: " + hostname);
         }
 
@@ -125,12 +132,15 @@ public class ShellyThingCreator {
 
         // Check general mapping
         if (!deviceType.isEmpty()) {
-            String str = THING_TYPE_MAPPING.get(deviceType);
-            if (str != null) {
-                return str;
+            String res = THING_TYPE_MAPPING.get(deviceType);
+            if (res != null) {
+                return res;
             }
         }
-
-        return THING_TYPE_MAPPING.getOrDefault(type, THING_TYPE_SHELLYUNKNOWN_STR);
+        String res = THING_TYPE_MAPPING.get(type);
+        if (res != null) {
+            return res;
+        }
+        return THING_TYPE_SHELLYUNKNOWN_STR;
     }
 }
index 62be437c9bb05ae3bb5691f91788fdbfdf53835d..ef9983c38a68db10333158e9fbfa33d1ecf9a115 100755 (executable)
@@ -26,7 +26,6 @@ import java.util.TreeMap;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.commons.lang.time.StopWatch;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.HttpClient;
@@ -43,11 +42,14 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
 import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
+import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
 import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
+import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
@@ -72,7 +74,7 @@ import org.slf4j.LoggerFactory;
 @NonNullByDefault
 public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener {
     protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
-    protected final ShellyChannelDefinitionsDTO channelDefinitions;
+    protected final ShellyChannelDefinitions channelDefinitions;
 
     public String thingName = "";
     public String thingType = "";
@@ -84,14 +86,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
     private final ShellyCoapHandler coap;
     public boolean autoCoIoT = false;
 
-    private final ShellyTranslationProvider messages;
+    public final ShellyTranslationProvider messages;
     protected boolean stopping = false;
     private boolean channelsCreated = false;
 
     private long lastUptime = 0;
     private long lastAlarmTs = 0;
     private long lastTimeoutErros = -1;
-    private final StopWatch watchdog = new StopWatch();
+    private long watchdog = now();
 
     private @Nullable ScheduledFuture<?> statusJob;
     public int scheduledUpdates = 0;
@@ -127,7 +129,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
 
         this.messages = translationProvider;
         this.cache = new ShellyChannelCache(this);
-        this.channelDefinitions = new ShellyChannelDefinitionsDTO(messages);
+        this.channelDefinitions = new ShellyChannelDefinitions(messages);
         this.bindingConfig = bindingConfig;
 
         this.localIP = localIP;
@@ -154,7 +156,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
                         "{}: Configured Events: Button: {}, Switch (on/off): {}, Push: {}, Roller: {}, Sensor: {}, CoIoT: {}, Enable AutoCoIoT: {}",
                         thingName, config.eventsButton, config.eventsSwitch, config.eventsPush, config.eventsRoller,
                         config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT);
-                updateStatus(ThingStatus.UNKNOWN);
+                updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
+                        messages.get("status.unknown.initializing"));
                 start = initializeThing();
             } catch (ShellyApiException e) {
                 ShellyApiResult res = e.getApiResult();
@@ -253,6 +256,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
 
         // update thing properties
         ShellySettingsStatus status = api.getStatus();
+        tmpPrf.updateFromStatus(status);
         updateProperties(tmpPrf, status);
         checkVersion(tmpPrf, status);
         if (autoCoIoT) {
@@ -289,11 +293,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
     public void handleCommand(ChannelUID channelUID, Command command) {
         try {
             if (command instanceof RefreshType) {
+                String channelId = channelUID.getId();
+                State value = cache.getValue(channelId);
+                if (value != UnDefType.NULL) {
+                    updateState(channelId, value);
+                }
                 return;
             }
 
             if (!profile.isInitialized()) {
-                logger.debug("{}: {}", thingName, messages.get("message.command.init", command));
+                logger.debug("{}: {}", thingName, messages.get("command.init", command));
                 initializeThing();
             } else {
                 profile = getProfile(false);
@@ -350,7 +359,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
 
             skipUpdate++;
             ThingStatus thingStatus = getThing().getStatus();
-
             if (refreshSettings || (scheduledUpdates > 0) || (skipUpdate % skipCount == 0)) {
                 if (!profile.isInitialized() || ((thingStatus == ThingStatus.OFFLINE))
                         || (thingStatus == ThingStatus.UNKNOWN)) {
@@ -362,6 +370,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
 
                 logger.trace("{}: Updating status", thingName);
                 ShellySettingsStatus status = api.getStatus();
+                profile.updateFromStatus(status);
 
                 // If status update was successful the thing must be online
                 setThingOnline();
@@ -370,14 +379,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
                 updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME, getStringType(profile.settings.name));
                 updated |= this.updateDeviceStatus(status);
                 updated |= ShellyComponents.updateDeviceStatus(this, status);
-                // if (!channelsCreated || !cache.isEnabled() || (coap.getVersion() <
-                // ShellyCoapJSonDTO.COIOT_VERSION_2)) {
+                updated |= updateInputs(status);
                 updated |= updateMeters(this, status);
                 updated |= updateSensors(this, status);
-                updated |= updateInputs(status);
-                // } else {
-                // logger.debug("Skipping Meter/Sensor/Input updates, because device is running CoIoT version 2");
-                // }
 
                 // All channels must be created after the first cycle
                 channelsCreated = true;
@@ -397,8 +401,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
             if (isWatchdogStarted()) {
                 if (!isWatchdogExpired()) {
                     logger.debug("{}: Ignore API Timeout, retry later", thingName);
-                } else if (profile.hasBattery) {
-                    logger.debug("{}: Ignore API Timeout for battery powered device", thingName);
                 } else {
                     logger.debug("{}: Watchdog expired after {}sec,", thingName, profile.updatePeriod);
                     if (isThingOnline()) {
@@ -410,6 +412,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
             } else if (e.isJSONException()) {
                 status = "offline.status-error-unexpected-api-result";
                 logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e);
+            } else if (res.isHttpTimeout()) {
+                // Watchdog not started, e.g. device in sleep mode
+                if (isThingOnline()) { // ignore when already offline
+                    status = "offline.status-error-watchdog";
+                }
             } else {
                 status = "offline.status-error-unexpected-api-result";
                 logger.debug("{}: Unexpected API result: {}", thingName, res.response, e);
@@ -454,30 +461,30 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
         if (!isThingOffline()) {
             logger.info("{}: Thing goes OFFLINE: {}", thingName, messages.get(messageKey));
             updateStatus(ThingStatus.OFFLINE, detail, "@text/" + messageKey);
-            watchdog.reset();
+            watchdog = 0;
             channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
         }
     }
 
     public synchronized void restartWatchdog() {
+        watchdog = now();
         updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT, getTimestamp());
-        watchdog.reset();
-        watchdog.start();
+        logger.trace("{}: Watchdog restarted (expires in {} sec)", thingName, profile.updatePeriod);
     }
 
     private boolean isWatchdogExpired() {
-        return watchdog.getTime() > profile.updatePeriod;
+        long timeout = profile.hasBattery ? profile.updatePeriod : profile.updatePeriod;
+        long delta = now() - watchdog;
+        if ((watchdog > 0) && (delta > timeout)) {
+            logger.trace("{}: Watchdog expired after {}sec (started={}, now={}", thingName, delta, watchdog, now());
+            return true;
+        }
+        return false;
     }
 
     private boolean isWatchdogStarted() {
-        try {
-            if (isThingOnline()) {
-                watchdog.getStartTime();
-            }
-            return true;
-        } catch (IllegalStateException e) {
-            return false;
-        }
+        logger.trace("{}: Watchdog is {}", thingName, watchdog > 0 ? "started" : "inactive");
+        return watchdog > 0;
     }
 
     public void reinitializeThing() {
@@ -494,8 +501,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
         ShellyComponents.updateDeviceStatus(this, status);
 
         if (api.isInitialized() && (lastTimeoutErros != api.getTimeoutErrors())) {
-            propertyUpdates.put(PROPERTY_STATS_TIMEOUTS, new Integer(api.getTimeoutErrors()).toString());
-            propertyUpdates.put(PROPERTY_STATS_TRECOVERED, new Integer(api.getTimeoutsRecovered()).toString());
+            propertyUpdates.put(PROPERTY_STATS_TIMEOUTS, String.valueOf(api.getTimeoutErrors()));
+            propertyUpdates.put(PROPERTY_STATS_TRECOVERED, String.valueOf(api.getTimeoutsRecovered()));
             lastTimeoutErros = api.getTimeoutErrors();
         }
 
@@ -585,8 +592,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
                     case SHELLY_EVENT_TRIPLE_SHORTPUSH:
                     case SHELLY_EVENT_LONGPUSH:
                         if (isButton) {
-                            triggerButton(group, mapButtonEvent(event));
-                            channel = CHANNEL_BUTTON_TRIGGER;
+                            triggerButton(group, idx, mapButtonEvent(event));
+                            channel = CHANNEL_BUTTON_TRIGGER + profile.getInputSuffix(idx);
                             payload = ShellyApiJsonDTO.mapButtonEvent(event);
                         } else {
                             logger.debug("{}: Relay button is not in memontary or detached mode, ignore SHORT/LONGPUSH",
@@ -705,7 +712,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
                 config.deviceIp = saddr;
             }
         } catch (UnknownHostException e) {
-            logger.debug("{}: Unable to resolehostname {}", thingName, config.deviceIp);
+            logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp);
         }
 
         config.localIp = localIP;
@@ -722,7 +729,20 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
             config.updateInterval = UPDATE_MIN_DELAY;
         }
 
+        // Try to get updatePeriod from properties
+        // For battery devinities the REST call to get the settings will most likely fail, because the device is in
+        // sleep mode. Therefore we use the last saved property value as default. Will be overwritten, when device is
+        // initialized successfully by the REST call.
+        String lastPeriod = getString(properties.get(PROPERTY_UPDATE_PERIOD));
+        if (!lastPeriod.isEmpty()) {
+            int period = Integer.parseInt(lastPeriod);
+            if (period > 0) {
+                profile.updatePeriod = period;
+            }
+        }
+
         skipCount = config.updateInterval / UPDATE_STATUS_INTERVAL_SECONDS;
+        logger.trace("{}: updateInterval = {}s -> skipCount = {}", thingName, config.updateInterval, skipCount);
     }
 
     private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) {
@@ -743,13 +763,13 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
                 }
                 autoCoIoT = true;
             }
+            if (status.update.hasUpdate && !version.checkBeta(getString(prf.fwVersion))) {
+                logger.info("{}: {}", thingName,
+                        messages.get("versioncheck.update", status.update.oldVersion, status.update.newVersion));
+            }
         } catch (NullPointerException e) { // could be inconsistant format of beta version
             logger.debug("{}: {}", thingName, messages.get("versioncheck.failed", prf.fwVersion));
         }
-        if (status.update.hasUpdate) {
-            logger.info("{}: {}", thingName,
-                    messages.get("versioncheck.update", status.update.oldVersion, status.update.newVersion));
-        }
     }
 
     /**
@@ -841,57 +861,34 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
      * @param status Shelly device status
      * @return true: one or more inputs were updated
      */
-    public boolean updateInputs(String groupName, ShellySettingsStatus status, int index) {
-        if ((status.input == null)) {
-            return false;
-        }
-
-        boolean updated = false;
-        if (index == 0) {
-            // RGBW2: a single int rather than an array
-            updated |= updateChannel(groupName, CHANNEL_INPUT,
-                    getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
-        } else {
-            if (profile.isDimmer || profile.isRoller) {
-                ShellyInputState state1 = status.inputs.get(0);
-                ShellyInputState state2 = status.inputs.get(1);
-                logger.trace("{}: Updating {}#input1 with {}, input2 with {}", thingName, groupName,
-                        getOnOff(state1.input), getOnOff(state2.input));
-                updated |= updateChannel(groupName, CHANNEL_INPUT + "1", getOnOff(state1.input));
-                updated |= updateChannel(groupName, CHANNEL_INPUT + "2", getOnOff(state2.input));
-            } else {
-                if (index < status.inputs.size()) {
-                    ShellyInputState state = status.inputs.get(index);
-                    updated |= updateChannel(groupName, CHANNEL_INPUT, getOnOff(state.input));
-                } else {
-                    logger.debug("{}: Unable to update input, index is out of range ({}/{}", thingName, index,
-                            status.inputs.size());
-                }
-            }
-        }
-        return updated;
-    }
-
     public boolean updateInputs(ShellySettingsStatus status) {
         boolean updated = false;
-        String groupName = "";
 
-        if (status.input != null) {
-            // RGBW2: a single int rather than an array
-            return updateChannel(groupName, CHANNEL_INPUT,
-                    getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
-        }
         if (status.inputs != null) {
             int idx = 0;
+            boolean multiInput = status.inputs.size() >= 2; // device has multiple SW (inputs)
             for (ShellyInputState input : status.inputs) {
                 String group = profile.getControlGroup(idx);
-                updated |= updateChannel(group, CHANNEL_INPUT, getOnOff(input.input));
+                String suffix = multiInput ? profile.getInputSuffix(idx) : "";
+
+                if (!areChannelsCreated()) {
+                    updateChannelDefinitions(
+                            ShellyChannelDefinitions.createInputChannels(thing, profile, status, group));
+                }
+
+                updated |= updateChannel(group, CHANNEL_INPUT + suffix, getOnOff(input.input));
                 if (input.event != null) {
-                    updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE, getStringType(input.event));
-                    updated |= updateChannel(group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(input.eventCount));
+                    updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
+                    updated |= updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + suffix, getDecimal(input.eventCount));
                 }
                 idx++;
             }
+        } else {
+            if (status.input != null) {
+                // RGBW2: a single int rather than an array
+                return updateChannel(profile.getControlGroup(0), CHANNEL_INPUT,
+                        getInteger(status.input) == 0 ? OnOffType.OFF : OnOffType.ON);
+            }
         }
         return updated;
     }
@@ -911,14 +908,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
         return changed;
     }
 
-    public void triggerButton(String group, String value) {
+    public void triggerButton(String group, int idx, String value) {
         String trigger = mapButtonEvent(value);
         if (trigger.isEmpty()) {
             return;
         }
 
         logger.debug("{}: Update button state with {}/{}", thingName, value, trigger);
-        triggerChannel(group, CHANNEL_BUTTON_TRIGGER, trigger);
+        triggerChannel(group,
+                profile.isRoller ? CHANNEL_EVENT_TRIGGER : CHANNEL_BUTTON_TRIGGER + profile.getInputSuffix(idx),
+                trigger);
         updateChannel(group, CHANNEL_LAST_UPDATE, getTimestamp());
         if (!profile.hasBattery) {
             // refresh status of the input channel
@@ -927,8 +926,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
     }
 
     public void publishState(String channelId, State value) {
-        if (!stopping) {
-            updateState(channelId.contains("$") ? substringBefore(channelId, "$") : channelId, value);
+        String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId;
+        if (!stopping && isLinked(id)) {
+            updateState(id, value);
         }
     }
 
@@ -937,14 +937,26 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
     }
 
     public boolean updateChannel(String channelId, State value, boolean force) {
-        return !stopping && (channelId.contains("$") || isLinked(channelId))
-                && cache.updateChannel(channelId, value, force);
+        return !stopping && cache.updateChannel(channelId, value, force);
     }
 
     public State getChannelValue(String group, String channel) {
         return cache.getValue(group, channel);
     }
 
+    public double getChannelDouble(String group, String channel) {
+        State value = getChannelValue(group, channel);
+        if (value != UnDefType.NULL) {
+            if (value instanceof QuantityType) {
+                return ((QuantityType<?>) value).toBigDecimal().doubleValue();
+            }
+            if (value instanceof DecimalType) {
+                return ((DecimalType) value).doubleValue();
+            }
+        }
+        return -1;
+    }
+
     /**
      * Update Thing's channels according to available status information from the API
      *
@@ -1078,14 +1090,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
             properties.put(PROPERTY_NUM_RELAYS, String.valueOf(profile.numRelays));
             properties.put(PROPERTY_NUM_ROLLERS, String.valueOf(profile.numRollers));
             properties.put(PROPERTY_NUM_METER, String.valueOf(profile.numMeters));
+            properties.put(PROPERTY_UPDATE_PERIOD, String.valueOf(profile.updatePeriod));
             if (!profile.hwRev.isEmpty()) {
                 properties.put(PROPERTY_HWREV, profile.hwRev);
                 properties.put(PROPERTY_HWBATCH, profile.hwBatchId);
             }
-
-            if (profile.updatePeriod >= 0) {
-                properties.put(PROPERTY_UPDATE_PERIOD, String.valueOf(profile.updatePeriod));
-            }
         }
         return properties;
     }
diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyChannelDefinitionsDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyChannelDefinitionsDTO.java
deleted file mode 100644 (file)
index e49f7dc..0000000
+++ /dev/null
@@ -1,393 +0,0 @@
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.shelly.internal.handler;
-
-import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
-import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
-import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
-import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.binding.builder.ChannelBuilder;
-import org.openhab.core.thing.type.ChannelTypeUID;
-
-/**
- * The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
- * added on the first thing status update
- *
- * @author Markus Michels - Initial contribution
- */
-@NonNullByDefault
-public class ShellyChannelDefinitionsDTO {
-
-    private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
-
-    // shortcuts to avoid line breaks (make code more readable)
-    private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
-    private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
-    private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
-    private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
-    private static final String CHGR_METER = CHANNEL_GROUP_METER;
-    private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
-    private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
-
-    public static final String ITEM_TYPE_NUMBER = "Number";
-    public static final String ITEM_TYPE_STRING = "String";
-    public static final String ITEM_TYPE_SWITCH = "Switch";
-    public static final String ITEM_TYPE_CONTACT = "Contact";
-    public static final String ITEM_TYPE_DATETIME = "DateTime";
-    public static final String ITEM_TYPE_TEMP = "Number:Temperature";
-    public static final String ITEM_TYPE_LUX = "Number:Illuminance";
-    public static final String ITEM_TYPE_POWER = "Number:Power";
-    public static final String ITEM_TYPE_ENERGY = "Number:Energy";
-    public static final String ITEM_TYPE_VOLT = "Number:ElectricPotential";
-    public static final String ITEM_TYPE_AMP = "Number:ElectricPotential";
-    public static final String ITEM_TYPE_PERCENT = "Number:Dimensionless";
-    public static final String ITEM_TYPE_ANGLE = "Number:Angle";
-
-    public static final String PREFIX_GROUP = "definitions.shelly.group.";
-    public static final String PREFIX_CHANNEL = "channel-type.shelly.";
-    public static final String SUFFIX_LABEL = ".label";
-    public static final String SUFFIX_DESCR = ".description";
-
-    public ShellyChannelDefinitionsDTO(ShellyTranslationProvider m) {
-        // Device: Internal Temp
-        CHANNEL_DEFINITIONS
-                // Device
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEM_TYPE_TEMP))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEM_TYPE_POWER))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEM_TYPE_POWER))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEM_TYPE_POWER))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEM_TYPE_NUMBER))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEM_TYPE_DATETIME))
-                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEM_TYPE_SWITCH))
-
-                // Relay
-                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEM_TYPE_STRING))
-
-                // Roller
-                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEM_TYPE_STRING))
-
-                // RGBW2
-                .add(new ShellyChannel(m, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
-
-                // Power Meter
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEM_TYPE_POWER))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEM_TYPE_ENERGY))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEM_TYPE_ENERGY))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
-
-                // EMeter
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEM_TYPE_ENERGY))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEM_TYPE_POWER))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEM_TYPE_VOLT))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEM_TYPE_AMP))
-                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEM_TYPE_NUMBER))
-
-                // Sensors
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEM_TYPE_TEMP))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEM_TYPE_PERCENT))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEM_TYPE_LUX))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEM_TYPE_CONTACT))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEM_TYPE_ANGLE))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEM_TYPE_NUMBER))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
-
-                // Button/ix3
-                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
-                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "eventType", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEM_TYPE_NUMBER))
-                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system.button", ITEM_TYPE_STRING))
-                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
-
-                // Addon with external sensors
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEM_TYPE_TEMP))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEM_TYPE_TEMP))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEM_TYPE_TEMP))
-                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEM_TYPE_PERCENT))
-
-                // Battery
-                .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level",
-                        ITEM_TYPE_PERCENT))
-                .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEM_TYPE_SWITCH));
-    }
-
-    public static ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
-        String group = substringBefore(channelName, "#");
-        String channel = substringAfter(channelName, "#");
-        if (group.contains(CHANNEL_GROUP_METER)) {
-            group = CHANNEL_GROUP_METER; // map meter1..n to meter
-        } else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
-            group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
-        } else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
-            group = CHANNEL_GROUP_LIGHT_CHANNEL;
-        } else if (group.contains(CHANNEL_GROUP_STATUS)) {
-            group = CHANNEL_GROUP_STATUS; // map status1..n to meter
-        }
-        String channelId = group + "#" + channel;
-        return CHANNEL_DEFINITIONS.get(channelId);
-    }
-
-    /**
-     * Auto-create relay channels depending on relay type/mode
-     *
-     * @return ArrayList<Channel> of channels to be added to the thing
-     */
-    public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
-            final ShellySettingsStatus status) {
-        Map<String, Channel> add = new LinkedHashMap<>();
-
-        addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
-
-        if (!profile.isSensor) {
-            // Only some devices report the internal device temp
-            addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
-                    CHANNEL_DEVST_ITEMP);
-        }
-
-        // RGBW2
-        addChannel(thing, add, status.input != null, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT);
-
-        // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
-        boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
-                && !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
-        addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
-        addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
-        addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
-        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
-        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
-        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
-
-        if (profile.settings.ledPowerDisable != null) {
-            addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
-        }
-        if (profile.settings.ledStatusDisable != null) {
-            addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
-        }
-        return add;
-    }
-
-    /**
-     * Auto-create relay channels depending on relay type/mode
-     *
-     * @return ArrayList<Channel> of channels to be added to the thing
-     */
-    public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
-            final ShellyStatusRelay relay, int idx) {
-        Map<String, Channel> add = new LinkedHashMap<>();
-        String group = profile.getControlGroup(idx);
-
-        ShellySettingsRelay rs = profile.settings.relays.get(idx);
-        addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
-
-        // Shelly 1/1PM Addon
-        if (relay.extTemperature != null) {
-            addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
-            addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
-            addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
-        }
-        if (relay.extHumidity != null) {
-            addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
-        }
-
-        return add;
-    }
-
-    public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
-        Map<String, Channel> add = new LinkedHashMap<>();
-        addChannel(thing, add, roller.state != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
-        return add;
-    }
-
-    public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
-        Map<String, Channel> newChannels = new LinkedHashMap<>();
-        addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
-        addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
-        if (meter.counters != null) {
-            addChannel(thing, newChannels, meter.counters[0] != null, group, CHANNEL_METER_LASTMIN1);
-        }
-        addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
-        return newChannels;
-    }
-
-    public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
-            String group) {
-        Map<String, Channel> newChannels = new LinkedHashMap<>();
-        addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
-        addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
-        addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
-        addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
-        addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
-        addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
-        addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
-        addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
-        return newChannels;
-    }
-
-    public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
-            final ShellyStatusSensor sdata) {
-        Map<String, Channel> newChannels = new LinkedHashMap<>();
-
-        // Sensor data
-        addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
-        addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
-        addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
-        if (sdata.accel != null) {
-            addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
-                    CHANNEL_SENSOR_VIBRATION);
-            addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
-        }
-        addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
-        addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
-        addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
-                CHANNEL_SENSOR_ILLUM);
-        addChannel(thing, newChannels, sdata.contact != null && sdata.contact.state != null, CHANNEL_GROUP_SENSOR,
-                CHANNEL_SENSOR_CONTACT);
-        addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
-        addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
-        addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
-        addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
-
-        // Gas
-        if (sdata.gasSensor != null) {
-            addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
-            addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
-                    CHANNEL_SENSOR_SSTATE);
-            addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
-                    CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
-            addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
-            addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
-                    CHANNEL_SENSOR_ALARM_STATE);
-        }
-
-        // Battery
-        if (sdata.bat != null) {
-            addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
-            addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
-        }
-
-        addChannel(thing, newChannels, true, profile.getControlGroup(0), CHANNEL_LAST_UPDATE);
-        return newChannels;
-    }
-
-    private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
-            String channelName) throws IllegalArgumentException {
-        if (supported) {
-            final String channelId = group + "#" + channelName;
-            final ShellyChannel channelDef = getDefinition(channelId);
-            final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
-            final ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
-                    ? new ChannelTypeUID(channelDef.typeId)
-                    : new ChannelTypeUID(BINDING_ID, channelDef.typeId);
-            Channel channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
-            newChannels.put(channelId, channel);
-        }
-    }
-
-    public class ShellyChannel {
-        private final ShellyTranslationProvider messages;
-        public String group = "";
-        public String groupLabel = "";
-        public String groupDescription = "";
-
-        public String channel = "";
-        public String label = "";
-        public String description = "";
-        public String itemType = "";
-        public String typeId = "";
-        public String category = "";
-        public Set<String> tags = new HashSet<>();
-
-        public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
-                String itemType, String... category) {
-            this.messages = messages;
-            this.group = group;
-            this.channel = channel;
-            this.itemType = itemType;
-            this.typeId = typeId;
-
-            groupLabel = getText(PREFIX_GROUP + group + SUFFIX_LABEL);
-            groupDescription = getText(PREFIX_GROUP + group + SUFFIX_DESCR);
-            label = getText(PREFIX_CHANNEL + channel + SUFFIX_LABEL);
-            description = getText(PREFIX_CHANNEL + channel + SUFFIX_DESCR);
-        }
-
-        public String getChanneId() {
-            return group + "#" + channel;
-        }
-
-        private String getText(String key) {
-            String text = messages.get(key);
-            return text != null ? text : "";
-        }
-    }
-
-    public static class ChannelMap {
-        private final Map<String, ShellyChannel> map = new LinkedHashMap<>();
-
-        private ChannelMap add(ShellyChannel def) {
-            map.put(def.getChanneId(), def);
-            return this;
-        }
-
-        public ShellyChannel get(String channelName) throws IllegalArgumentException {
-            ShellyChannel def = null;
-            if (channelName.contains("#")) {
-                def = map.get(channelName);
-            }
-            for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
-                if (entry.getValue().channel.contains("#" + channelName)) {
-                    def = entry.getValue();
-                    break;
-                }
-            }
-
-            if (def == null) {
-                throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
-            }
-
-            return def;
-        }
-    }
-}
index 5fc9fc89039b3832af086e952f3832324c269cbf..54efbe09af659c603d4b3c5ef55bd06d8c516c11 100644 (file)
@@ -65,16 +65,16 @@ public class ShellyColorUtils {
         setTemp(col.temp);
     }
 
-    void setMode(String mode) {
+    public void setMode(String mode) {
         this.mode = mode;
     }
 
-    void setMinMaxTemp(int min, int max) {
+    public void setMinMaxTemp(int min, int max) {
         minTemp = min;
         maxTemp = max;
     }
 
-    boolean setRGBW(int red, int green, int blue, int white) {
+    public boolean setRGBW(int red, int green, int blue, int white) {
         setRed(red);
         setGreen(green);
         setBlue(blue);
@@ -82,56 +82,56 @@ public class ShellyColorUtils {
         return true;
     }
 
-    boolean setRed(int value) {
+    public boolean setRed(int value) {
         boolean changed = red != value;
         red = value;
         percentRed = toPercent(red);
         return changed;
     }
 
-    boolean setGreen(int value) {
+    public boolean setGreen(int value) {
         boolean changed = green != value;
         green = value;
         percentGreen = toPercent(green);
         return changed;
     }
 
-    boolean setBlue(int value) {
+    public boolean setBlue(int value) {
         boolean changed = blue != value;
         blue = value;
         percentBlue = toPercent(blue);
         return changed;
     }
 
-    boolean setWhite(int value) {
+    public boolean setWhite(int value) {
         boolean changed = white != value;
         white = value;
         percentWhite = toPercent(white);
         return changed;
     }
 
-    boolean setBrightness(int value) {
+    public boolean setBrightness(int value) {
         boolean changed = brightness != value;
         brightness = value;
         percentBrightness = toPercent(brightness, SHELLY_MIN_BRIGHTNESS, SHELLY_MAX_BRIGHTNESS);
         return changed;
     }
 
-    boolean setGain(int value) {
+    public boolean setGain(int value) {
         boolean changed = gain != value;
         gain = value;
         percentGain = toPercent(gain, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN);
         return changed;
     }
 
-    boolean setTemp(int value) {
+    public boolean setTemp(int value) {
         boolean changed = temp != value;
         temp = value;
         percentTemp = toPercent(temp, minTemp, maxTemp);
         return changed;
     }
 
-    boolean setEffect(int value) {
+    public boolean setEffect(int value) {
         boolean changed = effect != value;
         effect = value;
         return changed;
@@ -168,18 +168,22 @@ public class ShellyColorUtils {
         return values;
     }
 
+    public boolean isRgbValid() {
+        return (red != -1) && (blue != -1) && (green != -1);
+    }
+
     public static PercentType toPercent(Integer value) {
         return toPercent(value, 0, SHELLY_MAX_COLOR);
     }
 
     public static PercentType toPercent(Integer _value, Integer min, Integer max) {
-        Double range = max.doubleValue() - min.doubleValue();
-        Double value = _value.doubleValue();
+        double range = max.doubleValue() - min.doubleValue();
+        double value = _value.doubleValue();
         value = value < min ? min.doubleValue() : value;
         value = value > max ? max.doubleValue() : value;
-        Double percent = 0.0;
+        double percent = 0.0;
         if (range > 0) {
-            percent = new Double(Math.round((value - min) / range * 100));
+            percent = Math.round((value - min) / range * 100);
         }
         return new PercentType(new BigDecimal(percent));
     }
index c4945dead9501dbdc7dccd6e956ae7911300b6dd..e1b89986754d89fc3b431fa97d5dd03a25319e7d 100644 (file)
@@ -16,15 +16,15 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
 import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
 
-import java.io.IOException;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.shelly.internal.api.ShellyApiException;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor.ShellyADC;
 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
+import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
 import org.openhab.core.library.unit.ImperialUnits;
@@ -48,13 +48,13 @@ public class ShellyComponents {
      */
     public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
         if (!thingHandler.areChannelsCreated()) {
-            thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
-                    .createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status));
+            thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
+                    thingHandler.getProfile(), status));
         }
 
         Integer rssi = getInteger(status.wifiSta.rssi);
         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
-                toQuantityType(new Double(getLong(status.uptime)), DIGITS_NONE, Units.SECOND));
+                toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND));
         thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
         if (status.tmp != null) {
             thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
@@ -109,7 +109,7 @@ public class ShellyComponents {
                             if (!thingHandler.areChannelsCreated()) {
                                 // skip for Shelly Bulb: JSON has a meter, but values don't get updated
                                 if (!profile.isBulb) {
-                                    thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
+                                    thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
                                             .createMeterChannels(thingHandler.getThing(), meter, groupName));
                                 }
                             }
@@ -141,7 +141,7 @@ public class ShellyComponents {
                             String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString()
                                     : CHANNEL_GROUP_METER;
                             if (!thingHandler.areChannelsCreated()) {
-                                thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
+                                thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
                                         .createEMeterChannels(thingHandler.getThing(), emeter, groupName));
                             }
 
@@ -197,7 +197,7 @@ public class ShellyComponents {
                 }
                 // Create channels for 1 Meter
                 if (!thingHandler.areChannelsCreated()) {
-                    thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
+                    thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
                             .createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
                 }
 
@@ -237,7 +237,7 @@ public class ShellyComponents {
      * @param profile ShellyDeviceProfile
      * @param status Last ShellySettingsStatus
      *
-     * @throws IOException
+     * @throws ShellyApiException
      */
     public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
             throws ShellyApiException {
@@ -250,20 +250,20 @@ public class ShellyComponents {
             if (!thingHandler.areChannelsCreated()) {
                 thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
                 thingHandler.updateChannelDefinitions(
-                        ShellyChannelDefinitionsDTO.createSensorChannels(thingHandler.getThing(), profile, sdata));
+                        ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata));
             }
 
             updated |= thingHandler.updateWakeupReason(sdata.actReasons);
 
-            if ((sdata.contact != null) && sdata.contact.isValid) {
+            if ((sdata.sensor != null) && sdata.sensor.isValid) {
                 // Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
-                        getString(sdata.contact.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
+                        getString(sdata.sensor.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
                                 : OpenClosedType.CLOSED);
                 boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
                         getStringType(sdata.sensorError));
                 if (changed) {
-                    thingHandler.postEvent(sdata.sensorError, true);
+                    thingHandler.postEvent(getString(sdata.sensorError), true);
                 }
                 updated |= changed;
             }
@@ -320,7 +320,11 @@ public class ShellyComponents {
                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
                         getDecimal(sdata.concentration.ppm));
             }
-
+            if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) {
+                ShellyADC adc = sdata.adcs.get(0);
+                updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE,
+                        getDecimal(adc.voltage));
+            }
             if (sdata.bat != null) { // no update for Sense
                 thingHandler.logger.trace("{}: Updating battery", thingHandler.thingName);
                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
@@ -332,10 +336,18 @@ public class ShellyComponents {
                     thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
                 }
             }
-            if (sdata.motion != null) {
+            if (sdata.motion != null) { // Shelly Sense
                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
                         getOnOff(sdata.motion));
             }
+            if (sdata.sensor != null) { // Shelly Motion
+                updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
+                        getOnOff(sdata.sensor.motion));
+                updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
+                        getTimestamp(getString(profile.settings.timezone), sdata.sensor.motionTimestamp));
+                updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
+                        getOnOff(sdata.sensor.vibration));
+            }
             if (sdata.charger != null) {
                 updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
                         getOnOff(sdata.charger));
index 3d75df99b6cb283330e848dbf89e5d984334eb33..ebc220e65023968e3407e8c5af3e3b2ace7261d8 100644 (file)
@@ -22,6 +22,7 @@ import java.util.TreeMap;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.shelly.internal.api.ShellyApiException;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
@@ -29,7 +30,8 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLigh
 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
+import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.HSBType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
@@ -146,9 +148,8 @@ public class ShellyLightHandler extends ShellyBaseHandler {
                         col.power = getOnOff(light.ison);
                         col.setBrightness(light.brightness);
                         updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", col.power);
-                        updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
-                                toQuantityType(new Double(col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE,
-                                        Units.PERCENT));
+                        updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
+                                (double) (col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE, Units.PERCENT));
                         update = false;
                         break;
                     }
@@ -264,17 +265,15 @@ public class ShellyLightHandler extends ShellyBaseHandler {
                 logger.debug("{}: {} brightness by {}", thingName, command, SHELLY_DIM_STEPSIZE);
                 PercentType percent = (PercentType) super.getChannelValue(CHANNEL_GROUP_COLOR_CONTROL,
                         CHANNEL_BRIGHTNESS);
-                if (percent != null) {
-                    int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
-                    int newBrightness = currentBrightness;
-                    if (command == IncreaseDecreaseType.DECREASE) {
-                        newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
-                    } else {
-                        newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
-                    }
-                    col.brightness = newBrightness;
-                    updated = currentBrightness != newBrightness;
+                int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
+                int newBrightness = currentBrightness;
+                if (command == IncreaseDecreaseType.DECREASE) {
+                    newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
+                } else {
+                    newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
                 }
+                col.brightness = newBrightness;
+                updated = currentBrightness != newBrightness;
             }
         }
         return updated;
@@ -303,14 +302,13 @@ public class ShellyLightHandler extends ShellyBaseHandler {
     }
 
     private ShellyColorUtils getCurrentColors(int lightId) {
-        ShellyColorUtils col;
-        if (!channelColors.containsKey(lightId)) {
+        ShellyColorUtils col = channelColors.get(lightId);
+        if (col == null) {
             col = new ShellyColorUtils(); // create a new entry
             col.setMinMaxTemp(profile.minTemp, profile.maxTemp);
             channelColors.put(lightId, col);
             logger.trace("{}: Colors entry created for lightId {}", thingName, lightId);
         } else {
-            col = channelColors.get(lightId);
             logger.trace(
                     "{}: Colors loaded for lightId {}: power={}, RGBW={}/{}/{}/{}, gain={}, brightness={}, color temp={} (min={}, max={}",
                     thingName, lightId, col.power, col.red, col.green, col.blue, col.white, col.gain, col.brightness,
@@ -339,6 +337,7 @@ public class ShellyLightHandler extends ShellyBaseHandler {
         for (ShellyStatusLightChannel light : status.lights) {
             Integer channelId = lightId + 1;
             String controlGroup = buildControlGroupName(profile, channelId);
+            createLightChannels(light, lightId);
             // The bulb has a combined channel set for color or white mode
             // The RGBW2 uses 2 different thing types: color=1 channel, white=4 channel
             if (profile.isBulb) {
@@ -349,11 +348,12 @@ public class ShellyLightHandler extends ShellyBaseHandler {
             col.power = getOnOff(light.ison);
 
             // Channel control/timer
-            // ShellyStatusLightChannel light = status.lights.get(i);
+            ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId);
+            updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn));
+            updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff));
             updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
-            updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(light.autoOn));
-            updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(light.autoOff));
-            updated |= updateInputs(controlGroup, genericStatus, lightId);
+            updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer));
+
             if (getBool(light.overpower)) {
                 postEvent(ALARM_TYPE_OVERPOWER, false);
             }
@@ -386,8 +386,8 @@ public class ShellyLightHandler extends ShellyBaseHandler {
                 col.setBrightness(getInteger(light.brightness));
                 updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Switch", col.power);
                 updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Value",
-                        toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : new Double(0),
-                                DIGITS_NONE, Units.PERCENT));
+                        toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : 0, DIGITS_NONE,
+                                Units.PERCENT));
 
                 if ((profile.isBulb || profile.isDuo) && (light.temp != null)) {
                     col.setTemp(getInteger(light.temp));
@@ -403,14 +403,20 @@ public class ShellyLightHandler extends ShellyBaseHandler {
         return updated;
     }
 
+    private void createLightChannels(ShellyStatusLightChannel status, int idx) {
+        if (!areChannelsCreated()) {
+            updateChannelDefinitions(ShellyChannelDefinitions.createLightChannels(getThing(), profile, status, idx));
+        }
+    }
+
     private Integer setColor(Integer lightId, String colorName, Command command, Integer minValue, Integer maxValue)
             throws ShellyApiException, IllegalArgumentException {
         Integer value = -1;
         logger.debug("{}: Set {} to {} ({})", thingName, colorName, command, command.getClass());
         if (command instanceof PercentType) {
             PercentType percent = (PercentType) command;
-            Double v = new Double(maxValue) * percent.doubleValue() / 100.0;
-            value = v.intValue();
+            double v = (double) maxValue * percent.doubleValue() / 100.0;
+            value = (int) v;
             logger.debug("{}: Value for {} is in percent: {}%={}", thingName, colorName, percent, value);
         } else if (command instanceof DecimalType) {
             value = ((DecimalType) command).intValue();
@@ -499,14 +505,13 @@ public class ShellyLightHandler extends ShellyBaseHandler {
                 lightId, col.red, col.green, col.blue, col.white, col.gain, col.brightness, col.temp);
     }
 
-    private Integer getColorFromHSB(PercentType colorPercent) {
-        return getColorFromHSB(colorPercent, new Double(SATURATION_FACTOR));
+    private int getColorFromHSB(PercentType colorPercent) {
+        return getColorFromHSB(colorPercent, SATURATION_FACTOR);
     }
 
-    private Integer getColorFromHSB(PercentType colorPercent, Double factor) {
-        Double value = new Double(Math.round(colorPercent.doubleValue() * factor));
-        logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, value.intValue(),
-                factor);
-        return value.intValue();
+    private int getColorFromHSB(PercentType colorPercent, double factor) {
+        double value = Math.round(colorPercent.doubleValue() * factor);
+        logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, (int) value, factor);
+        return (int) value;
     }
 }
index b8dd9d918a8c179668db32e75a3d033d4b59957f..38880fd9a2cdba0068fd53558b3b36b68c023c6b 100644 (file)
@@ -16,10 +16,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.core.thing.Thing;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * The {@link ShellyProtectedHandler} implements a dummy handler for password protected devices.
@@ -28,8 +26,6 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class ShellyProtectedHandler extends ShellyBaseHandler {
-    private final Logger logger = LoggerFactory.getLogger(ShellyProtectedHandler.class);
-
     /**
      * Constructor
      *
index 152b48daf9bf1e0f9788f55d4b51d0143ef0926e..c011644730e1bcaeb934fbc7f5a0032d4d14e8cd 100644 (file)
@@ -30,7 +30,8 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortStatu
 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
-import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
+import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.IncreaseDecreaseType;
 import org.openhab.core.library.types.OnOffType;
@@ -118,6 +119,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                 requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
                 break;
 
+            case CHANNEL_ROL_CONTROL_FAV:
+                if (command instanceof Number) {
+                    int id = ((Number) command).intValue() - 1;
+                    int pos = profile.getRollerFav(id);
+                    if (pos > 0) {
+                        logger.debug("{}: Selecting favorite {}, position = {}", thingName, id, pos);
+                        api.setRollerPos(rIndex, pos);
+                        break;
+                    }
+                }
+                logger.debug("{}: Invalid favorite index: {}", thingName, command);
+                break;
+
             case CHANNEL_TIMER_AUTOON:
                 logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
                 api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command));
@@ -152,7 +166,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
             return;
         } else if (command instanceof IncreaseDecreaseType) {
             ShellyShortLightStatus light = api.getLightStatus(index);
-            if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
+            if (command == IncreaseDecreaseType.INCREASE) {
                 value = Math.min(light.brightness + DIM_STEPSIZE, 100);
             } else {
                 value = Math.max(light.brightness - DIM_STEPSIZE, 0);
@@ -172,14 +186,16 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
     }
 
     private void updateBrightnessChannel(int lightId, OnOffType power, int brightness) throws ShellyApiException {
+        updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
         if (brightness > 0) {
             api.setBrightness(lightId, brightness, config.brightnessAutoOn);
         } else {
             api.setRelayTurn(lightId, power == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
+            if (brightness >= 0) { // ignore -1
+                updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
+                        toQuantityType((double) (power == OnOffType.ON ? brightness : 0), DIGITS_NONE, Units.PERCENT));
+            }
         }
-        updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
-        updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
-                toQuantityType(new Double(power == OnOffType.ON ? brightness : 0), DIGITS_NONE, Units.PERCENT));
     }
 
     @Override
@@ -209,10 +225,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
             ShellyControlRoller rstatus = api.getRollerStatus(index);
 
             if (!getString(rstatus.state).isEmpty() && !getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_STOP)) {
-                boolean up = command instanceof UpDownType && (UpDownType) command == UpDownType.UP;
-                boolean down = command instanceof UpDownType && (UpDownType) command == UpDownType.DOWN;
-                if ((up && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
-                        || (down && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
+                if ((command == UpDownType.UP && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
+                        || (command == UpDownType.DOWN
+                                && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
                     logger.debug("{}: Roller is already moving ({}), ignore command {}", thingName,
                             getString(rstatus.state), command);
                     requestUpdates(1, false);
@@ -220,20 +235,31 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                 }
             }
 
-            if (((command instanceof UpDownType) && UpDownType.UP.equals(command))
-                    || ((command instanceof OnOffType) && OnOffType.ON.equals(command))) {
+            if ((command == UpDownType.UP) || (command == OnOffType.ON)) {
                 logger.debug("{}: Open roller", thingName);
                 api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
-                position = SHELLY_MAX_ROLLER_POS;
-
-            }
-            if (((command instanceof UpDownType) && UpDownType.DOWN.equals(command))
-                    || ((command instanceof OnOffType) && OnOffType.OFF.equals(command))) {
+                int pos = profile.getRollerFav(config.favoriteUP - 1);
+                position = pos > 0 ? pos : SHELLY_MAX_ROLLER_POS;
+                if (pos > 0) {
+                    logger.debug("{}: Use favoriteUP id {} for positioning roller({}%)", thingName, config.favoriteUP,
+                            pos);
+                }
+            } else if ((command == UpDownType.DOWN) || (command == OnOffType.OFF)) {
                 logger.debug("{}: Closing roller", thingName);
-                api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
-                position = SHELLY_MIN_ROLLER_POS;
+                int pos = profile.getRollerFav(config.favoriteDOWN - 1);
+                if (pos > 0) {
+                    // use favorite position
+                    if (pos > 0) {
+                        logger.debug("{}: Use favoriteDOWN id {} for positioning roller ({}%)", thingName,
+                                config.favoriteDOWN, pos);
+                    }
+                    api.setRollerPos(index, pos);
+                } else {
+                    api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
+                }
+                position = SHELLY_MAX_ROLLER_POS - pos;
             }
-        } else if ((command instanceof StopMoveType) && StopMoveType.STOP.equals(command)) {
+        } else if (command == StopMoveType.STOP) {
             logger.debug("{}: Stop roller", thingName);
             api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_STOP);
         } else {
@@ -246,7 +272,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                 position = d.intValue();
             } else {
                 throw new IllegalArgumentException(
-                        "Invalid value type for roller control/posiution" + command.getClass().toString());
+                        "Invalid value type for roller control/position" + command.getClass().toString());
             }
 
             // take position from RollerShutter control and map to Shelly positon (OH:
@@ -274,13 +300,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
      */
     private void createRelayChannels(ShellyStatusRelay relay, int idx) {
         if (!areChannelsCreated()) {
-            updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRelayChannels(getThing(), profile, relay, idx));
+            updateChannelDefinitions(ShellyChannelDefinitions.createRelayChannels(getThing(), profile, relay, idx));
+        }
+    }
+
+    private void createDimmerChannels(ShellySettingsStatus dstatus, int idx) {
+        if (!areChannelsCreated()) {
+            updateChannelDefinitions(ShellyChannelDefinitions.createDimmerChannels(getThing(), profile, dstatus, idx));
         }
     }
 
     private void createRollerChannels(ShellyControlRoller roller) {
         if (!areChannelsCreated()) {
-            updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRollerChannels(getThing(), roller));
+            updateChannelDefinitions(ShellyChannelDefinitions.createRollerChannels(getThing(), roller));
         }
     }
 
@@ -343,16 +375,11 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                         updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
                                 toQuantityType(getDouble(rsettings.autoOff), Units.SECOND));
                     }
-
-                    // Update input(s) state
-                    updated |= updateInputs(groupName, status, i);
                 }
                 i++;
             }
-        }
-
-        // Check for Relay in Roller Mode
-        if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
+        } else if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
+            // Check for Relay in Roller Mode
             logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
             int i = 0;
 
@@ -373,15 +400,15 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                     if (state.equals(SHELLY_ALWD_ROLLER_TURN_STOP)) { // only valid in stop state
                         int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
                         updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
-                                toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
+                                toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
                         updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
-                                toQuantityType(new Double(pos), Units.PERCENT));
+                                toQuantityType((double) pos, Units.PERCENT));
                         scheduledUpdates = 1; // one more poll and then stop
                     }
 
                     updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
                     updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR, getStringType(control.stopReason));
-                    updated |= updateInputs(groupName, status, i);
+                    updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_SAFETY, getOnOff(control.safetySwitch));
 
                     i++;
                 }
@@ -406,7 +433,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
             // the same structure as lights[] from Bulb,RGBW2 and Duo. The tag gets replaced by dimmers[] so that Gson
             // maps to a different structure (ShellyShortLight).
             Gson gson = new Gson();
-            ShellySettingsStatus dstatus = gson.fromJson(ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
+            ShellySettingsStatus dstatus = fromJson(gson, ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
                     ShellySettingsStatus.class);
 
             logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
@@ -416,17 +443,19 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                 String groupName = profile.numRelays <= 1 ? CHANNEL_GROUP_DIMMER_CONTROL
                         : CHANNEL_GROUP_DIMMER_CONTROL + r.toString();
 
+                createDimmerChannels(dstatus, l);
+
                 // On a status update we map a dimmer.ison = false to brightness 0 rather than the device's brightness
                 // and send a OFF status to the same channel.
                 // When the device's brightness is > 0 we send the new value to the channel and a ON command
                 if (dimmer.ison) {
                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.ON);
                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
-                            toQuantityType(new Double(getInteger(dimmer.brightness)), DIGITS_NONE, Units.PERCENT));
+                            toQuantityType((double) getInteger(dimmer.brightness), DIGITS_NONE, Units.PERCENT));
                 } else {
                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.OFF);
                     updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
-                            toQuantityType(new Double(0), DIGITS_NONE, Units.PERCENT));
+                            toQuantityType(0.0, DIGITS_NONE, Units.PERCENT));
                 }
 
                 ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
@@ -437,7 +466,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
                             toQuantityType(getDouble(dsettings.autoOff), Units.SECOND));
                 }
 
-                updated |= updateInputs(groupName, orgStatus, l);
                 l++;
             }
         }
diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java
new file mode 100644 (file)
index 0000000..0cce91e
--- /dev/null
@@ -0,0 +1,608 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.shelly.internal.provider;
+
+import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
+import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyInputState;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLightChannel;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
+import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
+import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
+import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
+ * added on the first thing status update
+ *
+ * @author Markus Michels - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ShellyChannelDefinitions.class)
+public class ShellyChannelDefinitions {
+
+    public static final String ITEMT_STRING = "String";
+    public static final String ITEMT_NUMBER = "Number";
+    public static final String ITEMT_SWITCH = "Switch";
+    public static final String ITEMT_CONTACT = "Contact";
+    public static final String ITEMT_ROLLER = "Rollershutter";
+    public static final String ITEMT_DIMMER = "Dimmer";
+    public static final String ITEMT_LOCATION = "Location";
+    public static final String ITEMT_DATETIME = "DateTime";
+    public static final String ITEMT_TEMP = "Number:Temperature";
+    public static final String ITEMT_LUX = "Number:Illuminance";
+    public static final String ITEMT_POWER = "Number:Power";
+    public static final String ITEMT_ENERGY = "Number:Energy";
+    public static final String ITEMT_VOLT = "Number:ElectricPotential";
+    public static final String ITEMT_AMP = "Number:ElectricPotential";
+    public static final String ITEMT_ANGLE = "Number:Angle";
+    public static final String ITEMT_DISTANCE = "Number:Length";
+    public static final String ITEMT_SPEED = "Number:Speed";
+    public static final String ITEMT_VOLUME = "Number:Volume";
+    public static final String ITEMT_TIME = "Number:Time";
+    public static final String ITEMT_PERCENT = "Number:Dimensionless";
+
+    // shortcuts to avoid line breaks (make code more readable)
+    private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
+    private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
+    private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
+    private static final String CHGR_LIGHT = CHANNEL_GROUP_LIGHT_CONTROL;
+    private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
+    private static final String CHGR_METER = CHANNEL_GROUP_METER;
+    private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
+    private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
+
+    public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
+    public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
+
+    private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
+
+    @Activate
+    public ShellyChannelDefinitions(@Reference ShellyTranslationProvider translationProvider) {
+        ShellyTranslationProvider m = translationProvider;
+
+        // Device: Internal Temp
+        CHANNEL_DEFINITIONS
+                // Device
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEMT_TEMP))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEMT_POWER))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEMT_POWER))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEMT_POWER))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEMT_DATETIME))
+                .add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEMT_SWITCH))
+
+                // Relay
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT, "system:power", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
+                .add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
+
+                // Dimmer
+                .add(new ShellyChannel(m, CHANNEL_GROUP_DIMMER_CONTROL, CHANNEL_BRIGHTNESS, "dimmerBrightness",
+                        ITEMT_DIMMER))
+
+                // Roller
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL, "rollerShutter", ITEMT_ROLLER))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS, "rollerPosition", ITEMT_DIMMER))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV, "rollerFavorite", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR, "rollerStop", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY, "rollerSafety", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER, "system:button", "system:button"))
+
+                // RGBW2
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_LIGHT_POWER, "system:power", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOON, "timerAutoOn", ITEMT_TIME))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
+                .add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
+
+                // Power Meter
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEMT_POWER))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEMT_ENERGY))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEMT_ENERGY))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
+
+                // EMeter
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEMT_ENERGY))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEMT_POWER))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEMT_VOLT))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
+                .add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
+
+                // Sensors
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEMT_PERCENT))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEMT_LUX))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VOLTAGE, "sensorADC", ITEMT_VOLT))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEMT_CONTACT))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEMT_ANGLE))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_TS, "motionTimestamp", ITEMT_DATETIME))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
+
+                // Button/ix3
+                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEMT_SWITCH))
+                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "lastEvent", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEMT_NUMBER))
+                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system:button", ITEMT_STRING))
+                .add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
+
+                // Addon with external sensors
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEMT_TEMP))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEMT_TEMP))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEMT_TEMP))
+                .add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEMT_PERCENT))
+
+                // Battery
+                .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level", ITEMT_PERCENT))
+                .add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEMT_SWITCH));
+    }
+
+    public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
+        String group = substringBefore(channelName, "#");
+        String channel = substringAfter(channelName, "#");
+
+        if (group.contains(CHANNEL_GROUP_METER)) {
+            group = CHANNEL_GROUP_METER; // map meter1..n to meter
+        } else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
+            group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
+        } else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
+            group = CHANNEL_GROUP_LIGHT_CHANNEL;
+        } else if (group.contains(CHANNEL_GROUP_STATUS)) {
+            group = CHANNEL_GROUP_STATUS; // map status1..n to meter
+        }
+
+        if (channel.startsWith(CHANNEL_INPUT)) {
+            channel = CHANNEL_INPUT;
+        } else if (channel.startsWith(CHANNEL_BUTTON_TRIGGER)) {
+            channel = CHANNEL_BUTTON_TRIGGER;
+        } else if (channel.startsWith(CHANNEL_STATUS_EVENTTYPE)) {
+            channel = CHANNEL_STATUS_EVENTTYPE;
+        } else if (channel.startsWith(CHANNEL_STATUS_EVENTCOUNT)) {
+            channel = CHANNEL_STATUS_EVENTCOUNT;
+        }
+
+        String channelId = group + "#" + channel;
+        return CHANNEL_DEFINITIONS.get(channelId);
+    }
+
+    /**
+     * Auto-create relay channels depending on relay type/mode
+     *
+     * @return ArrayList<Channel> of channels to be added to the thing
+     */
+    public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellySettingsStatus status) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+
+        addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
+
+        if (!profile.isSensor) {
+            // Only some devices report the internal device temp
+            addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
+                    CHANNEL_DEVST_ITEMP);
+        }
+
+        // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
+        boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
+                && !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
+        addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
+        addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
+        addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
+        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
+        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
+        addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
+
+        if (profile.settings.ledPowerDisable != null) {
+            addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
+        }
+        if (profile.settings.ledStatusDisable != null) {
+            addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
+        }
+        return add;
+    }
+
+    /**
+     * Auto-create relay channels depending on relay type/mode
+     *
+     * @return ArrayList<Channel> of channels to be added to the thing
+     */
+    public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellyStatusRelay relay, int idx) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+        String group = profile.getControlGroup(idx);
+
+        ShellySettingsRelay rs = profile.settings.relays.get(idx);
+        addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
+        addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
+        addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
+        addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
+        addChannel(thing, add, rs.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
+
+        // Shelly 1/1PM Addon
+        if (relay.extTemperature != null) {
+            addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
+            addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
+            addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
+        }
+        if (relay.extHumidity != null) {
+            addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
+        }
+
+        return add;
+    }
+
+    public static Map<String, Channel> createDimmerChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellySettingsStatus dstatus, int idx) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+        String group = profile.getControlGroup(idx);
+
+        // Shelly Dimmer has an additional brightness channel
+        addChannel(thing, add, profile.isDimmer, group, CHANNEL_BRIGHTNESS);
+
+        ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
+        addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
+        addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
+        return add;
+    }
+
+    public static Map<String, Channel> createLightChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellyStatusLightChannel status, int idx) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+        String group = profile.getControlGroup(idx);
+
+        ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
+        // The is no brightness channel in color mode, so we need a power channel
+        addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
+
+        addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
+        addChannel(thing, add, light.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
+        addChannel(thing, add, status.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
+        return add;
+    }
+
+    public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellySettingsStatus status, String group) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+        if (status.inputs != null) {
+            // Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
+            // created by adding the index to the channel name
+            boolean multi = ((profile.numRelays == 1) || profile.isDimmer || profile.isRoller)
+                    && (profile.numInputs >= 2);
+            for (int i = 0; i < profile.numInputs; i++) {
+                String suffix = multi ? String.valueOf(i + 1) : "";
+                ShellyInputState input = status.inputs.get(i);
+                addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
+                if (profile.inButtonMode(i)) {
+                    addChannel(thing, add, input.event != null, group, CHANNEL_STATUS_EVENTTYPE + suffix);
+                    addChannel(thing, add, input.eventCount != null, group, CHANNEL_STATUS_EVENTCOUNT + suffix);
+                }
+                addChannel(thing, add, true, group,
+                        (!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
+            }
+        } else if (status.input != null) {
+            // old RGBW2 firmware
+            addChannel(thing, add, true, group, CHANNEL_INPUT);
+            addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
+        }
+        return add;
+    }
+
+    public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
+        Map<String, Channel> add = new LinkedHashMap<>();
+        addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_CONTROL);
+        addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
+        addChannel(thing, add, true, CHGR_ROLLER, CHANNEL_EVENT_TRIGGER);
+        addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_POS);
+        addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR);
+        addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY);
+
+        ShellyBaseHandler handler = (ShellyBaseHandler) thing.getHandler();
+        if (handler != null) {
+            ShellySettingsGlobal settings = handler.getProfile().settings;
+            if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) {
+                addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
+            }
+        }
+        return add;
+    }
+
+    public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
+        Map<String, Channel> newChannels = new LinkedHashMap<>();
+        addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
+        addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
+        addChannel(thing, newChannels, (meter.counters != null) && (meter.counters[0] != null), group,
+                CHANNEL_METER_LASTMIN1);
+        addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
+        return newChannels;
+    }
+
+    public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
+            String group) {
+        Map<String, Channel> newChannels = new LinkedHashMap<>();
+        addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
+        addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
+        addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
+        addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
+        addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
+        addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
+        addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
+        addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
+        return newChannels;
+    }
+
+    public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
+            final ShellyStatusSensor sdata) {
+        Map<String, Channel> newChannels = new LinkedHashMap<>();
+
+        // Sensor data
+        addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
+        addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
+        addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
+        addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
+                CHANNEL_SENSOR_ILLUM);
+        addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
+        addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
+        addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
+        addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
+        if (sdata.sensor != null) { // DW2 or Motion
+            addChannel(thing, newChannels, sdata.sensor.state != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT); // DW/DW2
+            addChannel(thing, newChannels, sdata.sensor.motionTimestamp != null, CHANNEL_GROUP_SENSOR, // Motion
+                    CHANNEL_SENSOR_MOTION_TS);
+            addChannel(thing, newChannels, sdata.sensor.vibration != null, CHANNEL_GROUP_SENSOR,
+                    CHANNEL_SENSOR_VIBRATION);
+        }
+        if (sdata.accel != null) { // DW2
+            addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
+                    CHANNEL_SENSOR_VIBRATION);
+            addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
+        }
+
+        // Gas
+        if (sdata.gasSensor != null) {
+            addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
+            addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
+                    CHANNEL_SENSOR_SSTATE);
+            addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
+                    CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
+            addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
+            addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
+                    CHANNEL_SENSOR_ALARM_STATE);
+        }
+
+        addChannel(thing, newChannels, sdata.adcs != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE); // UNI
+
+        // Battery
+        if (sdata.bat != null) {
+            addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
+            addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
+        }
+
+        addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
+        addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
+        addChannel(thing, newChannels, true, profile.isButton ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_SENSOR,
+                CHANNEL_LAST_UPDATE);
+        return newChannels;
+    }
+
+    private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
+            String channelName) throws IllegalArgumentException {
+        if (supported) {
+            String channelId = group + "#" + channelName;
+            ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
+            ShellyChannel channelDef = getDefinition(channelId);
+            if (channelDef != null) {
+                ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
+                        ? new ChannelTypeUID(channelDef.typeId)
+                        : new ChannelTypeUID(BINDING_ID, channelDef.typeId);
+                Channel channel;
+                if (channelDef.typeId.equalsIgnoreCase("system:button")) {
+                    channel = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER)
+                            .withType(channelTypeUID).build();
+                } else {
+                    channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
+                }
+                newChannels.put(channelId, channel);
+            }
+        }
+    }
+
+    public class ShellyChannel {
+        private final ShellyTranslationProvider messages;
+        public String group = "";
+        public String groupLabel = "";
+        public String groupDescription = "";
+
+        public String channel = "";
+        public String label = "";
+        public String description = "";
+        public String itemType = "";
+        public String typeId = "";
+        public String category = "";
+        public Set<String> tags = new HashSet<>();
+        public @Nullable Unit<?> unit;
+        public Optional<Integer> min = Optional.empty();
+        public Optional<Integer> max = Optional.empty();
+        public Optional<Integer> step = Optional.empty();
+        public Optional<String> pattern = Optional.empty();
+
+        public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
+                String itemType, String... category) {
+            this.messages = messages;
+            this.group = group;
+            this.channel = channel;
+            this.itemType = itemType;
+            this.typeId = typeId;
+
+            groupLabel = getText(PREFIX_GROUP + group + ".label");
+            groupDescription = getText(PREFIX_GROUP + group + ".description");
+            label = getText(PREFIX_CHANNEL + channel + ".label");
+            description = getText(PREFIX_CHANNEL + channel + ".description");
+        }
+
+        public String getChanneId() {
+            return group + "#" + channel;
+        }
+
+        public String getGroupLabel() {
+            return getGroupAttribute("group");
+        }
+
+        public String getGroupDescription() {
+            return getGroupAttribute("group");
+        }
+
+        public String getLabel() {
+            return getChannelAttribute("label");
+        }
+
+        public String getDescription() {
+            return getChannelAttribute("description");
+        }
+
+        public boolean getAdvanced() {
+            String attr = getChannelAttribute("advanced");
+            return attr.isEmpty() ? false : Boolean.valueOf(attr);
+        }
+
+        public boolean getReadyOnly() {
+            String attr = getChannelAttribute("advanced");
+            return attr.isEmpty() ? false : Boolean.valueOf(attr);
+        }
+
+        public String getCategory() {
+            return getChannelAttribute("category");
+        }
+
+        public String getMin() {
+            return getChannelAttribute("min");
+        }
+
+        public String getMax() {
+            return getChannelAttribute("max");
+        }
+
+        public String getStep() {
+            return getChannelAttribute("step");
+        }
+
+        public String getPattern() {
+            return getChannelAttribute("pattern");
+        }
+
+        public String getGroupAttribute(String attribute) {
+            String key = PREFIX_GROUP + group + "." + attribute;
+            String value = messages.getText(key);
+            return value != null && !value.equals(key) ? value : "";
+        }
+
+        public String getChannelAttribute(String attribute) {
+            String key = PREFIX_CHANNEL + channel + "." + attribute;
+            String value = messages.getText(key);
+            return value != null && !value.equals(key) ? value : "";
+        }
+
+        private String getText(String key) {
+            String text = messages.get(key);
+            return text != null ? text : "";
+        }
+    }
+
+    public static class ChannelMap {
+        private final Map<String, ShellyChannel> map = new HashMap<>();
+
+        private ChannelMap add(ShellyChannel def) {
+            map.put(def.getChanneId(), def);
+            return this;
+        }
+
+        public ShellyChannel get(String channelName) throws IllegalArgumentException {
+            ShellyChannel def = null;
+            if (channelName.contains("#")) {
+                def = map.get(channelName);
+                if (def != null) {
+                    return def;
+                }
+            }
+            for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
+                if (entry.getValue().channel.contains("#" + channelName)) {
+                    def = entry.getValue();
+                    break;
+                }
+            }
+
+            if (def == null) {
+                throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
+            }
+
+            return def;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyTranslationProvider.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyTranslationProvider.java
new file mode 100644 (file)
index 0000000..b39225c
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * 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);
+    }
+}
index f9b42823dd2829a197eea848c660b44ddde821cf..6b573b9b8a00b424fb02268a626b750cb8b755f3 100644 (file)
@@ -93,7 +93,7 @@ public class ShellyChannelCache {
             }
         } catch (IllegalArgumentException e) {
             logger.debug("{}: Unable to update channel {} with {} (type {}): {} ({})", thingName, channelId, newValue,
-                    newValue.getClass(), ShellyUtils.getMessage(e), e.getClass());
+                    newValue.getClass(), ShellyUtils.getMessage(e), e.getClass(), e);
         }
         return false;
     }
@@ -119,7 +119,8 @@ public class ShellyChannelCache {
     }
 
     public State getValue(String channelId) {
-        return channelData.getOrDefault(channelId, UnDefType.NULL);
+        State st = channelData.get(channelId);
+        return st != null ? st : UnDefType.NULL;
     }
 
     public void resetChannel(String channelId) {
diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyTranslationProvider.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyTranslationProvider.java
deleted file mode 100644 (file)
index 0b6134f..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.shelly.internal.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);
-    }
-}
index dd74d6d29a665d5400588124641144ee9aba10e8..5767d9b964a48fbec5f33a7824e8f45c8bbc09a3 100644 (file)
@@ -16,6 +16,7 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
 
 import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.time.DateTimeException;
@@ -40,6 +41,10 @@ import org.openhab.core.types.Command;
 import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
 
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.internal.Primitives;
+
 /**
  * {@link ShellyUtils} provides general utility functions
  *
@@ -47,6 +52,49 @@ import org.openhab.core.types.UnDefType;
  */
 @NonNullByDefault
 public class ShellyUtils {
+    private final static String PRE = "Unable to create object of type ";
+
+    public static <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT) throws ShellyApiException {
+        @Nullable
+        T o = fromJson(gson, json, classOfT, true);
+        if (o == null) {
+            throw new ShellyApiException("Unable to create JSON object");
+        }
+        return o;
+    }
+
+    public static @Nullable <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT, boolean exceptionOnNull)
+            throws ShellyApiException {
+        String className = substringAfter(classOfT.getName(), "$");
+
+        if (json == null) {
+            if (exceptionOnNull) {
+                throw new IllegalArgumentException(PRE + className + ": json is null!");
+            } else {
+                return null;
+            }
+        }
+
+        if (classOfT.isInstance(json)) {
+            return Primitives.wrap(classOfT).cast(json);
+        } else if (json.isEmpty()) { // update GSON might return null
+            throw new ShellyApiException(PRE + className + "from empty JSON");
+        } else {
+            try {
+                @Nullable
+                T obj = gson.fromJson(json, classOfT);
+                if ((obj == null) && exceptionOnNull) { // new in OH3: fromJson may return null
+                    throw new ShellyApiException(PRE + className + "from JSON: " + json);
+                }
+                return obj;
+            } catch (JsonSyntaxException e) {
+                throw new ShellyApiException(PRE + className + "from JSON (syntax/format error): " + json, e);
+            } catch (RuntimeException e) {
+                throw new ShellyApiException(PRE + className + "from JSON: " + json, e);
+            }
+        }
+    }
+
     public static String mkChannelId(String group, String channel) {
         return group + "#" + channel;
     }
@@ -171,7 +219,7 @@ public class ShellyUtils {
             return UnDefType.NULL;
         }
         BigDecimal bd = new BigDecimal(value.doubleValue());
-        return toQuantityType(bd.setScale(digits, BigDecimal.ROUND_HALF_UP), unit);
+        return toQuantityType(bd.setScale(digits, RoundingMode.HALF_UP), unit);
     }
 
     public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
@@ -233,7 +281,7 @@ public class ShellyUtils {
     }
 
     public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
-        return profile.isBulb || profile.isDuo && !profile.inColor ? CHANNEL_GROUP_WHITE_CONTROL
+        return profile.isBulb || profile.isDuo ? CHANNEL_GROUP_WHITE_CONTROL
                 : CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
     }
 
index 2a61d171b1130d27dadbdf9b521298eafc2cbd2a..dc4b3cb4be8a8a57627202cfb65bb17c568d4879 100644 (file)
@@ -5,22 +5,28 @@
 
        <name>Shelly Binding</name>
        <description>This binding supports the Shelly series of devices.</description>
+       <author>Markus Michels</author>
 
-       <config-description>
+       <config-description uri="binding:shelly">
                <parameter name="defaultUserId" type="text">
                        <default>admin</default>
-                       <label>Default User</label>
-                       <description>Default userId to access protected Shelly devices.</description>
+                       <label>@text/binding.shelly.config.defaultUserId.label</label>
+                       <description>@text/binding.shelly.config.defaultUserId.description</description>
                </parameter>
                <parameter name="defaultPassword" type="text">
                        <default>admin</default>
-                       <label>Default Password</label>
-                       <description>Default password to access protected Shelly devices.</description>
+                       <label>@text/binding.shelly.config.defaultPassword.label</label>
+                       <description>@text/binding.shelly.config.defaultPassword.description</description>
+               </parameter>
+               <parameter name="localIP" type="text">
+                       <label>@text/binding.shelly.config.localIP.label</label>
+                       <description>@text/binding.shelly.config.localIP.description</description>
+                       <default></default>
                </parameter>
                <parameter name="autoCoIoT" type="boolean">
                        <default>true</default>
-                       <label>Auto-enable CoIoT</label>
-                       <description>True: Enable CoIoT events by default when firmware 1.6+ is detected</description>
+                       <label>@text/binding.shelly.config.autoCoIoT.label</label>
+                       <description>@text/binding.shelly.config.autoCoIoT.description</description>
                </parameter>
        </config-description>
 
index e5b07c39ce81d8f07d55fccb09a2561d66ba50ae..6e28459b3bdb1b8681b2fba187934fcde849a859 100644 (file)
@@ -43,8 +43,8 @@
                        <advanced>true</advanced>
                        <default>true</default>
                </parameter>
-               <parameter name="updateInterval" type="integer" required="true" unit="s">
-                       <label>Update Interval</label>
+               <parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
+                       <label>General Update Interval</label>
                        <description>Interval to query an update from the device.</description>
                        <default>60</default>
                        <unitLabel>seconds</unitLabel>
                        <description>Password for HTTP API access.</description>
                        <context>password</context>
                </parameter>
+               <parameter name="favoriteUP" type="integer" min="0" max="4" required="false">
+                       <label>Favorite Id for UP</label>
+                       <description>Which position favorite should be used for UP (0=none, favorites are defined in the Shelly App)</description>
+                       <default>0</default>
+               </parameter>
+               <parameter name="favoriteDOWN" type="integer" min="0" max="4" required="false">
+                       <label>Favorite Id for DOWN</label>
+                       <description>Which position favorite should be used for DOWN (0=none, favorites are defined in the Shelly App)</description>
+                       <default>0</default>
+               </parameter>
                <parameter name="deviceIp" type="text" required="true">
                        <label>Device IP Address</label>
                        <description>IP-Address of the Shelly device.</description>
@@ -71,7 +81,7 @@
                        <label>Enable Roller Events (Roller only)</label>
                        <description>True if the binding should register to get Roller Events.</description>
                        <advanced>true</advanced>
-                       <default>true</default>
+                       <default>false</default>
                </parameter>
                <parameter name="eventsCoIoT" type="boolean" required="false">
                        <label>Enable CoIoT Events</label>
@@ -79,8 +89,8 @@
                        <advanced>true</advanced>
                        <default>true</default>
                </parameter>
-               <parameter name="updateInterval" type="integer" required="true" unit="s">
-                       <label>Update Interval</label>
+               <parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
+                       <label>General Update Interval</label>
                        <description>Interval to query an update from the device.</description>
                        <default>60</default>
                        <unitLabel>seconds</unitLabel>
                </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>
index 6ab9cecfdaf5126b97a081d16b1ebfdfb630bead..abc679655cc7a7246c2e5d4fb4916616d126283b 100644 (file)
@@ -1,3 +1,12 @@
+binding.shelly.config.defaultUserId.label = Default UserId
+binding.shelly.config.defaultUserId.description = This default user id will be used for device access if no one is specified in the thing configuration.
+binding.shelly.config.defaultPassword.label = Default Password
+binding.shelly.config.defaultPassword.description = Default password for device access if none is specified in the thing configuration.
+binding.shelly.config.localIP.label = Host Interface IP
+binding.shelly.config.localIP.description = This interface will be used to setup CoIoT listen and build Action URLs. openHAB's network configuration will be used if this is not set (recommended)
+binding.shelly.config.autoCoIoT.label = Auto-CoIoT
+binding.shelly.config.autoCoIoT.description = If enabled CoIoT will be automatically used when the devices runs a firmware version 1.6 or newer; false: Use thing configuration to enabled/disable CoIoT events.  
+
 discovery.failed# Config status messages
 config-status.error.missing-device-ip=IP address of the Shelly device is missing.
 
@@ -20,12 +29,14 @@ message.init.noipaddress = Unable to detect local IP address. Please make sure t
 message.init.protected = Device is password protected, enter correct credentials in thing configuration.
 message.command.failed = ERROR: Unable to process command {0} for channel {1}
 message.command.init = Thing not yet initialized, command {0} triggers initialization
+message.status.unknown.initializing = Initializing or device in sleep mode.
 message.statusupdate.failed = Unable to update status
 message.event.triggered = Event triggered: {0}
 message.coap.init.failed = Unable to start CoIoT: {0}
 message.discovery.disabled = Device is marked as non-discoverable -> skip
 message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
 message.discovery.failed = Device discovery of device  with IP address {0} failed: {1}
+message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App
 
 # Device
 channel-type.shelly.deviceName.label = Device Name
@@ -42,6 +53,8 @@ channel-type.shelly.temperature3.label = Temperature 3
 channel-type.shelly.temperature3.description = Temperature of external Sensor #3
 channel-type.shelly.humidity.label = Humidity
 channel-type.shelly.humidity.description = Relative humidity (0..100%)
+channel-type.shelly.motionTimestamp.label = Last Motion
+channel-type.shelly.motionTimestamp.description = Timestamp when last motion was detected.
 
 # Roller
 channel-type.shelly.rollerState.label = State
index 3d3890cfdaa46f5ee81cbf8ff61ac7ecdcc2bba3..a3ed00cb4abc66177dddede89c1901c9f25f7554 100644 (file)
@@ -1,6 +1,14 @@
 # binding
 binding.shelly.name = Shelly Binding
 binding.shelly.description = Dieses Binding integriert Shelly-Komponenten, die über WiFi gesteuert werden können.
+binding.shelly.config.defaultUserId.label = Standardbenutzerkennung
+binding.shelly.config.defaultUserId.description = Sofern konfiguriert, wird diese für den Zugriff auf das Gerät verwendet, wenn in der Thing-Konfiguration keine angegeben ist.
+binding.shelly.config.defaultPassword.label = Standardpasswort
+binding.shelly.config.defaultPassword.description = Dieses Passwort wird für den Gerätezugriff verwendet, wenn in der Thing-Konfiguration keines gesetzt ist.
+binding.shelly.config.localIP.label = Host Interface IP
+binding.shelly.config.localIP.description = Lokale IP-Adresse der Netzwerk-Schnittstelle, welche für Verbindungen genutzt wird (CoIoT Listen und http-Callback). Default: Voreingestelltes Interface aus der openHAB Netzwerkkonfiguration.
+binding.shelly.config.autoCoIoT.label = Auto-CoIoT
+binding.shelly.config.autoCoIoT.description = Bei aktiviertem Auto-CoIoT wird das Protokoll aktiviert, sobald das Gerät eine Firmwareversion 1.6 oder neuer verwendet. Andernfalls wird dies über die Thing-Konfiguration gesteuert.  
 
 # Config status messages
 config-status.error.missing-deviceip=Die IP-Adresse des Shelly Gerätes ist nicht konfiguriert.
@@ -27,6 +35,7 @@ message.init.noipaddress = Es konnte keine lokale IP-Adresse ermittelt werden. B
 message.init.protected = Das Gerät ist passwortgeschützt, die Zugangsdaten müssen in der Thing Konfiguration hinterlegt werden.
 message.command.failed = FEHLER: Der Befehl {0} für Kanal {1} kann nicht verarbeitet werden
 message.command.init = Thing aktuell nicht initialisiert, der Befehl {0} führt zur Initialisierung
+message.status.unknown.initializing = Initialisierung oder Gerät im Schlafmodus. 
 message.statusupdate.failed = Status konnte nicht aktualisiert werden
 message.event.triggered = Event erzeugt: {0}
 message.coap.init.failed = CoAP/CoIoT konnte nicht gestartet werden: {0}
@@ -34,10 +43,13 @@ message.discovery.disabled = Das Ger
 message.discovery.protected = Das Gerät mit der IP-Adresse {0} ist zugriffsgeschützt und keine Zugangsdaten konfiguriert.
 message.discovery.failed = Erkennung des Gerätes mit der IP-Adresse {0} ist fehlgeschlagen
 message.roller.calibrating = Das Gerät ist nicht kalibriert. Es ist eine Kalibrierung mit der Shelly App erforderlich.
+message.roller.favmissing = Positions-Favoriten werden von der installierten Firmwareversion nicht unterstützt (ab 1.9.2), oder sind nicht in der Shelly App konfiguriert.
 
 # thing types
-thing-type.shelly.shelly1.label = Shelly1 (SHSW-1)
+thing-type.shelly.shelly1.label = Shelly 1 (SHSW-1)
 thing-type.shelly.shelly1.description = Shelly 1 (1 Relay)
+thing-type.shelly.shelly1l.label = Shelly 1L (SHSW-L)
+thing-type.shelly.shelly1l.description = Shelly 1L (1 Relay)
 thing-type.shelly.shelly1pm.label = Shelly 1PM (SHSW-PM)
 thing-type.shelly.shelly1pm.description = Shelly 1PM mit 1xRelais und Strommesser
 thing-type.shelly.shellyem.label = Shelly EM (SHEM)
@@ -56,8 +68,14 @@ thing-type.shelly.shelly4pro.label = Shelly4 Pro Relay (SHSW-4)
 thing-type.shelly.shelly4pro.description = Shelly 4 Pro mit 4 Relais und Strommessern
 thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
 thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
+thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
+thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
+thing-type.shelly.shellyplugu1.label = Shelly Plug US (SHPLG-U1)
+thing-type.shelly.shellyplugu1.description = Shelly Plug-US als schaltbare Steckdose (110V)
 thing-type.shelly.shellyplugs.label = Shelly Plug-S (SHPLG-S)
 thing-type.shelly.shellyplugs.description = Shelly Plug-S als schaltbare Steckdose
+thing-type.shelly.shellyuni.label = Shelly UNI (SHUNI-1)
+thing-type.shelly.shellyuni.description = Shelly UNI
 thing-type.shelly.shellydimmer.label = Shelly Dimmer (SHDM-1)
 thing-type.shelly.shellydimmer.description = Shelly mit Dimmer-Funktion
 thing-type.shelly.shellydimmer2.label = Shelly Dimmer (SHDM-2)
@@ -80,6 +98,8 @@ thing-type.shelly.shellybulb.label = Shelly Bulb (SHBLB-1)
 thing-type.shelly.shellybulb.description = Shelly Glühbirne weiß/Farbe
 thing-type.shelly.shellybulbduo.label = Shelly Duo (SHBDUO-1)
 thing-type.shelly.shellybulbduo.description = Shelly Duo Glühbirne
+thing-type.shelly.shellycolorbulb.label = Shelly Duo Color (SHCB-1)
+thing-type.shelly.shellycolorbulb.description = Farbige Shelly Duo Glühbirne (Farb- und Weißmodus)
 thing-type.shelly.shellyvintage.label = Shelly Vintage (SHVIN-1)
 thing-type.shelly.shellyvintage.description = Shelly Vintage Glühbirne
 thing-type.shelly.shellyrgbw2-color.label = Shelly RGBW2 Color Mode (SHRGBW2)
@@ -87,25 +107,61 @@ thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW-Controller im Farb
 thing-type.shelly.shellyrgbw2-white.label = Shelly RGBW2 White Mode (SHRGBW2)
 thing-type.shelly.shellyrgbw2-white.description = Shelly RGBW-Controller im Weiß-Modus, 4 Streifen
 
-# thing config - generic
-thing-type.config.shelly.generic.userId.label = Benutzer
-thing-type.config.shelly.generic.userId.description = Benutzerkennung für API-Zugriff
-thing-type.config.shelly.generic.password.label = Passwort
-thing-type.config.shelly.generic.password.description = Passwort für API-Zugriff
-thing-type.config.shelly.generic.deviceIp.label = IP Adresse
-thing-type.config.shelly.generic.deviceIp.description = IP Adresse der Shelly-Komponente
-thing-type.config.shelly.generic.weakSignal.label = Schwaches Signal (dBm)
-thing-type.config.shelly.generic.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
-thing-type.config.shelly.generic.eventsButton.label = Button Events
-thing-type.config.shelly.generic.eventsButton.description = Aktiviert die Button Action URLS
-thing-type.config.shelly.generic.eventsPush.label = Push Events
-thing-type.config.shelly.generic.eventsPush.description = Aktiviert die Push Button Action URLS
-thing-type.config.shelly.generic.eventsSwitch.label = Output Events
-thing-type.config.shelly.generic.eventsSwitch.description = Aktiviert die Output Action URLS
-thing-type.config.shelly.generic.eventsCoIoT.label = CoIoT aktivieren
-thing-type.config.shelly.generic.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
-thing-type.config.shelly.generic.updateInterval.label = Status-Intervall 
-thing-type.config.shelly.generic.updateInterval.description = Intervall für die Hintergundaktualisierung
+# thing config - relay
+thing-type.config.shelly.relay.userId.label = Benutzer
+thing-type.config.shelly.relay.userId.description = Benutzerkennung für API-Zugriff
+thing-type.config.shelly.relay.password.label = Passwort
+thing-type.config.shelly.relay.password.description = Passwort für API-Zugriff
+thing-type.config.shelly.relay.deviceIp.label = IP Adresse
+thing-type.config.shelly.relay.deviceIp.description = IP Adresse der Shelly-Komponente
+thing-type.config.shelly.relay.eventsButton.label = Button Events
+thing-type.config.shelly.relay.eventsButton.description = Aktiviert die Button Action URLS
+thing-type.config.shelly.relay.eventsPush.label = Push Events
+thing-type.config.shelly.relay.eventsPush.description = Aktiviert die Push Button Action URLS
+thing-type.config.shelly.relay.eventsSwitch.label = Output Events
+thing-type.config.shelly.relay.eventsSwitch.description = Aktiviert die Output Action URLS
+thing-type.config.shelly.relay.eventsCoIoT.label = CoIoT aktivieren
+thing-type.config.shelly.relay.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
+thing-type.config.shelly.relay.updateInterval.label = Status-Intervall 
+thing-type.config.shelly.relay.updateInterval.description = Intervall für die Hintergundaktualisierung
+
+# thing config - roller
+thing-type.config.shelly.roller.userId.label = Benutzer
+thing-type.config.shelly.roller.userId.description = Benutzerkennung für API-Zugriff
+thing-type.config.shelly.roller.password.label = Passwort
+thing-type.config.shelly.roller.password.description = Passwort für API-Zugriff
+thing-type.config.shelly.roller.favoriteUP.label = Favoriten ID für UP
+thing-type.config.shelly.roller.favoriteUP.description = Gibt die Favoriten ID an, die beim Command UP auf den roller#control channel verwendet wird (Konfiguration in der Shelly App)
+thing-type.config.shelly.roller.favoriteDOWN.label = Favoriten ID für DOWN
+thing-type.config.shelly.roller.favoriteDOWN.description = Gibt die Favoriten ID an, die beim Command DOWN auf den roller#control channel verwendet wird (Konfiguration in der Shelly App)
+thing-type.config.shelly.roller.deviceIp.label = IP Adresse
+thing-type.config.shelly.roller.deviceIp.description = IP Adresse der Shelly-Komponente
+thing-type.config.shelly.roller.eventsRoller.label = Rollladen-Events
+thing-type.config.shelly.roller.eventsRoller.description = Aktiviert die Roller Action URLS
+thing-type.config.shelly.roller.eventsCoIoT.label = CoIoT aktivieren
+thing-type.config.shelly.roller.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
+thing-type.config.shelly.roller.updateInterval.label = Status-Intervall 
+thing-type.config.shelly.roller.updateInterval.description = Intervall für die Hintergundaktualisierung
+
+# thing config - dimmer
+thing-type.config.shelly.dimmer.userId.label = Benutzer
+thing-type.config.shelly.dimmer.userId.description = Benutzerkennung für API-Zugriff
+thing-type.config.shelly.dimmer.password.label = Passwort
+thing-type.config.shelly.dimmer.password.description = Passwort für API-Zugriff
+thing-type.config.shelly.dimmer.deviceIp.label = IP Adresse
+thing-type.config.shelly.dimmer.deviceIp.description = IP Adresse der Shelly-Komponente
+thing-type.config.shelly.dimmer.brightnessAutoOn.label = Helligkeit Auto-EIN
+thing-type.config.shelly.dimmer.brightnessAutoOn.description = an: Licht wird eingeschaltet, wenn ein Helligkeitswert größer 0 gesetzt wird; aus: Helligkeit wird genetzt, aber das Gerät nicht eingeschaltet
+thing-type.config.shelly.dimmer.eventsButton.label = Button Events
+thing-type.config.shelly.dimmer.eventsButton.description = Aktiviert die Button Action URLS
+thing-type.config.shelly.dimmer.eventsPush.label = Push Events
+thing-type.config.shelly.dimmer.eventsPush.description = Aktiviert die Push Button Action URLS
+thing-type.config.shelly.dimmer.eventsSwitch.label = Output Events
+thing-type.config.shelly.dimmer.eventsSwitch.description = Aktiviert die Output Action URLS
+thing-type.config.shelly.dimmer.eventsCoIoT.label = CoIoT aktivieren
+thing-type.config.shelly.dimmer.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
+thing-type.config.shelly.dimmer.updateInterval.label = Status-Intervall 
+thing-type.config.shelly.dimmer.updateInterval.description = Intervall für die Hintergundaktualisierung
 
 # thing config - light
 thing-type.config.shelly.light.userId.label = Benutzer
@@ -114,8 +170,6 @@ thing-type.config.shelly.light.password.label = Passwort
 thing-type.config.shelly.light.password.description = Passwort für API-Zugriff
 thing-type.config.shelly.light.deviceIp.label = IP Adresse
 thing-type.config.shelly.light.deviceIp.description = IP Adresse der Shelly-Komponente
-thing-type.config.shelly.light.weakSignal.label = Schwaches Signal (dBm)
-thing-type.config.shelly.light.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
 thing-type.config.shelly.light.brightnessAutoOn.label = Helligkeit Auto-EIN
 thing-type.config.shelly.light.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
 thing-type.config.shelly.light.eventsButton.label = Button Events
@@ -129,6 +183,20 @@ thing-type.config.shelly.light.eventsCoIoT.description = Aktiviert CoIoT-Protoko
 thing-type.config.shelly.light.updateInterval.label = Status-Intervall 
 thing-type.config.shelly.light.updateInterval.description = Intervall für die Hintergrundaktualisierung
 
+# thing config - RGBW2
+thing-type.config.shelly.rgbw2.userId.label = Benutzer
+thing-type.config.shelly.rgbw2.userId.description = Benutzerkennung für API-Zugriff
+thing-type.config.shelly.rgbw2.password.label = Passwort
+thing-type.config.shelly.rgbw2.password.description = Passwort für API-Zugriff
+thing-type.config.shelly.rgbw2.deviceIp.label = IP Adresse
+thing-type.config.shelly.rgbw2.deviceIp.description = IP Adresse der Shelly-Komponente
+thing-type.config.shelly.rgbw2.brightnessAutoOn.label = Helligkeit Auto-EIN
+thing-type.config.shelly.rgbw2.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
+thing-type.config.shelly.rgbw2.eventsCoIoT.label = CoIoT aktivieren
+thing-type.config.shelly.rgbw2.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
+thing-type.config.shelly.rgbw2.updateInterval.label = Status-Intervall 
+thing-type.config.shelly.rgbw2.updateInterval.description = Intervall für die Hintergrundaktualisierung
+
 # thing config - battery
 thing-type.config.shelly.battery.userId.label = Benutzer
 thing-type.config.shelly.battery.userId.description = Benutzerkennung für API-Zugriff
@@ -140,8 +208,6 @@ thing-type.config.shelly.battery.eventsSensorReport.label = Sensor Events
 thing-type.config.shelly.battery.eventsSensorReport.description = Aktiviert die Sensor Action URLS
 thing-type.config.shelly.battery.eventsCoIoT.label = CoIoT aktivieren
 thing-type.config.shelly.battery.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
-thing-type.config.shelly.battery.weakSignal.label = Schwaches Signal (dBm)
-thing-type.config.shelly.battery.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
 thing-type.config.shelly.battery.lowBattery.label = Batterieladung niedrig (%)
 thing-type.config.shelly.battery.lowBattery.description = Ein Alarm wird ausgelöst, wenn das Gerät eine Batterieladung kleiner diesem Schwellwert meldet. Default: 20% 
 thing-type.config.shelly.battery.updateInterval.label = Status-Intervall 
@@ -170,6 +236,14 @@ thing-type.shelly.shelly1pm.group.sensors.label = Externe Sensoren
 thing-type.shelly.shelly1pm.group.sensors.description = Werte der externen Sensoren (nur wenn angeschlossen) 
 thing-type.shelly.shelly1pm.group.device.label = Gerätestatus
 thing-type.shelly.shelly1pm.group.device.description = Informationen zum Gerätestatus
+thing-type.shelly.shelly1l.group.relay.label = Relais
+thing-type.shelly.shelly1l.group.relay.description = Relais Ein-/Ausgänge und Status
+thing-type.shelly.shelly1l.group.meter.label = Verbrauch
+thing-type.shelly.shelly1l.group.meter.description = Verbrauchswerte und andere Informationen
+thing-type.shelly.shelly1l.group.sensors.label = Externe Sensoren
+thing-type.shelly.shelly1l.group.sensors.description = Temperaturwerte der externen Sensoren (nur wenn angeschlossen) 
+thing-type.shelly.shelly1l.group.device.label = Gerätestatus
+thing-type.shelly.shelly1l.group.device.description = Informationen zum Gerätestatus
 thing-type.shelly.shellyem.group.relay.label = Relais
 thing-type.shelly.shellyem.group.relay.description = Relais Ein-/Ausgänge und Status
 thing-type.shelly.shellyem.group.meter1.label = Verbrauch 1
@@ -248,6 +322,20 @@ thing-type.shelly.shellyplugs.group.meter.label = Verbrauch
 thing-type.shelly.shellyplugs.group.meter.description = Verbrauchswerte und andere Informationen
 thing-type.shelly.shellyplugs.group.device.label = Gerätestatus
 thing-type.shelly.shellyplugs.group.device.description = Informationen zum Gerätestatus
+thing-type.shelly.shellyplugu1.group.relay.label = Relais
+thing-type.shelly.shellyplugu1.group.relay.description = Relais Ein-/Ausgänge und Status
+thing-type.shelly.shellyplugu1.group.meter.label = Verbrauch
+thing-type.shelly.shellyplugu1.group.meter.description = Verbrauchswerte und andere Informationen
+thing-type.shelly.shellyplugu1.group.device.label = Gerätestatus
+thing-type.shelly.shellyplugu1.group.device.description = Informationen zum Gerätestatus
+thing-type.shelly.shellyuni.group.relay1.label = Relais 1
+thing-type.shelly.shellyuni.group.relay1.description = Relais Ein-/Ausgänge und Status
+thing-type.shelly.shellyuni.group.relay2.label = Relais 2
+thing-type.shelly.shellyuni.group.relay2.description = Relais Ein-/Ausgänge und Status
+thing-type.shelly.shellyuni.group.sensors.label = Sensordaten
+thing-type.shelly.shellyuni.group.sensors.description = Daten der angeschlossenen Sensoren
+thing-type.shelly.shellyuni.group.device.label = Gerätestatus
+thing-type.shelly.shellyuni.group.device.description = Informationen zum Gerätestatus
 thing-type.shelly.shellydimmer.group.relay.label = Relais
 thing-type.shelly.shellydimmer.group.relay.description = Relais Ein-/Ausgänge und Status
 thing-type.shelly.shellydimmer.group.meter.label = Verbrauch
@@ -260,11 +348,11 @@ thing-type.shelly.shellydimmer2.group.meter.label = Verbrauch
 thing-type.shelly.shellydimmer2.group.meter.description = Verbrauchswerte und andere Informationen
 thing-type.shelly.shellydimmer2.group.device.label = Gerätestatus
 thing-type.shelly.shellydimmer2.group.device.description = Informationen zum Gerätestatus
-thing-type.shelly.shellyix3.group.status1.label = Eingang #1
+thing-type.shelly.shellyix3.group.status1.label = Eingang 1
 thing-type.shelly.shellyix3.group.status1.description = Status Informationen zum Eingang 1
-thing-type.shelly.shellyix3.group.status2.label = Eingang #2
+thing-type.shelly.shellyix3.group.status2.label = Eingang 2
 thing-type.shelly.shellyix3.group.status2.description = Status Informationen zum Eingang 2
-thing-type.shelly.shellyix3.group.status3.label = Eingang #3
+thing-type.shelly.shellyix3.group.status3.label = Eingang 3
 thing-type.shelly.shellyix3.group.status3.description = Status Informationen zum Eingang 3
 thing-type.shelly.shellyix3.group.device.label = Gerätestatus
 thing-type.shelly.shellyix3.group.device.description = Informationen zum Gerätestatus
@@ -290,10 +378,18 @@ thing-type.shelly.shellybulbduo.group.meter.label = Verbrauch
 thing-type.shelly.shellybulbduo.group.meter.description = Verbrauchswerte
 thing-type.shelly.shellybulbduo.group.device.label = Gerätestatus
 thing-type.shelly.shellybulbduo.group.device.description = Informationen zum Gerätestatus
+thing-type.shelly.shellycolorbulb.group.control.label = Steuerung
+thing-type.shelly.shellycolorbulb.group.control.description = Steuerung des Lichts
+thing-type.shelly.shellycolorbulb.group.white.label = Weißwerte
+thing-type.shelly.shellycolorbulb.group.white.description = Einstellungen für den Weiß-Modus
+thing-type.shelly.shellycolorbulb.group.meter.label = Verbrauch
+thing-type.shelly.shellycolorbulb.group.meter.description = Verbrauchswerte
+thing-type.shelly.shellycolorbulb.group.device.label = Gerätestatus
+thing-type.shelly.shellycolorbulb.group.device.description = Informationen zum Gerätestatus
 thing-type.shelly.shellyvintage.group.control.label = Steuerung
 thing-type.shelly.shellyvintage.group.control.description = Steuerung des Lichts
-thing-type.shelly.shellyvintage.group.white.label = Weißwerte
-thing-type.shelly.shellyvintage.group.white.description = Einstellungen für den Weiß-Modus
+thing-type.shelly.shellyvintage.group.white.label = Steuerung
+thing-type.shelly.shellyvintage.group.white.description = Geräteeinstellungen
 thing-type.shelly.shellyvintage.group.meter.label = Verbrauch
 thing-type.shelly.shellyvintage.group.meter.description = Verbrauchswerte
 thing-type.shelly.shellyvintage.group.device.label = Gerätestatus
@@ -358,6 +454,12 @@ thing-type.shelly.shellygas.group.sensors.label = Sensordaten
 thing-type.shelly.shellygas.group.sensors.description = Messwerte und Status des Sensors
 thing-type.shelly.shellygas.group.device.label = Gerätestatus
 thing-type.shelly.shellygas.group.device.description = Informationen zum Gerätestatus
+thing-type.shelly.shellymotion.group.sensors.label = Sensordaten
+thing-type.shelly.shellymotion.group.sensors.description = Messwerte und Status des Sensors
+thing-type.shelly.shellymotion.group.battery.label = Batteriestatus
+thing-type.shelly.shellymotion.group.battery.description = Informationen zum Akku
+thing-type.shelly.shellymotion.group.device.label = Gerätestatus
+thing-type.shelly.shellymotion.group.device.description = Informationen zum Gerätestatus
 
 
 # channels
@@ -370,17 +472,19 @@ channel-type.shelly.timerAutoOff.description = Wenn das Relais eingeschaltet wir
 channel-type.shelly.timerActive.label = Timer aktiv
 channel-type.shelly.timerActive.description = ON: Auto-On/Off Timer ist aktiv
 channel-type.shelly.temperature1.label = Temperatur 1
-channel-type.shelly.temperature1.description = Temperatur des externen Sensors #1
+channel-type.shelly.temperature1.description = Temperatur des externen Sensors 1
 channel-type.shelly.temperature2.label = Temperatur 2
-channel-type.shelly.temperature2.description = Temperatur des externen Sensors #2
+channel-type.shelly.temperature2.description = Temperatur des externen Sensors 2
 channel-type.shelly.temperature3.label = Temperatur 3
-channel-type.shelly.temperature3.description = Temperatur des externen Sensors #3
+channel-type.shelly.temperature3.description = Temperatur des externen Sensors 3
 channel-type.shelly.humidity.label = Luftfeuchtigkeit
 channel-type.shelly.humidity.description = Relative Luftfeuchtigkeit (0..100%)
 channel-type.shelly.rollerShutter.label = Steuerung (0=offen, 100=geschlossen)
 channel-type.shelly.rollerShutter.description = Steuerung für den Rollladen: UP, DOWN, STOP, Position (0=offen, 100=geschlossen)
 channel-type.shelly.rollerPosition.label = Position (100=offen, 0=zu)
 channel-type.shelly.rollerPosition.description = Invertierte Position des Rollladen: 100=offen, 0=zu
+channel-type.shelly.rollerFavorite.label = Positionsfavorit
+channel-type.shelly.rollerFavorite.description = Wählt den Positionsfavoriten 1-4, Positionen werden in der Shelly App konfiguriert; 0=undefiniert (kein Favorit gewählt)
 channel-type.shelly.rollerState.label = Status
 channel-type.shelly.rollerState.description = Zustand des Rollladen (open/closed/stopped).
 channel-type.shelly.rollerState.state.option.open = öffnet
@@ -402,12 +506,12 @@ channel-type.shelly.whiteBrightness.label = Helligkeit
 channel-type.shelly.whiteBrightness.description = Helligkeit (0-100%, 0=aus)
 channel-type.shelly.meterWatts.label = Leistung
 channel-type.shelly.meterWatts.description = Aktueller Stromverbrauch in Watt
-channel-type.shelly.meterAccuWatts.label = Kumulierter Verbrauch
-channel-type.shelly.meterAccuWatts.description = Kumulierter Verbrauch in Watt
+channel-type.shelly.meterAccuWatts.label = Kumulierte Verbrauch
+channel-type.shelly.meterAccuWatts.description = Kumulierterr Verbrauch in Watt
 channel-type.shelly.meterAccuTotal.label = Kumulierter Gesamtverbrauch
 channel-type.shelly.meterAccuTotal.description = Kumulierter Gesamtverbrauch in kW/h
-channel-type.shelly.meterAccuReturned.label = Kumulierter Einspeisung
-channel-type.shelly.meterAccuReturned.description = Kumulierter Einspeisung in kW/h
+channel-type.shelly.meterAccuReturned.label = Kumulierte Einspeisung
+channel-type.shelly.meterAccuReturned.description = Kumulierte Einspeisung in kW/h
 channel-type.shelly.meterCurrent.label = Stromstärke
 channel-type.shelly.meterCurrent.description = Aktuelle gemessene Stromstärke
 channel-type.shelly.meterTotal.label = Gesamtverbrauch
@@ -487,10 +591,16 @@ channel-type.shelly.sensorIllumination.state.option.unknown = Unbekannt
 channel-type.shelly.sensorIllumination.description = Angabe zum erkannten Tageslichtwert 
 channel-type.shelly.sensorPPM.label = Gas-Konzentration
 channel-type.shelly.sensorPPM.description = Gemessene Konzentration in PPM
+channel-type.shelly.sensorADC.label = Spannung (ADC)
+channel-type.shelly.sensorADC.description = Gemessene Spannung
 channel-type.shelly.sensorTilt.label = Öffnungswinkel
 channel-type.shelly.sensorTilt.description = Öffnungswinkel in Grad (erfordert Kalibrierung in der App)
 channel-type.shelly.sensorVibration.label = Vibration
 channel-type.shelly.sensorVibration.description = ON: Sensor hat eine Vibration erkannt
+channel-type.shelly.sensorMotion.label = Bewegung
+channel-type.shelly.sensorMotion.description = ON: Es wurde eine Bewegung erkannt
+channel-type.shelly.motionTimestamp.label = Letzte Bewegung
+channel-type.shelly.motionTimestamp.description = Datum/Uhrzeit, wann die letzte Bewegung erkannt wurde.
 channel-type.shelly.sensorValve.label = Ventil
 channel-type.shelly.sensorValve.description = Gibt den Status des Ventils an, sofern eines angeschlossen ist.
 channel-type.shelly.sensorValve.state.option.closed = geschlossen
index 98808b656a37ce199614a3fbce62358d9360ded6..3e2ba36c5d52d2536a55fd1089912b638f092419 100644 (file)
                <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">
index b3daaf21b0ae9aefded92b476787a1c4a1e318b7..a7e2e932473066e2b0314fe5dfd97303a2f610af 100644 (file)
                <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>
index 649d1093864ca0e1e2f97eaa18bf55c65ecd5c3b..c5d6e696d12f5a444ecbb1215b8dca83e26a7759 100644 (file)
@@ -6,7 +6,7 @@
 
        <thing-type id="shelly1">
                <label>Shelly1 (SHSW-1)</label>
-               <description>Shelly1 device with single relay</description>
+               <description>Shelly1 device with 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">
index d877c0c694ce5daaa35ae037cd837ed51890df9f..172ae7e338cdcd18de13812ec8f320da538e688e 100644 (file)
@@ -9,7 +9,7 @@
                <description>Shelly H&amp;T Sensor</description>
 
                <channel-groups>
-                       <channel-group id="sensors" typeId="htSensor"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
@@ -23,7 +23,7 @@
                <description>Shelly Smoke Sensor (battery powered)</description>
 
                <channel-groups>
-                       <channel-group id="sensors" typeId="smokeSensor"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
@@ -37,7 +37,7 @@
                <description>Shelly Gas Sensor</description>
 
                <channel-groups>
-                       <channel-group id="sensors" typeId="gasSensor"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
 
@@ -49,7 +49,7 @@
                <label>Shelly Flood (SHWT-1)</label>
                <description>Shelly Flood Sensor (battery powered)</description>
                <channel-groups>
-                       <channel-group id="sensors" typeId="floodSensor"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
@@ -63,7 +63,7 @@
                <description>Shelly Door/Window Sensor (battery powered)</description>
 
                <channel-groups>
-                       <channel-group id="sensors" typeId="doorWinSensors"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
@@ -77,7 +77,7 @@
                <description>Shelly Door/Window 2 Sensor (battery powered)</description>
 
                <channel-groups>
-                       <channel-group id="sensors" typeId="doorWinSensors"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
@@ -91,7 +91,7 @@
                <description>Shelly Sense Remote IR Controller</description>
                <channel-groups>
                        <channel-group id="control" typeId="senseControl"/>
-                       <channel-group id="sensors" typeId="senseSensors"/>
+                       <channel-group id="sensors" typeId="sensorData"/>
                        <channel-group id="battery" typeId="batteryStatus"/>
                        <channel-group id="device" typeId="deviceStatus"/>
                </channel-groups>
                <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">