From c4a3b1e6bae2df7e151d0b7abf1a14b6e231f506 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Wed, 3 Feb 2021 22:25:19 +0100 Subject: [PATCH] [shelly] Improved documentation, support for UNI, 1L, Color Bulb (#9508) * This PR merges the 2.5 code base with new features, a bunch of bug fixes and improved documentation. Parts of the code has been re-factored and vaious issues are fixed/optimized along the way. Signed-off-by: Markus Michels * Minor fixed, support for Shelly Color Bulb, Shelly Motion Signed-off-by: Markus Michels * Review changes applied Signed-off-by: Markus Michels * review change applied Signed-off-by: Markus Michels * review changes Signed-off-by: Markus Michels * Review changes Signed-off-by: Markus Michels * review changes Signed-off-by: Markus Michels * README updated Signed-off-by: Markus Michels * review change Signed-off-by: Markus Michels * review change Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 148 ++++- .../doc/AdvancedUsers.md | 54 +- .../doc/UseCaseSmartRoller.md | 8 +- .../internal/ShellyBindingConstants.java | 48 +- .../shelly/internal/ShellyHandlerFactory.java | 32 +- .../internal/api/ShellyApiException.java | 5 +- .../shelly/internal/api/ShellyApiJsonDTO.java | 90 ++- .../internal/api/ShellyDeviceProfile.java | 118 ++-- .../internal/api/ShellyEventServlet.java | 2 +- .../shelly/internal/api/ShellyHttpApi.java | 34 +- .../internal/coap/ShellyCoIoTInterface.java | 12 +- .../internal/coap/ShellyCoIoTProtocol.java | 84 ++- .../internal/coap/ShellyCoIoTVersion1.java | 22 +- .../internal/coap/ShellyCoIoTVersion2.java | 95 ++- .../internal/coap/ShellyCoapHandler.java | 126 +++- .../internal/coap/ShellyCoapJSonDTO.java | 2 +- .../internal/coap/ShellyCoapServer.java | 8 +- .../config/ShellyBindingConfiguration.java | 8 +- .../config/ShellyThingConfiguration.java | 3 + .../discovery/ShellyDiscoveryParticipant.java | 15 +- .../discovery/ShellyThingCreator.java | 22 +- .../internal/handler/ShellyBaseHandler.java | 185 +++--- .../handler/ShellyChannelDefinitionsDTO.java | 393 ----------- .../internal/handler/ShellyColorUtils.java | 34 +- .../internal/handler/ShellyComponents.java | 42 +- .../internal/handler/ShellyLightHandler.java | 69 +- .../handler/ShellyProtectedHandler.java | 6 +- .../internal/handler/ShellyRelayHandler.java | 100 +-- .../provider/ShellyChannelDefinitions.java | 608 ++++++++++++++++++ .../ShellyTranslationProvider.java | 20 +- .../internal/util/ShellyChannelCache.java | 5 +- .../shelly/internal/util/ShellyUtils.java | 52 +- .../main/resources/OH-INF/binding/binding.xml | 20 +- .../main/resources/OH-INF/config/config.xml | 45 +- .../resources/OH-INF/i18n/shelly.properties | 13 + .../OH-INF/i18n/shelly_de.properties | 182 ++++-- .../main/resources/OH-INF/thing/device.xml | 6 +- .../main/resources/OH-INF/thing/lights.xml | 24 +- .../src/main/resources/OH-INF/thing/relay.xml | 150 +++-- .../main/resources/OH-INF/thing/sensor.xml | 97 +-- 40 files changed, 1939 insertions(+), 1048 deletions(-) delete mode 100644 bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyChannelDefinitionsDTO.java create mode 100644 bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java rename bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/{util => provider}/ShellyTranslationProvider.java (76%) diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 92fd1a71a83..9006b94dd37 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -7,19 +7,20 @@ Allterco provides a rich set of smart home devices. All of them are WiFi enabled The binding is officially acknowledged by Allterco and openHAB is listed as a reference and directly supports the openHAB community. The binding controls the devices independently from the Allterco Shelly Cloud (in fact it can be disabled). -The binding co-exists with Shelly App for Smartphones, Shelly Web App, Shelly Cloud, mqqt and other 3rd party Apps. +The binding co-exists with Shelly App for Smartphones, Shelly Device Web UI, Shelly Cloud, MQTT and other 3rd party Apps. The binding focuses on reporting the device status and device control. -Initial setup and device configuration has to be performed using the Shelly Apps (Web or Smartphone App). +Initial setup and device configuration has to be performed using the Shelly Apps (Web UI or Smartphone App). The binding gets in sync with the next status refresh. Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB Shelly integration, e.g. firmware update, network communication or log filtering. ## Supported Devices -| Thing Type | Model | Vendor ID | +| thing-type | Model | Vendor ID | |--------------------|--------------------------------------------------------|-----------| | shelly1 | Shelly 1 Single Relay Switch | SHSW-1 | +| shelly1l | Shelly 1L Single Relay Switch | SHSW-L | | shelly1pm | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM | | shelly2-relay | Shelly Double Relay Switch in relay mode | SHSW-21 | | shelly2-roller | Shelly2 in Roller Mode | SHSW-21 | @@ -35,11 +36,14 @@ Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB | shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 | | shellyrgbw2 | Shelly RGB Controller | SHRGBW2 | | shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 | -| shellybulbduo | Shelly Duo (White Mode) | SHBDUO-1 | +| shellybulbduo | Shelly Duo White | SHBDUO-1 | +| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 | +| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 | | shellyvintage | Shelly Vintage (White Mode) | SHVIN-1 | | shellyht | Shelly Sensor (temp+humidity) | SHHT-1 | | shellyflood | Shelly Flood Sensor | SHWT-1 | | shellysmoke | Shelly Smoke Sensor | SHSM-1 | +| shellymotion | Shelly Motion Sensor | SHMOS-01 | | shellygas | Shelly Gas Sensor | SHGS-1 | | shellydw | Shelly Door/Window | SHDW-1 | | shellydw2 | Shelly Door/Window 2 | SHDW-2 | @@ -115,6 +119,14 @@ For those open the case, press that button and the LED starts flashing. Wait a moment and then start the discovery. The device should show up in the Inbox and can be added. Sometimes you need to run the discovery multiple times. +### Roller Favorites + +Firmware 1.9.2 for Shelly 2.5 in roller mode supports so called favorites for positions. +You could use the Shelly App to setup 4 different positions (percentage) and assign id 1-4. +The channel `roller#rollerFav` allows to select those from openHAB and the roller moves to the desired position. +In the Thing configuration you could also configure an id when the `roller#control` channel receives UP or DOWN. +Values 1-4 are selecting the corresponding favorite id in the Shelly App, 0 means no favorite. + ### Thing Status The binding sets the following Thing status depending on the device status: @@ -168,6 +180,8 @@ You could also create a rule to catch those status changes or device alarms (see |eventsSensorReport|true: register event "posted updated sensor data" | no |true for sensor devices | |eventsCoIoT |true: Listen for CoIoT/COAP events | no |true for battery devices, false for others | |eventsRoller |true: register event "trigger" when the roller updates status | no |true for roller devices | +|favoriteUP |0-4: Favorite id for UP (see Roller Favorites) | no |0 = no favorite id | +|favoriteDOWN |0-4: Favorite id for DOWN (see Roller Favorites) | no |0 = no favorite id | ### General Notes @@ -189,6 +203,7 @@ Every device has a channel group `device` with the following channels: | |updateAvailable |Switch |yes |ON: A firmware update is available | | |statusLed |Switch |r/w |ON: Status LED is disabled, OFF: LED enabled | | |powerLed |Switch |r/w |ON: Power LED is disabled, OFF: LED enabled | +| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. | Availability of channels is depending on the device type. The binding detects many of those channels on-the-fly (when Thing changes to ONLINE state) and adjusts the Thing's channel structure. @@ -296,6 +311,8 @@ Depending on the device type and firmware release channels might be not availabl | |outputName |String |yes |Logical name of this relay output as configured in the Shelly App | | |input |Switch |yes |ON: Input/Button is powered, see general notes on channels | | |button |Trigger |yes |Event trigger with payload, see SHORT_PRESSED or LONG_PRESSED | +| |lastEvent |String |yes |Last event type (S/SS/SSS/L) | +| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. | | |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds| | |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active | @@ -304,6 +321,34 @@ Depending on the device type and firmware release channels might be not availabl | |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) | | |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) | +### Shelly 1L (thing-type: shelly1l) + +|Group |Channel |Type |read-only|Description | +|----------|-------------|---------|---------|---------------------------------------------------------------------------------| +|relay |output |Switch |r/w |Controls the relay's output channel (on/off) | +| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App | +| |input1 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels | +| |button1 |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent1 |String |yes |Last event type (S/SS/SSS/L) for input 1 | +| |eventCount1 |Number |yes |Counter gets incremented every time the device issues a button event. | +| |input2 |Switch |yes |ON: Input/Button for channel 2 is powered, see general notes on channels | +| |button2 |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent2 |String |yes |Last event type (S/SS/SSS/L) for input 2 | +| |eventCount2 |Number |yes |Counter gets incremented every time the device issues a button event. | +| |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds| +| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds| +| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active | +|meter |currentWatts |Number |yes |Current power consumption in Watts | +| |lastUpdate |DateTime |yes |Timestamp of the last measurement | +|sensors |temperature1 |Number |yes |Temperature value of external sensor #1 (if connected to temp/hum addon) | +| |temperature2 |Number |yes |Temperature value of external sensor #2 (if connected to temp/hum addon) | +| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) | +| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) | + +Note: The `meter`for the Shelly 1L is kind of fake. +It doesn't have a real power meter, but you could setup an estimated consumption in the Shelly App, e.g. 60W if you have attached a good old light bulb to the output channel. +In this case the is no real measurement based on power consumption, but the Shelly reports the configured value when the relay is ON. + ### Shelly 1PM (thing-type: shelly1pm) |Group |Channel |Type |read-only|Description | @@ -330,6 +375,8 @@ Depending on the device type and firmware release channels might be not availabl | |outputName |String |yes |Logical name of this relay output as configured in the Shelly App | | |input |Switch |yes |ON: Input/Button is powered, see general notes on channels | | |button |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent |String |yes |Last event type (S/SS/SSS/L) | +| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. | | |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds| | |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active | @@ -357,6 +404,8 @@ The Thing id is derived from the service name, so that's the reason why the Thin | |outputName |String |yes |Logical name of this relay output as configured in the Shelly App | | |input |Switch |yes |ON: Input/Button is powered, see general notes on channels | | |button |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent |String |yes |Last event type (S/SS/SSS/L) | +| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. | | |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds| | |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active | @@ -419,6 +468,7 @@ The Thing id is derived from the service name, so that's the reason why the Thin | |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels | | |event |Trigger |yes |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP | | |rollerpos |Number |r/w |Roller position: 100%=open...0%=closed; gets updated when the roller stops, see Notes | +| |rollerFav |Number |r/w |Select roller position favorite (1-4, 0=no), see Notes | | |state |String |yes |Roller state: open/close/stop | | |stopReason |String |yes |Last stop reasons: normal, safety_switch or obstacle | | |safety |Switch |yes |Indicates status of the Safety Switch, ON=problem detected, powered off | @@ -495,8 +545,13 @@ The Shelly 4Pro provides 4 relays and 4 power meters. |relay |brightness |Dimmer |r/w |Currently selected brightness. | | |outputName |String |yes |Logical name of this relay output as configured in the Shelly App | | |input1 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels | -| |input2 |Switch |yes |ON: Input/Button for input 1 is powered, see general notes on channels | -| |button |Trigger |yes |Event trigger, see section Button Events | +| |button1 |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent1 |String |yes |Last event type (S/SS/SSS/L) for input 1 | +| |eventCount1 |Number |yes |Counter gets incremented every time the device issues a button event. | +| |input2 |Switch |yes |ON: Input/Button for channel 2 is powered, see general notes on channels | +| |button2 |Trigger |yes |Event trigger, see section Button Events | +| |lastEvent2 |String |yes |Last event type (S/SS/SSS/L) for input 2 | +| |eventCount2 |Number |yes |Counter gets incremented every time the device issues a button event. | | |autoOn |Number |r/w |Relay #1: Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON command; in seconds| | |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active | @@ -521,7 +576,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the | |input3 |Switch |yes |State of Input 3 | | |button |Trigger |yes |Event trigger: Event trigger, see section Button Events | | |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush | -| |eventCount |Number |yes |Number of button events | +| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. | ### Shelly Bulb (thing-type: shellybulb) @@ -533,7 +588,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the | |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec | | |timerActive |Switch |yes |ON: An auto-on/off timer is active | |color | | | |Color settings: only valid in COLOR mode | -| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, bight not white | +| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white | | |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode | | | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" | | |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) | @@ -547,9 +602,16 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the |white | | | |Color settings: only valid in WHITE mode | | |temperature |Number |r/w |color temperature (K): 0..100% or 3000..6500 | | |brightness |Dimmer | |Brightness: 0..100% or 0..100 | - + +Note: The openHAB color picker has only values for red/green/blue (RGB), not for white as supported by the RGBW2. +Beside channel `hsb` the binding also offers the `white` channel (hsb as only RGB values). +Or control each color separately with channels `red`, `blue`, `green` (those are advanced channels). + + #### Shelly Duo (thing-type: shellybulbduo) +This information applies to the Shelly Duo-1 as well as the Duo White for the G10 socket. + |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| |control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec | @@ -569,16 +631,44 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the |----------|-------------|---------|---------|-----------------------------------------------------------------------| |control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec | | |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec | -| |timerActive |Switch |yes |ON: An auto-on/off timer is active | +| |timerActive |Switch |yes |ON: An auto-on/off timer is active | |white | | | |Color settings: only valid in WHITE mode | | |brightness |Dimmer | |Brightness: 0..100% or 0..100 | |meter |currentWatts |Number |yes |Current power consumption in Watts | | |lastPower1 |Number |yes |Energy consumption for a round minute, 1 minute ago | -| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)| +| |totalKWH |Number |yes |Total energy consumption in kWh since the device powered up (resets on restart)| | |lastUpdate |DateTime |yes |Timestamp of the last measurement | +## Shelly Duo Color (thing-type: shellyduocolor-color) - ## Shelly RGBW2 in Color Mode (thing-type: shellyrgbw2-color) +|Group |Channel |Type |read-only|Description | +|----------|-------------|---------|---------|-----------------------------------------------------------------------| +|control |power |Switch |r/w |Switch light ON/OFF | +| |button |Trigger |yes |Event trigger, see section Button Events | +| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds| +| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds| +| |timerActive |Switch |yes |ON: An auto-on/off timer is active | +|color | | | |Color settings: only valid in COLOR mode | +| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white | +| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode | +| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" | +| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) | +| |green |Dimmer |r/w |Green brightness: 0..100% or 0..255 (control only the green channel) | +| |blue |Dimmer |r/w |Blue brightness: 0..100% or 0..255 (control only the blue channel) | +| |white |Dimmer |r/w |White brightness: 0..100% or 0..255 (control only the white channel) | +| |gain |Dimmer |r/w |Gain setting: 0..100% or 0..100 | +| |effect |Number |r/w |Puts the light into effect mode: 0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash | +|white | | | |Color settings: only valid in WHITE mode | +| |temperature |Number |r/w |color temperature (K): 0..100% or 3000..6500 | +| |brightness |Dimmer | |Brightness: 0..100% or 0..100 | +|meter |currentWatts |Number |yes |Current power consumption in Watts | + +Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set. +`true`: Brightness will be set and device output is powered = light turns on with the new brightness +`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off. + + +## Shelly Duo RGBW Color Bulb (thing-type: shellycolorbulb) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| @@ -600,6 +690,8 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the | | | | |0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash | |meter |currentWatts |Number |yes |Current power consumption in Watts | +Channels in group `color`or `white`apply depending on the selected mode - they are not active at the same time. + Using the Thing configuration option `brightnessAutoOn` you could decide if the light is turned on when a brightness > 0 is set. `true`: Brightness will be set and device output is powered = light turns on with the new brightness `false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off. @@ -624,7 +716,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the | |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds| | |timerActive |Switch |yes |ON: An auto-on/off timer is active | -|channel4 |brightness |Dimmer |r/w |Channel 5: Brightness: 0..100, control power state with ON/OFF | +|channel4 |brightness |Dimmer |r/w |Channel 4: Brightness: 0..100, control power state with ON/OFF | | |button |Trigger |yes |Event trigger, see section Button Events | | |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds| | |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds| @@ -651,12 +743,13 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa |----------|-------------|---------|---------|-----------------------------------------------------------------------| |sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit | | |humidity |Number |yes |Relative humidity in % | -| |charger |Number |yes |ON: USB charging cable is | | |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) | |battery |batteryLevel |Number |yes |Battery Level in % | | |lowBattery |Switch |yes |Low battery alert (< 20%) | -### Shelly Flood (thing type: shellyflood) +`Please Note:` If you have connected an USB cable to the H&T, but channel charger is off make sure that "Use external power supply" settings is activated in the Shelly App's device settings. + +### Shelly Flood (thing-type: shellyflood) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| @@ -666,7 +759,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa |battery |batteryLevel |Number |yes |Battery Level in % | | |lowBattery |Switch |yes |Low battery alert (< 20%) | -### Shelly Door/Window (thing type: shellydw) +### Shelly Door/Window (thing-type: shellydw, shellydw2) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| @@ -680,12 +773,26 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa |battery |batteryLevel |Number |yes |Battery Level in % | | |lowBattery |Switch |yes |Low battery alert (< 20%) | -### Shelly Button 1 (thing type: shellybutton1) +### Shelly Motion (thing-type: shellymotion) + +|Group |Channel |Type |read-only|Description | +|----------|---------------|---------|---------|---------------------------------------------------------------------| +|sensors |motion |Switch |yes |ON: Motion was detected | +| |motionTimestamp|DateTime |yes |Time when motion started/was detected | +| |lux |Number |yes |Brightness in Lux | +| |illumination |String |yes |Current illumination: dark/twilight/bright | +| |vibration |Switch |yes |ON: Vibration detected | +| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. | +| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) | +|battery |batteryLevel |Number |yes |Battery Level in % | +| |lowBattery |Switch |yes |Low battery alert (< 20%) | + +### Shelly Button 1 (thing-type: shellybutton1) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| |status |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush | -| |eventCount |Number |yes |Number of button events | +| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. | | |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels | | |button |Trigger |yes |Event trigger with payload SHORT_PRESSED, DOUBLE_PRESSED... | | |lastUpdate |DateTime |yes |Timestamp of the last update (any value changed) | @@ -694,7 +801,7 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa You should calibrate the sensor using the Shelly App to get information on the tilt status. -### Shelly Smoke(thing type: shellysmoke) +### Shelly Smoke (thing-type: shellysmoke) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| @@ -705,7 +812,7 @@ You should calibrate the sensor using the Shelly App to get information on the t |battery |batteryLevel |Number |yes |Battery Level in % | | |lowBattery |Switch |yes |Low battery alert (< 20%) | -### Shelly Smoke(thing type: shellygas) +### Shelly Smoke(thing-type: shellysmoke) |Group |Channel |Type |read-only|Description | |----------|-------------|---------|---------|-----------------------------------------------------------------------| @@ -866,6 +973,7 @@ end #### Control CCT LED stripes Usage & Requirements: + - 4 Items per Thing required. Example: ``` diff --git a/bundles/org.openhab.binding.shelly/doc/AdvancedUsers.md b/bundles/org.openhab.binding.shelly/doc/AdvancedUsers.md index 069e03378ac..be86a015fd6 100644 --- a/bundles/org.openhab.binding.shelly/doc/AdvancedUsers.md +++ b/bundles/org.openhab.binding.shelly/doc/AdvancedUsers.md @@ -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] >generated link>" from the terminal. + +Then you run +``` +curl -s [-u user:password] +``` +from the command line. + This should show a JSON result, make sure that it shows "status:updating". Wait 15sec and access the device's Web UI, go to Settings:Firmware Upgrade and make sure than the new version was installed successful. -- Manual download and installation of the firmware -Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link. -Once you downloaded the file you need to copy it to an http server. -Open the following url http://<shelly ip>/ota?url=http://<web server>/<path>/<zip-file> -Again, make sure that the file is downloaded and installed properly. + +### Manual download and installation of the firmware + +- Manually pick the download link from the [Shelly Firmware Repository](https://api.shelly.cloud/files/firmware) and get the release or beta link. +- Once you downloaded the file you need to copy it to an http server. +- Open the following url http://<shelly ip>/ota?url=http://<web server>/<path>/<zip-file> +- Again, make sure that the file is downloaded and installed properly. ## Trouble Shooting @@ -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 `...` +- Search for tag RollingFile +- and add a tag `...` The attribute `regex` of this tag defines the regular expression, `onMatch="DENY"` the the logger to discard those lines diff --git a/bundles/org.openhab.binding.shelly/doc/UseCaseSmartRoller.md b/bundles/org.openhab.binding.shelly/doc/UseCaseSmartRoller.md index 3f904b48769..9060e1c6770 100644 --- a/bundles/org.openhab.binding.shelly/doc/UseCaseSmartRoller.md +++ b/bundles/org.openhab.binding.shelly/doc/UseCaseSmartRoller.md @@ -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. diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 5e4b5a813a6..c3bf59eaa0a 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -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 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 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; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index e5ed391a6eb..9e0d3c02b0e 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -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 deviceListeners = new ConcurrentHashSet<>(); + private final Set deviceListeners = ConcurrentHashMap.newKeySet(); private static final Set 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 configProperties) { + @Reference ShellyTranslationProvider translationProvider, @Reference HttpClientFactory httpClientFactory, + ComponentContext componentContext, Map 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); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java index 5b018c8e5b2..0e7df9d5a38 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java @@ -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; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java index 003f1555107..739861d62ea 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java @@ -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 relays; public ArrayList dimmers; + public ArrayList lights; public ArrayList emeters; public ArrayList 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 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 relays; // relay status public ArrayList meters; // current meter value + public ArrayList 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 inputs; // Firmware 1.5.6+ + + // Shelly UNI FW 1.9+ + public ArrayList 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 lights; - public ArrayList 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 lights; + public ArrayList meters; } public static class ShellySenseKeyCode { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 127c72a9048..00cdb38fa12 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -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; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyEventServlet.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyEventServlet.java index 5578e95e760..5d7863c14af 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyEventServlet.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyEventServlet.java @@ -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(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java index 0a34797b7d4..7a383f2a027 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java @@ -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 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 callApi(String uri, Class 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) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTInterface.java index ca8d861dc11..7073d71671d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTInterface.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTInterface.java @@ -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 blkMap); + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap); - public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, CoIotSensor s, - Map updates); + public void completeMissingSensorDefinition(Map sensorMap); + + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map updates, ShellyColorUtils col); + + public String getLastWakeup(); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java index 6d6f40ea82c..db9d7f614af 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java @@ -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 blkMap; protected final Map 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 blkMap, Map sensorMap) { @@ -64,7 +71,7 @@ public class ShellyCoIoTProtocol { } protected boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, CoIotSensor s, - Map updates) { + Map 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 updates, String group, String channel, State value) { + public static boolean updateChannel(Map 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 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 updates) { + protected void handleInputEvent(CoIotDescrSen sen, String type, int count, int serial, Map 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 blkMap) { - return sen; + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap) { + return sen != null ? sen : new CoIotDescrSen(); + } + + public void completeMissingSensorDefinition(Map sensorMap) { + } + + protected void addSensor(Map 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; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java index 4ef26e88290..c650efeea7f 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java @@ -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 sensorUpdates, CoIotDescrSen sen, CoIotSensor s, - Map updates) { + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map 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 blkMap) { + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map 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); + } } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java index ebc22077735..78e2cc7f01e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java @@ -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 sensorUpdates, CoIotDescrSen sen, CoIotSensor s, - Map updates) { + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map 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 blkMap) { - return sen; + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map 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 sensorMap) { + if (profile.isDuo && profile.inColor) { + addSensor(sensorMap, "4101", ID_4101_DESCR); + addSensor(sensorMap, "4103", ID_4103_DESCR); + } + super.completeMissingSensorDefinition(sensorMap); } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java index d2ece5034ec..8b2db841032 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java @@ -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 blkMap = new LinkedHashMap<>(); private Map 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 updates = new TreeMap(); logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size()); int failed = 0; + ShellyColorUtils col = new ShellyColorUtils(); for (int i = 0; i < sensorUpdates.size(); i++) { try { CoIotSensor s = sensorUpdates.get(i); - if (!sensorMap.containsKey(s.id)) { - logger.debug("{}: Invalid id in sensor description: {}, index {}", thingName, s.id, i); - failed++; - continue; - } CoIotDescrSen sen = sensorMap.get(s.id); - Objects.requireNonNull(sen); + if (sen == null) { + logger.debug("{}: Unable to sensor definition for id={}, payload={}", thingName, s.id, payload); + continue; + } // find matching sensor definition from device description, use the Link ID as index + CoIotDescrBlk element = null; sen = coiot.fixDescription(sen, blkMap); - if (!blkMap.containsKey(sen.links)) { - logger.debug("{}: Invalid CoAP description: sen.links({}", thingName, getString(sen.links)); + element = blkMap.get(sen.links); + if (element == null) { + logger.debug("{}: Unable to find BLK for link {} from sen.id={}, payload={}", thingName, sen.links, + sen.id, payload); continue; } - - if (!blkMap.containsKey(sen.links)) { - logger.debug("{}: Unable to find BLK for link {} from sen.id={}", thingName, sen.links, sen.id); - continue; - } - CoIotDescrBlk element = blkMap.get(sen.links); logger.trace("{}: Sensor value[{}]: id={}, Value={} ({}, Type={}, Range={}, Link={}: {})", thingName, i, s.id, getString(s.valueStr).isEmpty() ? s.value : s.valueStr, sen.desc, sen.type, sen.range, sen.links, element.desc); - if (!coiot.handleStatusUpdate(sensorUpdates, sen, s, updates)) { + if (!coiot.handleStatusUpdate(sensorUpdates, sen, serial, s, updates, col)) { logger.debug("{}: CoIoT data for id {}, type {}/{} not processed, value={}; payload={}", thingName, sen.id, sen.type, sen.desc, s.value, payload); } @@ -418,7 +441,8 @@ public class ShellyCoapHandler implements ShellyCoapListener { if (!updates.isEmpty()) { int updated = 0; for (Map.Entry 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 diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapJSonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapJSonDTO.java index 35612611ab0..352e928a938 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapJSonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapJSonDTO.java @@ -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") diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapServer.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapServer.java index 1cfc49f4a96..c1cc0399d8c 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapServer.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapServer.java @@ -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 coapListeners = new ConcurrentHashSet<>(); + private final Set 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(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java index 27e7570c89a..1d53b441e60 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java @@ -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 properties) { for (Map.Entry 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; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java index d2626a01834..6e57a2c8e29 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java @@ -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 diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 843150f4631..81bc2415828 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -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); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java index 4e58cce2cd5..67336c2b7e7 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java @@ -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; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 62be437c9bb..ef9983c38a6 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -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 index e49f7dc51cb..00000000000 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyChannelDefinitionsDTO.java +++ /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 of channels to be added to the thing - */ - public static Map createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile, - final ShellySettingsStatus status) { - Map 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 of channels to be added to the thing - */ - public static Map createRelayChannels(final Thing thing, final ShellyDeviceProfile profile, - final ShellyStatusRelay relay, int idx) { - Map 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 createRollerChannels(Thing thing, final ShellyControlRoller roller) { - Map add = new LinkedHashMap<>(); - addChannel(thing, add, roller.state != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE); - return add; - } - - public static Map createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) { - Map 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 createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter, - String group) { - Map 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 createSensorChannels(final Thing thing, final ShellyDeviceProfile profile, - final ShellyStatusSensor sdata) { - Map 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 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 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 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 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/handler/ShellyColorUtils.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyColorUtils.java index 5fc9fc89039..54efbe09af6 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyColorUtils.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyColorUtils.java @@ -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)); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index c4945dead95..e1b89986754 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -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)); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java index 3d75df99b6c..ebc220e6502 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java @@ -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; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyProtectedHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyProtectedHandler.java index b8dd9d918a8..38880fd9a2c 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyProtectedHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyProtectedHandler.java @@ -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 * diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java index 152b48daf9b..c011644730e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java @@ -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 index 00000000000..0cce91ea6a4 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -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 of channels to be added to the thing + */ + public static Map createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellySettingsStatus status) { + Map 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 of channels to be added to the thing + */ + public static Map createRelayChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellyStatusRelay relay, int idx) { + Map 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 createDimmerChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellySettingsStatus dstatus, int idx) { + Map 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 createLightChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellyStatusLightChannel status, int idx) { + Map 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 createInputChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellySettingsStatus status, String group) { + Map 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 createRollerChannels(Thing thing, final ShellyControlRoller roller) { + Map 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 createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) { + Map 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 createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter, + String group) { + Map 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 createSensorChannels(final Thing thing, final ShellyDeviceProfile profile, + final ShellyStatusSensor sdata) { + Map 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 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 tags = new HashSet<>(); + public @Nullable Unit unit; + public Optional min = Optional.empty(); + public Optional max = Optional.empty(); + public Optional step = Optional.empty(); + public Optional 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 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 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/util/ShellyTranslationProvider.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyTranslationProvider.java similarity index 76% rename from bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyTranslationProvider.java rename to bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyTranslationProvider.java index 0b6134f2840..b39225c99a3 100644 --- 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/provider/ShellyTranslationProvider.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.shelly.internal.util; +package org.openhab.binding.shelly.internal.provider; import java.util.Locale; @@ -19,6 +19,10 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TranslationProvider; import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** * {@link ShellyTranslationProvider} provides i18n message lookup @@ -26,24 +30,20 @@ import org.osgi.framework.Bundle; * @author Markus Michels - Initial contribution */ @NonNullByDefault +@Component(service = ShellyTranslationProvider.class) public class ShellyTranslationProvider { - private final Bundle bundle; private final TranslationProvider i18nProvider; private final LocaleProvider localeProvider; - public ShellyTranslationProvider(Bundle bundle, TranslationProvider i18nProvider, LocaleProvider localeProvider) { - this.bundle = bundle; + @Activate + public ShellyTranslationProvider(@Reference TranslationProvider i18nProvider, + @Reference LocaleProvider localeProvider) { + this.bundle = FrameworkUtil.getBundle(this.getClass()); this.i18nProvider = i18nProvider; this.localeProvider = localeProvider; } - public ShellyTranslationProvider(final ShellyTranslationProvider other) { - this.bundle = other.bundle; - this.i18nProvider = other.i18nProvider; - this.localeProvider = other.localeProvider; - } - public @Nullable String get(String key, @Nullable Object... arguments) { return getText(key.contains("@text/") || key.contains(".shelly.") ? key : "message." + key, arguments); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java index f9b42823dd2..6b573b9b8a0 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java @@ -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/ShellyUtils.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java index dd74d6d29a6..5767d9b964a 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java @@ -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 fromJson(Gson gson, @Nullable String json, Class 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 fromJson(Gson gson, @Nullable String json, Class 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(); } diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/binding/binding.xml index 2a61d171b11..dc4b3cb4be8 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/binding/binding.xml @@ -5,22 +5,28 @@ Shelly Binding This binding supports the Shelly series of devices. + Markus Michels - + admin - - Default userId to access protected Shelly devices. + + @text/binding.shelly.config.defaultUserId.description admin - - Default password to access protected Shelly devices. + + @text/binding.shelly.config.defaultPassword.description + + + + @text/binding.shelly.config.localIP.description + true - - True: Enable CoIoT events by default when firmware 1.6+ is detected + + @text/binding.shelly.config.autoCoIoT.description diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml index e5b07c39ce8..6e28459b3bd 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml @@ -43,8 +43,8 @@ true true - - + + Interval to query an update from the device. 60 seconds @@ -62,6 +62,16 @@ Password for HTTP API access. password + + + Which position favorite should be used for UP (0=none, favorites are defined in the Shelly App) + 0 + + + + Which position favorite should be used for DOWN (0=none, favorites are defined in the Shelly App) + 0 + IP-Address of the Shelly device. @@ -71,7 +81,7 @@ True if the binding should register to get Roller Events. true - true + false @@ -79,8 +89,8 @@ true true - - + + Interval to query an update from the device. 60 seconds @@ -104,7 +114,8 @@ - true: Turn device ON if brightness>0 is set; false: don't touch power status when brightness is set. + true: Turn device ON if brightness above 0 is set; false: don't touch power status when brightness is + set. true @@ -131,8 +142,8 @@ true true - - + + Interval to query an update from the device. 60 seconds @@ -172,8 +183,8 @@ true true - - + + Interval to query an update from the device. 60 seconds @@ -207,8 +218,8 @@ true true - - + + Interval to query an update from the device. 60 seconds @@ -250,10 +261,10 @@ true true - - + + Interval to query an update from the device. - 3600 + 900 seconds true @@ -286,10 +297,10 @@ true true - + Interval to query an update from the device. - 3600 + 60 seconds true diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index 6ab9cecfdaf..abc679655cc 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -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 diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties index 3d3890cfdaa..a3ed00cb4ab 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly_de.properties @@ -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 diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml index 98808b656a3..3e2ba36c5d5 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml @@ -69,14 +69,14 @@ Number:ElectricPotential Battery voltage in V - + Number:Time Number of seconds since the device was powered up - + @@ -102,7 +102,7 @@ CurrentTemperature - + diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/lights.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/lights.xml index b3daaf21b0a..a7e2e932473 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/lights.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/lights.xml @@ -32,6 +32,21 @@ + + + Shelly Duo Color Bulb in Color or White Mode + + + + + + + + + deviceName + + + Shelly Vintage Light Bulb @@ -109,12 +124,6 @@ Control your light channels - - - - - - @@ -211,7 +220,7 @@ blue, 0..255, only in Color Mode - + Dimmer white, 0..255, applies in Color Mode @@ -234,6 +243,7 @@ Brightness: 0..100% DimmableLight + Number diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml index 649d1093864..c5d6e696d12 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml @@ -6,7 +6,7 @@ - Shelly1 device with single relay + Shelly1 device with a single relay @@ -17,6 +17,20 @@ + + + Shelly 1L device with a single relay + + + + + + + + deviceName + + + Shelly1PM device with single relay and power meter @@ -218,6 +232,25 @@ + + + Embedded Shelly device + + + + + + + + + + + + + deviceName + + + Shelly Dimmer @@ -260,62 +293,38 @@ + + + Shelly UNI device + + + + + + + deviceName + + + A Shelly relay channel - - - - - - - - A Shelly relay channel - - - - - - A Shelly Dimmer channel - - - - - - - - Input Status - - - - - - Controlling the roller mode - - - - - - - - - @@ -337,13 +346,13 @@ Number:Time ON: After the output was turned off it turns on automatically after xx seconds; 0 disables the timer - + Number:Time ON: After the output was turned on it turns off automatically after xx seconds; 0 disables the timer - + Switch @@ -362,7 +371,13 @@ Dimmer Position the roller (100..0 in %, where 100%=open, 0%=closed) - + + + + Number + + Set roller position by selecting favorite 1-4 (needs to be defined in the Shelly App, 0=n/a) + String @@ -400,6 +415,13 @@ + + Switch + + Status of the safety switch + + + Switch @@ -431,91 +453,77 @@ Number:Power Current power consumption in Watt - + Number:Power Accumulated current power consumption in Watt from all meters - + Number:Power - Accumulated total power consumption in kw/h from all meters - + Accumulated total power consumption from all meters + Number:Power - Accumulated returned power consumption in kw/h from all meters - + Accumulated returned power consumption from all meters + Number:Power Instantaneous reactive power in Watts (W) - + Number:Energy Last power consumption #1 - one rounded minute - - - - - Number:Energy - - Last power consumption #2 - one rounded minute - - - - - Number:Energy - - Last power consumption #3 - one rounded minute - + Number:Energy - Total power consumption in kw/h - + Total power consumption + Number:Energy - - Total returned energy in kw/h - + + Total returned energy + Number:ElectricPotential RMS voltage, Volts - + Number:ElectricPotential Current in A - + Number - + diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml index d877c0c694c..172ae7e338c 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml @@ -9,7 +9,7 @@ Shelly H&T Sensor - + @@ -23,7 +23,7 @@ Shelly Smoke Sensor (battery powered) - + @@ -37,7 +37,7 @@ Shelly Gas Sensor - + @@ -49,7 +49,7 @@ Shelly Flood Sensor (battery powered) - + @@ -63,7 +63,7 @@ Shelly Door/Window Sensor (battery powered) - + @@ -77,7 +77,7 @@ Shelly Door/Window 2 Sensor (battery powered) - + @@ -91,7 +91,7 @@ Shelly Sense Remote IR Controller - + @@ -114,33 +114,25 @@ - + + + Shelly Motion Sensor (battery powered) + + + + + + + + deviceName + + + + - Data from the HT Sensor + Data from the various sensors - - - Data from the Flood Sensor - - - - - Data from the Flood Sensor - - - - Data from the Gas Sensor - - - - Data from the sensors - - - - - Data from the Sense sensors - @@ -154,12 +146,6 @@ Status of the Button - - - - - - @@ -221,7 +207,7 @@ Number Event Count - + @@ -233,7 +219,7 @@ CurrentTemperature - + @@ -244,7 +230,7 @@ CurrentTemperature - + @@ -255,7 +241,7 @@ CurrentHumidity - + @@ -265,7 +251,7 @@ CurrentHumidity - + Switch @@ -285,7 +271,7 @@ Number:Illuminance Brightness from the sensor (Lux) - + @@ -300,6 +286,20 @@ + + DateTime + + Timestamp of last detected motion + + + + + Switch + + ON: Motion detected + + + Switch @@ -311,14 +311,21 @@ Number:Angle Tilt in degrees (requires calibration) - + Number:Density Gas concentration in ppm - + + + + + Number:ElectricPotential + + ADC voltage in V +