[shelly] Add Plus/Pro support, some bugfixes (#13439)
* Plus/Pro support and some refactoring, bugfixes Signed-off-by: Markus Michels <markus7017@gmail.com> * Review changes applied Signed-off-by: Markus Michels <markus7017@gmail.com> * review changes Signed-off-by: Markus Michels <markus7017@gmail.com> Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
parent
d2dde9768f
commit
6835c278e1
|
@ -35,7 +35,6 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
|
||||||
| shelly1l | Shelly 1L Single Relay Switch | SHSW-L |
|
| shelly1l | Shelly 1L Single Relay Switch | SHSW-L |
|
||||||
| shelly1pm | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM |
|
| shelly1pm | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM |
|
||||||
| shelly2-relay | Shelly Double Relay Switch in relay mode | SHSW-21 |
|
| shelly2-relay | Shelly Double Relay Switch in relay mode | SHSW-21 |
|
||||||
| shelly2-roller | Shelly2 in Roller Mode | SHSW-21 |
|
|
||||||
| shelly25-relay | Shelly 2.5 in Relay Switch | SHSW-25 |
|
| shelly25-relay | Shelly 2.5 in Relay Switch | SHSW-25 |
|
||||||
| shelly25-roller | Shelly 2.5 in Roller Mode | SHSW-25 |
|
| shelly25-roller | Shelly 2.5 in Roller Mode | SHSW-25 |
|
||||||
| shelly4pro | Shelly 4x Relay Switch | SHSW-44 |
|
| shelly4pro | Shelly 4x Relay Switch | SHSW-44 |
|
||||||
|
@ -55,7 +54,7 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
|
||||||
| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 |
|
| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 |
|
||||||
| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 |
|
| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 |
|
||||||
| shellyvintage | Shelly Vintage (White Mode) | SHVIN-1 |
|
| shellyvintage | Shelly Vintage (White Mode) | SHVIN-1 |
|
||||||
| shellyht | Shelly Sensor (temp+humidity) | SHHT-1 |
|
| shellyht | Shelly Sensor (temperature+humidity) | SHHT-1 |
|
||||||
| shellyflood | Shelly Flood Sensor | SHWT-1 |
|
| shellyflood | Shelly Flood Sensor | SHWT-1 |
|
||||||
| shellysmoke | Shelly Smoke Sensor | SHSM-1 |
|
| shellysmoke | Shelly Smoke Sensor | SHSM-1 |
|
||||||
| shellymotion | Shelly Motion Sensor | SHMOS-01 |
|
| shellymotion | Shelly Motion Sensor | SHMOS-01 |
|
||||||
|
@ -69,6 +68,31 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
|
||||||
| shellytrv | Shelly TRV | SHTRV-01 |
|
| shellytrv | Shelly TRV | SHTRV-01 |
|
||||||
| shellydevice | A password protected Shelly device or an unknown type | |
|
| shellydevice | A password protected Shelly device or an unknown type | |
|
||||||
|
|
||||||
|
### Generation 2 Plus series:
|
||||||
|
|
||||||
|
| thing-type | Model | Vendor ID |
|
||||||
|
|---------------------|----------------------------------------------------------|----------------|
|
||||||
|
| shellyplus1 | Shelly Plus 1 with 1x relay | SNSW-001X16EU |
|
||||||
|
| shellyplus1pm | Shelly Plus 1PM with 1x relay + power meter | SNSW-001P16EU |
|
||||||
|
| shellyplus2pm-relay | Shelly Plus 2PM with 2x relay + power meter, relay mode | SNSW-002P16EU |
|
||||||
|
| shellyplus2pm-roller| Shelly Plus 2PM with 2x relay + power meter, roller mode | SNSW-002P16EU |
|
||||||
|
| shellyplusi4 | Shelly Plus i4 with 4x AC input | SNSN-0024X |
|
||||||
|
| shellyplusi4dc | Shelly Plus i4 with 4x DC input | SNSN-0D24X |
|
||||||
|
| shellyplusht | Shelly Plus HT with temperature + humidity sensor | SNSN-0013A |
|
||||||
|
|
||||||
|
### Generation 2 Pro series:
|
||||||
|
|
||||||
|
| thing-type | Model | Vendor ID |
|
||||||
|
|---------------------|----------------------------------------------------------|----------------|
|
||||||
|
| shellypro1 | Shelly Pro 1 with 1x relay | SPSW-001XE16EU |
|
||||||
|
| shellypro1pm | Shelly Pro 1 PM with 1x relay + power meter | SPSW-001PE16EU |
|
||||||
|
| shellypro2-relay | Shelly Pro 2 with 2x relay, relay mode | SPSW-002XE16EU |
|
||||||
|
| shellypro2pm-relay | Shelly Pro 2 PM with 2x relay + power meter, relay mode | SPSW-002PE16EU |
|
||||||
|
| shellypro2pm-roller | Shelly Pro 2 PM with 2x relay + power meter, roller mode | SPSW-002PE16EU |
|
||||||
|
| shellypro3 | Shelly Pro 3 with 3x relay (dry contacts) | SPSW-003XE16EU |
|
||||||
|
| shellypro4pm | Shelly Pro 4 PM with 4x relay + power meter | SPSW-004PE16EU |
|
||||||
|
|
||||||
|
|
||||||
## Binding Configuration
|
## Binding Configuration
|
||||||
|
|
||||||
The binding has the following configuration options:
|
The binding has the following configuration options:
|
||||||
|
@ -157,11 +181,13 @@ Values 1-4 are selecting the corresponding favorite id in the Shelly App, 0 mean
|
||||||
The binding sets the following Thing status depending on the device status:
|
The binding sets the following Thing status depending on the device status:
|
||||||
|
|
||||||
| Status |Description |
|
| Status |Description |
|
||||||
|--------------|------------------------------------------------------------------|
|
|----------------|------------------------------------------------------------------|
|
||||||
| INITIALIZING | This is the default status while initializing the Thing. Once the initialization is triggered the Thing switches to Status UNKNOWN. |
|
| INITIALIZING | This is the default status while initializing the Thing. Once the initialization is triggered the Thing switches to Status UNKNOWN. |
|
||||||
| UNKNOWN | Indicates that the status is currently unknown, which must not show a problem. Usually the Thing stays in this status when the device is in sleep mode. Once the device is reachable and was initialized the Thing switches to status ONLINE.|
|
| UNKNOWN | Indicates that the status is currently unknown, which must not show a problem. Usually the Thing stays in this status when the device is in sleep mode. Once the device is reachable and was initialized the Thing switches to status ONLINE.|
|
||||||
| ONLINE | ONLINE indicates that the device can be accessed and is responding properly. Battery powered devices also stay ONLINE when in sleep mode. The binding has an integrated watchdog timer supervising the device, see below. The Thing switches to status OFFLINE when some type of communication error occurs. |
|
| ONLINE | ONLINE indicates that the device can be accessed and is responding properly. Battery powered devices also stay ONLINE when in sleep mode. The binding has an integrated watchdog timer supervising the device, see below. The Thing switches to status OFFLINE when some type of communication error occurs. |
|
||||||
| OFFLINE | Communication with the device failed. Check the Thing status in the UI and openHAB's log for an indication of the error. Try restarting OH or deleting and re-discovering the Thing. You could also post to the community thread if the problem persists. |
|
| OFFLINE | Communication with the device failed. Check the Thing status in the UI and openHAB's log for an indication of the error. Try restarting OH or deleting and re-discovering the Thing. You could also post to the community thread if the problem persists. |
|
||||||
|
| CONFIG PENDING | CONFIG PENDING description |
|
||||||
|
| ERROR: COMM | ERROR: COMM description |
|
||||||
|
|
||||||
`Battery powered devices:`
|
`Battery powered devices:`
|
||||||
If the device is in sleep mode and can't be reached by the binding, the Thing will change into CONFIG_PENDING.
|
If the device is in sleep mode and can't be reached by the binding, the Thing will change into CONFIG_PENDING.
|
||||||
|
@ -389,7 +415,6 @@ In this case the is no real measurement based on power consumption, but the Shel
|
||||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
| |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 |
|
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||||
| |button |Trigger |yes |Event trigger, see section Button Events |
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
|
||||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||||
| |lastPower1 |Number |yes |Energy consumption for a round minute, 1 minute ago |
|
| |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 Watts since the device powered up (resets on restart)|
|
||||||
|
@ -469,7 +494,7 @@ The Thing id is derived from the service name, so that's the reason why the Thin
|
||||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
|
||||||
### Shelly 2 - relay mode thing-type: shelly2-relay)
|
### Shelly 2 - relay mode (thing-type: shelly2-relay)
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|Group |Channel |Type |read-only|Description |
|
||||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
@ -480,7 +505,6 @@ The Thing id is derived from the service name, so that's the reason why the Thin
|
||||||
| |autoOff |Number |r/w |Relay #1: Sets a timer to turn the device OFF after every ON 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 |
|
| |timerActive |Switch |yes |Relay #1: ON: An auto-on/off timer is active |
|
||||||
| |button |Trigger |yes |Event trigger, see section Button Events |
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
|
||||||
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
||||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
| |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 |
|
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||||
|
@ -488,34 +512,11 @@ The Thing id is derived from the service name, so that's the reason why the Thin
|
||||||
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
||||||
| |button |Trigger |yes |Event trigger, see section Button Events |
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
|
||||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||||
| |lastPower1 |Number |yes |Energy consumption for a round minute, 1 minute ago |
|
| |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 Watts since the device powered up (resets on restart)|
|
||||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
### Shelly 2 - roller mode thing-type: shelly2-roller)
|
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|
||||||
|----------|-------------|---------|---------|--------------------------------------------------------------------------------------|
|
|
||||||
|roller |control |Rollershutter|r/w |can be open (0%), stop, or close (100%); could also handle ON (open) and OFF (close) |
|
|
||||||
| |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 |
|
|
||||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
|
||||||
| |lastPower1 |Number |yes |Accumulated energy consumption in Watts for the full last minute |
|
|
||||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (reset on restart) |
|
|
||||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
|
||||||
|
|
||||||
`Note: The Roller should be calibrated using the device Web UI or Shelly App, otherwise the position can .`
|
|
||||||
|
|
||||||
The roller positioning calibration has to be performed using the Shelly Web UI or App before the position can be set in percent.
|
|
||||||
Refer to [Smartify Roller Shutters with openHAB and Shelly](doc/UseCaseSmartRoller.md) for more information on roller integration.
|
|
||||||
|
|
||||||
### Shelly 2.5 - relay mode (thing-type:shelly25-relay)
|
### Shelly 2.5 - relay mode (thing-type:shelly25-relay)
|
||||||
|
|
||||||
The Shelly 2.5 includes 2 meters, one for each channel.
|
The Shelly 2.5 includes 2 meters, one for each channel.
|
||||||
|
@ -605,14 +606,16 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|Group |Channel |Type |read-only|Description |
|
||||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||||
|status |input1 |Switch |yes |State of Input 1 |
|
|status1 |input |Switch |yes |State of Input 1 |
|
||||||
| |input2 |Switch |yes |State of Input 2 |
|
|
||||||
| |input3 |Switch |yes |State of Input 3 |
|
|
||||||
| |button |Trigger |yes |Event trigger: Event trigger, see section Button Events |
|
| |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 |
|
| |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
|
||||||
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
|
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
|
||||||
|
|status2 | | | |Same for Input 2 |
|
||||||
|
|status3 | | | |Same for Input 3 |
|
||||||
|
|
||||||
### Shelly UNI - Low voltage sensor/actor: shellyuni)
|
Channels lastEvent and eventCount are only available if input type is set to momentary button
|
||||||
|
|
||||||
|
### Shelly UNI (thing-type: shellyuni)
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|Group |Channel |Type |read-only|Description |
|
||||||
|----------|-------------|---------|---------|----------------------------------------------------------------------------|
|
|----------|-------------|---------|---------|----------------------------------------------------------------------------|
|
||||||
|
@ -659,7 +662,7 @@ Beside channel `hsb` the binding also offers the `white` channel (hsb as only RG
|
||||||
Or control each color separately with channels `red`, `blue`, `green` (those are advanced channels).
|
Or control each color separately with channels `red`, `blue`, `green` (those are advanced channels).
|
||||||
|
|
||||||
|
|
||||||
#### Shelly Duo (thing-type: shellybulbduo)
|
### Shelly Duo (thing-type: shellybulbduo)
|
||||||
|
|
||||||
This information applies to the Shelly Duo-1 as well as the Duo White for the G10 socket.
|
This information applies to the Shelly Duo-1 as well as the Duo White for the G10 socket.
|
||||||
|
|
||||||
|
@ -690,7 +693,7 @@ This information applies to the Shelly Duo-1 as well as the Duo White for the G1
|
||||||
| |totalKWH |Number |yes |Total energy consumption in kWh 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 |
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
## Shelly Duo Color (thing-type: shellyduocolor-color)
|
### Shelly Duo Color (thing-type: shellyduocolor-color)
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|Group |Channel |Type |read-only|Description |
|
||||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||||
|
@ -719,7 +722,7 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
|
||||||
`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
|
`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)
|
### Shelly Duo RGBW Color Bulb (thing-type: shellycolorbulb)
|
||||||
|
|
||||||
|Group |Channel |Type |read-only|Description |
|
|Group |Channel |Type |read-only|Description |
|
||||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||||
|
@ -906,7 +909,7 @@ You should calibrate the valve using the device Web UI or Shelly App before star
|
||||||
|control |targetTemp |Number |no |Temperature in °C: 4=Low/Min; 5..30=target temperature;31=Hi/Max |
|
|control |targetTemp |Number |no |Temperature in °C: 4=Low/Min; 5..30=target temperature;31=Hi/Max |
|
||||||
| |position |Dimmer |no |Set valve to manual mode (0..100%) disables auto-temp) |
|
| |position |Dimmer |no |Set valve to manual mode (0..100%) disables auto-temp) |
|
||||||
| |mode |String |no |Switch between manual and automatic mode |
|
| |mode |String |no |Switch between manual and automatic mode |
|
||||||
| |selectedProfile|String |no |Select profile: Profile name or 0=disable, 1-n: profile index |
|
| |selectedProfile|String |no |Select profile Id: "0"=disable, "1"-"n": profile index |
|
||||||
| |boost |Number |no |Enable/disable boost mode (full heating power) |
|
| |boost |Number |no |Enable/disable boost mode (full heating power) |
|
||||||
| |boostTimer |Number |no |Number of minutes to heat at full power while boost mode is enabled |
|
| |boostTimer |Number |no |Number of minutes to heat at full power while boost mode is enabled |
|
||||||
| |schedule |Switch |yes |ON: Schedule is active |
|
| |schedule |Switch |yes |ON: Schedule is active |
|
||||||
|
@ -964,6 +967,244 @@ You should calibrate the valve using the device Web UI or Shelly App before star
|
||||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||||
| |batteryAlert |Switch |yes |Low battery alert |
|
| |batteryAlert |Switch |yes |Low battery alert |
|
||||||
|
|
||||||
|
## Shelly Plus Series
|
||||||
|
|
||||||
|
### Shelly Plus 1 (thing-type: shellyplus1)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|
||||||
|
### Shelly Plus 1PM (thing-type: shellyplus1pm)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
### Shelly Plus 2PM - relay mode (thing-type: shellyplus2pm-relay)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|meter1 |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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |autoOn |Number |r/w |Relay #2: Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||||
|
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
|
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|meter2 |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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
### Shelly Plus 2PM - roller mode (thing-type: shellyplus2pm-roller)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|-------------------------------------------------------------------------------------|
|
||||||
|
|roller |control |Rollershutter |r/w |can be open (0%), stop, or close (100%); could also handle ON (open) and OFF (close) |
|
||||||
|
| |rollerPos |Dimmer |r/w |Roller position: 100%=open...0%=closed; gets updated when the roller stopped |
|
||||||
|
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||||
|
| |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 |
|
||||||
|
| |event |Trigger |yes |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP |
|
||||||
|
|meter | | | |See group meter description |
|
||||||
|
|
||||||
|
The roller positioning calibration has to be performed using the Shelly Web UI or App before the position can be set in percent.
|
||||||
|
Refer to [Smartify Roller Shutters with openHAB and Shelly](doc/UseCaseSmartRoller.md) for more information on roller integration.
|
||||||
|
|
||||||
|
### Shelly Plus i4, i4DC (thing-types: shellyplusi4, shellyplusi4dc)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||||
|
|status1 |input |Switch |yes |State of Input 1 |
|
||||||
|
| |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 |Counter gets incremented every time the device issues a button event. |
|
||||||
|
|status2 | | | |Same for Input 2 |
|
||||||
|
|status3 | | | |Same for Input 3 |
|
||||||
|
|status4 | | | |Same for Input 4 |
|
||||||
|
|
||||||
|
Channels lastEvent and eventCount are only available if input type is set to momentary button
|
||||||
|
|
||||||
|
### Shelly Plus HT (thing-type: shellyplusht)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||||
|
|sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit |
|
||||||
|
| |humidity |Number |yes |Relative humidity in % |
|
||||||
|
| |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 Pro Series
|
||||||
|
|
||||||
|
### Shelly Pro 1 (thing-type: shellypro1)
|
||||||
|
|
||||||
|
|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 |
|
||||||
|
|
||||||
|
### Shelly Pro 1 PM (thing-type: shellypro1pm)
|
||||||
|
|
||||||
|
|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 |
|
||||||
|
| |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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
|
||||||
|
### Shelly Pro 2 (thing-type: shellypro2-relay)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |autoOn |Number |r/w |Relay #2: Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||||
|
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
|
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|
||||||
|
|
||||||
|
### Shelly Pro 2 PM - relay mode (thing-type: shellypro2pm-relay)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |autoOn |Number |r/w |Relay #2: Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||||
|
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
|
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
### Shelly Pro 2 PM - roller mode (thing-type: shellypro2pm-roller)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|-------------------------------------------------------------------------------------|
|
||||||
|
|roller |control |Rollershutter |r/w |can be open (0%), stop, or close (100%); could also handle ON (open) and OFF (close) |
|
||||||
|
| |rollerPos |Dimmer |r/w |Roller position: 100%=open...0%=closed; gets updated when the roller stopped |
|
||||||
|
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||||
|
| |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 |
|
||||||
|
| |event |Trigger |yes |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP |
|
||||||
|
|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)|
|
||||||
|
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||||
|
|
||||||
|
|
||||||
|
### Shelly Pro 3 (thing-type: shellypro3)
|
||||||
|
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|relay2 |output |Switch |r/w |Relay #2: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |autoOn |Number |r/w |Relay #2: Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||||
|
| |autoOff |Number |r/w |Relay #2: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
|
| |timerActive |Switch |yes |Relay #2: ON: An auto-on/off timer is active |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|relay3 |output |Switch |r/w |Relay #3: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |autoOn |Number |r/w |Relay #3: Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||||
|
| |autoOff |Number |r/w |Relay #3: Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||||
|
| |timerActive |Switch |yes |Relay #3: ON: An auto-on/off timer is active |
|
||||||
|
| |button |Trigger |yes |Relay #3: Event trigger, see section Button Events |
|
||||||
|
|
||||||
|
### Shelly Pro 4PM (thing-type: shelly4pro)
|
||||||
|
|Group |Channel |Type |read-only|Description |
|
||||||
|
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||||
|
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||||
|
| |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 |
|
||||||
|
| |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 |
|
||||||
|
| |button |Trigger |yes |Event trigger, see section Button Events |
|
||||||
|
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,15 @@
|
||||||
|
|
||||||
|
|
||||||
<artifactId>org.openhab.binding.shelly</artifactId>
|
<artifactId>org.openhab.binding.shelly</artifactId>
|
||||||
<name>openHAB Add-ons :: Bundles :: Shelly Binding</name>
|
<name>openHAB Add-ons :: Bundles :: Shelly Binding Gen1+2</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||||
|
<artifactId>websocket-server</artifactId>
|
||||||
|
<version>9.4.46.v20220331</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -35,18 +35,23 @@ public class ShellyBindingConstants {
|
||||||
public static final String BINDING_ID = "shelly";
|
public static final String BINDING_ID = "shelly";
|
||||||
public static final String SYSTEM_ID = "system";
|
public static final String SYSTEM_ID = "system";
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
|
||||||
.unmodifiableSet(Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM,
|
.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM,
|
||||||
THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER,
|
THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY25_RELAY,
|
||||||
THING_TYPE_SHELLY25_RELAY, THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
|
THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG, THING_TYPE_SHELLYPLUGS,
|
||||||
THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER,
|
THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2,
|
||||||
THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO,
|
THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE,
|
||||||
THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR,
|
THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR, THING_TYPE_SHELLYRGBW2_WHITE,
|
||||||
THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT, THING_TYPE_SHELLYTRV, THING_TYPE_SHELLYSENSE,
|
THING_TYPE_SHELLYHT, THING_TYPE_SHELLYTRV, THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE,
|
||||||
THING_TYPE_SHELLYEYE, THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD,
|
THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN,
|
||||||
THING_TYPE_SHELLYDOORWIN, THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1,
|
THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLYBUTTON2,
|
||||||
THING_TYPE_SHELLYBUTTON2, THING_TYPE_SHELLMOTION, THING_TYPE_SHELLMOTION,
|
THING_TYPE_SHELLMOTION, THING_TYPE_SHELLMOTION, THING_TYPE_SHELLYPLUS1, THING_TYPE_SHELLYPLUS1PM,
|
||||||
THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
|
THING_TYPE_SHELLYPLUS2PM_RELAY, THING_TYPE_SHELLYPLUS2PM_ROLLER, THING_TYPE_SHELLYPRO1,
|
||||||
|
THING_TYPE_SHELLYPRO1PM, THING_TYPE_SHELLYPRO2_RELAY, THING_TYPE_SHELLYPRO2PM_RELAY,
|
||||||
|
THING_TYPE_SHELLYPRO2PM_ROLLER, THING_TYPE_SHELLYPRO3, THING_TYPE_SHELLYPRO4PM,
|
||||||
|
THING_TYPE_SHELLYPLUSI4, THING_TYPE_SHELLYPLUSI4DC, THING_TYPE_SHELLYPLUSHT,
|
||||||
|
THING_TYPE_SHELLYPLUSPLUGUS, THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN)
|
||||||
|
.collect(Collectors.toSet()));
|
||||||
|
|
||||||
// Thing Configuration Properties
|
// Thing Configuration Properties
|
||||||
public static final String CONFIG_DEVICEIP = "deviceIp";
|
public static final String CONFIG_DEVICEIP = "deviceIp";
|
||||||
|
@ -218,6 +223,7 @@ public class ShellyBindingConstants {
|
||||||
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
|
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
|
||||||
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
|
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
|
||||||
public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature
|
public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature
|
||||||
|
public static final String SHELLY2_API_MIN_FWVERSION = "v0.10.2"; // Gen 2 minimum FW
|
||||||
|
|
||||||
// Alarm types/messages
|
// Alarm types/messages
|
||||||
public static final String ALARM_TYPE_NONE = "NONE";
|
public static final String ALARM_TYPE_NONE = "NONE";
|
||||||
|
@ -238,7 +244,8 @@ public class ShellyBindingConstants {
|
||||||
public static final String EVENT_TYPE_SENSORDATA = "report";
|
public static final String EVENT_TYPE_SENSORDATA = "report";
|
||||||
|
|
||||||
// URI for the EventServlet
|
// URI for the EventServlet
|
||||||
public static final String SHELLY_CALLBACK_URI = "/shelly/event";
|
public static final String SHELLY1_CALLBACK_URI = "/shelly/event";
|
||||||
|
public static final String SHELLY2_CALLBACK_URI = "/shelly/wsevent";
|
||||||
|
|
||||||
public static final int DIM_STEPSIZE = 5;
|
public static final int DIM_STEPSIZE = 5;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,12 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.shelly.internal.api;
|
package org.openhab.binding.shelly.internal.api;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.net.PortUnreachableException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -65,19 +70,21 @@ public class ShellyApiException extends Exception {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String message = nonNullString(super.getMessage());
|
String message = nonNullString(super.getMessage()).replace("java.util.concurrent.ExecutionException: ", "")
|
||||||
|
.replace("java.net.", "");
|
||||||
String cause = getCauseClass().toString();
|
String cause = getCauseClass().toString();
|
||||||
|
String url = apiResult.getUrl();
|
||||||
if (!isEmpty()) {
|
if (!isEmpty()) {
|
||||||
if (isUnknownHost()) {
|
if (isUnknownHost()) {
|
||||||
String[] string = message.split(": "); // java.net.UnknownHostException: api.rach.io
|
String[] string = message.split(": "); // java.net.UnknownHostException: api.rach.io
|
||||||
message = MessageFormat.format("Unable to connect to {0} (Unknown host / Network down / Low signal)",
|
message = MessageFormat.format("Unable to connect to {0} (Unknown host / Network down / Low signal)",
|
||||||
string[1]);
|
string[1]);
|
||||||
} else if (isMalformedURL()) {
|
} else if (isMalformedURL()) {
|
||||||
message = MessageFormat.format("Invalid URL: {0}", apiResult.getUrl());
|
message = "Invalid URL: " + url;
|
||||||
} else if (isTimeout()) {
|
} else if (isTimeout()) {
|
||||||
message = MessageFormat.format("Device unreachable or API Timeout ({0})", apiResult.getUrl());
|
message = "API Timeout for " + url;
|
||||||
} else {
|
} else if (!isConnectionError()) {
|
||||||
message = MessageFormat.format("{0} ({1})", message, cause);
|
message = message + "(" + cause + ")";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message = apiResult.toString();
|
message = apiResult.toString();
|
||||||
|
@ -91,23 +98,30 @@ public class ShellyApiException extends Exception {
|
||||||
|
|
||||||
public boolean isTimeout() {
|
public boolean isTimeout() {
|
||||||
Class<?> extype = !isEmpty() ? getCauseClass() : null;
|
Class<?> extype = !isEmpty() ? getCauseClass() : null;
|
||||||
return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
|
return (extype != null) && ((extype == TimeoutException.class) || extype == InterruptedException.class
|
||||||
|| (extype == InterruptedException.class)
|
|| extype == SocketTimeoutException.class
|
||||||
|| nonNullString(getMessage()).toLowerCase().contains("timeout"));
|
|| nonNullString(getMessage()).toLowerCase().contains("timeout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isConnectionError() {
|
||||||
|
Class<?> exType = getCauseClass();
|
||||||
|
return isUnknownHost() || isMalformedURL() || exType == ConnectException.class
|
||||||
|
|| exType == SocketException.class || exType == PortUnreachableException.class
|
||||||
|
|| exType == NoRouteToHostException.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnknownHost() {
|
||||||
|
return getCauseClass() == UnknownHostException.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMalformedURL() {
|
||||||
|
return getCauseClass() == MalformedURLException.class;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isHttpAccessUnauthorized() {
|
public boolean isHttpAccessUnauthorized() {
|
||||||
return apiResult.isHttpAccessUnauthorized();
|
return apiResult.isHttpAccessUnauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnknownHost() {
|
|
||||||
return getCauseClass() == MalformedURLException.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMalformedURL() {
|
|
||||||
return getCauseClass() == UnknownHostException.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isJSONException() {
|
public boolean isJSONException() {
|
||||||
return getCauseClass() == JsonSyntaxException.class;
|
return getCauseClass() == JsonSyntaxException.class;
|
||||||
}
|
}
|
||||||
|
@ -126,6 +140,9 @@ public class ShellyApiException extends Exception {
|
||||||
|
|
||||||
private Class<?> getCauseClass() {
|
private Class<?> getCauseClass() {
|
||||||
Throwable cause = getCause();
|
Throwable cause = getCause();
|
||||||
|
if (cause != null && cause.getClass() == ExecutionException.class) {
|
||||||
|
cause = cause.getCause();
|
||||||
|
}
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
return cause.getClass();
|
return cause.getClass();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerSt
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
||||||
|
@ -59,7 +60,7 @@ public interface ShellyApiInterface {
|
||||||
|
|
||||||
public void setRollerPos(int relayIndex, int position) throws ShellyApiException;
|
public void setRollerPos(int relayIndex, int position) throws ShellyApiException;
|
||||||
|
|
||||||
public void setTimer(int index, String timerName, int value) throws ShellyApiException;
|
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException;
|
||||||
|
|
||||||
public ShellyStatusSensor getSensorStatus() throws ShellyApiException;
|
public ShellyStatusSensor getSensorStatus() throws ShellyApiException;
|
||||||
|
|
||||||
|
@ -92,12 +93,20 @@ public interface ShellyApiInterface {
|
||||||
|
|
||||||
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException;
|
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException;
|
||||||
|
|
||||||
|
public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException;
|
||||||
|
|
||||||
public ShellySettingsLogin getLoginSettings() throws ShellyApiException;
|
public ShellySettingsLogin getLoginSettings() throws ShellyApiException;
|
||||||
|
|
||||||
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException;
|
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException;
|
||||||
|
|
||||||
public String setWiFiRecovery(boolean enable) throws ShellyApiException;
|
public String setWiFiRecovery(boolean enable) throws ShellyApiException;
|
||||||
|
|
||||||
|
public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException;
|
||||||
|
|
||||||
|
public boolean setEthernet(boolean enable) throws ShellyApiException;
|
||||||
|
|
||||||
|
public boolean setBluetooth(boolean enable) throws ShellyApiException;
|
||||||
|
|
||||||
public String deviceReboot() throws ShellyApiException;
|
public String deviceReboot() throws ShellyApiException;
|
||||||
|
|
||||||
public String setDebug(boolean enabled) throws ShellyApiException;
|
public String setDebug(boolean enabled) throws ShellyApiException;
|
||||||
|
@ -123,4 +132,6 @@ public interface ShellyApiInterface {
|
||||||
public void setActionURLs() throws ShellyApiException;
|
public void setActionURLs() throws ShellyApiException;
|
||||||
|
|
||||||
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;
|
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;
|
||||||
|
|
||||||
|
public void close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,15 +112,20 @@ public class ShellyDeviceProfile {
|
||||||
public ShellyDeviceProfile() {
|
public ShellyDeviceProfile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShellyDeviceProfile initialize(String thingType, String json) throws ShellyApiException {
|
public ShellyDeviceProfile initialize(String thingType, String jsonIn) throws ShellyApiException {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
|
|
||||||
initialized = false;
|
initialized = false;
|
||||||
|
|
||||||
initFromThingType(thingType);
|
initFromThingType(thingType);
|
||||||
|
|
||||||
|
String json = jsonIn;
|
||||||
|
if (json.contains("\"ext_temperature\":{\"0\":[{")) {
|
||||||
|
// Shelly UNI uses ext_temperature array, reformat to avoid GSON exception
|
||||||
|
json = json.replace("ext_temperature", "ext_temperature_array");
|
||||||
|
}
|
||||||
settingsJson = json;
|
settingsJson = json;
|
||||||
ShellySettingsGlobal gs = fromJson(gson, json, ShellySettingsGlobal.class);
|
settings = fromJson(gson, json, ShellySettingsGlobal.class);
|
||||||
settings = gs; // only update when no exception
|
|
||||||
|
|
||||||
// General settings
|
// General settings
|
||||||
name = getString(settings.name);
|
name = getString(settings.name);
|
||||||
|
@ -282,6 +287,7 @@ public class ShellyDeviceProfile {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("null")
|
||||||
public boolean inButtonMode(int idx) {
|
public boolean inButtonMode(int idx) {
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
|
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
|
||||||
|
@ -323,8 +329,8 @@ public class ShellyDeviceProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRollerFav(int id) {
|
public int getRollerFav(int id) {
|
||||||
if ((id >= 0) && getBool(settings.favoritesEnabled) && (settings.favorites != null)
|
if (id >= 0 && getBool(settings.favoritesEnabled) && settings.favorites != null
|
||||||
&& (id < settings.favorites.size())) {
|
&& id < settings.favorites.size()) {
|
||||||
return settings.favorites.get(id).pos;
|
return settings.favorites.get(id).pos;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -348,8 +354,11 @@ public class ShellyDeviceProfile {
|
||||||
|
|
||||||
public static String extractFwVersion(@Nullable String version) {
|
public static String extractFwVersion(@Nullable String version) {
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
// fix version e.g. 20210319-122304/v.1.10-Dimmer1-gfd4cc10 (with v.1. instead of v1.)
|
// fix version e.g.
|
||||||
String vers = version.replace("/v.1.10-", "/v1.10.0-");
|
// 20210319-122304/v.1.10-Dimmer1-gfd4cc10 (with v.1. instead of v1.)
|
||||||
|
// 20220809-125346/v1.12-g99f7e0b (.0 in 1.12.0 missing)
|
||||||
|
String vers = version.replace("/v.1.10-", "/v1.10.0-") //
|
||||||
|
.replace("/v1.12-", "/v1.12.0");
|
||||||
|
|
||||||
// Extract version from string, e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master
|
// Extract version from string, e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master
|
||||||
Matcher matcher = VERSION_PATTERN.matcher(vers);
|
Matcher matcher = VERSION_PATTERN.matcher(vers);
|
||||||
|
|
|
@ -21,89 +21,86 @@ import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.annotation.WebServlet;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2RpcSocket;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||||
import org.osgi.service.component.annotations.Deactivate;
|
import org.osgi.service.component.annotations.Deactivate;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
import org.osgi.service.http.HttpService;
|
|
||||||
import org.osgi.service.http.NamespaceException;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ShellyEventServlet} implements a servlet. which is called by the Shelly device to signnal events (button,
|
* {@link Shelly2RpcServlet} implements the WebSocket callback for Gen2 devices
|
||||||
* relay output, sensor data). The binding automatically sets those vent urls on startup (when not disabled in the thing
|
|
||||||
* config).
|
|
||||||
*
|
*
|
||||||
* @author Markus Michels - Initial contribution
|
* @author Markus Michels - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@WebServlet(name = "ShellyEventServlet", urlPatterns = { SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI })
|
||||||
@Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
|
@Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
|
||||||
public class ShellyEventServlet extends HttpServlet {
|
public class ShellyEventServlet extends WebSocketServlet {
|
||||||
private static final long serialVersionUID = 549582869577534569L;
|
private static final long serialVersionUID = -1210354558091063207L;
|
||||||
private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
|
private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
|
||||||
|
|
||||||
private final HttpService httpService;
|
|
||||||
private final ShellyHandlerFactory handlerFactory;
|
private final ShellyHandlerFactory handlerFactory;
|
||||||
|
private final ShellyThingTable thingTable;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public ShellyEventServlet(@Reference HttpService httpService, @Reference ShellyHandlerFactory handlerFactory,
|
public ShellyEventServlet(@Reference ShellyHandlerFactory handlerFactory, @Reference ShellyThingTable thingTable) {
|
||||||
Map<String, Object> config) {
|
|
||||||
this.httpService = httpService;
|
|
||||||
this.handlerFactory = handlerFactory;
|
this.handlerFactory = handlerFactory;
|
||||||
try {
|
this.thingTable = thingTable;
|
||||||
httpService.registerServlet(SHELLY_CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
|
logger.debug("Shelly EventServlet started at {} and {}", SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI);
|
||||||
logger.debug("ShellyEventServlet started at '{}'", SHELLY_CALLBACK_URI);
|
|
||||||
} catch (NamespaceException | ServletException | IllegalArgumentException e) {
|
|
||||||
logger.warn("Could not start CallbackServlet", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deactivate
|
@Deactivate
|
||||||
protected void deactivate() {
|
protected void deactivate() {
|
||||||
httpService.unregister(SHELLY_CALLBACK_URI);
|
logger.debug("ShellyEventServlet: Stopping");
|
||||||
logger.debug("ShellyEventServlet stopped");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servlet handler. Shelly1: http request, Shelly2: WebSocket call
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
|
protected void service(HttpServletRequest request, HttpServletResponse resp)
|
||||||
throws ServletException, IOException, IllegalArgumentException {
|
throws ServletException, IOException, IllegalArgumentException {
|
||||||
String path = "";
|
String path = getString(request.getRequestURI()).toLowerCase();
|
||||||
String deviceName = "";
|
|
||||||
String index = "";
|
|
||||||
String type = "";
|
|
||||||
|
|
||||||
if ((request == null) || (resp == null)) {
|
if (path.equals(SHELLY2_CALLBACK_URI)) { // Shelly2 WebSocket
|
||||||
logger.debug("request or resp must not be null!");
|
super.service(request, resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Shelly1: http events, URL looks like
|
||||||
path = getString(request.getRequestURI()).toLowerCase();
|
|
||||||
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
|
|
||||||
if (ipAddress == null) {
|
|
||||||
ipAddress = request.getRemoteAddr();
|
|
||||||
}
|
|
||||||
Map<String, String[]> parameters = request.getParameterMap();
|
|
||||||
logger.debug("CallbackServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
|
|
||||||
request.getRemotePort(), path, parameters.toString());
|
|
||||||
if (!path.toLowerCase().startsWith(SHELLY_CALLBACK_URI) || !path.contains("/event/shelly")) {
|
|
||||||
logger.warn("CallbackServlet received unknown request: path = {}", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL looks like
|
|
||||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
|
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
|
||||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
|
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
|
||||||
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
||||||
|
String deviceName = "";
|
||||||
|
String index = "";
|
||||||
|
String type = "";
|
||||||
|
try {
|
||||||
|
String ipAddress = request.getRemoteAddr();
|
||||||
|
Map<String, String[]> parameters = request.getParameterMap();
|
||||||
|
logger.debug("ShellyEventServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
|
||||||
|
request.getRemotePort(), path, parameters.toString());
|
||||||
|
if (!path.toLowerCase().startsWith(SHELLY1_CALLBACK_URI) || !path.contains("/event/shelly")) {
|
||||||
|
logger.warn("ShellyEventServlet received unknown request: path = {}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
deviceName = substringBetween(path, "/event/", "/").toLowerCase();
|
deviceName = substringBetween(path, "/event/", "/").toLowerCase();
|
||||||
if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
|
if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
|
||||||
|| path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
|
|| path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
|
||||||
|
@ -114,8 +111,8 @@ public class ShellyEventServlet extends HttpServlet {
|
||||||
type = substringAfterLast(path, "/").toLowerCase();
|
type = substringAfterLast(path, "/").toLowerCase();
|
||||||
}
|
}
|
||||||
logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
|
logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
|
||||||
Map<String, String> parms = new TreeMap<>();
|
|
||||||
|
|
||||||
|
Map<String, String> parms = new TreeMap<>();
|
||||||
for (Map.Entry<String, String[]> p : parameters.entrySet()) {
|
for (Map.Entry<String, String[]> p : parameters.entrySet()) {
|
||||||
parms.put(p.getKey(), p.getValue()[0]);
|
parms.put(p.getKey(), p.getValue()[0]);
|
||||||
|
|
||||||
|
@ -129,4 +126,39 @@ public class ShellyEventServlet extends HttpServlet {
|
||||||
resp.getWriter().write("");
|
resp.getWriter().write("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @Override
|
||||||
|
* public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
* {
|
||||||
|
* response.getWriter().println("HTTP GET method not implemented.");
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* WebSocket: register Shelly2RpcSocket class
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void configure(@Nullable WebSocketServletFactory factory) {
|
||||||
|
if (factory != null) {
|
||||||
|
factory.getPolicy().setIdleTimeout(15000);
|
||||||
|
factory.setCreator(new Shelly2WebSocketCreator(thingTable));
|
||||||
|
factory.register(Shelly2RpcSocket.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2WebSocketCreator implements WebSocketCreator {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Shelly2WebSocketCreator.class);
|
||||||
|
|
||||||
|
private final ShellyThingTable thingTable;
|
||||||
|
|
||||||
|
public Shelly2WebSocketCreator(ShellyThingTable thingTable) {
|
||||||
|
this.thingTable = thingTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(@Nullable ServletUpgradeRequest req, @Nullable ServletUpgradeResponse resp) {
|
||||||
|
logger.debug("WebSocket: Create socket from servlet");
|
||||||
|
return new Shelly2RpcSocket(thingTable, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -64,7 +65,6 @@ public class ShellyHttpClient {
|
||||||
public ShellyHttpClient(String thingName, ShellyThingInterface thing) {
|
public ShellyHttpClient(String thingName, ShellyThingInterface thing) {
|
||||||
this(thingName, thing.getThingConfig(), thing.getHttpClient());
|
this(thingName, thing.getThingConfig(), thing.getHttpClient());
|
||||||
this.profile = thing.getProfile();
|
this.profile = thing.getProfile();
|
||||||
profile.initFromThingType(thingName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShellyHttpClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
public ShellyHttpClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||||
|
@ -111,8 +111,9 @@ public class ShellyHttpClient {
|
||||||
}
|
}
|
||||||
return apiResult.response; // successful
|
return apiResult.response; // successful
|
||||||
} catch (ShellyApiException e) {
|
} catch (ShellyApiException e) {
|
||||||
if ((!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound() || profile.hasBattery
|
if (e.isConnectionError()
|
||||||
|| (retries == 0)) {
|
|| (!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound()
|
||||||
|
|| profile.hasBattery || (retries == 0)) {
|
||||||
// Sensor in sleep mode or API exception for non-battery device or retry counter expired
|
// Sensor in sleep mode or API exception for non-battery device or retry counter expired
|
||||||
throw e; // non-timeout exception
|
throw e; // non-timeout exception
|
||||||
}
|
}
|
||||||
|
@ -154,6 +155,16 @@ public class ShellyHttpClient {
|
||||||
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
||||||
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
||||||
|
|
||||||
|
if (response.contains("\"error\":{")) { // Gen2
|
||||||
|
Shelly2RpcBaseMessage message = gson.fromJson(response, Shelly2RpcBaseMessage.class);
|
||||||
|
if (message != null && message.error != null) {
|
||||||
|
apiResult.httpCode = message.error.code;
|
||||||
|
apiResult.response = message.error.message;
|
||||||
|
if (getInteger(message.error.code) == HttpStatus.UNAUTHORIZED_401) {
|
||||||
|
apiResult.authResponse = getString(message.error.message).replaceAll("\\\"", "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
HttpFields headers = contentResponse.getHeaders();
|
HttpFields headers = contentResponse.getHeaders();
|
||||||
String auth = headers.get(HttpHeader.WWW_AUTHENTICATE);
|
String auth = headers.get(HttpHeader.WWW_AUTHENTICATE);
|
||||||
if (!getString(auth).isEmpty()) {
|
if (!getString(auth).isEmpty()) {
|
||||||
|
@ -171,7 +182,7 @@ public class ShellyHttpClient {
|
||||||
}
|
}
|
||||||
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
|
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
|
||||||
ShellyApiException ex = new ShellyApiException(apiResult, e);
|
ShellyApiException ex = new ShellyApiException(apiResult, e);
|
||||||
if (!ex.isTimeout()) { // will be handled by the caller
|
if (!ex.isConnectionError() && !ex.isTimeout()) { // will be handled by the caller
|
||||||
logger.trace("{}: API call returned exception", thingName, ex);
|
logger.trace("{}: API call returned exception", thingName, ex);
|
||||||
}
|
}
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|
|
@ -283,6 +283,7 @@ public class Shelly1ApiJsonDTO {
|
||||||
public Boolean enabled;
|
public Boolean enabled;
|
||||||
public String ssid;
|
public String ssid;
|
||||||
public String key;
|
public String key;
|
||||||
|
public Boolean rangeExtender; // Gen2 only
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShellySettingsWiFiNetwork {
|
public static class ShellySettingsWiFiNetwork {
|
||||||
|
@ -605,6 +606,7 @@ public class Shelly1ApiJsonDTO {
|
||||||
@SerializedName("max_power")
|
@SerializedName("max_power")
|
||||||
public Double maxPower;
|
public Double maxPower;
|
||||||
public Boolean calibrated;
|
public Boolean calibrated;
|
||||||
|
|
||||||
public Double voltage; // AC voltage for Shelly 2.5
|
public Double voltage; // AC voltage for Shelly 2.5
|
||||||
@SerializedName("supply_voltage")
|
@SerializedName("supply_voltage")
|
||||||
public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V
|
public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V
|
||||||
|
@ -675,6 +677,10 @@ public class Shelly1ApiJsonDTO {
|
||||||
|
|
||||||
@SerializedName("sleep_time") // Shelly Motion
|
@SerializedName("sleep_time") // Shelly Motion
|
||||||
public Integer sleepTime;
|
public Integer sleepTime;
|
||||||
|
|
||||||
|
// Gen2
|
||||||
|
public Boolean ethernet;
|
||||||
|
public Boolean bluetooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShellySettingsAttributes {
|
public static class ShellySettingsAttributes {
|
||||||
|
@ -701,7 +707,8 @@ public class Shelly1ApiJsonDTO {
|
||||||
public String name; // FW 1.8: Symbolic Device name is configurable
|
public String name; // FW 1.8: Symbolic Device name is configurable
|
||||||
|
|
||||||
@SerializedName("wifi_sta")
|
@SerializedName("wifi_sta")
|
||||||
public ShellySettingsWiFiNetwork wifiSta = new ShellySettingsWiFiNetwork();
|
public ShellySettingsWiFiNetwork wifiSta = new ShellySettingsWiFiNetwork(); // WiFi client configuration. See
|
||||||
|
// /settings/sta for details
|
||||||
public ShellyStatusCloud cloud = new ShellyStatusCloud();
|
public ShellyStatusCloud cloud = new ShellyStatusCloud();
|
||||||
public ShellyStatusMqtt mqtt = new ShellyStatusMqtt();
|
public ShellyStatusMqtt mqtt = new ShellyStatusMqtt();
|
||||||
|
|
||||||
|
@ -715,13 +722,14 @@ public class Shelly1ApiJsonDTO {
|
||||||
public Integer cfgChangedCount; // FW 1.8
|
public Integer cfgChangedCount; // FW 1.8
|
||||||
@SerializedName("actions_stats")
|
@SerializedName("actions_stats")
|
||||||
public ShellyActionsStats astats;
|
public ShellyActionsStats astats;
|
||||||
public Double voltage; // Shelly 2.5
|
|
||||||
|
|
||||||
public Integer input; // RGBW2 has no JSON array
|
|
||||||
public ArrayList<ShellySettingsRelay> relays;
|
public ArrayList<ShellySettingsRelay> relays;
|
||||||
public ArrayList<ShellyRollerStatus> rollers;
|
public Double voltage; // Shelly 2.5
|
||||||
public ArrayList<ShellyShortLightStatus> dimmers;
|
public Integer input; // RGBW2 has no JSON array
|
||||||
public ArrayList<ShellyInputState> inputs;
|
public ArrayList<ShellyInputState> inputs;
|
||||||
|
public ArrayList<ShellyShortLightStatus> dimmers;
|
||||||
|
public ArrayList<ShellyRollerStatus> rollers;
|
||||||
|
public ArrayList<ShellySettingsLight> lights;
|
||||||
public ArrayList<ShellySettingsMeter> meters;
|
public ArrayList<ShellySettingsMeter> meters;
|
||||||
public ArrayList<ShellySettingsEMeter> emeters;
|
public ArrayList<ShellySettingsEMeter> emeters;
|
||||||
@SerializedName("ext_temperature")
|
@SerializedName("ext_temperature")
|
||||||
|
@ -743,7 +751,6 @@ public class Shelly1ApiJsonDTO {
|
||||||
public ArrayList<ShellyThermnostat> thermostats;
|
public ArrayList<ShellyThermnostat> thermostats;
|
||||||
|
|
||||||
public ShellySettingsUpdate update = new ShellySettingsUpdate();
|
public ShellySettingsUpdate update = new ShellySettingsUpdate();
|
||||||
|
|
||||||
@SerializedName("ram_total")
|
@SerializedName("ram_total")
|
||||||
public Long ramTotal;
|
public Long ramTotal;
|
||||||
@SerializedName("ram_free")
|
@SerializedName("ram_free")
|
||||||
|
@ -798,7 +805,6 @@ public class Shelly1ApiJsonDTO {
|
||||||
public Boolean ison; // Whether output channel is on or off
|
public Boolean ison; // Whether output channel is on or off
|
||||||
public String mode; // color or white - valid only for Bulb and RGBW2 even Dimmer returns it also
|
public String mode; // color or white - valid only for Bulb and RGBW2 even Dimmer returns it also
|
||||||
public Integer brightness; // brightness: 0.100%
|
public Integer brightness; // brightness: 0.100%
|
||||||
|
|
||||||
@SerializedName("has_timer")
|
@SerializedName("has_timer")
|
||||||
public Boolean hasTimer;
|
public Boolean hasTimer;
|
||||||
}
|
}
|
||||||
|
@ -914,6 +920,7 @@ public class Shelly1ApiJsonDTO {
|
||||||
|
|
||||||
public static class ShellyStatusSensor {
|
public static class ShellyStatusSensor {
|
||||||
// https://shelly-api-docs.shelly.cloud/#h-amp-t-settings
|
// https://shelly-api-docs.shelly.cloud/#h-amp-t-settings
|
||||||
|
|
||||||
public static class ShellySensorHum {
|
public static class ShellySensorHum {
|
||||||
public Double value; // relative humidity in %
|
public Double value; // relative humidity in %
|
||||||
}
|
}
|
||||||
|
@ -964,6 +971,7 @@ public class Shelly1ApiJsonDTO {
|
||||||
|
|
||||||
public static class ShellyExtTemperature {
|
public static class ShellyExtTemperature {
|
||||||
public static class ShellyShortTemp {
|
public static class ShellyShortTemp {
|
||||||
|
public String hwID; // e.g. "2882379497020381",
|
||||||
public Double tC; // temperature in deg C
|
public Double tC; // temperature in deg C
|
||||||
public Double tF; // temperature in deg F
|
public Double tF; // temperature in deg F
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,8 +152,7 @@ public class Shelly1CoIoTVersion1 extends Shelly1CoIoTProtocol implements Shelly
|
||||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE));
|
toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE));
|
||||||
break;
|
break;
|
||||||
case "pf":
|
case "pf":
|
||||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR,
|
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||||
toQuantityType(getDecimal(s.value), Units.PERCENT));
|
|
||||||
break;
|
break;
|
||||||
case "position":
|
case "position":
|
||||||
// work around: Roller reports 101% instead max 100
|
// work around: Roller reports 101% instead max 100
|
||||||
|
|
|
@ -84,8 +84,6 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
private boolean updatesRequested = false;
|
private boolean updatesRequested = false;
|
||||||
private int coiotPort = COIOT_PORT;
|
private int coiotPort = COIOT_PORT;
|
||||||
|
|
||||||
private long coiotMessages = 0;
|
|
||||||
private long coiotErrors = 0;
|
|
||||||
private int lastSerial = -1;
|
private int lastSerial = -1;
|
||||||
private String lastPayload = "";
|
private String lastPayload = "";
|
||||||
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
|
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
|
||||||
|
@ -164,7 +162,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
@Override
|
@Override
|
||||||
public void processResponse(@Nullable Response response) {
|
public void processResponse(@Nullable Response response) {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
coiotErrors++;
|
thingHandler.incProtErrors();
|
||||||
return; // other device instance
|
return; // other device instance
|
||||||
}
|
}
|
||||||
ResponseCode code = response.getCode();
|
ResponseCode code = response.getCode();
|
||||||
|
@ -172,7 +170,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
// error handling
|
// error handling
|
||||||
logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, code,
|
logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, code,
|
||||||
response.getPayloadString());
|
response.getPayloadString());
|
||||||
coiotErrors++;
|
thingHandler.incProtErrors();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,14 +203,14 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
String uri = "";
|
String uri = "";
|
||||||
int serial = -1;
|
int serial = -1;
|
||||||
try {
|
try {
|
||||||
coiotMessages++;
|
thingHandler.incProtMessages();
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
|
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
|
||||||
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
|
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
|
||||||
}
|
}
|
||||||
if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
|
if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
|
||||||
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
|
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
|
||||||
coiotErrors++;
|
thingHandler.incProtErrors();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +283,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
}
|
}
|
||||||
} catch (ShellyApiException e) {
|
} catch (ShellyApiException e) {
|
||||||
logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
|
logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
|
||||||
coiotErrors++;
|
thingHandler.incProtErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!updatesRequested) {
|
if (!updatesRequested) {
|
||||||
|
@ -296,7 +294,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
} catch (JsonSyntaxException | IllegalArgumentException | NullPointerException e) {
|
} catch (JsonSyntaxException | IllegalArgumentException | NullPointerException e) {
|
||||||
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
|
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
|
||||||
resetSerial();
|
resetSerial();
|
||||||
coiotErrors++;
|
thingHandler.incProtErrors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,6 +498,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
// Aggregate Meter Data from different Coap updates
|
// Aggregate Meter Data from different Coap updates
|
||||||
int i = 1;
|
int i = 1;
|
||||||
double totalCurrent = 0.0;
|
double totalCurrent = 0.0;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
double totalKWH = 0.0;
|
double totalKWH = 0.0;
|
||||||
boolean updateMeter = false;
|
boolean updateMeter = false;
|
||||||
while (i <= thingHandler.getProfile().numMeters) {
|
while (i <= thingHandler.getProfile().numMeters) {
|
||||||
|
@ -663,14 +662,6 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||||
coiotBound = false;
|
coiotBound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMessageCount() {
|
|
||||||
return coiotMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getErrorCount() {
|
|
||||||
return coiotErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
String json = "";
|
String json = "";
|
||||||
try {
|
try {
|
||||||
json = httpRequest(SHELLY_URL_STATUS);
|
json = httpRequest(SHELLY_URL_STATUS);
|
||||||
|
|
||||||
// Dimmer2 returns invalid json type for loaderror :-(
|
// Dimmer2 returns invalid json type for loaderror :-(
|
||||||
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,")
|
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,")
|
||||||
.replace("\"loaderror\":1,", "\"loaderror\":true,")
|
.replace("\"loaderror\":1,", "\"loaderror\":true,")
|
||||||
|
@ -223,18 +224,23 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
// SHelly H&T uses external_power, Sense uses charger
|
// SHelly H&T uses external_power, Sense uses charger
|
||||||
status.charger = profile.settings.externalPower != 0;
|
status.charger = profile.settings.externalPower != 0;
|
||||||
}
|
}
|
||||||
|
if (status.tmp != null && status.tmp.tC == null && status.tmp.value != null) { // Motion is is missing tC and tF
|
||||||
|
status.tmp.tC = getString(status.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)
|
||||||
|
? ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(status.tmp.value).doubleValue()
|
||||||
|
: status.tmp.value;
|
||||||
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTimer(int index, String timerName, int value) throws ShellyApiException {
|
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
|
||||||
String type = SHELLY_CLASS_RELAY;
|
String type = SHELLY_CLASS_RELAY;
|
||||||
if (profile.isRoller) {
|
if (profile.isRoller) {
|
||||||
type = SHELLY_CLASS_ROLLER;
|
type = SHELLY_CLASS_ROLLER;
|
||||||
} else if (profile.isLight) {
|
} else if (profile.isLight) {
|
||||||
type = SHELLY_CLASS_LIGHT;
|
type = SHELLY_CLASS_LIGHT;
|
||||||
}
|
}
|
||||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value;
|
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + (int) value;
|
||||||
httpRequest(uri);
|
httpRequest(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,11 +357,27 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
return callApi(SHELLY_URL_SETTINGS + "?ap_roaming_enabled=" + (enable ? "true" : "false"), String.class);
|
return callApi(SHELLY_URL_SETTINGS + "?ap_roaming_enabled=" + (enable ? "true" : "false"), String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setEthernet(boolean enable) throws ShellyApiException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setBluetooth(boolean enable) throws ShellyApiException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String resetStaCache() throws ShellyApiException { // FW 1.10+: Reset cached STA/AP list and to a rescan
|
public String resetStaCache() throws ShellyApiException { // FW 1.10+: Reset cached STA/AP list and to a rescan
|
||||||
return callApi("/sta_cache_reset", String.class);
|
return callApi("/sta_cache_reset", String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException {
|
public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException {
|
||||||
return callApi("/ota?" + uri, ShellySettingsUpdate.class);
|
return callApi("/ota?" + uri, ShellySettingsUpdate.class);
|
||||||
}
|
}
|
||||||
|
@ -559,7 +581,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
// H&T adds the type=xx to report_url itself, so we need to ommit here
|
// H&T adds the type=xx to report_url itself, so we need to ommit here
|
||||||
String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType;
|
String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType;
|
||||||
String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
|
String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
|
||||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
|
||||||
+ profile.thingName + "/" + eclass + urlParm;
|
+ profile.thingName + "/" + eclass + urlParm;
|
||||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||||
String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||||
|
@ -581,7 +603,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
throws ShellyApiException {
|
throws ShellyApiException {
|
||||||
for (String eventType : eventTypes) {
|
for (String eventType : eventTypes) {
|
||||||
if (profile.containsEventUrl(eventType)) {
|
if (profile.containsEventUrl(eventType)) {
|
||||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
|
||||||
+ profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
|
+ profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
|
||||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||||
String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
|
String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
|
||||||
|
@ -713,4 +735,8 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||||
public int getTimeoutsRecovered() {
|
public int getTimeoutsRecovered() {
|
||||||
return timeoutsRecovered;
|
return timeoutsRecovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,548 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.api2;
|
||||||
|
|
||||||
|
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.CHANNEL_INPUT;
|
||||||
|
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyHttpClient;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRoller;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRequest;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||||
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyComponents;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Shelly2ApiClient} Low level part of the RPC API
|
||||||
|
*
|
||||||
|
* @author Markus Michels - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Shelly2ApiClient extends ShellyHttpClient {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
|
||||||
|
protected final Random random = new Random();
|
||||||
|
protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
|
||||||
|
protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
|
||||||
|
protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
|
||||||
|
protected @Nullable ShellyThingInterface thing;
|
||||||
|
protected @Nullable Shelly2AuthRequest authReq;
|
||||||
|
|
||||||
|
public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
|
||||||
|
super(thingName, thing);
|
||||||
|
this.thing = thing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||||
|
super(thingName, config, httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Map<String, String> MAP_INMODE_BTNTYPE = new HashMap<>();
|
||||||
|
static {
|
||||||
|
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY);
|
||||||
|
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE);
|
||||||
|
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE);
|
||||||
|
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
|
||||||
|
static {
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH);
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Map<String, String> MAP_INPUT_EVENT_ID = new HashMap<>();
|
||||||
|
static {
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH);
|
||||||
|
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Map<String, String> MAP_INPUT_MODE = new HashMap<>();
|
||||||
|
static {
|
||||||
|
MAP_INPUT_MODE.put(SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON);
|
||||||
|
MAP_INPUT_MODE.put(SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE);
|
||||||
|
MAP_INPUT_MODE.put(SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final Map<String, String> MAP_ROLLER_STATE = new HashMap<>();
|
||||||
|
static {
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN);
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE);
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING); // Gen2-only
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING); // Gen2-only
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP);
|
||||||
|
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
|
||||||
|
Shelly2GetConfigResult dc) {
|
||||||
|
if (dc.switch0 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
|
||||||
|
addRelaySettings(relays, dc.switch0);
|
||||||
|
addRelaySettings(relays, dc.switch1);
|
||||||
|
addRelaySettings(relays, dc.switch2);
|
||||||
|
addRelaySettings(relays, dc.switch3);
|
||||||
|
return relays;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
|
||||||
|
@Nullable Shelly2DevConfigSwitch cs) {
|
||||||
|
if (cs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsRelay rsettings = new ShellySettingsRelay();
|
||||||
|
rsettings.name = cs.name;
|
||||||
|
rsettings.ison = false;
|
||||||
|
rsettings.autoOn = getBool(cs.autoOn) ? cs.autoOnDelay : 0;
|
||||||
|
rsettings.autoOff = getBool(cs.autoOff) ? cs.autoOffDelay : 0;
|
||||||
|
rsettings.hasTimer = false;
|
||||||
|
rsettings.btnType = mapValue(MAP_INMODE_BTNTYPE, getString(cs.mode).toLowerCase());
|
||||||
|
relays.add(rsettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
|
||||||
|
boolean channelUpdate) throws ShellyApiException {
|
||||||
|
boolean updated = false;
|
||||||
|
|
||||||
|
updated |= updateInputStatus(status, result, channelUpdate);
|
||||||
|
updated |= updateRelayStatus(status, result.switch0, channelUpdate);
|
||||||
|
updated |= updateRelayStatus(status, result.switch1, channelUpdate);
|
||||||
|
updated |= updateRelayStatus(status, result.switch2, channelUpdate);
|
||||||
|
updated |= updateRelayStatus(status, result.switch3, channelUpdate);
|
||||||
|
updated |= updateRollerStatus(status, result.cover0, channelUpdate);
|
||||||
|
if (channelUpdate) {
|
||||||
|
updated |= ShellyComponents.updateMeters(getThing(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHumidityStatus(sensorData, result.humidity0);
|
||||||
|
updateTemperatureStatus(sensorData, result.temperature0);
|
||||||
|
updateBatteryStatus(sensorData, result.devicepower0);
|
||||||
|
updated |= ShellyComponents.updateSensors(getThing(), status);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
|
||||||
|
boolean channelUpdate) throws ShellyApiException {
|
||||||
|
if (rs == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ShellyDeviceProfile profile = getProfile();
|
||||||
|
if (rs.id >= profile.numRelays) {
|
||||||
|
throw new IllegalArgumentException("Update for invalid relay index");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsRelay rstatus = status.relays.get(rs.id);
|
||||||
|
ShellyShortStatusRelay sr = relayStatus.relays.get(rs.id);
|
||||||
|
sr.isValid = rstatus.isValid = true;
|
||||||
|
sr.name = rstatus.name = status.name;
|
||||||
|
if (rs.output != null) {
|
||||||
|
sr.ison = rstatus.ison = getBool(rs.output);
|
||||||
|
}
|
||||||
|
if (getDouble(rs.timerStartetAt) > 0) {
|
||||||
|
int duration = (int) (now() - rs.timerStartetAt);
|
||||||
|
sr.timerRemaining = duration;
|
||||||
|
}
|
||||||
|
if (rs.temperature != null) {
|
||||||
|
status.tmp.isValid = true;
|
||||||
|
status.tmp.tC = rs.temperature.tC;
|
||||||
|
status.tmp.tF = rs.temperature.tF;
|
||||||
|
status.tmp.units = "C";
|
||||||
|
sr.temperature = getDouble(rs.temperature.tC);
|
||||||
|
if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
|
||||||
|
status.temperature = sr.temperature;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status.tmp.isValid = false;
|
||||||
|
}
|
||||||
|
if (rs.voltage != null) {
|
||||||
|
if (status.voltage == null || rs.voltage > status.voltage) {
|
||||||
|
status.voltage = rs.voltage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rs.errors != null) {
|
||||||
|
for (String error : rs.errors) {
|
||||||
|
sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
|
||||||
|
status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
|
||||||
|
status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
|
||||||
|
}
|
||||||
|
sr.overtemperature = status.overtemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsMeter sm = new ShellySettingsMeter();
|
||||||
|
ShellySettingsEMeter emeter = status.emeters.get(rs.id);
|
||||||
|
sm.isValid = emeter.isValid = true;
|
||||||
|
if (rs.apower != null) {
|
||||||
|
sm.power = emeter.power = rs.apower;
|
||||||
|
}
|
||||||
|
if (rs.aenergy != null) {
|
||||||
|
sm.total = emeter.total = rs.aenergy.total;
|
||||||
|
sm.counters = rs.aenergy.byMinute;
|
||||||
|
sm.timestamp = rs.aenergy.minuteTs;
|
||||||
|
}
|
||||||
|
if (rs.voltage != null) {
|
||||||
|
emeter.voltage = rs.voltage;
|
||||||
|
}
|
||||||
|
if (rs.current != null) {
|
||||||
|
emeter.current = rs.current;
|
||||||
|
}
|
||||||
|
if (rs.pf != null) {
|
||||||
|
emeter.pf = rs.pf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update internal structures
|
||||||
|
status.relays.set(rs.id, rstatus);
|
||||||
|
status.meters.set(rs.id, sm);
|
||||||
|
status.emeters.set(rs.id, emeter);
|
||||||
|
relayStatus.relays.set(rs.id, sr);
|
||||||
|
relayStatus.meters.set(rs.id, sm);
|
||||||
|
|
||||||
|
return channelUpdate ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rs.id) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
|
||||||
|
Shelly2GetConfigResult dc) {
|
||||||
|
if (dc.cover0 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
|
||||||
|
|
||||||
|
addRollerSettings(rollers, dc.cover0);
|
||||||
|
fillRollerFavorites(profile, dc);
|
||||||
|
return rollers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
|
||||||
|
@Nullable Shelly2DevConfigCover coverConfig) {
|
||||||
|
if (coverConfig == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsRoller settings = new ShellySettingsRoller();
|
||||||
|
settings.isValid = true;
|
||||||
|
settings.defaultState = coverConfig.initialState;
|
||||||
|
settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
|
||||||
|
settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
|
||||||
|
settings.swapInputs = coverConfig.swapInputs;
|
||||||
|
settings.maxtime = 0.0; // n/a
|
||||||
|
settings.maxtimeOpen = coverConfig.maxtimeOpen;
|
||||||
|
settings.maxtimeClose = coverConfig.maxtimeClose;
|
||||||
|
if (coverConfig.safetySwitch != null) {
|
||||||
|
settings.safetySwitch = coverConfig.safetySwitch.enable;
|
||||||
|
settings.safetyAction = coverConfig.safetySwitch.action;
|
||||||
|
}
|
||||||
|
if (coverConfig.obstructionDetection != null) {
|
||||||
|
settings.obstacleAction = coverConfig.obstructionDetection.action;
|
||||||
|
settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
|
||||||
|
settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
|
||||||
|
}
|
||||||
|
rollers.add(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
|
||||||
|
if (dc.sys.uiData.cover != null) {
|
||||||
|
String[] favorites = dc.sys.uiData.cover.split(",");
|
||||||
|
profile.settings.favorites = new ArrayList<>();
|
||||||
|
for (int i = 0; i < favorites.length; i++) {
|
||||||
|
ShellyFavPos fav = new ShellyFavPos();
|
||||||
|
fav.pos = Integer.parseInt(favorites[i]);
|
||||||
|
fav.name = fav.pos + "%";
|
||||||
|
profile.settings.favorites.add(fav);
|
||||||
|
}
|
||||||
|
profile.settings.favoritesEnabled = profile.settings.favorites.size() > 0;
|
||||||
|
logger.debug("{}: Roller Favorites loaded: {}", thingName,
|
||||||
|
profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
|
||||||
|
boolean updateChannels) throws ShellyApiException {
|
||||||
|
if (cs == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellyRollerStatus rs = status.rollers.get(cs.id);
|
||||||
|
ShellySettingsMeter sm = status.meters.get(cs.id);
|
||||||
|
ShellySettingsEMeter emeter = status.emeters.get(cs.id);
|
||||||
|
rs.isValid = sm.isValid = emeter.isValid = true;
|
||||||
|
if (cs.state != null) {
|
||||||
|
if (!getString(rs.state).equals(cs.state)) {
|
||||||
|
logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
|
||||||
|
mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
|
||||||
|
}
|
||||||
|
rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
|
||||||
|
rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
|
||||||
|
}
|
||||||
|
if (cs.currentPos != null) {
|
||||||
|
rs.currentPos = cs.currentPos;
|
||||||
|
}
|
||||||
|
if (cs.moveStartedAt != null) {
|
||||||
|
rs.duration = (int) (now() - cs.moveStartedAt.longValue());
|
||||||
|
}
|
||||||
|
if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
|
||||||
|
status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
|
||||||
|
}
|
||||||
|
if (cs.apower != null) {
|
||||||
|
rs.power = sm.power = emeter.power = cs.apower;
|
||||||
|
}
|
||||||
|
if (cs.aenergy != null) {
|
||||||
|
sm.total = emeter.total = cs.aenergy.total;
|
||||||
|
sm.counters = cs.aenergy.byMinute;
|
||||||
|
sm.timestamp = (long) cs.aenergy.minuteTs;
|
||||||
|
}
|
||||||
|
if (cs.voltage != null) {
|
||||||
|
emeter.voltage = cs.voltage;
|
||||||
|
}
|
||||||
|
if (cs.current != null) {
|
||||||
|
emeter.current = cs.current;
|
||||||
|
}
|
||||||
|
if (cs.pf != null) {
|
||||||
|
emeter.pf = cs.pf;
|
||||||
|
}
|
||||||
|
|
||||||
|
rollerStatus.set(cs.id, rs);
|
||||||
|
status.rollers.set(cs.id, rs);
|
||||||
|
relayStatus.meters.set(cs.id, sm);
|
||||||
|
status.meters.set(cs.id, sm);
|
||||||
|
status.emeters.set(cs.id, emeter);
|
||||||
|
|
||||||
|
postAlarms(cs.errors);
|
||||||
|
if (rs.calibrating != null && rs.calibrating) {
|
||||||
|
getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sdata.hum == null) {
|
||||||
|
sdata.hum = new ShellySensorHum();
|
||||||
|
}
|
||||||
|
sdata.hum.value = getDouble(value.rh);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sdata.tmp == null) {
|
||||||
|
sdata.tmp = new ShellySensorTmp();
|
||||||
|
}
|
||||||
|
sdata.tmp.isValid = true;
|
||||||
|
sdata.tmp.units = SHELLY_TEMP_CELSIUS;
|
||||||
|
sdata.tmp.tC = value.tC;
|
||||||
|
sdata.tmp.tF = value.tF;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sdata.bat == null) {
|
||||||
|
sdata.bat = new ShellySensorBat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.battery != null) {
|
||||||
|
sdata.bat.voltage = value.battery.volt;
|
||||||
|
sdata.bat.value = value.battery.percent;
|
||||||
|
}
|
||||||
|
if (value.external != null) {
|
||||||
|
sdata.charger = value.external.present;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
|
||||||
|
if (errors != null) {
|
||||||
|
for (String e : errors) {
|
||||||
|
if (e != null) {
|
||||||
|
getThing().postEvent(e, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
|
||||||
|
Shelly2GetConfigResult dc) {
|
||||||
|
if (dc.input0 == null) {
|
||||||
|
return null; // device has no input
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
|
||||||
|
addInputSettings(inputs, dc.input0);
|
||||||
|
addInputSettings(inputs, dc.input1);
|
||||||
|
addInputSettings(inputs, dc.input2);
|
||||||
|
addInputSettings(inputs, dc.input3);
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
|
||||||
|
if (ic == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsInput settings = new ShellySettingsInput();
|
||||||
|
settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
|
||||||
|
: SHELLY_BTNT_EDGE;
|
||||||
|
inputs.add(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
|
||||||
|
boolean updateChannels) throws ShellyApiException {
|
||||||
|
boolean updated = false;
|
||||||
|
updated |= addInputStatus(ds.input0, updateChannels);
|
||||||
|
updated |= addInputStatus(ds.input1, updateChannels);
|
||||||
|
updated |= addInputStatus(ds.input2, updateChannels);
|
||||||
|
updated |= addInputStatus(ds.input3, updateChannels);
|
||||||
|
status.inputs = relayStatus.inputs;
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addInputStatus(@Nullable Shelly2InputStatus is, boolean updateChannels) throws ShellyApiException {
|
||||||
|
if (is == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ShellyDeviceProfile profile = getProfile();
|
||||||
|
if (is.id == null || is.id > profile.numInputs) {
|
||||||
|
logger.debug("{}: Invalid input id: {}", thingName, is.id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String group = profile.getInputGroup(is.id);
|
||||||
|
ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
|
||||||
|
: new ShellyInputState();
|
||||||
|
boolean updated = false;
|
||||||
|
input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
|
||||||
|
if (input.event == null && profile.inButtonMode(is.id)) {
|
||||||
|
input.event = "";
|
||||||
|
input.eventCount = 0;
|
||||||
|
}
|
||||||
|
relayStatus.inputs.set(is.id, input);
|
||||||
|
if (updateChannels) {
|
||||||
|
updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
|
||||||
|
Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
|
||||||
|
request.id = Math.abs(random.nextInt());
|
||||||
|
request.src = thingName;
|
||||||
|
request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
|
||||||
|
request.params = params;
|
||||||
|
request.auth = authReq;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm,
|
||||||
|
String password) throws ShellyApiException {
|
||||||
|
Shelly2AuthRequest authReq = new Shelly2AuthRequest();
|
||||||
|
authReq.username = "admin";
|
||||||
|
authReq.realm = realm;
|
||||||
|
authReq.nonce = authParm.nonce;
|
||||||
|
authReq.cnonce = (long) Math.floor(Math.random() * 10e8);
|
||||||
|
authReq.nc = authParm.nc != null ? authParm.nc : 1;
|
||||||
|
authReq.authType = SHELLY2_AUTHTTYPE_DIGEST;
|
||||||
|
authReq.algorithm = SHELLY2_AUTHALG_SHA256;
|
||||||
|
String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password);
|
||||||
|
String ha2 = SHELLY2_AUTH_NOISE;
|
||||||
|
authReq.response = sha256(
|
||||||
|
ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2);
|
||||||
|
return authReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String mapValue(Map<String, String> map, @Nullable String key) {
|
||||||
|
String value;
|
||||||
|
boolean known = key != null && !key.isEmpty() && map.containsKey(key);
|
||||||
|
value = known ? getString(map.get(key)) : "";
|
||||||
|
logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
|
||||||
|
return getThing().updateChannel(group, channel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ShellyThingInterface getThing() throws ShellyApiException {
|
||||||
|
ShellyThingInterface t = thing;
|
||||||
|
if (t != null) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
throw new ShellyApiException("Thing/profile not initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellyDeviceProfile getProfile() throws ShellyApiException {
|
||||||
|
if (thing != null) {
|
||||||
|
return thing.getProfile();
|
||||||
|
}
|
||||||
|
throw new ShellyApiException("Unable to get profile, thing not initialized!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,763 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.api2;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage.Shelly2RpcMessageError;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Shelly2ApiJsonDTO} wraps the Shelly REST API and provides various low level function to access the device api
|
||||||
|
* (not
|
||||||
|
* cloud api).
|
||||||
|
*
|
||||||
|
* @author Markus Michels - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Shelly2ApiJsonDTO {
|
||||||
|
public static final String SHELLYRPC_METHOD_CLASS_SHELLY = "Shelly";
|
||||||
|
public static final String SHELLYRPC_METHOD_CLASS_SWITCH = "Switch";
|
||||||
|
|
||||||
|
public static final String SHELLYRPC_METHOD_GETDEVCONFIG = "GetDeviceInfo";
|
||||||
|
public static final String SHELLYRPC_METHOD_GETSYSCONFIG = "GetSysConfig"; // only sys
|
||||||
|
public static final String SHELLYRPC_METHOD_GETCONFIG = "GetConfig"; // sys + components
|
||||||
|
public static final String SHELLYRPC_METHOD_GETSYSSTATUS = "GetSysStatus"; // only sys
|
||||||
|
public static final String SHELLYRPC_METHOD_GETSTATUS = "GetStatus"; // sys + components
|
||||||
|
public static final String SHELLYRPC_METHOD_REBOOT = "Shelly.Reboot";
|
||||||
|
public static final String SHELLYRPC_METHOD_RESET = "Shelly.FactoryReset";
|
||||||
|
public static final String SHELLYRPC_METHOD_CHECKUPD = "Shelly.CheckForUpdate";
|
||||||
|
public static final String SHELLYRPC_METHOD_UPDATE = "Shelly.Update";
|
||||||
|
public static final String SHELLYRPC_METHOD_AUTHSET = "Shelly.SetAuth";
|
||||||
|
public static final String SHELLYRPC_METHOD_SWITCH_STATUS = "Switch.GetStatus";
|
||||||
|
public static final String SHELLYRPC_METHOD_SWITCH_SET = "Switch.Set";
|
||||||
|
public static final String SHELLYRPC_METHOD_SWITCH_SETCONFIG = "Switch.SetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_COVER_SETPOS = "Cover.GoToPosition";
|
||||||
|
public static final String SHELLY2_COVER_CMD_OPEN = "Open";
|
||||||
|
public static final String SHELLY2_COVER_CMD_CLOSE = "Close";
|
||||||
|
public static final String SHELLY2_COVER_CMD_STOP = "Stop";
|
||||||
|
public static final String SHELLYRPC_METHOD_WIFIGETCONG = "Wifi.GetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_WIFISETCONG = "Wifi.SetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_ETHGETCONG = "Eth.GetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_ETHSETCONG = "Eth.SetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_BLEGETCONG = "BLE.GetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_BLESETCONG = "BLE.SetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_CLOUDSET = "Cloud.SetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_WSGETCONFIG = "WS.GetConfig";
|
||||||
|
public static final String SHELLYRPC_METHOD_WSSETCONFIG = "WS.SetConfig";
|
||||||
|
|
||||||
|
public static final String SHELLYRPC_METHOD_NOTIFYSTATUS = "NotifyStatus"; // inbound status
|
||||||
|
public static final String SHELLYRPC_METHOD_NOTIFYFULLSTATUS = "NotifyFullStatus"; // inbound status from bat device
|
||||||
|
public static final String SHELLYRPC_METHOD_NOTIFYEVENT = "NotifyEvent"; // inbound event
|
||||||
|
|
||||||
|
// Component types
|
||||||
|
public static final String SHELLY2_PROFILE_RELAY = "switch";
|
||||||
|
public static final String SHELLY2_PROFILE_ROLLER = "cover";
|
||||||
|
|
||||||
|
// Button types/modes
|
||||||
|
public static final String SHELLY2_BTNT_MOMENTARY = "momentary";
|
||||||
|
public static final String SHELLY2_BTNT_FLIP = "flip";
|
||||||
|
public static final String SHELLY2_BTNT_FOLLOW = "follow";
|
||||||
|
public static final String SHELLY2_BTNT_DETACHED = "detached";
|
||||||
|
|
||||||
|
// Input types
|
||||||
|
public static final String SHELLY2_INPUTT_SWITCH = "switch";
|
||||||
|
public static final String SHELLY2_INPUTT_BUTTON = "button";
|
||||||
|
|
||||||
|
// Switcm modes
|
||||||
|
public static final String SHELLY2_API_MODE_DETACHED = "detached";
|
||||||
|
public static final String SHELLY2_API_MODE_FOLLOW = "follow";
|
||||||
|
|
||||||
|
// Initial switch states
|
||||||
|
public static final String SHELLY2_API_ISTATE_ON = "on";
|
||||||
|
public static final String SHELLY2_API_ISTATE_OFF = "off";
|
||||||
|
public static final String SHELLY2_API_ISTATE_FOLLOWLAST = "restore_last";
|
||||||
|
public static final String SHELLY2_API_ISTATE_MATCHINPUT = "match_input";
|
||||||
|
|
||||||
|
// Cover/Roller modes
|
||||||
|
public static final String SHELLY2_RMODE_SINGLE = "single";
|
||||||
|
public static final String SHELLY2_RMODE_DUAL = "dual";
|
||||||
|
public static final String SHELLY2_RMODE_DETACHED = "detached";
|
||||||
|
|
||||||
|
public static final String SHELLY2_RSTATE_OPENING = "opening";
|
||||||
|
public static final String SHELLY2_RSTATE_OPEN = "open";
|
||||||
|
public static final String SHELLY2_RSTATE_CLOSING = "closing";
|
||||||
|
public static final String SHELLY2_RSTATE_CLOSED = "closed";
|
||||||
|
public static final String SHELLY2_RSTATE_STOPPED = "stopped";
|
||||||
|
public static final String SHELLY2_RSTATE_CALIB = "calibrating";
|
||||||
|
|
||||||
|
// Event notifications
|
||||||
|
public static final String SHELLY2_EVENT_BTNUP = "btn_up";
|
||||||
|
public static final String SHELLY2_EVENT_BTNDOWN = "btn_down";
|
||||||
|
public static final String SHELLY2_EVENT_1PUSH = "single_push";
|
||||||
|
public static final String SHELLY2_EVENT_2PUSH = "double_push";
|
||||||
|
public static final String SHELLY2_EVENT_3PUSH = "triple_push";
|
||||||
|
public static final String SHELLY2_EVENT_LPUSH = "long_push";
|
||||||
|
public static final String SHELLY2_EVENT_SLPUSH = "short_long_push";
|
||||||
|
public static final String SHELLY2_EVENT_LSPUSH = "long_short_push";
|
||||||
|
|
||||||
|
public static final String SHELLY2_EVENT_SLEEP = "sleep";
|
||||||
|
public static final String SHELLY2_EVENT_CFGCHANGED = "config_changed";
|
||||||
|
public static final String SHELLY2_EVENT_OTASTART = "ota_begin";
|
||||||
|
public static final String SHELLY2_EVENT_OTAPROGRESS = "ota_progress";
|
||||||
|
public static final String SHELLY2_EVENT_OTADONE = "ota_success";
|
||||||
|
public static final String SHELLY2_EVENT_WIFICONNFAILED = "sta_connect_fail";
|
||||||
|
public static final String SHELLY2_EVENT_WIFIDISCONNECTED = "sta_disconnected";
|
||||||
|
|
||||||
|
// Error Codes
|
||||||
|
public static final String SHELLY2_ERROR_OVERPOWER = "overpower";
|
||||||
|
public static final String SHELLY2_ERROR_OVERTEMP = "overtemp";
|
||||||
|
public static final String SHELLY2_ERROR_OVERVOLTAGE = "overvoltage";
|
||||||
|
|
||||||
|
// Wakeup reasons (e.g. Plus HT)
|
||||||
|
public static final String SHELLY2_WAKEUPO_BOOT_POWERON = "poweron";
|
||||||
|
public static final String SHELLY2_WAKEUPO_BOOT_RESTART = "software_restart";
|
||||||
|
public static final String SHELLY2_WAKEUPO_BOOT_WAKEUP = "deepsleep_wake";
|
||||||
|
public static final String SHELLY2_WAKEUPO_BOOT_INTERNAL = "internal";
|
||||||
|
public static final String SHELLY2_WAKEUPO_BOOT_UNKNOWN = "unknown";
|
||||||
|
|
||||||
|
public static final String SHELLY2_WAKEUPOCAUSE_BUTTON = "button";
|
||||||
|
public static final String SHELLY2_WAKEUPOCAUSE_USB = "usb";
|
||||||
|
public static final String SHELLY2_WAKEUPOCAUSE_PERIODIC = "periodic";
|
||||||
|
public static final String SHELLY2_WAKEUPOCAUSE_UPDATE = "status_update";
|
||||||
|
public static final String SHELLY2_WAKEUPOCAUSE_UNDEFINED = "undefined";
|
||||||
|
|
||||||
|
public class Shelly2DevConfigBle {
|
||||||
|
public Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DevConfigEth {
|
||||||
|
public Boolean enable;
|
||||||
|
public String ipv4mode;
|
||||||
|
public String ip;
|
||||||
|
public String netmask;
|
||||||
|
public String gw;
|
||||||
|
public String nameserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceSettings {
|
||||||
|
public String name;
|
||||||
|
public String id;
|
||||||
|
public String mac;
|
||||||
|
public String model;
|
||||||
|
public Integer gen;
|
||||||
|
@SerializedName("fw_id")
|
||||||
|
public String firmware;
|
||||||
|
public String ver;
|
||||||
|
public String app;
|
||||||
|
@SerializedName("auth_en")
|
||||||
|
public Boolean authEnable;
|
||||||
|
@SerializedName("auth_domain")
|
||||||
|
public String authDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceConfigAp {
|
||||||
|
public static class Shelly2DeviceConfigApRE {
|
||||||
|
public Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean enable;
|
||||||
|
public String ssid;
|
||||||
|
public String password;
|
||||||
|
@SerializedName("is_open")
|
||||||
|
public Boolean isOpen;
|
||||||
|
@SerializedName("range_extender")
|
||||||
|
Shelly2DeviceConfigApRE rangeExtender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceConfig {
|
||||||
|
public class Shelly2DeviceConfigSys {
|
||||||
|
public class Shelly2DeviceConfigDevice {
|
||||||
|
public String name;
|
||||||
|
public String mac;
|
||||||
|
@SerializedName("fw_id")
|
||||||
|
public String fwId;
|
||||||
|
public String profile;
|
||||||
|
@SerializedName("eco_mode")
|
||||||
|
public Boolean ecoMode;
|
||||||
|
public Boolean discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigLocation {
|
||||||
|
public String tz;
|
||||||
|
public Double lat;
|
||||||
|
public Double lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigSntp {
|
||||||
|
public String server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigSleep {
|
||||||
|
@SerializedName("wakeup_period")
|
||||||
|
public Integer wakeupPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigDebug {
|
||||||
|
public class Shelly2DeviceConfigDebugMqtt {
|
||||||
|
public Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigDebugWebSocket {
|
||||||
|
public Boolean enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigDebugUdp {
|
||||||
|
public String addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2DeviceConfigDebugMqtt mqtt;
|
||||||
|
public Shelly2DeviceConfigDebugWebSocket websocket;
|
||||||
|
public Shelly2DeviceConfigDebugUdp udp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigUiData {
|
||||||
|
public String cover; // hold comma seperated list of roller favorites
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigRpcUdp {
|
||||||
|
@SerializedName("dst_addr")
|
||||||
|
public String dstAddr;
|
||||||
|
@SerializedName("listenPort")
|
||||||
|
public String listenPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("cfg_rev")
|
||||||
|
public Integer cfgRevision;
|
||||||
|
public Shelly2DeviceConfigDevice device;
|
||||||
|
public Shelly2DeviceConfigLocation location;
|
||||||
|
public Shelly2DeviceConfigSntp sntp;
|
||||||
|
public Shelly2DeviceConfigSleep sleep;
|
||||||
|
public Shelly2DeviceConfigDebug debug;
|
||||||
|
@SerializedName("ui_data")
|
||||||
|
public Shelly2DeviceConfigUiData uiData;
|
||||||
|
@SerializedName("rpc_udp")
|
||||||
|
public Shelly2DeviceConfigRpcUdp rpcUdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DevConfigInput {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public String type;
|
||||||
|
public Boolean invert;
|
||||||
|
@SerializedName("factory_reset")
|
||||||
|
public Boolean factoryReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DevConfigSwitch {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@SerializedName("in_mode")
|
||||||
|
public String mode;
|
||||||
|
|
||||||
|
@SerializedName("initial_state")
|
||||||
|
public String initialState;
|
||||||
|
@SerializedName("auto_on")
|
||||||
|
public Boolean autoOn;
|
||||||
|
@SerializedName("auto_on_delay")
|
||||||
|
public Double autoOnDelay;
|
||||||
|
@SerializedName("auto_off")
|
||||||
|
public Boolean autoOff;
|
||||||
|
@SerializedName("auto_off_delay")
|
||||||
|
public Double autoOffDelay;
|
||||||
|
@SerializedName("power_limit")
|
||||||
|
public Integer powerLimit;
|
||||||
|
@SerializedName("voltage_limit")
|
||||||
|
public Integer voltageLimit;
|
||||||
|
@SerializedName("current_limit")
|
||||||
|
public Double currentLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DevConfigCover {
|
||||||
|
public class Shelly2DeviceConfigCoverMotor {
|
||||||
|
@SerializedName("idle_power_thr")
|
||||||
|
public Double idle_powerThr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigCoverSafetySwitch {
|
||||||
|
public Boolean enable;
|
||||||
|
public String direction;
|
||||||
|
public String action;
|
||||||
|
@SerializedName("allowed_move")
|
||||||
|
public String allowedMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigCoverObstructionDetection {
|
||||||
|
public Boolean enable;
|
||||||
|
public String direction;
|
||||||
|
public String action;
|
||||||
|
@SerializedName("power_thr")
|
||||||
|
public Integer powerThr;
|
||||||
|
public Double holdoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public Shelly2DeviceConfigCoverMotor motor;
|
||||||
|
@SerializedName("maxtime_open")
|
||||||
|
public Double maxtimeOpen;
|
||||||
|
@SerializedName("maxtime_close")
|
||||||
|
public Double maxtimeClose;
|
||||||
|
@SerializedName("initial_state")
|
||||||
|
public String initialState;
|
||||||
|
@SerializedName("invert_directions")
|
||||||
|
public Boolean invertDirections;
|
||||||
|
@SerializedName("in_mode")
|
||||||
|
public String inMode;
|
||||||
|
@SerializedName("swap_inputs")
|
||||||
|
public Boolean swapInputs;
|
||||||
|
@SerializedName("safety_switch")
|
||||||
|
public Shelly2DeviceConfigCoverSafetySwitch safetySwitch;
|
||||||
|
@SerializedName("power_limit")
|
||||||
|
public Integer powerLimit;
|
||||||
|
@SerializedName("voltage_limit")
|
||||||
|
public Integer voltageLimit;
|
||||||
|
@SerializedName("current_limit")
|
||||||
|
public Double currentLimit;
|
||||||
|
@SerializedName("obstruction_detection")
|
||||||
|
public Shelly2DeviceConfigCoverObstructionDetection obstructionDetection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2GetConfigResult {
|
||||||
|
|
||||||
|
public class Shelly2DevConfigCloud {
|
||||||
|
public Boolean enable;
|
||||||
|
public String server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DevConfigMqtt {
|
||||||
|
public Boolean enable;
|
||||||
|
public String server;
|
||||||
|
public String user;
|
||||||
|
@SerializedName("topic_prefix:0")
|
||||||
|
public String topicPrefix;
|
||||||
|
@SerializedName("rpc_ntf")
|
||||||
|
public String rpcNtf;
|
||||||
|
@SerializedName("status_ntf")
|
||||||
|
public String statusNtf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2DevConfigBle ble;
|
||||||
|
public Shelly2DevConfigEth eth;
|
||||||
|
public Shelly2DevConfigCloud cloud;
|
||||||
|
public Shelly2DevConfigMqtt mqtt;
|
||||||
|
public Shelly2DeviceConfigSys sys;
|
||||||
|
public Shelly2DeviceConfigWiFi wifi;
|
||||||
|
|
||||||
|
@SerializedName("input:0")
|
||||||
|
public Shelly2DevConfigInput input0;
|
||||||
|
@SerializedName("input:1")
|
||||||
|
public Shelly2DevConfigInput input1;
|
||||||
|
@SerializedName("input:2")
|
||||||
|
public Shelly2DevConfigInput input2;
|
||||||
|
@SerializedName("input:3")
|
||||||
|
public Shelly2DevConfigInput input3;
|
||||||
|
|
||||||
|
@SerializedName("switch:0")
|
||||||
|
public Shelly2DevConfigSwitch switch0;
|
||||||
|
@SerializedName("switch:1")
|
||||||
|
public Shelly2DevConfigSwitch switch1;
|
||||||
|
@SerializedName("switch:2")
|
||||||
|
public Shelly2DevConfigSwitch switch2;
|
||||||
|
@SerializedName("switch:3")
|
||||||
|
public Shelly2DevConfigSwitch switch3;
|
||||||
|
|
||||||
|
@SerializedName("cover:0")
|
||||||
|
public Shelly2DevConfigCover cover0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigSta {
|
||||||
|
public String ssid;
|
||||||
|
public String password;
|
||||||
|
@SerializedName("is_open")
|
||||||
|
public Boolean isOpen;
|
||||||
|
public Boolean enable;
|
||||||
|
public String ipv4mode;
|
||||||
|
public String ip;
|
||||||
|
public String netmask;
|
||||||
|
public String gw;
|
||||||
|
public String nameserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigRoam {
|
||||||
|
@SerializedName("rssi_thr")
|
||||||
|
public Integer rssiThr;
|
||||||
|
public Integer interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceConfigWiFi {
|
||||||
|
public Shelly2DeviceConfigAp ap;
|
||||||
|
public Shelly2DeviceConfigSta sta;
|
||||||
|
public Shelly2DeviceConfigSta sta1;
|
||||||
|
public Shelly2DeviceConfigRoam roam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id;
|
||||||
|
public String src;
|
||||||
|
public Shelly2GetConfigResult result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceStatus {
|
||||||
|
public class Shelly2InputStatus {
|
||||||
|
public Integer id;
|
||||||
|
public Boolean state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceStatusResult {
|
||||||
|
public class Shelly2DeviceStatusBle {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusCloud {
|
||||||
|
public Boolean connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusMqqt {
|
||||||
|
public Boolean connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2CoverStatus {
|
||||||
|
public Integer id;
|
||||||
|
public String source;
|
||||||
|
public String state;
|
||||||
|
public Double apower;
|
||||||
|
public Double voltage;
|
||||||
|
public Double current;
|
||||||
|
public Double pf;
|
||||||
|
public Shelly2Energy aenergy;
|
||||||
|
@SerializedName("current_pos")
|
||||||
|
public Integer currentPos;
|
||||||
|
@SerializedName("target_pos")
|
||||||
|
public Integer targetPos;
|
||||||
|
@SerializedName("move_timeout")
|
||||||
|
public Double moveTimeout;
|
||||||
|
@SerializedName("move_started_at")
|
||||||
|
public Double moveStartedAt;
|
||||||
|
@SerializedName("pos_control")
|
||||||
|
public Boolean posControl;
|
||||||
|
public Shelly2DeviceStatusTemp temperature;
|
||||||
|
public ArrayList<String> errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusHumidity {
|
||||||
|
public Integer id;
|
||||||
|
public Double rh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusTempId extends Shelly2DeviceStatusTemp {
|
||||||
|
public Integer id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceStatusPower {
|
||||||
|
public static class Shelly2DeviceStatusBattery {
|
||||||
|
@SerializedName("V")
|
||||||
|
public Double volt;
|
||||||
|
public Double percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceStatusCharger {
|
||||||
|
public Boolean present;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer id;
|
||||||
|
public Shelly2DeviceStatusBattery battery;
|
||||||
|
public Shelly2DeviceStatusCharger external;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2DeviceStatusBle ble;
|
||||||
|
public Shelly2DeviceStatusCloud cloud;
|
||||||
|
public Shelly2DeviceStatusMqqt mqtt;
|
||||||
|
public Shelly2DeviceStatusSys sys;
|
||||||
|
public Shelly2DeviceStatusSysWiFi wifi;
|
||||||
|
|
||||||
|
@SerializedName("input:0")
|
||||||
|
public Shelly2InputStatus input0;
|
||||||
|
@SerializedName("input:1")
|
||||||
|
public Shelly2InputStatus input1;
|
||||||
|
@SerializedName("input:2")
|
||||||
|
public Shelly2InputStatus input2;
|
||||||
|
@SerializedName("input:3")
|
||||||
|
public Shelly2InputStatus input3;
|
||||||
|
|
||||||
|
@SerializedName("switch:0")
|
||||||
|
public Shelly2RelayStatus switch0;
|
||||||
|
@SerializedName("switch:1")
|
||||||
|
public Shelly2RelayStatus switch1;
|
||||||
|
@SerializedName("switch:2")
|
||||||
|
public Shelly2RelayStatus switch2;
|
||||||
|
@SerializedName("switch:3")
|
||||||
|
public Shelly2RelayStatus switch3;
|
||||||
|
|
||||||
|
@SerializedName("cover:0")
|
||||||
|
public Shelly2CoverStatus cover0;
|
||||||
|
|
||||||
|
@SerializedName("humidity:0")
|
||||||
|
public Shelly2DeviceStatusHumidity humidity0;
|
||||||
|
@SerializedName("temperature:0")
|
||||||
|
public Shelly2DeviceStatusTempId temperature0;
|
||||||
|
@SerializedName("devicepower:0")
|
||||||
|
public Shelly2DeviceStatusPower devicepower0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusSys {
|
||||||
|
public class Shelly2DeviceStatusSysAvlUpdate {
|
||||||
|
public class Shelly2DeviceStatusSysUpdate {
|
||||||
|
public String version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2DeviceStatusSysUpdate stable;
|
||||||
|
public Shelly2DeviceStatusSysUpdate beta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusWakeup {
|
||||||
|
public String boot;
|
||||||
|
public String cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String mac;
|
||||||
|
@SerializedName("restart_required")
|
||||||
|
public Boolean restartRequired;
|
||||||
|
public String time;
|
||||||
|
public Long unixtime;
|
||||||
|
public Long uptime;
|
||||||
|
@SerializedName("ram_size")
|
||||||
|
public Long ramSize;
|
||||||
|
@SerializedName("ram_free")
|
||||||
|
public Long ramFree;
|
||||||
|
@SerializedName("fs_size")
|
||||||
|
public Long fsSize;
|
||||||
|
@SerializedName("fs_free")
|
||||||
|
public Long fsFree;
|
||||||
|
@SerializedName("cfg_rev")
|
||||||
|
public Integer cfg_rev;
|
||||||
|
@SerializedName("available_updates")
|
||||||
|
public Shelly2DeviceStatusSysAvlUpdate availableUpdates;
|
||||||
|
@SerializedName("webhook_rev")
|
||||||
|
public Integer webHookRev;
|
||||||
|
@SerializedName("wakeup_reason")
|
||||||
|
public Shelly2DeviceStatusWakeup wakeUpReason;
|
||||||
|
@SerializedName("wakeup_period")
|
||||||
|
public Integer wakeupPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2DeviceStatusSysWiFi {
|
||||||
|
@SerializedName("sta_ip")
|
||||||
|
public String staIP;
|
||||||
|
public String status;
|
||||||
|
public String ssid;
|
||||||
|
public Integer rssi;
|
||||||
|
@SerializedName("ap_client_count")
|
||||||
|
public Integer apClientCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String id;
|
||||||
|
public String src;
|
||||||
|
public Shelly2DeviceStatusResult result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2RelayStatus {
|
||||||
|
public Integer id;
|
||||||
|
public String source;
|
||||||
|
public Boolean output;
|
||||||
|
@SerializedName("timer_started_at")
|
||||||
|
public Double timerStartetAt;
|
||||||
|
@SerializedName("timer_duration")
|
||||||
|
public Integer timerDuration;
|
||||||
|
public Double apower;
|
||||||
|
public Double voltage;
|
||||||
|
public Double current;
|
||||||
|
public Double pf;
|
||||||
|
public Shelly2Energy aenergy;
|
||||||
|
public Shelly2DeviceStatusTemp temperature;
|
||||||
|
public String[] errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2DeviceStatusTemp {
|
||||||
|
public Double tC;
|
||||||
|
public Double tF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2Energy {
|
||||||
|
// "switch:1":{"id":1,"aenergy":{"total":0.003,"by_minute":[0.000,0.000,0.000],"minute_ts":1619910239}}}}
|
||||||
|
public Double total;
|
||||||
|
@SerializedName("by_minute")
|
||||||
|
public Double[] byMinute;
|
||||||
|
@SerializedName("minute_ts")
|
||||||
|
public Long minuteTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2ConfigParms {
|
||||||
|
public String name;
|
||||||
|
public Boolean enable;
|
||||||
|
public String server;
|
||||||
|
@SerializedName("ssl_ca")
|
||||||
|
public String sslCA;
|
||||||
|
|
||||||
|
// WiFi.SetConfig
|
||||||
|
public Shelly2DeviceConfigAp ap;
|
||||||
|
|
||||||
|
// Switch.SetConfig
|
||||||
|
@SerializedName("auto_on")
|
||||||
|
public Boolean autoOn;
|
||||||
|
@SerializedName("auto_on_delay")
|
||||||
|
public Double autoOnDelay;
|
||||||
|
@SerializedName("auto_off")
|
||||||
|
public Boolean autoOff;
|
||||||
|
@SerializedName("auto_off_delay")
|
||||||
|
public Double autoOffDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2RpcRequest {
|
||||||
|
public Integer id = 0;
|
||||||
|
public String method;
|
||||||
|
|
||||||
|
public static class Shelly2RpcRequestParams {
|
||||||
|
public Integer id = 1;
|
||||||
|
|
||||||
|
// Cover
|
||||||
|
public Integer pos;
|
||||||
|
public Boolean on;
|
||||||
|
|
||||||
|
// Shelly.SetAuth
|
||||||
|
public String user;
|
||||||
|
public String realm;
|
||||||
|
public String ha1;
|
||||||
|
|
||||||
|
// Shelly.Update
|
||||||
|
public String stage;
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
// Cloud.SetConfig
|
||||||
|
public Shelly2ConfigParms config;
|
||||||
|
|
||||||
|
public Shelly2RpcRequestParams withConfig() {
|
||||||
|
config = new Shelly2ConfigParms();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||||
|
|
||||||
|
public Shelly2RpcRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2RpcRequest withMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2RpcRequest withId(int id) {
|
||||||
|
params.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2RpcRequest withPos(int pos) {
|
||||||
|
params.pos = pos;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2WsConfigResponse {
|
||||||
|
public Integer id;
|
||||||
|
public String src;
|
||||||
|
|
||||||
|
public static class Shelly2WsConfigResult {
|
||||||
|
@SerializedName("restart_required")
|
||||||
|
public Boolean restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2WsConfigResult result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2RpcBaseMessage {
|
||||||
|
// Basic message format, e.g.
|
||||||
|
// {"id":1,"src":"localweb528","method":"Shelly.GetConfig"}
|
||||||
|
public class Shelly2RpcMessageError {
|
||||||
|
public Integer code;
|
||||||
|
public String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer id;
|
||||||
|
public String src;
|
||||||
|
public String dst;
|
||||||
|
public String method;
|
||||||
|
public Object params;
|
||||||
|
public Object result;
|
||||||
|
public Shelly2AuthRequest auth;
|
||||||
|
public Shelly2RpcMessageError error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2RpcNotifyStatus {
|
||||||
|
public static class Shelly2NotifyStatus extends Shelly2DeviceStatusResult {
|
||||||
|
public Double ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer id;
|
||||||
|
public String src;
|
||||||
|
public String dst;
|
||||||
|
public String method;
|
||||||
|
public Shelly2NotifyStatus params;
|
||||||
|
public Shelly2NotifyStatus result;
|
||||||
|
public Shelly2RpcMessageError error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String SHELLY2_AUTHTTYPE_DIGEST = "digest";
|
||||||
|
public static String SHELLY2_AUTHTTYPE_STRING = "string";
|
||||||
|
public static String SHELLY2_AUTHALG_SHA256 = "SHA-256";
|
||||||
|
// = ':auth:'+HexHash("dummy_method:dummy_uri");
|
||||||
|
public static String SHELLY2_AUTH_NOISE = "6370ec69915103833b5222b368555393393f098bfbfbb59f47e0590af135f062";
|
||||||
|
|
||||||
|
public static class Shelly2AuthRequest {
|
||||||
|
public String username;
|
||||||
|
public Long nonce;
|
||||||
|
public Long cnonce;
|
||||||
|
public Integer nc;
|
||||||
|
public String realm;
|
||||||
|
public String algorithm;
|
||||||
|
public String response;
|
||||||
|
@SerializedName("auth_type")
|
||||||
|
public String authType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2AuthResponse { // on 401 message contains the auth info
|
||||||
|
@SerializedName("auth_type")
|
||||||
|
public String authType;
|
||||||
|
public Long nonce;
|
||||||
|
public Integer nc;
|
||||||
|
public String realm;
|
||||||
|
public String algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2NotifyEvent {
|
||||||
|
public Integer id;
|
||||||
|
public Double ts;
|
||||||
|
public String component;
|
||||||
|
public String event;
|
||||||
|
public String msg;
|
||||||
|
public Integer reason;
|
||||||
|
@SerializedName("cfg_rev")
|
||||||
|
public Integer cfgRev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Shelly2NotifyEventData {
|
||||||
|
public Double ts;
|
||||||
|
public ArrayList<Shelly2NotifyEvent> events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Shelly2RpcNotifyEvent {
|
||||||
|
public Double ts;
|
||||||
|
Shelly2NotifyEventData params;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,931 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.api2;
|
||||||
|
|
||||||
|
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsWiFiNetwork;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2ConfigParms;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DeviceConfigSta;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp.Shelly2DeviceConfigApRE;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceSettings;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusSys.Shelly2DeviceStatusSysAvlUpdate;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus.Shelly2NotifyStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest.Shelly2RpcRequestParams;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse.Shelly2WsConfigResult;
|
||||||
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Shelly2ApiRpc} implements Gen2 RPC interface
|
||||||
|
*
|
||||||
|
* @author Markus Michels - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterface, Shelly2RpctInterface {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Shelly2ApiRpc.class);
|
||||||
|
private final @Nullable ShellyThingTable thingTable;
|
||||||
|
|
||||||
|
private boolean initialized = false;
|
||||||
|
private boolean discovery = false;
|
||||||
|
private Shelly2RpcSocket rpcSocket = new Shelly2RpcSocket();
|
||||||
|
private Shelly2AuthResponse authInfo = new Shelly2AuthResponse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular constructor - called by Thing handler
|
||||||
|
*
|
||||||
|
* @param thingName Symbolic thing name
|
||||||
|
* @param thing Thing Handler (ThingHandlerInterface)
|
||||||
|
*/
|
||||||
|
public Shelly2ApiRpc(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
|
||||||
|
super(thingName, thing);
|
||||||
|
this.thingName = thingName;
|
||||||
|
this.thing = thing;
|
||||||
|
this.thingTable = thingTable;
|
||||||
|
try {
|
||||||
|
getProfile().initFromThingType(thing.getThingType());
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
logger.info("{}: Shelly2 API initialization failed!", thingName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple initialization - called by discovery handler
|
||||||
|
*
|
||||||
|
* @param thingName Symbolic thing name
|
||||||
|
* @param config Thing Configuration
|
||||||
|
* @param httpClient HTTP Client to be passed to ShellyHttpClient
|
||||||
|
*/
|
||||||
|
public Shelly2ApiRpc(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||||
|
super(thingName, config, httpClient);
|
||||||
|
this.thingName = thingName;
|
||||||
|
this.thingTable = null;
|
||||||
|
this.discovery = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() throws ShellyApiException {
|
||||||
|
if (!initialized) {
|
||||||
|
rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp);
|
||||||
|
rpcSocket.addMessageHandler(this);
|
||||||
|
initialized = true;
|
||||||
|
} else {
|
||||||
|
if (rpcSocket.isConnected()) {
|
||||||
|
logger.debug("{}: Disconnect Rpc Socket on initialize", thingName);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||||
|
ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
|
||||||
|
|
||||||
|
Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class);
|
||||||
|
profile.isGen2 = true;
|
||||||
|
profile.settingsJson = gson.toJson(dc);
|
||||||
|
profile.thingName = thingName;
|
||||||
|
profile.settings.name = profile.status.name = dc.sys.device.name;
|
||||||
|
profile.name = getString(profile.settings.name);
|
||||||
|
profile.settings.timezone = getString(dc.sys.location.tz);
|
||||||
|
profile.settings.discoverable = getBool(dc.sys.device.discoverable);
|
||||||
|
if (dc.wifi != null && dc.wifi.ap != null && dc.wifi.ap.rangeExtender != null) {
|
||||||
|
profile.settings.wifiAp.rangeExtender = getBool(dc.wifi.ap.rangeExtender.enable);
|
||||||
|
}
|
||||||
|
if (dc.cloud != null) {
|
||||||
|
profile.settings.cloud.enabled = getBool(dc.cloud.enable);
|
||||||
|
}
|
||||||
|
if (dc.mqtt != null) {
|
||||||
|
profile.settings.mqtt.enable = getBool(dc.mqtt.enable);
|
||||||
|
}
|
||||||
|
if (dc.sys.sntp != null) {
|
||||||
|
profile.settings.sntp.server = dc.sys.sntp.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.isRoller = dc.cover0 != null;
|
||||||
|
profile.settings.relays = fillRelaySettings(profile, dc);
|
||||||
|
profile.settings.inputs = fillInputSettings(profile, dc);
|
||||||
|
profile.settings.rollers = fillRollerSettings(profile, dc);
|
||||||
|
|
||||||
|
profile.isEMeter = true;
|
||||||
|
profile.numInputs = profile.settings.inputs != null ? profile.settings.inputs.size() : 0;
|
||||||
|
profile.numRelays = profile.settings.relays != null ? profile.settings.relays.size() : 0;
|
||||||
|
profile.numRollers = profile.settings.rollers != null ? profile.settings.rollers.size() : 0;
|
||||||
|
profile.hasRelays = profile.numRelays > 0 || profile.numRollers > 0;
|
||||||
|
profile.mode = "";
|
||||||
|
if (profile.hasRelays) {
|
||||||
|
profile.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellySettingsDevice device = getDeviceInfo();
|
||||||
|
profile.settings.device = device;
|
||||||
|
profile.hostname = device.hostname;
|
||||||
|
profile.deviceType = device.type;
|
||||||
|
profile.mac = device.mac;
|
||||||
|
profile.auth = device.auth;
|
||||||
|
if (config.serviceName.isEmpty()) {
|
||||||
|
config.serviceName = getString(profile.hostname);
|
||||||
|
}
|
||||||
|
profile.fwDate = substringBefore(device.fw, "/");
|
||||||
|
profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-");
|
||||||
|
profile.status.update.oldVersion = profile.fwVersion;
|
||||||
|
profile.status.hasUpdate = profile.status.update.hasUpdate = false;
|
||||||
|
|
||||||
|
if (dc.eth != null) {
|
||||||
|
profile.settings.ethernet = getBool(dc.eth.enable);
|
||||||
|
}
|
||||||
|
if (dc.ble != null) {
|
||||||
|
profile.settings.bluetooth = getBool(dc.ble.enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.settings.wifiSta = new ShellySettingsWiFiNetwork();
|
||||||
|
profile.settings.wifiSta1 = new ShellySettingsWiFiNetwork();
|
||||||
|
fillWiFiSta(dc.wifi.sta, profile.settings.wifiSta);
|
||||||
|
fillWiFiSta(dc.wifi.sta1, profile.settings.wifiSta1);
|
||||||
|
|
||||||
|
if (profile.hasRelays) {
|
||||||
|
profile.status.relays = new ArrayList<>();
|
||||||
|
profile.status.meters = new ArrayList<>();
|
||||||
|
profile.status.emeters = new ArrayList<>();
|
||||||
|
relayStatus.relays = new ArrayList<>();
|
||||||
|
relayStatus.meters = new ArrayList<>();
|
||||||
|
profile.numMeters = profile.isRoller ? profile.numRollers : profile.numRelays;
|
||||||
|
for (int i = 0; i < profile.numRelays; i++) {
|
||||||
|
profile.status.relays.add(new ShellySettingsRelay());
|
||||||
|
relayStatus.relays.add(new ShellyShortStatusRelay());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < profile.numMeters; i++) {
|
||||||
|
profile.status.meters.add(new ShellySettingsMeter());
|
||||||
|
profile.status.emeters.add(new ShellySettingsEMeter());
|
||||||
|
relayStatus.meters.add(new ShellySettingsMeter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.numInputs > 0) {
|
||||||
|
profile.status.inputs = new ArrayList<>();
|
||||||
|
relayStatus.inputs = new ArrayList<>();
|
||||||
|
for (int i = 0; i < profile.numInputs; i++) {
|
||||||
|
ShellyInputState input = new ShellyInputState();
|
||||||
|
input.input = 0;
|
||||||
|
input.event = "";
|
||||||
|
input.eventCount = 0;
|
||||||
|
profile.status.inputs.add(input);
|
||||||
|
relayStatus.inputs.add(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.isRoller) {
|
||||||
|
profile.status.rollers = new ArrayList<>();
|
||||||
|
for (int i = 0; i < profile.numRollers; i++) {
|
||||||
|
ShellyRollerStatus rs = new ShellyRollerStatus();
|
||||||
|
profile.status.rollers.add(rs);
|
||||||
|
rollerStatus.add(rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.status.dimmers = profile.isDimmer ? new ArrayList<>() : null;
|
||||||
|
profile.status.lights = profile.isBulb ? new ArrayList<>() : null;
|
||||||
|
profile.status.thermostats = profile.isTRV ? new ArrayList<>() : null;
|
||||||
|
|
||||||
|
if (profile.hasBattery) {
|
||||||
|
profile.settings.sleepMode = new ShellySensorSleepMode();
|
||||||
|
profile.settings.sleepMode.unit = "m";
|
||||||
|
profile.settings.sleepMode.period = dc.sys.sleep != null ? dc.sys.sleep.wakeupPeriod / 60 : 720;
|
||||||
|
checkSetWsCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.initialized = true;
|
||||||
|
if (!discovery) {
|
||||||
|
getStatus(); // make sure profile.status is initialized (e.g,. relay/meter status)
|
||||||
|
asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillWiFiSta(@Nullable Shelly2DeviceConfigSta from, ShellySettingsWiFiNetwork to) {
|
||||||
|
to.enabled = from != null && !getString(from.ssid).isEmpty();
|
||||||
|
if (from != null) {
|
||||||
|
to.ssid = from.ssid;
|
||||||
|
to.ip = from.ip;
|
||||||
|
to.mask = from.netmask;
|
||||||
|
to.dns = from.nameserver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSetWsCallback() throws ShellyApiException {
|
||||||
|
Shelly2ConfigParms wsConfig = apiRequest(SHELLYRPC_METHOD_WSGETCONFIG, null, Shelly2ConfigParms.class);
|
||||||
|
String url = "ws://" + config.localIp + ":" + config.localPort + "/shelly/wsevent";
|
||||||
|
if (!getBool(wsConfig.enable) || !url.equalsIgnoreCase(getString(wsConfig.server))) {
|
||||||
|
logger.debug("{}: A battery device was detected without correct callback, fix it", thingName);
|
||||||
|
wsConfig.enable = true;
|
||||||
|
wsConfig.server = url;
|
||||||
|
Shelly2RpcRequest request = new Shelly2RpcRequest();
|
||||||
|
request.id = 0;
|
||||||
|
request.method = SHELLYRPC_METHOD_WSSETCONFIG;
|
||||||
|
request.params.config = wsConfig;
|
||||||
|
Shelly2WsConfigResponse response = apiRequest(SHELLYRPC_METHOD_WSSETCONFIG, request.params,
|
||||||
|
Shelly2WsConfigResponse.class);
|
||||||
|
if (response.result != null && response.result.restartRequired) {
|
||||||
|
logger.info("{}: WebSocket callback was updated, device is restarting", thingName);
|
||||||
|
getThing().getApi().deviceReboot();
|
||||||
|
getThing().reinitializeThing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnect(String deviceIp, boolean connected) {
|
||||||
|
if (thing == null && thingTable != null) {
|
||||||
|
thing = thingTable.getThing(deviceIp);
|
||||||
|
logger.debug("{}: Get thing from thingTable", thingName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotifyStatus(Shelly2RpcNotifyStatus message) {
|
||||||
|
logger.debug("{}: NotifyStatus update received: {}", thingName, gson.toJson(message));
|
||||||
|
try {
|
||||||
|
ShellyThingInterface t = thing;
|
||||||
|
if (t == null) {
|
||||||
|
logger.debug("{}: No matching thing on NotifyStatus for {}, ignore (src={}, dst={}, discovery={})",
|
||||||
|
thingName, thingName, message.src, message.dst, discovery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!t.isThingOnline() && t.getThingStatusDetail() != ThingStatusDetail.CONFIGURATION_PENDING) {
|
||||||
|
logger.debug("{}: Thing is not in online state/connectable, ignore NotifyStatus", thingName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getThing().incProtMessages();
|
||||||
|
if (message.error != null) {
|
||||||
|
if (message.error.code == HttpStatus.UNAUTHORIZED_401 && !getString(message.error.message).isEmpty()) {
|
||||||
|
// Save nonce for notification
|
||||||
|
Shelly2AuthResponse auth = gson.fromJson(message.error.message, Shelly2AuthResponse.class);
|
||||||
|
if (auth != null && auth.realm == null) {
|
||||||
|
logger.debug("{}: Authentication data received: {}", thingName, message.error.message);
|
||||||
|
authInfo = auth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("{}: Error status received - {} {}", thingName, message.error.code,
|
||||||
|
message.error.message);
|
||||||
|
incProtErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shelly2NotifyStatus params = message.params;
|
||||||
|
if (params != null) {
|
||||||
|
if (getThing().getThingStatusDetail() != ThingStatusDetail.FIRMWARE_UPDATING) {
|
||||||
|
getThing().setThingOnline();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean updated = false;
|
||||||
|
ShellyDeviceProfile profile = getProfile();
|
||||||
|
ShellySettingsStatus status = profile.status;
|
||||||
|
if (params.sys != null) {
|
||||||
|
if (getBool(params.sys.restartRequired)) {
|
||||||
|
logger.warn("{}: Device requires restart to activate changes", thingName);
|
||||||
|
}
|
||||||
|
status.uptime = params.sys.uptime;
|
||||||
|
}
|
||||||
|
status.temperature = SHELLY_API_INVTEMP; // mark invalid
|
||||||
|
updated |= fillDeviceStatus(status, message.params, true);
|
||||||
|
if (getDouble(status.temperature) == SHELLY_API_INVTEMP) {
|
||||||
|
// no device temp available
|
||||||
|
status.temperature = null;
|
||||||
|
} else {
|
||||||
|
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||||
|
toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.status = status;
|
||||||
|
if (updated) {
|
||||||
|
getThing().restartWatchdog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
logger.debug("{}: Unable to process status update", thingName, e);
|
||||||
|
incProtErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
|
||||||
|
try {
|
||||||
|
logger.debug("{}: NotifyEvent received: {}", thingName, gson.toJson(message));
|
||||||
|
ShellyDeviceProfile profile = getProfile();
|
||||||
|
|
||||||
|
getThing().incProtMessages();
|
||||||
|
getThing().restartWatchdog();
|
||||||
|
|
||||||
|
for (Shelly2NotifyEvent e : message.params.events) {
|
||||||
|
switch (e.event) {
|
||||||
|
case SHELLY2_EVENT_BTNUP:
|
||||||
|
case SHELLY2_EVENT_BTNDOWN:
|
||||||
|
String bgroup = getProfile().getInputGroup(e.id);
|
||||||
|
updateChannel(bgroup, CHANNEL_INPUT + profile.getInputSuffix(e.id),
|
||||||
|
getOnOff(SHELLY2_EVENT_BTNDOWN.equals(getString(e.event))));
|
||||||
|
getThing().triggerButton(profile.getInputGroup(e.id), e.id,
|
||||||
|
mapValue(MAP_INPUT_EVENT_ID, e.event));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SHELLY2_EVENT_1PUSH:
|
||||||
|
case SHELLY2_EVENT_2PUSH:
|
||||||
|
case SHELLY2_EVENT_3PUSH:
|
||||||
|
case SHELLY2_EVENT_LPUSH:
|
||||||
|
case SHELLY2_EVENT_SLPUSH:
|
||||||
|
case SHELLY2_EVENT_LSPUSH:
|
||||||
|
if (e.id < profile.numInputs) {
|
||||||
|
ShellyInputState input = relayStatus.inputs.get(e.id);
|
||||||
|
input.event = getString(MAP_INPUT_EVENT_TYPE.get(e.event));
|
||||||
|
input.eventCount = getInteger(input.eventCount) + 1;
|
||||||
|
relayStatus.inputs.set(e.id, input);
|
||||||
|
profile.status.inputs.set(e.id, input);
|
||||||
|
|
||||||
|
String group = getProfile().getInputGroup(e.id);
|
||||||
|
updateChannel(group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(e.id),
|
||||||
|
getStringType(input.event));
|
||||||
|
updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(e.id),
|
||||||
|
getDecimal(input.eventCount));
|
||||||
|
getThing().triggerButton(profile.getInputGroup(e.id), e.id,
|
||||||
|
mapValue(MAP_INPUT_EVENT_ID, e.event));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_CFGCHANGED:
|
||||||
|
logger.debug("{}: Configuration update detected, re-initialize", thingName);
|
||||||
|
getThing().requestUpdates(1, true); // refresh config
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SHELLY2_EVENT_OTASTART:
|
||||||
|
logger.debug("{}: Firmware update started: {}", thingName, getString(e.msg));
|
||||||
|
getThing().postEvent(e.event, true);
|
||||||
|
getThing().setThingOffline(ThingStatusDetail.FIRMWARE_UPDATING,
|
||||||
|
"offline.status-error-fwupgrade");
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_OTAPROGRESS:
|
||||||
|
logger.debug("{}: Firmware update in progress: {}", thingName, getString(e.msg));
|
||||||
|
getThing().postEvent(e.event, false);
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_OTADONE:
|
||||||
|
logger.debug("{}: Firmware update completed: {}", thingName, getString(e.msg));
|
||||||
|
getThing().setThingOffline(ThingStatusDetail.CONFIGURATION_PENDING,
|
||||||
|
"offline.status-error-restarted");
|
||||||
|
getThing().requestUpdates(1, true); // refresh config
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_SLEEP:
|
||||||
|
logger.debug("{}: Device went to sleep mode", thingName);
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_WIFICONNFAILED:
|
||||||
|
logger.debug("{}: WiFi connect failed, check setup, reason {}", thingName,
|
||||||
|
getInteger(e.reason));
|
||||||
|
getThing().postEvent(e.event, false);
|
||||||
|
break;
|
||||||
|
case SHELLY2_EVENT_WIFIDISCONNECTED:
|
||||||
|
logger.debug("{}: WiFi disconnected, reason {}", thingName, getInteger(e.reason));
|
||||||
|
getThing().postEvent(e.event, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.debug("{}: Event {} was not handled", thingName, e.event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
logger.debug("{}: Unable to process event", thingName, e);
|
||||||
|
incProtErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String message) {
|
||||||
|
logger.debug("{}: Unexpected RPC message received: {}", thingName, message);
|
||||||
|
incProtErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(int statusCode, String reason) {
|
||||||
|
try {
|
||||||
|
logger.debug("{}: WebSocket connection closed, status = {}/{}", thingName, statusCode, getString(reason));
|
||||||
|
if (statusCode == StatusCode.ABNORMAL && !discovery && getProfile().alwaysOn) { // e.g. device rebooted
|
||||||
|
thingOffline("WebSocket connection closed abnormal");
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
logger.debug("{}: Exception on onClose()", thingName, e);
|
||||||
|
incProtErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable cause) {
|
||||||
|
logger.debug("{}: WebSocket error", thingName);
|
||||||
|
if (thing != null && thing.getProfile().alwaysOn) {
|
||||||
|
thingOffline("WebSocket error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void thingOffline(String reason) {
|
||||||
|
if (thing != null) { // do not reinit of battery powered devices with sleep mode
|
||||||
|
thing.setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-unexpected-error",
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
|
||||||
|
Shelly2DeviceSettings device = callApi("/shelly", Shelly2DeviceSettings.class);
|
||||||
|
ShellySettingsDevice info = new ShellySettingsDevice();
|
||||||
|
info.hostname = getString(device.id);
|
||||||
|
info.fw = getString(device.firmware);
|
||||||
|
info.type = getString(device.model);
|
||||||
|
info.mac = getString(device.mac);
|
||||||
|
info.auth = getBool(device.authEnable);
|
||||||
|
info.gen = getInteger(device.gen);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsStatus getStatus() throws ShellyApiException {
|
||||||
|
ShellyDeviceProfile profile = getProfile();
|
||||||
|
ShellySettingsStatus status = profile.status;
|
||||||
|
Shelly2DeviceStatusResult ds = apiRequest(SHELLYRPC_METHOD_GETSTATUS, null, Shelly2DeviceStatusResult.class);
|
||||||
|
status.time = ds.sys.time;
|
||||||
|
status.uptime = ds.sys.uptime;
|
||||||
|
status.cloud.connected = getBool(ds.cloud.connected);
|
||||||
|
status.mqtt.connected = getBool(ds.mqtt.connected);
|
||||||
|
status.wifiSta.ssid = getString(ds.wifi.ssid);
|
||||||
|
status.wifiSta.enabled = !status.wifiSta.ssid.isEmpty();
|
||||||
|
status.wifiSta.ip = getString(ds.wifi.staIP);
|
||||||
|
status.wifiSta.rssi = getInteger(ds.wifi.rssi);
|
||||||
|
status.fsFree = ds.sys.fsFree;
|
||||||
|
status.fsSize = ds.sys.fsSize;
|
||||||
|
status.discoverable = getBool(profile.settings.discoverable);
|
||||||
|
|
||||||
|
if (ds.sys.wakeupPeriod != null) {
|
||||||
|
profile.settings.sleepMode.period = ds.sys.wakeupPeriod / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.hasUpdate = status.update.hasUpdate = false;
|
||||||
|
status.update.oldVersion = getProfile().fwVersion;
|
||||||
|
if (ds.sys.availableUpdates != null) {
|
||||||
|
status.update.hasUpdate = ds.sys.availableUpdates.stable != null;
|
||||||
|
if (ds.sys.availableUpdates.stable != null) {
|
||||||
|
status.update.newVersion = "v" + getString(ds.sys.availableUpdates.stable.version);
|
||||||
|
}
|
||||||
|
if (ds.sys.availableUpdates.beta != null) {
|
||||||
|
status.update.betaVersion = "v" + getString(ds.sys.availableUpdates.beta.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ds.sys.wakeUpReason != null && ds.sys.wakeUpReason.boot != null) {
|
||||||
|
List<Object> values = new ArrayList<>();
|
||||||
|
String boot = getString(ds.sys.wakeUpReason.boot);
|
||||||
|
String cause = getString(ds.sys.wakeUpReason.cause);
|
||||||
|
|
||||||
|
// Index 0 is aggregated status, 1 boot, 2 cause
|
||||||
|
String reason = boot.equals(SHELLY2_WAKEUPO_BOOT_RESTART) ? ALARM_TYPE_RESTARTED : cause;
|
||||||
|
values.add(reason);
|
||||||
|
values.add(ds.sys.wakeUpReason.boot);
|
||||||
|
values.add(ds.sys.wakeUpReason.cause);
|
||||||
|
getThing().updateWakeupReason(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
fillDeviceStatus(status, ds, false);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSleepTime(int value) throws ShellyApiException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException {
|
||||||
|
if (getProfile().status.wifiSta.ssid == null) {
|
||||||
|
// Update status when not yet initialized
|
||||||
|
getStatus();
|
||||||
|
}
|
||||||
|
return relayStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||||
|
params.id = id;
|
||||||
|
params.on = SHELLY_API_ON.equals(turnMode);
|
||||||
|
apiRequest(SHELLYRPC_METHOD_SWITCH_SET, params, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException {
|
||||||
|
if (rollerIndex < rollerStatus.size()) {
|
||||||
|
return rollerStatus.get(rollerIndex);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid rollerIndex on getRollerStatus");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException {
|
||||||
|
String operation = "";
|
||||||
|
switch (turnMode) {
|
||||||
|
case SHELLY_ALWD_ROLLER_TURN_OPEN:
|
||||||
|
operation = SHELLY2_COVER_CMD_OPEN;
|
||||||
|
break;
|
||||||
|
case SHELLY_ALWD_ROLLER_TURN_CLOSE:
|
||||||
|
operation = SHELLY2_COVER_CMD_CLOSE;
|
||||||
|
break;
|
||||||
|
case SHELLY_ALWD_ROLLER_TURN_STOP:
|
||||||
|
operation = SHELLY2_COVER_CMD_STOP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRequest(new Shelly2RpcRequest().withMethod("Cover." + operation).withId(relayIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollerPos(int relayIndex, int position) throws ShellyApiException {
|
||||||
|
apiRequest(
|
||||||
|
new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_COVER_SETPOS).withId(relayIndex).withPos(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
|
||||||
|
return sensorData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequest req = new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SWITCH_SETCONFIG).withId(index);
|
||||||
|
|
||||||
|
req.params.withConfig();
|
||||||
|
req.params.config.name = "Switch" + index;
|
||||||
|
if (timerName.equals(SHELLY_TIMER_AUTOON)) {
|
||||||
|
req.params.config.autoOn = value > 0;
|
||||||
|
req.params.config.autoOnDelay = value;
|
||||||
|
} else {
|
||||||
|
req.params.config.autoOff = value > 0;
|
||||||
|
req.params.config.autoOffDelay = value;
|
||||||
|
}
|
||||||
|
apiRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
|
||||||
|
return new ShellySettingsLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||||
|
params.user = "admin";
|
||||||
|
params.realm = config.serviceName;
|
||||||
|
params.ha1 = sha256(params.user + ":" + params.realm + ":" + password);
|
||||||
|
apiRequest(SHELLYRPC_METHOD_AUTHSET, params, String.class);
|
||||||
|
|
||||||
|
ShellySettingsLogin res = new ShellySettingsLogin();
|
||||||
|
res.enabled = true;
|
||||||
|
res.username = params.user;
|
||||||
|
res.password = password;
|
||||||
|
return new ShellySettingsLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||||
|
params.config.ap = new Shelly2DeviceConfigAp();
|
||||||
|
params.config.ap.rangeExtender = new Shelly2DeviceConfigApRE();
|
||||||
|
params.config.ap.rangeExtender.enable = enable;
|
||||||
|
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_WIFISETCONG, params, Shelly2WsConfigResult.class);
|
||||||
|
return res.restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setEthernet(boolean enable) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||||
|
params.config.enable = enable;
|
||||||
|
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_ETHSETCONG, params, Shelly2WsConfigResult.class);
|
||||||
|
return res.restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setBluetooth(boolean enable) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||||
|
params.config.enable = enable;
|
||||||
|
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_BLESETCONG, params, Shelly2WsConfigResult.class);
|
||||||
|
return res.restartRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String deviceReboot() throws ShellyApiException {
|
||||||
|
return apiRequest(SHELLYRPC_METHOD_REBOOT, null, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String factoryReset() throws ShellyApiException {
|
||||||
|
return apiRequest(SHELLYRPC_METHOD_RESET, null, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException {
|
||||||
|
Shelly2DeviceStatusSysAvlUpdate status = apiRequest(SHELLYRPC_METHOD_CHECKUPD, null,
|
||||||
|
Shelly2DeviceStatusSysAvlUpdate.class);
|
||||||
|
ShellyOtaCheckResult result = new ShellyOtaCheckResult();
|
||||||
|
result.status = status.stable != null || status.beta != null ? "new" : "ok";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsUpdate firmwareUpdate(String fwurl) throws ShellyApiException {
|
||||||
|
ShellySettingsUpdate res = new ShellySettingsUpdate();
|
||||||
|
boolean prod = fwurl.contains("update");
|
||||||
|
boolean beta = fwurl.contains("beta");
|
||||||
|
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||||
|
if (prod || beta) {
|
||||||
|
params.stage = prod || beta ? "stable" : "beta";
|
||||||
|
} else {
|
||||||
|
params.url = fwurl;
|
||||||
|
}
|
||||||
|
apiRequest(SHELLYRPC_METHOD_UPDATE, params, String.class);
|
||||||
|
res.status = "Update initiated";
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setCloud(boolean enable) throws ShellyApiException {
|
||||||
|
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||||
|
params.config.enable = enable;
|
||||||
|
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_CLOUDSET, params, Shelly2WsConfigResult.class);
|
||||||
|
return res.restartRequired ? "restart required" : "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setDebug(boolean enabled) throws ShellyApiException {
|
||||||
|
return "failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDebugLog(String id) throws ShellyApiException {
|
||||||
|
return ""; // Gen2 uses WS to publish debug log
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following API calls are not yet relevant, because currently there a no Plus/Pro (Gen2) devices of those
|
||||||
|
* categories (e.g. bulbs)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyStatusLight getLightStatus() throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLightMode(String mode) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValveMode(int valveId, boolean auto) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValvePosition(int valveId, double value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValveTemperature(int valveId, int value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValveProfile(int valveId, int value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValveBoostTime(int valveId, int value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startValveBoost(int valveId, int value) throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resetStaCache() throws ShellyApiException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActionURLs() throws ShellyApiException {
|
||||||
|
// not relevant for Gen2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
|
||||||
|
// not relevant for Gen2
|
||||||
|
return new ShellySettingsLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCoIoTDescription() {
|
||||||
|
return ""; // not relevant to Gen2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
|
||||||
|
throw new ShellyApiException("API call not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setWiFiRecovery(boolean enable) throws ShellyApiException {
|
||||||
|
return "failed"; // not supported by Gen2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setApRoaming(boolean enable) throws ShellyApiException {
|
||||||
|
return "false";// not supported by Gen2
|
||||||
|
}
|
||||||
|
|
||||||
|
private void asyncApiRequest(String method) throws ShellyApiException {
|
||||||
|
Shelly2RpcBaseMessage request = buildRequest(method, null);
|
||||||
|
reconnect();
|
||||||
|
rpcSocket.sendMessage(gson.toJson(request)); // submit, result wull be async
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T apiRequest(String method, @Nullable Object params, Class<T> classOfT) throws ShellyApiException {
|
||||||
|
String json = "";
|
||||||
|
Shelly2RpcBaseMessage req = buildRequest(method, params);
|
||||||
|
try {
|
||||||
|
reconnect(); // make sure WS is connected
|
||||||
|
|
||||||
|
if (authInfo.realm != null) {
|
||||||
|
req.auth = buildAuthRequest(authInfo, config.userId, config.serviceName, config.password);
|
||||||
|
}
|
||||||
|
json = rpcPost(gson.toJson(req));
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
ShellyApiResult res = e.getApiResult();
|
||||||
|
String auth = getString(res.authResponse);
|
||||||
|
if (res.isHttpAccessUnauthorized() && !auth.isEmpty()) {
|
||||||
|
String[] options = auth.split(",");
|
||||||
|
for (String o : options) {
|
||||||
|
String key = substringBefore(o, "=").stripLeading().trim();
|
||||||
|
String value = substringAfter(o, "=").replaceAll("\"", "").trim();
|
||||||
|
switch (key) {
|
||||||
|
case "Digest qop":
|
||||||
|
break;
|
||||||
|
case "realm":
|
||||||
|
authInfo.realm = value;
|
||||||
|
break;
|
||||||
|
case "nonce":
|
||||||
|
authInfo.nonce = Long.parseLong(value, 16);
|
||||||
|
break;
|
||||||
|
case "algorithm":
|
||||||
|
authInfo.algorithm = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authInfo.nc = 1;
|
||||||
|
req.auth = buildAuthRequest(authInfo, config.userId, authInfo.realm, config.password);
|
||||||
|
json = rpcPost(gson.toJson(req));
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json = gson.toJson(gson.fromJson(json, Shelly2RpcBaseMessage.class).result);
|
||||||
|
return fromJson(gson, json, classOfT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T apiRequest(Shelly2RpcRequest request, Class<T> classOfT) throws ShellyApiException {
|
||||||
|
return apiRequest(request.method, request.params, classOfT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String apiRequest(Shelly2RpcRequest request) throws ShellyApiException {
|
||||||
|
return apiRequest(request.method, request.params, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String rpcPost(String postData) throws ShellyApiException {
|
||||||
|
return httpPost("/rpc", postData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconnect() throws ShellyApiException {
|
||||||
|
if (!rpcSocket.isConnected()) {
|
||||||
|
logger.debug("{}: Connect Rpc Socket (discovery = {})", thingName, discovery);
|
||||||
|
rpcSocket.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disconnect() {
|
||||||
|
if (rpcSocket.isConnected()) {
|
||||||
|
rpcSocket.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelly2RpctInterface getRpcHandler() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName,
|
||||||
|
rpcSocket.isConnected() ? "connected" : "disconnected", discovery);
|
||||||
|
disconnect();
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incProtErrors() {
|
||||||
|
if (thing != null) {
|
||||||
|
thing.incProtErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,311 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.api2;
|
||||||
|
|
||||||
|
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||||
|
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||||
|
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Shelly1HttpApi} wraps the Shelly REST API and provides various low level function to access the device api
|
||||||
|
* (not
|
||||||
|
* cloud api).
|
||||||
|
*
|
||||||
|
* @author Markus Michels - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@WebSocket(maxIdleTime = Integer.MAX_VALUE)
|
||||||
|
public class Shelly2RpcSocket {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(Shelly2RpcSocket.class);
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
private String thingName = "";
|
||||||
|
private String deviceIp = "";
|
||||||
|
private boolean inbound = false;
|
||||||
|
private CountDownLatch connectLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private @Nullable Session session;
|
||||||
|
private @Nullable Shelly2RpctInterface websocketHandler;
|
||||||
|
private WebSocketClient client = new WebSocketClient();
|
||||||
|
private @Nullable ShellyThingTable thingTable;
|
||||||
|
|
||||||
|
public Shelly2RpcSocket() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular constructor for Thing and Discover handler
|
||||||
|
*
|
||||||
|
* @param thingName Thing/Service name
|
||||||
|
* @param thingTable
|
||||||
|
* @param deviceIp IP address for the device
|
||||||
|
*/
|
||||||
|
public Shelly2RpcSocket(String thingName, @Nullable ShellyThingTable thingTable, String deviceIp) {
|
||||||
|
this.thingName = thingName;
|
||||||
|
this.deviceIp = deviceIp;
|
||||||
|
this.thingTable = thingTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor called from Servlet handler
|
||||||
|
*
|
||||||
|
* @param thingTable
|
||||||
|
* @param inbound
|
||||||
|
*/
|
||||||
|
public Shelly2RpcSocket(ShellyThingTable thingTable, boolean inbound) {
|
||||||
|
this.thingTable = thingTable;
|
||||||
|
this.inbound = inbound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listener for inbound messages implementing Shelly2RpctInterface
|
||||||
|
*
|
||||||
|
* @param interfacehandler
|
||||||
|
*/
|
||||||
|
public void addMessageHandler(Shelly2RpctInterface interfacehandler) {
|
||||||
|
this.websocketHandler = interfacehandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect outbound Web Socket
|
||||||
|
*
|
||||||
|
* @throws ShellyApiException
|
||||||
|
*/
|
||||||
|
public void connect() throws ShellyApiException {
|
||||||
|
try {
|
||||||
|
disconnect(); // for safety
|
||||||
|
|
||||||
|
URI uri = new URI("ws://" + deviceIp + "/rpc");
|
||||||
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
|
request.setHeader(HttpHeaders.HOST, deviceIp);
|
||||||
|
request.setHeader("Origin", "http://" + deviceIp);
|
||||||
|
request.setHeader("Pragma", "no-cache");
|
||||||
|
request.setHeader("Cache-Control", "no-cache");
|
||||||
|
|
||||||
|
logger.debug("{}: Connect WebSocket, URI={}", thingName, uri);
|
||||||
|
client = new WebSocketClient();
|
||||||
|
connectLatch = new CountDownLatch(1);
|
||||||
|
client.start();
|
||||||
|
client.setConnectTimeout(5000);
|
||||||
|
client.setStopTimeout(0);
|
||||||
|
client.connect(this, uri, request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ShellyApiException("Unable to initialize WebSocket", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Socket is connected, lookup thing and create connectLatch to synchronize first sendMessage()
|
||||||
|
*
|
||||||
|
* @param session Newly created WebSocket connection
|
||||||
|
*/
|
||||||
|
@OnWebSocketConnect
|
||||||
|
public void onConnect(Session session) {
|
||||||
|
try {
|
||||||
|
if (session.getRemoteAddress() == null) {
|
||||||
|
logger.debug("{}: Invalid inbound WebSocket connect", thingName);
|
||||||
|
session.close(StatusCode.ABNORMAL, "Invalid remote IP");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.session = session;
|
||||||
|
if (deviceIp.isEmpty()) {
|
||||||
|
// This is the inbound event web socket
|
||||||
|
deviceIp = session.getRemoteAddress().getAddress().getHostAddress();
|
||||||
|
}
|
||||||
|
if (websocketHandler == null) {
|
||||||
|
if (thingTable != null) {
|
||||||
|
ShellyThingInterface thing = thingTable.getThing(deviceIp);
|
||||||
|
Shelly2ApiRpc api = (Shelly2ApiRpc) thing.getApi();
|
||||||
|
websocketHandler = api.getRpcHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectLatch.countDown();
|
||||||
|
|
||||||
|
logger.debug("{}: WebSocket connected {}<-{}, Idle Timeout={}", thingName, session.getLocalAddress(),
|
||||||
|
session.getRemoteAddress(), session.getIdleTimeout());
|
||||||
|
if (websocketHandler != null) {
|
||||||
|
websocketHandler.onConnect(deviceIp, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) { // unknown thing
|
||||||
|
// debug is below
|
||||||
|
}
|
||||||
|
|
||||||
|
if (websocketHandler == null && thingTable != null) {
|
||||||
|
logger.debug("Rpc: Unable to handle connection from {} (unknown/disabled thing), closing socket", deviceIp);
|
||||||
|
session.close(StatusCode.SHUTDOWN, "Thing not active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send request over WebSocket
|
||||||
|
*
|
||||||
|
* @param str API request message
|
||||||
|
* @throws ShellyApiException
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
public void sendMessage(String str) throws ShellyApiException {
|
||||||
|
if (session != null) {
|
||||||
|
try {
|
||||||
|
connectLatch.await();
|
||||||
|
session.getRemote().sendString(str);
|
||||||
|
return;
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new ShellyApiException("Error RpcSend failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ShellyApiException("Unable to send API request (No Rpc session)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close WebSocket session
|
||||||
|
*/
|
||||||
|
public void disconnect() {
|
||||||
|
try {
|
||||||
|
if (session != null) {
|
||||||
|
Session s = session;
|
||||||
|
if (s.isOpen()) {
|
||||||
|
logger.debug("{}: Disconnecting WebSocket ({} -> {})", thingName, s.getLocalAddress(),
|
||||||
|
s.getRemoteAddress());
|
||||||
|
s.disconnect();
|
||||||
|
}
|
||||||
|
s.close(StatusCode.NORMAL, "Socket closed");
|
||||||
|
session = null;
|
||||||
|
}
|
||||||
|
if (client.isStarted()) {
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e.getCause() instanceof InterruptedException) {
|
||||||
|
logger.debug("{}: Unable to close socket - interrupted", thingName); // e.g. device was rebooted
|
||||||
|
} else {
|
||||||
|
logger.debug("{}: Unable to close socket", thingName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inbound WebSocket message
|
||||||
|
*
|
||||||
|
* @param session WebSpcket session
|
||||||
|
* @param receivedMessage Textial API message
|
||||||
|
*/
|
||||||
|
@OnWebSocketMessage
|
||||||
|
public void onText(Session session, String receivedMessage) {
|
||||||
|
try {
|
||||||
|
Shelly2RpctInterface handler = websocketHandler;
|
||||||
|
Shelly2RpcBaseMessage message = fromJson(gson, receivedMessage, Shelly2RpcBaseMessage.class);
|
||||||
|
logger.trace("{}: Inbound Rpc message: {}", thingName, receivedMessage);
|
||||||
|
if (handler != null) {
|
||||||
|
if (thingName.isEmpty()) {
|
||||||
|
thingName = getString(message.src);
|
||||||
|
}
|
||||||
|
if (message.method == null) {
|
||||||
|
message.method = SHELLYRPC_METHOD_NOTIFYFULLSTATUS;
|
||||||
|
}
|
||||||
|
switch (getString(message.method)) {
|
||||||
|
case SHELLYRPC_METHOD_NOTIFYSTATUS:
|
||||||
|
case SHELLYRPC_METHOD_NOTIFYFULLSTATUS:
|
||||||
|
Shelly2RpcNotifyStatus status = fromJson(gson, receivedMessage, Shelly2RpcNotifyStatus.class);
|
||||||
|
if (status.params == null) {
|
||||||
|
status.params = status.result;
|
||||||
|
}
|
||||||
|
handler.onNotifyStatus(status);
|
||||||
|
return;
|
||||||
|
case SHELLYRPC_METHOD_NOTIFYEVENT:
|
||||||
|
handler.onNotifyEvent(fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
handler.onMessage(receivedMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("{}: No Rpc listener registered for device {}, skip message: {}", thingName,
|
||||||
|
getString(message.src), receivedMessage);
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException | IllegalArgumentException | NullPointerException e) {
|
||||||
|
logger.debug("{}: Unable to process Rpc message: {}", thingName, receivedMessage, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return session != null && session.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInbound() {
|
||||||
|
return inbound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web Socket closed, notify thing handler
|
||||||
|
*
|
||||||
|
* @param statusCode StatusCode
|
||||||
|
* @param reason Textual reason
|
||||||
|
*/
|
||||||
|
@OnWebSocketClose
|
||||||
|
public void onClose(int statusCode, String reason) {
|
||||||
|
if (statusCode != StatusCode.NORMAL) {
|
||||||
|
logger.trace("{}: Rpc connection closed: {} - {}", thingName, statusCode, getString(reason));
|
||||||
|
}
|
||||||
|
if (inbound) {
|
||||||
|
// Ignore disconnect: Device establishes the socket, sends NotifyxFullStatus and disconnects
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
disconnect();
|
||||||
|
if (websocketHandler != null) {
|
||||||
|
websocketHandler.onClose(statusCode, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket error handler
|
||||||
|
*
|
||||||
|
* @param cause WebSocket error/Exception
|
||||||
|
*/
|
||||||
|
@OnWebSocketError
|
||||||
|
public void onError(Throwable cause) {
|
||||||
|
if (inbound) {
|
||||||
|
// Ignore disconnect: Device establishes the socket, sends NotifyxFullStatus and disconnects
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (websocketHandler != null) {
|
||||||
|
websocketHandler.onError(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.api2;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link WebsocketInterface} is responsible for interfacing the Websocket.
|
||||||
|
*
|
||||||
|
* @author Markus Michels - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface Shelly2RpctInterface {
|
||||||
|
|
||||||
|
public void onConnect(String deviceIp, boolean connected);
|
||||||
|
|
||||||
|
public void onMessage(String decodedmessage);
|
||||||
|
|
||||||
|
public void onNotifyStatus(Shelly2RpcNotifyStatus message);
|
||||||
|
|
||||||
|
public void onNotifyEvent(Shelly2RpcNotifyEvent message);
|
||||||
|
|
||||||
|
public void onClose(int statusCode, String reason);
|
||||||
|
|
||||||
|
public void onError(Throwable cause);
|
||||||
|
}
|
|
@ -27,9 +27,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||||
|
@ -139,15 +141,20 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
config.userId = bindingConfig.defaultUserId;
|
config.userId = bindingConfig.defaultUserId;
|
||||||
config.password = bindingConfig.defaultPassword;
|
config.password = bindingConfig.defaultPassword;
|
||||||
|
|
||||||
|
boolean gen2 = "2".equals(service.getPropertyString("gen"));
|
||||||
try {
|
try {
|
||||||
Shelly1HttpApi api = new Shelly1HttpApi(name, config, httpClient);
|
ShellyApiInterface api = gen2 ? new Shelly2ApiRpc(name, config, httpClient)
|
||||||
|
: new Shelly1HttpApi(name, config, httpClient);
|
||||||
|
if (name.contains("plushat")) {
|
||||||
|
int i = 1;
|
||||||
|
}
|
||||||
|
api.initialize();
|
||||||
profile = api.getDeviceProfile(thingType);
|
profile = api.getDeviceProfile(thingType);
|
||||||
|
api.close();
|
||||||
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
|
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
|
||||||
deviceName = profile.name;
|
deviceName = profile.name;
|
||||||
model = profile.deviceType;
|
model = profile.deviceType;
|
||||||
mode = profile.mode;
|
mode = profile.mode;
|
||||||
|
|
||||||
properties = ShellyBaseHandler.fillDeviceProperties(profile);
|
properties = ShellyBaseHandler.fillDeviceProperties(profile);
|
||||||
logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
|
logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
|
||||||
profile.deviceType, mode.isEmpty() ? "<standard>" : mode, deviceName);
|
profile.deviceType, mode.isEmpty() ? "<standard>" : mode, deviceName);
|
||||||
|
@ -174,7 +181,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
addProperty(properties, PROPERTY_SERVICE_NAME, name);
|
addProperty(properties, PROPERTY_SERVICE_NAME, name);
|
||||||
addProperty(properties, PROPERTY_DEV_NAME, deviceName);
|
addProperty(properties, PROPERTY_DEV_NAME, deviceName);
|
||||||
addProperty(properties, PROPERTY_DEV_TYPE, thingType);
|
addProperty(properties, PROPERTY_DEV_TYPE, thingType);
|
||||||
addProperty(properties, PROPERTY_DEV_GEN, "1");
|
addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1");
|
||||||
addProperty(properties, PROPERTY_DEV_MODE, mode);
|
addProperty(properties, PROPERTY_DEV_MODE, mode);
|
||||||
|
|
||||||
logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
|
logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
|
||||||
|
|
|
@ -69,6 +69,8 @@ public class ShellyThingCreator {
|
||||||
public static final String SHELLYDT_PLUS1PMUL = "SNSW-001P15UL";
|
public static final String SHELLYDT_PLUS1PMUL = "SNSW-001P15UL";
|
||||||
public static final String SHELLYDT_PLUS2PM_RELAY = "SNSW-002P16EU-relay";
|
public static final String SHELLYDT_PLUS2PM_RELAY = "SNSW-002P16EU-relay";
|
||||||
public static final String SHELLYDT_PLUS2PM_ROLLER = "SNSW-002P16EU-roller";
|
public static final String SHELLYDT_PLUS2PM_ROLLER = "SNSW-002P16EU-roller";
|
||||||
|
public static final String SHELLYDT_PLUS2PM_RELAY_2 = "SNSW-102P16EU-relay";
|
||||||
|
public static final String SHELLYDT_PLUS2PM_ROLLER_2 = "SNSW-102P16EU-roller";
|
||||||
public static final String SHELLYDT_PLUSPLUGUS = "SNPL-00116US";
|
public static final String SHELLYDT_PLUSPLUGUS = "SNPL-00116US";
|
||||||
public static final String SHELLYDT_PLUSI4 = "SNSN-0024X";
|
public static final String SHELLYDT_PLUSI4 = "SNSN-0024X";
|
||||||
public static final String SHELLYDT_PLUSI4DC = "SNSN-0D24X";
|
public static final String SHELLYDT_PLUSI4DC = "SNSN-0D24X";
|
||||||
|
@ -77,17 +79,19 @@ public class ShellyThingCreator {
|
||||||
// Shelly Pro Series
|
// Shelly Pro Series
|
||||||
public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU";
|
public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU";
|
||||||
public static final String SHELLYDT_PRO1_2 = "SPSW-101XE16EU";
|
public static final String SHELLYDT_PRO1_2 = "SPSW-101XE16EU";
|
||||||
|
public static final String SHELLYDT_PRO1_3 = "SPSW-201XE16EU";
|
||||||
public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU";
|
public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU";
|
||||||
public static final String SHELLYDT_PRO1PM_ = "SPSW-201PE16EU";
|
|
||||||
public static final String SHELLYDT_PRO1PM_2 = "SPSW-101PE16EU";
|
public static final String SHELLYDT_PRO1PM_2 = "SPSW-101PE16EU";
|
||||||
|
public static final String SHELLYDT_PRO1PM_3 = "SPSW-201PE16EU";
|
||||||
public static final String SHELLYDT_PRO2_RELAY = "SPSW-002XE16EU-relay";
|
public static final String SHELLYDT_PRO2_RELAY = "SPSW-002XE16EU-relay";
|
||||||
public static final String SHELLYDT_PRO2_ROLLER = "SPSW-002XE16EU-roller";
|
|
||||||
public static final String SHELLYDT_PRO2_RELAY_2 = "SPSW-102XE16EU-relay";
|
public static final String SHELLYDT_PRO2_RELAY_2 = "SPSW-102XE16EU-relay";
|
||||||
public static final String SHELLYDT_PRO2_ROLLER_2 = "SPSW-102XE16EU-roller";
|
public static final String SHELLYDT_PRO2_RELAY_3 = "SPSW-202XE16EU-relay";
|
||||||
public static final String SHELLYDT_PRO2PM_RELAY = "SPSW-002PE16EU-relay";
|
public static final String SHELLYDT_PRO2PM_RELAY = "SPSW-002PE16EU-relay";
|
||||||
public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller";
|
public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller";
|
||||||
public static final String SHELLYDT_PRO2PM_RELAY_2 = "SPSW-002PE16EU-relay";
|
public static final String SHELLYDT_PRO2PM_RELAY_2 = "SPSW-102PE16EU-relay";
|
||||||
public static final String SHELLYDT_PRO2PM_ROLLER_2 = "SPSW-002PE16EU-roller";
|
public static final String SHELLYDT_PRO2PM_ROLLER_2 = "SPSW-102PE16EU-roller";
|
||||||
|
public static final String SHELLYDT_PRO2PM_RELAY_3 = "SPSW-202PE16EU-relay";
|
||||||
|
public static final String SHELLYDT_PRO2PM_ROLLER_3 = "SPSW-202PE16EU-roller";
|
||||||
public static final String SHELLYDT_PRO3 = "SPSW-003XE16EU";
|
public static final String SHELLYDT_PRO3 = "SPSW-003XE16EU";
|
||||||
public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
|
public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
|
||||||
public static final String SHELLYDT_PRO4PM_2 = "SPSW-104PE16EU";
|
public static final String SHELLYDT_PRO4PM_2 = "SPSW-104PE16EU";
|
||||||
|
@ -100,7 +104,6 @@ public class ShellyThingCreator {
|
||||||
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
|
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
|
||||||
public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch";
|
public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch";
|
||||||
public static final String THING_TYPE_SHELLY2_RELAY_STR = "shelly2-relay";
|
public static final String THING_TYPE_SHELLY2_RELAY_STR = "shelly2-relay";
|
||||||
public static final String THING_TYPE_SHELLY2_ROLLER_STR = "shelly2-roller";
|
|
||||||
public static final String THING_TYPE_SHELLY25_PREFIX = "shellyswitch25";
|
public static final String THING_TYPE_SHELLY25_PREFIX = "shellyswitch25";
|
||||||
public static final String THING_TYPE_SHELLY25_RELAY_STR = "shelly25-relay";
|
public static final String THING_TYPE_SHELLY25_RELAY_STR = "shelly25-relay";
|
||||||
public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller";
|
public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller";
|
||||||
|
@ -147,7 +150,6 @@ public class ShellyThingCreator {
|
||||||
public static final String THING_TYPE_SHELLYPRO1_STR = "shellypro1";
|
public static final String THING_TYPE_SHELLYPRO1_STR = "shellypro1";
|
||||||
public static final String THING_TYPE_SHELLYPRO1PM_STR = "shellypro1pm";
|
public static final String THING_TYPE_SHELLYPRO1PM_STR = "shellypro1pm";
|
||||||
public static final String THING_TYPE_SHELLYPRO2_RELAY_STR = "shellypro2-relay";
|
public static final String THING_TYPE_SHELLYPRO2_RELAY_STR = "shellypro2-relay";
|
||||||
public static final String THING_TYPE_SHELLYPRO2_ROLLER_STR = "shellypro2-roller";
|
|
||||||
public static final String THING_TYPE_SHELLYPRO2PM_RELAY_STR = "shellypro2pm-relay";
|
public static final String THING_TYPE_SHELLYPRO2PM_RELAY_STR = "shellypro2pm-relay";
|
||||||
public static final String THING_TYPE_SHELLYPRO2PM_ROLLER_STR = "shellypro2pm-roller";
|
public static final String THING_TYPE_SHELLYPRO2PM_ROLLER_STR = "shellypro2pm-roller";
|
||||||
public static final String THING_TYPE_SHELLYPRO3_STR = "shellypro3";
|
public static final String THING_TYPE_SHELLYPRO3_STR = "shellypro3";
|
||||||
|
@ -164,8 +166,6 @@ public class ShellyThingCreator {
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
|
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||||
THING_TYPE_SHELLY2_RELAY_STR);
|
THING_TYPE_SHELLY2_RELAY_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLY2_ROLLER = new ThingTypeUID(BINDING_ID,
|
|
||||||
THING_TYPE_SHELLY2_ROLLER_STR);
|
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLY25_RELAY = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLY25_RELAY = new ThingTypeUID(BINDING_ID,
|
||||||
THING_TYPE_SHELLY25_RELAY_STR);
|
THING_TYPE_SHELLY25_RELAY_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||||
|
@ -233,8 +233,6 @@ public class ShellyThingCreator {
|
||||||
THING_TYPE_SHELLYPRO1PM_STR);
|
THING_TYPE_SHELLYPRO1PM_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2_RELAY = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLYPRO2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||||
THING_TYPE_SHELLYPRO2_RELAY_STR);
|
THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2_ROLLER = new ThingTypeUID(BINDING_ID,
|
|
||||||
THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_RELAY = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_RELAY = new ThingTypeUID(BINDING_ID,
|
||||||
THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_ROLLER = new ThingTypeUID(BINDING_ID,
|
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||||
|
@ -277,6 +275,8 @@ public class ShellyThingCreator {
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS1PMUL, THING_TYPE_SHELLYPLUS1PM_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUS1PMUL, THING_TYPE_SHELLYPLUS1PM_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_RELAY, THING_TYPE_SHELLYPLUS2PM_RELAY_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_RELAY, THING_TYPE_SHELLYPLUS2PM_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_ROLLER, THING_TYPE_SHELLYPLUS2PM_ROLLER_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_ROLLER, THING_TYPE_SHELLYPLUS2PM_ROLLER_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_RELAY_2, THING_TYPE_SHELLYPLUS2PM_RELAY_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_ROLLER_2, THING_TYPE_SHELLYPLUS2PM_ROLLER_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSPLUGUS, THING_TYPE_SHELLYPLUSPLUGUS_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUSPLUGUS, THING_TYPE_SHELLYPLUSPLUGUS_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4DC, THING_TYPE_SHELLYPLUSI4DC_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4DC, THING_TYPE_SHELLYPLUSI4DC_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4, THING_TYPE_SHELLYPLUSI4_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4, THING_TYPE_SHELLYPLUSI4_STR);
|
||||||
|
@ -285,16 +285,19 @@ public class ShellyThingCreator {
|
||||||
// Pro Series
|
// Pro Series
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1, THING_TYPE_SHELLYPRO1_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1, THING_TYPE_SHELLYPRO1_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1_2, THING_TYPE_SHELLYPRO1_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1_2, THING_TYPE_SHELLYPRO1_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1_3, THING_TYPE_SHELLYPRO1_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM, THING_TYPE_SHELLYPRO1PM_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM, THING_TYPE_SHELLYPRO1PM_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM_2, THING_TYPE_SHELLYPRO1PM_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM_2, THING_TYPE_SHELLYPRO1PM_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM_3, THING_TYPE_SHELLYPRO1PM_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY_2, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY_2, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_ROLLER, THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY_3, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_ROLLER_2, THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY_2, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY_2, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY_3, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER_2, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER_2, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||||
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER_3, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO3, THING_TYPE_SHELLYPRO3_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO3, THING_TYPE_SHELLYPRO3_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM, THING_TYPE_SHELLYPRO4PM_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM, THING_TYPE_SHELLYPRO4PM_STR);
|
||||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM_2, THING_TYPE_SHELLYPRO4PM_STR);
|
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM_2, THING_TYPE_SHELLYPRO4PM_STR);
|
||||||
|
@ -364,7 +367,7 @@ public class ShellyThingCreator {
|
||||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY25_RELAY_STR : THING_TYPE_SHELLY25_ROLLER_STR;
|
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY25_RELAY_STR : THING_TYPE_SHELLY25_ROLLER_STR;
|
||||||
}
|
}
|
||||||
if (name.startsWith(THING_TYPE_SHELLY2_PREFIX)) { // Shelly v2
|
if (name.startsWith(THING_TYPE_SHELLY2_PREFIX)) { // Shelly v2
|
||||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY2_RELAY_STR : THING_TYPE_SHELLY2_ROLLER_STR;
|
return THING_TYPE_SHELLY2_RELAY_STR;
|
||||||
}
|
}
|
||||||
if (name.startsWith(THING_TYPE_SHELLYPLUG_STR)) {
|
if (name.startsWith(THING_TYPE_SHELLYPLUG_STR)) {
|
||||||
// shellyplug-s needs to be mapped to shellyplugs to follow the schema
|
// shellyplug-s needs to be mapped to shellyplugs to follow the schema
|
||||||
|
|
|
@ -21,13 +21,9 @@ import static org.openhab.core.thing.Thing.*;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -39,19 +35,21 @@ import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||||
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyThermnostat;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapHandler;
|
import org.openhab.binding.shelly.internal.api1.Shelly1CoapHandler;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
|
import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||||
|
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
|
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
|
||||||
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
|
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
|
||||||
import org.openhab.binding.shelly.internal.provider.ShellyStateDescriptionProvider;
|
|
||||||
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
||||||
import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
|
import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
|
||||||
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
|
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
|
||||||
|
@ -66,7 +64,6 @@ import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
|
||||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
@ -84,23 +81,11 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Markus Michels - Initial contribution
|
* @author Markus Michels - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class ShellyBaseHandler extends BaseThingHandler
|
public abstract class ShellyBaseHandler extends BaseThingHandler
|
||||||
implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface {
|
implements ShellyThingInterface, ShellyDeviceListener, ShellyManagerInterface {
|
||||||
private class OptionEntry {
|
|
||||||
public ChannelTypeUID uid;
|
|
||||||
public String key;
|
|
||||||
public String value;
|
|
||||||
|
|
||||||
public OptionEntry(ChannelTypeUID uid, String key, String value) {
|
|
||||||
this.uid = uid;
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
|
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
|
||||||
protected final ShellyChannelDefinitions channelDefinitions;
|
protected final ShellyChannelDefinitions channelDefinitions;
|
||||||
private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
public String thingName = "";
|
public String thingName = "";
|
||||||
public String thingType = "";
|
public String thingType = "";
|
||||||
|
@ -111,28 +96,29 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
private ShellyBindingConfiguration bindingConfig;
|
private ShellyBindingConfiguration bindingConfig;
|
||||||
protected ShellyThingConfiguration config = new ShellyThingConfiguration();
|
protected ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||||
protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE
|
protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE
|
||||||
protected ShellyDeviceStats stats = new ShellyDeviceStats();
|
private ShellyDeviceStats stats = new ShellyDeviceStats();
|
||||||
private final Shelly1CoapHandler coap;
|
private @Nullable Shelly1CoapHandler coap;
|
||||||
public boolean autoCoIoT = false;
|
|
||||||
|
|
||||||
private final ShellyTranslationProvider messages;
|
private final ShellyTranslationProvider messages;
|
||||||
protected boolean stopping = false;
|
private final ShellyChannelCache cache;
|
||||||
|
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
|
||||||
|
|
||||||
|
private final boolean gen2;
|
||||||
|
protected boolean autoCoIoT = false;
|
||||||
|
|
||||||
|
// Thing status
|
||||||
private boolean channelsCreated = false;
|
private boolean channelsCreated = false;
|
||||||
|
private boolean stopping = false;
|
||||||
|
private int vibrationFilter = 0;
|
||||||
|
private String lastWakeupReason = "";
|
||||||
|
|
||||||
|
// Scheduler
|
||||||
private long watchdog = now();
|
private long watchdog = now();
|
||||||
|
protected int scheduledUpdates = 0;
|
||||||
private @Nullable ScheduledFuture<?> statusJob;
|
|
||||||
public int scheduledUpdates = 0;
|
|
||||||
private int skipCount = UPDATE_SKIP_COUNT;
|
private int skipCount = UPDATE_SKIP_COUNT;
|
||||||
private int skipUpdate = 0;
|
private int skipUpdate = 0;
|
||||||
private boolean refreshSettings = false;
|
private boolean refreshSettings = false;
|
||||||
|
private @Nullable ScheduledFuture<?> statusJob;
|
||||||
// delay before enabling channel
|
|
||||||
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
|
|
||||||
private final ShellyChannelCache cache;
|
|
||||||
|
|
||||||
private String lastWakeupReason = "";
|
|
||||||
private int vibrationFilter = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -155,26 +141,28 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
this.channelDefinitions = new ShellyChannelDefinitions(messages);
|
this.channelDefinitions = new ShellyChannelDefinitions(messages);
|
||||||
this.bindingConfig = bindingConfig;
|
this.bindingConfig = bindingConfig;
|
||||||
this.config = getConfigAs(ShellyThingConfiguration.class);
|
this.config = getConfigAs(ShellyThingConfiguration.class);
|
||||||
|
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.api = new Shelly1HttpApi(thingName, config, httpClient);
|
|
||||||
|
|
||||||
coap = new Shelly1CoapHandler(this, coapServer);
|
Map<String, String> properties = thing.getProperties();
|
||||||
|
String gen = getString(properties.get(PROPERTY_DEV_GEN));
|
||||||
|
String thingType = getThingType();
|
||||||
|
if (gen.isEmpty() && thingType.startsWith("shellyplus") || thingType.startsWith("shellypro")) {
|
||||||
|
gen = "2";
|
||||||
|
}
|
||||||
|
gen2 = "2".equals(gen);
|
||||||
|
this.api = !gen2 ? new Shelly1HttpApi(thingName, this) : new Shelly2ApiRpc(thingName, thingTable, this);
|
||||||
|
if (gen2) {
|
||||||
|
config.eventsCoIoT = false;
|
||||||
|
}
|
||||||
|
if (config.eventsCoIoT) {
|
||||||
|
this.coap = new Shelly1CoapHandler(this, coapServer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkRepresentation(String key) {
|
public boolean checkRepresentation(String key) {
|
||||||
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceIp)
|
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceIp)
|
||||||
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(thing.getUID().getAsString());
|
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(getThingName());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
|
||||||
return Set.of(ShellyStateDescriptionProvider.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUID() {
|
|
||||||
return getThing().getUID().getAsString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,6 +187,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
start = initializeThing();
|
start = initializeThing();
|
||||||
} catch (ShellyApiException e) {
|
} catch (ShellyApiException e) {
|
||||||
ShellyApiResult res = e.getApiResult();
|
ShellyApiResult res = e.getApiResult();
|
||||||
|
if (profile.alwaysOn && e.isConnectionError()) {
|
||||||
|
setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-connect",
|
||||||
|
e.toString());
|
||||||
|
}
|
||||||
if (isAuthorizationFailed(res)) {
|
if (isAuthorizationFailed(res)) {
|
||||||
start = false;
|
start = false;
|
||||||
}
|
}
|
||||||
|
@ -255,22 +247,25 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
lastWakeupReason = "";
|
lastWakeupReason = "";
|
||||||
cache.setThingName(thingName);
|
cache.setThingName(thingName);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
|
resetStats();
|
||||||
|
|
||||||
logger.debug("{}: Start initializing thing {}, type {}, ip address {}, CoIoT: {}", thingName,
|
logger.debug("{}: Start initializing for thing {}, type {}, IP address {}, Gen2: {}, CoIoT: {}", thingName,
|
||||||
getThing().getLabel(), thingType, config.deviceIp, config.eventsCoIoT);
|
getThing().getLabel(), thingType, config.deviceIp, gen2, config.eventsCoIoT);
|
||||||
if (config.deviceIp.isEmpty()) {
|
if (config.deviceIp.isEmpty()) {
|
||||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-ip");
|
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-ip");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing could not be
|
// Gen 1 only: Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing
|
||||||
// fully initialized here. In this case the CoAP messages triggers auto-initialization (like the Action URL does
|
// could not be fully initialized here. In this case the CoAP messages triggers auto-initialization (like the
|
||||||
// when enabled)
|
// Action URL does when enabled)
|
||||||
if (config.eventsCoIoT && !profile.alwaysOn) {
|
if (coap != null && config.eventsCoIoT && !profile.alwaysOn) {
|
||||||
coap.start(thingName, config);
|
coap.start(thingName, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize API access, exceptions will be catched by initialize()
|
// Initialize API access, exceptions will be catched by initialize()
|
||||||
|
api.initialize();
|
||||||
|
profile.initFromThingType(thingType);
|
||||||
ShellySettingsDevice devInfo = api.getDeviceInfo();
|
ShellySettingsDevice devInfo = api.getDeviceInfo();
|
||||||
if (getBool(devInfo.auth) && config.password.isEmpty()) {
|
if (getBool(devInfo.auth) && config.password.isEmpty()) {
|
||||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
|
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
|
||||||
|
@ -281,8 +276,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
api.setConfig(thingName, config);
|
api.setConfig(thingName, config);
|
||||||
api.initialize();
|
|
||||||
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType);
|
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType);
|
||||||
|
tmpPrf.isGen2 = gen2;
|
||||||
|
tmpPrf.auth = devInfo.auth; // missing in /settings
|
||||||
|
|
||||||
if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) {
|
if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) {
|
||||||
changeThingType(thingName, tmpPrf.mode);
|
changeThingType(thingName, tmpPrf.mode);
|
||||||
return false; // force re-initialization
|
return false; // force re-initialization
|
||||||
|
@ -290,7 +287,8 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
// Validate device mode
|
// Validate device mode
|
||||||
String reqMode = thingType.contains("-") ? substringAfter(thingType, "-") : "";
|
String reqMode = thingType.contains("-") ? substringAfter(thingType, "-") : "";
|
||||||
if (!reqMode.isEmpty() && !tmpPrf.mode.equals(reqMode)) {
|
if (!reqMode.isEmpty() && !tmpPrf.mode.equals(reqMode)) {
|
||||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode");
|
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", tmpPrf.mode,
|
||||||
|
reqMode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!getString(devInfo.coiot).isEmpty()) {
|
if (!getString(devInfo.coiot).isEmpty()) {
|
||||||
|
@ -311,82 +309,32 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
tmpPrf.updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
tmpPrf.updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpPrf.auth = devInfo.auth; // missing in /settings
|
tmpPrf.status = api.getStatus(); // update thing properties
|
||||||
tmpPrf.status = api.getStatus();
|
|
||||||
tmpPrf.updateFromStatus(tmpPrf.status);
|
tmpPrf.updateFromStatus(tmpPrf.status);
|
||||||
|
addStateOptions(tmpPrf);
|
||||||
|
|
||||||
if (tmpPrf.isTRV) {
|
// update thing properties
|
||||||
String[] profileNames = tmpPrf.getValveProfileList(0);
|
updateProperties(tmpPrf, tmpPrf.status);
|
||||||
String channelId = mkChannelId(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
|
||||||
logger.debug("{}: Adding TRV profile names to channel description: {}", thingName, profileNames);
|
|
||||||
clearStateOptions(channelId);
|
|
||||||
addStateOption(channelId, "0", "DISABLED");
|
|
||||||
for (int i = 0; i < profileNames.length; i++) {
|
|
||||||
addStateOption(channelId, "" + (i + 1), profileNames[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showThingConfig(tmpPrf);
|
|
||||||
checkVersion(tmpPrf, tmpPrf.status);
|
checkVersion(tmpPrf, tmpPrf.status);
|
||||||
|
|
||||||
if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) {
|
startCoap(config, tmpPrf);
|
||||||
String devpeer = getString(tmpPrf.settings.coiot.peer);
|
if (!gen2) {
|
||||||
String ourpeer = config.localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT;
|
api.setActionURLs(); // register event urls
|
||||||
if (!tmpPrf.settings.coiot.enabled || (profile.isMotion && devpeer.isEmpty())) {
|
|
||||||
try {
|
|
||||||
api.setCoIoTPeer(ourpeer);
|
|
||||||
logger.info("{}: CoIoT peer updated to {}", thingName, ourpeer);
|
|
||||||
} catch (ShellyApiException e) {
|
|
||||||
logger.warn("{}: Unable to set CoIoT peer: {}", thingName, e.toString());
|
|
||||||
}
|
|
||||||
} else if (!devpeer.isEmpty() && !devpeer.equals(ourpeer)) {
|
|
||||||
logger.warn("{}: CoIoT peer in device settings does not point this to this host", thingName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (autoCoIoT) {
|
|
||||||
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
|
|
||||||
config.eventsCoIoT = true;
|
|
||||||
config.eventsSwitch = false;
|
|
||||||
config.eventsButton = false;
|
|
||||||
config.eventsPush = false;
|
|
||||||
config.eventsRoller = false;
|
|
||||||
config.eventsSensorReport = false;
|
|
||||||
api.setConfig(thingName, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All initialization done, so keep the profile and set Thing to ONLINE
|
// All initialization done, so keep the profile and set Thing to ONLINE
|
||||||
fillDeviceStatus(tmpPrf.status, false);
|
fillDeviceStatus(tmpPrf.status, false);
|
||||||
postEvent(ALARM_TYPE_NONE, false);
|
postEvent(ALARM_TYPE_NONE, false);
|
||||||
api.setActionURLs(); // register event urls
|
|
||||||
if (config.eventsCoIoT) {
|
profile = tmpPrf;
|
||||||
logger.debug("{}: Starting CoIoT (autoCoIoT={}/{})", thingName, bindingConfig.autoCoIoT, autoCoIoT);
|
showThingConfig(profile);
|
||||||
coap.start(thingName, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("{}: Thing successfully initialized.", thingName);
|
logger.debug("{}: Thing successfully initialized.", thingName);
|
||||||
profile = tmpPrf;
|
updateProperties(profile, profile.status);
|
||||||
|
|
||||||
updateProperties(tmpPrf, tmpPrf.status);
|
|
||||||
setThingOnline(); // if API call was successful the thing must be online
|
setThingOnline(); // if API call was successful the thing must be online
|
||||||
return true; // success
|
return true; // success
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showThingConfig(ShellyDeviceProfile profile) {
|
|
||||||
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
|
|
||||||
profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion,
|
|
||||||
profile.fwDate);
|
|
||||||
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
|
|
||||||
logger.debug("{}: Device "
|
|
||||||
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
|
|
||||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
|
||||||
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
|
|
||||||
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, profile.isSensor,
|
|
||||||
profile.isDW, profile.hasBattery,
|
|
||||||
profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense,
|
|
||||||
profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor,
|
|
||||||
profile.alwaysOn, profile.updatePeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle Channel Commands
|
* Handle Channel Commands
|
||||||
*/
|
*/
|
||||||
|
@ -443,17 +391,31 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
break;
|
break;
|
||||||
case CHANNEL_CONTROL_PROFILE:
|
case CHANNEL_CONTROL_PROFILE:
|
||||||
logger.debug("{}: Select profile {}", thingName, command);
|
logger.debug("{}: Select profile {}", thingName, command);
|
||||||
|
int id = -1;
|
||||||
|
if (command instanceof Number) {
|
||||||
|
id = (int) getNumber(command);
|
||||||
|
} else {
|
||||||
String cmd = command.toString();
|
String cmd = command.toString();
|
||||||
int id = Integer.parseInt(cmd);
|
if (isDigit(cmd.charAt(0))) {
|
||||||
|
id = Integer.parseInt(cmd);
|
||||||
|
} else if (profile.settings.thermostats != null) {
|
||||||
|
ShellyThermnostat t = profile.settings.thermostats.get(0);
|
||||||
|
for (int i = 0; i < t.profileNames.length; i++) {
|
||||||
|
if (t.profileNames[i].equalsIgnoreCase(cmd)) {
|
||||||
|
id = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (id < 0 || id > 5) {
|
if (id < 0 || id > 5) {
|
||||||
logger.warn("{}: Invalid profile Id {} requested", thingName, profile);
|
logger.warn("{}: Invalid profile Id {} requested", thingName, profile);
|
||||||
} else {
|
break;
|
||||||
api.setValveProfile(0, id);
|
|
||||||
}
|
}
|
||||||
|
api.setValveProfile(0, id);
|
||||||
break;
|
break;
|
||||||
case CHANNEL_CONTROL_MODE:
|
case CHANNEL_CONTROL_MODE:
|
||||||
logger.debug("{}: Set mode to {}", thingName, command);
|
logger.debug("{}: Set mode to {}", thingName, command);
|
||||||
api.setValveMode(0, SHELLY_TRV_MODE_AUTO.equalsIgnoreCase(command.toString()));
|
api.setValveMode(0, CHANNEL_CONTROL_MODE.equalsIgnoreCase(command.toString()));
|
||||||
break;
|
break;
|
||||||
case CHANNEL_CONTROL_SETTEMP:
|
case CHANNEL_CONTROL_SETTEMP:
|
||||||
logger.debug("{}: Set temperature to {}", thingName, command);
|
logger.debug("{}: Set temperature to {}", thingName, command);
|
||||||
|
@ -519,7 +481,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
|
|
||||||
if (vibrationFilter > 0) {
|
if (vibrationFilter > 0) {
|
||||||
vibrationFilter--;
|
vibrationFilter--;
|
||||||
logger.debug("{}: Vibration events are absorbed for {} more seconds", thingName,
|
logger.debug("{}: Vibration events are absorbed for {} more seconds", thingName,
|
||||||
vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS);
|
vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,6 +495,9 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
// Get profile, if refreshSettings == true reload settings from device
|
// Get profile, if refreshSettings == true reload settings from device
|
||||||
ShellySettingsStatus status = api.getStatus();
|
ShellySettingsStatus status = api.getStatus();
|
||||||
|
if (status.uptime != null && status.uptime == 0 && profile.alwaysOn) {
|
||||||
|
status = api.getStatus();
|
||||||
|
}
|
||||||
boolean restarted = checkRestarted(status);
|
boolean restarted = checkRestarted(status);
|
||||||
profile = getProfile(refreshSettings || restarted);
|
profile = getProfile(refreshSettings || restarted);
|
||||||
profile.status = status;
|
profile.status = status;
|
||||||
|
@ -563,7 +528,11 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
// sleep mode. Once the next update is successful the device goes back online
|
// sleep mode. Once the next update is successful the device goes back online
|
||||||
String status = "";
|
String status = "";
|
||||||
ShellyApiResult res = e.getApiResult();
|
ShellyApiResult res = e.getApiResult();
|
||||||
if (isWatchdogStarted()) {
|
if (profile.alwaysOn && e.isConnectionError()) {
|
||||||
|
status = "offline.status-error-connect";
|
||||||
|
} else if (res.isHttpAccessUnauthorized()) {
|
||||||
|
status = "offline.conf-error-access-denied";
|
||||||
|
} else if (isWatchdogStarted()) {
|
||||||
if (!isWatchdogExpired()) {
|
if (!isWatchdogExpired()) {
|
||||||
logger.debug("{}: Ignore API Timeout, retry later", thingName);
|
logger.debug("{}: Ignore API Timeout, retry later", thingName);
|
||||||
} else {
|
} else {
|
||||||
|
@ -571,8 +540,6 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
status = "offline.status-error-watchdog";
|
status = "offline.status-error-watchdog";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (res.isHttpAccessUnauthorized()) {
|
|
||||||
status = "offline.conf-error-access-denied";
|
|
||||||
} else if (e.isJSONException()) {
|
} else if (e.isJSONException()) {
|
||||||
status = "offline.status-error-unexpected-api-result";
|
status = "offline.status-error-unexpected-api-result";
|
||||||
logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e);
|
logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e);
|
||||||
|
@ -603,31 +570,73 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showThingConfig(ShellyDeviceProfile profile) {
|
||||||
|
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
|
||||||
|
profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion,
|
||||||
|
profile.fwDate);
|
||||||
|
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
|
||||||
|
logger.debug("{}: Device "
|
||||||
|
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
|
||||||
|
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||||
|
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
|
||||||
|
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, profile.isSensor,
|
||||||
|
profile.isDW, profile.hasBattery,
|
||||||
|
profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense,
|
||||||
|
profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor,
|
||||||
|
profile.alwaysOn, profile.updatePeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStateOptions(ShellyDeviceProfile prf) {
|
||||||
|
if (prf.isTRV) {
|
||||||
|
String[] profileNames = prf.getValveProfileList(0);
|
||||||
|
String channelId = mkChannelId(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
||||||
|
logger.debug("{}: Adding TRV profile names to channel description: {}", thingName, profileNames);
|
||||||
|
channelDefinitions.clearStateOptions(channelId);
|
||||||
|
int fid = 1;
|
||||||
|
for (String name : profileNames) {
|
||||||
|
channelDefinitions.addStateOption(channelId, "" + fid, fid + ": " + name);
|
||||||
|
fid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prf.isRoller && prf.settings.favorites != null) {
|
||||||
|
String channelId = mkChannelId(CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_FAV);
|
||||||
|
logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName,
|
||||||
|
prf.settings.favorites.size());
|
||||||
|
channelDefinitions.clearStateOptions(channelId);
|
||||||
|
int fid = 1;
|
||||||
|
for (ShellyFavPos fav : prf.settings.favorites) {
|
||||||
|
channelDefinitions.addStateOption(channelId, "" + fid, fid + ": " + fav.name);
|
||||||
|
fid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getThingType() {
|
||||||
|
return thing.getThingTypeUID().getId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThingStatus getThingStatus() {
|
public ThingStatus getThingStatus() {
|
||||||
return getThing().getStatus();
|
return thing.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThingStatusDetail getThingStatusDetail() {
|
public ThingStatusDetail getThingStatusDetail() {
|
||||||
return getThing().getStatusInfo().getStatusDetail();
|
return thing.getStatusInfo().getStatusDetail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isThingOnline() {
|
public boolean isThingOnline() {
|
||||||
return getThing().getStatus() == ThingStatus.ONLINE;
|
return getThingStatus() == ThingStatus.ONLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isThingOffline() {
|
public boolean isThingOffline() {
|
||||||
return getThing().getStatus() == ThingStatus.OFFLINE;
|
return getThingStatus() == ThingStatus.OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThingOnline() {
|
public void setThingOnline() {
|
||||||
if (stopping) {
|
|
||||||
logger.debug("{}: Thing should go ONLINE, but handler is shutting down, ignore!", thingName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isThingOnline()) {
|
if (!isThingOnline()) {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
|
||||||
|
@ -640,16 +649,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThingOffline(ThingStatusDetail detail, String messageKey) {
|
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments) {
|
||||||
String message = messages.get(messageKey);
|
|
||||||
if (stopping) {
|
|
||||||
logger.debug("{}: Thing should go OFFLINE with status {}, but handler is shutting down -> ignore",
|
|
||||||
thingName, message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isThingOffline()) {
|
if (!isThingOffline()) {
|
||||||
updateStatus(ThingStatus.OFFLINE, detail, message);
|
updateStatus(ThingStatus.OFFLINE, detail, messages.get(messageKey, arguments));
|
||||||
|
api.close(); // Gen2: disconnect WS/close http sessions
|
||||||
watchdog = 0;
|
watchdog = 0;
|
||||||
channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
|
channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
|
||||||
}
|
}
|
||||||
|
@ -723,15 +726,22 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
if (status.uptime != null) {
|
if (status.uptime != null) {
|
||||||
stats.lastUptime = getLong(status.uptime);
|
stats.lastUptime = getLong(status.uptime);
|
||||||
}
|
}
|
||||||
if (coap != null) {
|
|
||||||
stats.coiotMessages = coap.getMessageCount();
|
|
||||||
stats.coiotErrors = coap.getErrorCount();
|
|
||||||
}
|
|
||||||
if (!alarm.isEmpty()) {
|
if (!alarm.isEmpty()) {
|
||||||
postEvent(alarm, false);
|
postEvent(alarm, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incProtMessages() {
|
||||||
|
stats.protocolMessages++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void incProtErrors() {
|
||||||
|
stats.protocolErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if device has restarted and needs a new Thing initialization
|
* Check if device has restarted and needs a new Thing initialization
|
||||||
*
|
*
|
||||||
|
@ -741,8 +751,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
private boolean checkRestarted(ShellySettingsStatus status) {
|
private boolean checkRestarted(ShellySettingsStatus status) {
|
||||||
if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */
|
if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */
|
||||||
&& (status.uptime != null && status.uptime < stats.lastUptime
|
&& (status.uptime != null && status.uptime < stats.lastUptime
|
||||||
|| !profile.status.update.oldVersion.isEmpty()
|
|| (!profile.status.update.oldVersion.isEmpty()
|
||||||
&& !status.update.oldVersion.equals(profile.status.update.oldVersion))) {
|
&& !status.update.oldVersion.equals(profile.status.update.oldVersion)))) {
|
||||||
|
logger.debug("{}: Device has been restarted, uptime={}/{}, firmware={}/{}", thingName, stats.lastUptime,
|
||||||
|
getLong(status.uptime), profile.status.update.oldVersion, status.update.oldVersion);
|
||||||
updateProperties(profile, status);
|
updateProperties(profile, status);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -785,6 +797,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateScheduled() {
|
||||||
|
return scheduledUpdates > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for device events
|
* Callback for device events
|
||||||
*
|
*
|
||||||
|
@ -980,12 +996,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
if (version.checkBeta(getString(prf.fwVersion))) {
|
if (version.checkBeta(getString(prf.fwVersion))) {
|
||||||
logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate));
|
logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate));
|
||||||
} else {
|
} else {
|
||||||
if ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWVERSION) < 0) && !profile.isMotion) {
|
String minVersion = !gen2 ? SHELLY_API_MIN_FWVERSION : SHELLY2_API_MIN_FWVERSION;
|
||||||
|
if (version.compare(prf.fwVersion, minVersion) < 0) {
|
||||||
logger.warn("{}: {}", prf.hostname,
|
logger.warn("{}: {}", prf.hostname,
|
||||||
messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, SHELLY_API_MIN_FWVERSION));
|
messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, minVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
|
if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
|
||||||
|| (prf.fwVersion.equalsIgnoreCase("production_test"))) {
|
|| (prf.fwVersion.equalsIgnoreCase("production_test"))) {
|
||||||
if (!config.eventsCoIoT) {
|
if (!config.eventsCoIoT) {
|
||||||
logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot"));
|
logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot"));
|
||||||
|
@ -1001,6 +1018,50 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String checkForUpdate() {
|
||||||
|
try {
|
||||||
|
ShellyOtaCheckResult result = api.checkForUpdate();
|
||||||
|
return result.status;
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCoap(ShellyThingConfiguration config, ShellyDeviceProfile profile) throws ShellyApiException {
|
||||||
|
if (coap == null || !config.eventsCoIoT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (profile.settings.coiot != null && profile.settings.coiot.enabled != null) {
|
||||||
|
String devpeer = getString(profile.settings.coiot.peer);
|
||||||
|
String ourpeer = config.localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT;
|
||||||
|
if (!profile.settings.coiot.enabled || (profile.isMotion && devpeer.isEmpty())) {
|
||||||
|
try {
|
||||||
|
api.setCoIoTPeer(ourpeer);
|
||||||
|
logger.info("{}: CoIoT peer updated to {}", thingName, ourpeer);
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
logger.debug("{}: Unable to set CoIoT peer: {}", thingName, e.toString());
|
||||||
|
}
|
||||||
|
} else if (!devpeer.isEmpty() && !devpeer.equals(ourpeer)) {
|
||||||
|
logger.warn("{}: CoIoT peer in device settings does not point this to this host", thingName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (autoCoIoT) {
|
||||||
|
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
|
||||||
|
config.eventsCoIoT = true;
|
||||||
|
config.eventsSwitch = false;
|
||||||
|
config.eventsButton = false;
|
||||||
|
config.eventsPush = false;
|
||||||
|
config.eventsRoller = false;
|
||||||
|
config.eventsSensorReport = false;
|
||||||
|
api.setConfig(thingName, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("{}: Starting CoIoT (autoCoIoT={}/{})", thingName, bindingConfig.autoCoIoT, autoCoIoT);
|
||||||
|
if (coap != null) {
|
||||||
|
coap.start(thingName, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the http response for authorization error.
|
* Checks the http response for authorization error.
|
||||||
* If the authorization failed the binding can't access the device settings and determine the thing type. In this
|
* If the authorization failed the binding can't access the device settings and determine the thing type. In this
|
||||||
|
@ -1081,10 +1142,6 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdateScheduled() {
|
|
||||||
return scheduledUpdates > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map input states to channels
|
* Map input states to channels
|
||||||
*
|
*
|
||||||
|
@ -1098,17 +1155,15 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
boolean updated = false;
|
boolean updated = false;
|
||||||
|
|
||||||
if (status.inputs != null) {
|
if (status.inputs != null) {
|
||||||
|
if (!areChannelsCreated()) {
|
||||||
|
updateChannelDefinitions(ShellyChannelDefinitions.createInputChannels(thing, profile, status));
|
||||||
|
}
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
boolean multiInput = status.inputs.size() >= 2; // device has multiple SW (inputs)
|
boolean multiInput = !profile.isIX && status.inputs.size() >= 2; // device has multiple SW (inputs)
|
||||||
for (ShellyInputState input : status.inputs) {
|
for (ShellyInputState input : status.inputs) {
|
||||||
String group = profile.getInputGroup(idx);
|
String group = profile.getInputGroup(idx);
|
||||||
String suffix = multiInput ? profile.getInputSuffix(idx) : "";
|
String suffix = multiInput ? profile.getInputSuffix(idx) : "";
|
||||||
|
|
||||||
if (!areChannelsCreated()) {
|
|
||||||
updateChannelDefinitions(
|
|
||||||
ShellyChannelDefinitions.createInputChannels(thing, profile, status, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
updated |= updateChannel(group, CHANNEL_INPUT + suffix, getOnOff(input.input));
|
updated |= updateChannel(group, CHANNEL_INPUT + suffix, getOnOff(input.input));
|
||||||
if (input.event != null) {
|
if (input.event != null) {
|
||||||
updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
|
updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
|
||||||
|
@ -1248,12 +1303,14 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
*/
|
*/
|
||||||
public void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
|
public void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
|
||||||
Map<String, Object> properties = fillDeviceProperties(profile);
|
Map<String, Object> properties = fillDeviceProperties(profile);
|
||||||
|
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
|
||||||
String deviceName = getString(profile.settings.name);
|
String deviceName = getString(profile.settings.name);
|
||||||
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
|
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
|
||||||
properties.put(PROPERTY_DEV_GEN, "1");
|
properties.put(PROPERTY_DEV_GEN, "1");
|
||||||
if (!deviceName.isEmpty()) {
|
if (!deviceName.isEmpty()) {
|
||||||
properties.put(PROPERTY_DEV_NAME, deviceName);
|
properties.put(PROPERTY_DEV_NAME, deviceName);
|
||||||
}
|
}
|
||||||
|
properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2");
|
||||||
|
|
||||||
// add status properties
|
// add status properties
|
||||||
if (status.wifiSta != null) {
|
if (status.wifiSta != null) {
|
||||||
|
@ -1372,32 +1429,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
public @Nullable List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
||||||
List<StateOption> options = new ArrayList<>();
|
List<StateOption> options = channelDefinitions.getStateOptions(uid);
|
||||||
for (OptionEntry oe : stateOptions) {
|
|
||||||
if (oe.uid.equals(uid)) {
|
|
||||||
options.add(new StateOption(oe.key, oe.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.isEmpty()) {
|
if (!options.isEmpty()) {
|
||||||
logger.debug("{}: Return {} state options for channel uid {}", thingName, options.size(), uid.getId());
|
logger.debug("{}: Return {} state options for channel uid {}", thingName, options.size(), uid.getId());
|
||||||
}
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
private void addStateOption(String channelId, String key, String value) {
|
|
||||||
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
|
|
||||||
stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearStateOptions(String channelId) {
|
|
||||||
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
|
|
||||||
for (OptionEntry oe : stateOptions) {
|
|
||||||
if (oe.uid.equals(uid)) {
|
|
||||||
stateOptions.remove(oe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ShellyDeviceProfile getDeviceProfile() {
|
protected ShellyDeviceProfile getDeviceProfile() {
|
||||||
|
@ -1431,10 +1469,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
statusJob = null;
|
statusJob = null;
|
||||||
logger.debug("{}: Shelly statusJob stopped", thingName);
|
logger.debug("{}: Shelly statusJob stopped", thingName);
|
||||||
}
|
}
|
||||||
|
api.close();
|
||||||
if (coap != null) {
|
|
||||||
coap.stop();
|
|
||||||
}
|
|
||||||
profile.initialized = false;
|
profile.initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1443,6 +1478,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
logger.debug("{}: Stopping Thing", thingName);
|
||||||
stopping = true;
|
stopping = true;
|
||||||
stop();
|
stop();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -1455,6 +1491,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUID() {
|
||||||
|
return getThing().getUID().getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device specific handlers are overriding this method to do additional stuff
|
* Device specific handlers are overriding this method to do additional stuff
|
||||||
*/
|
*/
|
||||||
|
@ -1483,22 +1523,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getStatsProp() {
|
|
||||||
return stats.asProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getScheduledUpdates() {
|
public long getScheduledUpdates() {
|
||||||
return scheduledUpdates;
|
return scheduledUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String checkForUpdate() {
|
public Map<String, String> getStatsProp() {
|
||||||
try {
|
return stats.asProperties();
|
||||||
ShellyOtaCheckResult result = api.checkForUpdate();
|
|
||||||
return result.status;
|
|
||||||
} catch (ShellyApiException e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,8 +33,8 @@ public class ShellyDeviceStats {
|
||||||
public long alarms = 0;
|
public long alarms = 0;
|
||||||
public String lastAlarm = "";
|
public String lastAlarm = "";
|
||||||
public long lastAlarmTs = 0;
|
public long lastAlarmTs = 0;
|
||||||
public long coiotMessages = 0;
|
public long protocolMessages = 0;
|
||||||
public long coiotErrors = 0;
|
public long protocolErrors = 0;
|
||||||
public int wifiRssi = 0;
|
public int wifiRssi = 0;
|
||||||
public int maxInternalTemp = 0;
|
public int maxInternalTemp = 0;
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@ public class ShellyDeviceStats {
|
||||||
prop.put("alarmCount", String.valueOf(alarms));
|
prop.put("alarmCount", String.valueOf(alarms));
|
||||||
prop.put("lastAlarm", lastAlarm);
|
prop.put("lastAlarm", lastAlarm);
|
||||||
prop.put("lastAlarmTs", ShellyUtils.convertTimestamp(lastAlarmTs));
|
prop.put("lastAlarmTs", ShellyUtils.convertTimestamp(lastAlarmTs));
|
||||||
prop.put("coiotMessages", String.valueOf(coiotMessages));
|
prop.put("protocolMessages", String.valueOf(protocolMessages));
|
||||||
prop.put("coiotErrors", String.valueOf(coiotErrors));
|
prop.put("protocolErrors", String.valueOf(protocolErrors));
|
||||||
prop.put("wifiRssi", String.valueOf(wifiRssi));
|
prop.put("wifiRssi", String.valueOf(wifiRssi));
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,11 @@ public interface ShellyManagerInterface {
|
||||||
|
|
||||||
public void setThingOnline();
|
public void setThingOnline();
|
||||||
|
|
||||||
public void setThingOffline(ThingStatusDetail detail, String messageKey);
|
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||||
|
|
||||||
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||||
|
|
||||||
|
public void incProtMessages();
|
||||||
|
|
||||||
|
public void incProtErrors();
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
|
channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
|
||||||
|
|
||||||
// request updates the next 45sec to update roller position after it stopped
|
// request updates the next 45sec to update roller position after it stopped
|
||||||
requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
if (!autoCoIoT && !profile.isGen2) {
|
||||||
|
requestUpdates(45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CHANNEL_ROL_CONTROL_FAV:
|
case CHANNEL_ROL_CONTROL_FAV:
|
||||||
|
@ -129,11 +131,11 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
|
|
||||||
case CHANNEL_TIMER_AUTOON:
|
case CHANNEL_TIMER_AUTOON:
|
||||||
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
||||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).intValue());
|
api.setAutoTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).doubleValue());
|
||||||
break;
|
break;
|
||||||
case CHANNEL_TIMER_AUTOOFF:
|
case CHANNEL_TIMER_AUTOOFF:
|
||||||
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
||||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).intValue());
|
api.setAutoTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).doubleValue());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -241,7 +243,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
position = shpos;
|
position = shpos;
|
||||||
} else {
|
} else {
|
||||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||||
position = SHELLY_MIN_ROLLER_POS;
|
|
||||||
}
|
}
|
||||||
} else if (command == UpDownType.DOWN || command == OnOffType.OFF
|
} else if (command == UpDownType.DOWN || command == OnOffType.OFF
|
||||||
|| ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 0))) {
|
|| ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 0))) {
|
||||||
|
@ -255,7 +256,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
position = shpos;
|
position = shpos;
|
||||||
} else {
|
} else {
|
||||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
||||||
position = SHELLY_MAX_ROLLER_POS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (command == StopMoveType.STOP) {
|
} else if (command == StopMoveType.STOP) {
|
||||||
|
@ -283,18 +283,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
logger.debug("{}: Changing roller position to {}", thingName, position);
|
logger.debug("{}: Changing roller position to {}", thingName, position);
|
||||||
api.setRollerPos(index, position);
|
api.setRollerPos(index, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position != -1) {
|
|
||||||
// make sure both are in sync
|
|
||||||
if (isControl) {
|
|
||||||
int pos = SHELLY_MAX_ROLLER_POS - Math.max(0, Math.min(position, SHELLY_MAX_ROLLER_POS));
|
|
||||||
logger.debug("{}: Set roller position for control channel to {}", thingName, pos);
|
|
||||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
|
|
||||||
} else {
|
|
||||||
logger.debug("{}: Set roller position channel to {}", thingName, position);
|
|
||||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -329,7 +317,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
*/
|
*/
|
||||||
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
|
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
|
||||||
boolean updated = false;
|
boolean updated = false;
|
||||||
|
|
||||||
if (profile.hasRelays && !profile.isDimmer) {
|
if (profile.hasRelays && !profile.isDimmer) {
|
||||||
double voltage = -1;
|
double voltage = -1;
|
||||||
if (status.voltage == null && profile.settings.supplyVoltage != null) {
|
if (status.voltage == null && profile.settings.supplyVoltage != null) {
|
||||||
|
@ -343,14 +330,12 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE,
|
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE,
|
||||||
toQuantityType(voltage, DIGITS_VOLT, Units.VOLT));
|
toQuantityType(voltage, DIGITS_VOLT, Units.VOLT));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (profile.hasRelays && !profile.isRoller) {
|
if (!profile.isRoller) {
|
||||||
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
||||||
for (int i = 0; i < status.relays.size(); i++) {
|
for (int i = 0; i < status.relays.size(); i++) {
|
||||||
createRelayChannels(status.relays.get(i), i);
|
createRelayChannels(status.relays.get(i), i);
|
||||||
updated |= ShellyComponents.updateRelay(this, status, i);
|
updated |= ShellyComponents.updateRelay(this, status, i);
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check for Relay in Roller Mode
|
// Check for Relay in Roller Mode
|
||||||
|
@ -361,7 +346,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||||
updated |= ShellyComponents.updateRoller(this, roller, i);
|
updated |= ShellyComponents.updateRoller(this, roller, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ public interface ShellyThingInterface {
|
||||||
|
|
||||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||||
|
|
||||||
public List<StateOption> getStateOptions(ChannelTypeUID uid);
|
public @Nullable List<StateOption> getStateOptions(ChannelTypeUID uid);
|
||||||
|
|
||||||
public double getChannelDouble(String group, String channel);
|
public double getChannelDouble(String group, String channel);
|
||||||
|
|
||||||
|
@ -51,7 +51,9 @@ public interface ShellyThingInterface {
|
||||||
|
|
||||||
public void setThingOnline();
|
public void setThingOnline();
|
||||||
|
|
||||||
public void setThingOffline(ThingStatusDetail detail, String messageKey);
|
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||||
|
|
||||||
|
public String getThingType();
|
||||||
|
|
||||||
public ThingStatus getThingStatus();
|
public ThingStatus getThingStatus();
|
||||||
|
|
||||||
|
@ -110,4 +112,8 @@ public interface ShellyThingInterface {
|
||||||
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
|
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
|
||||||
|
|
||||||
public boolean checkRepresentation(String key);
|
public boolean checkRepresentation(String key);
|
||||||
|
|
||||||
|
public void incProtMessages();
|
||||||
|
|
||||||
|
public void incProtErrors();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ public class ShellyThingTable {
|
||||||
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
|
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public void addThing(String key, ShellyThingInterface thing) {
|
public void addThing(String key, ShellyThingInterface thing) {
|
||||||
|
if (thingTable.containsKey(key)) {
|
||||||
|
thingTable.remove(key);
|
||||||
|
}
|
||||||
thingTable.put(key, thing);
|
thingTable.put(key, thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
String peer = getString(profile.settings.coiot.peer);
|
String peer = getString(profile.settings.coiot.peer);
|
||||||
boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer);
|
boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer) || profile.isMotion;
|
||||||
String newPeer = mcast ? localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
|
String newPeer = mcast ? localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
|
||||||
String displayPeer = mcast ? newPeer : "Multicast";
|
String displayPeer = mcast ? newPeer : "Multicast";
|
||||||
|
|
||||||
|
@ -252,7 +252,6 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||||
refreshTimer = 3;
|
refreshTimer = 3;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACTION_ENAPROAMING:
|
case ACTION_ENAPROAMING:
|
||||||
case ACTION_DISAPROAMING:
|
case ACTION_DISAPROAMING:
|
||||||
enable = ACTION_ENAPROAMING.equalsIgnoreCase(action);
|
enable = ACTION_ENAPROAMING.equalsIgnoreCase(action);
|
||||||
|
@ -269,6 +268,77 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||||
refreshTimer = 3;
|
refreshTimer = 3;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ACTION_ENRANGEEXT:
|
||||||
|
case ACTION_DISRANGEEXT:
|
||||||
|
enable = ACTION_ENRANGEEXT.equalsIgnoreCase(action);
|
||||||
|
if (!"yes".equalsIgnoreCase(update)) {
|
||||||
|
message = getMessage(
|
||||||
|
enable ? "action.setwifirangeext-enable" : "action.setwifirangeext-disable");
|
||||||
|
actionUrl = buildActionUrl(uid, action);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
boolean res = api.setWiFiRangeExtender(enable);
|
||||||
|
if (res) {
|
||||||
|
message = getMessageP("action.restart.info", MCINFO);
|
||||||
|
actionButtonLabel = "Ok";
|
||||||
|
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||||
|
} else {
|
||||||
|
message = getMessage("action.setwifirangeext-confirm", res ? "yes" : "no");
|
||||||
|
refreshTimer = 5;
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
message = getMessage("action.setwifirangeext-failed", e.toString());
|
||||||
|
refreshTimer = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACTION_ENETHERNET:
|
||||||
|
case ACTION_DISETHERNET:
|
||||||
|
enable = ACTION_ENETHERNET.equalsIgnoreCase(action);
|
||||||
|
if (!"yes".equalsIgnoreCase(update)) {
|
||||||
|
message = getMessage(enable ? "action.setethernet-enable" : "action.setethernet-disable");
|
||||||
|
actionUrl = buildActionUrl(uid, action);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
boolean res = api.setEthernet(enable);
|
||||||
|
if (res) {
|
||||||
|
message = getMessageP("action.restart.info", MCINFO);
|
||||||
|
actionButtonLabel = "Ok";
|
||||||
|
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||||
|
} else {
|
||||||
|
message = getMessage("action.setethernet-confirm", res ? "yes" : "no");
|
||||||
|
refreshTimer = 5;
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
message = getMessage("action.setethernet-failed", e.toString());
|
||||||
|
refreshTimer = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ACTION_ENBLUETOOTH:
|
||||||
|
case ACTION_DISBLUETOOTH:
|
||||||
|
enable = ACTION_ENBLUETOOTH.equalsIgnoreCase(action);
|
||||||
|
if (!"yes".equalsIgnoreCase(update)) {
|
||||||
|
message = getMessage(enable ? "action.setbluetooth-enable" : "action.setbluetooth-disable");
|
||||||
|
actionUrl = buildActionUrl(uid, action);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
boolean res = api.setBluetooth(enable);
|
||||||
|
if (res) {
|
||||||
|
message = getMessageP("action.restart.info", MCINFO);
|
||||||
|
actionButtonLabel = "Ok";
|
||||||
|
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||||
|
} else {
|
||||||
|
message = getMessage("action.setbluetooth-confirm", res ? "yes" : "no");
|
||||||
|
refreshTimer = 5;
|
||||||
|
}
|
||||||
|
} catch (ShellyApiException e) {
|
||||||
|
message = getMessage("action.setbluetooth-failed", e.toString());
|
||||||
|
refreshTimer = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case ACTION_GETDEB:
|
case ACTION_GETDEB:
|
||||||
case ACTION_GETDEB1:
|
case ACTION_GETDEB1:
|
||||||
|
@ -303,35 +373,51 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||||
|
|
||||||
public static Map<String, String> getActions(ShellyDeviceProfile profile) {
|
public static Map<String, String> getActions(ShellyDeviceProfile profile) {
|
||||||
Map<String, String> list = new LinkedHashMap<>();
|
Map<String, String> list = new LinkedHashMap<>();
|
||||||
|
boolean gen2 = profile.isGen2;
|
||||||
|
|
||||||
list.put(ACTION_RES_STATS, "Reset Statistics");
|
list.put(ACTION_RES_STATS, "Reset Statistics");
|
||||||
list.put(ACTION_RESTART, "Reboot Device");
|
list.put(ACTION_RESTART, "Reboot Device");
|
||||||
|
if (gen2) {
|
||||||
list.put(ACTION_PROTECT, "Protect Device");
|
list.put(ACTION_PROTECT, "Protect Device");
|
||||||
|
}
|
||||||
|
|
||||||
if ((profile.settings.coiot != null) && (profile.settings.coiot.peer != null) && !profile.isMotion) {
|
if ((profile.settings.coiot != null) && profile.settings.coiot.peer != null) {
|
||||||
boolean mcast = profile.settings.coiot.peer.isEmpty()
|
boolean mcast = profile.settings.coiot.peer.isEmpty()
|
||||||
|| SHELLY_COIOT_MCAST.equalsIgnoreCase(profile.settings.coiot.peer);
|
|| SHELLY_COIOT_MCAST.equalsIgnoreCase(profile.settings.coiot.peer) || profile.isMotion;
|
||||||
list.put(mcast ? ACTION_SETCOIOT_PEER : ACTION_SETCOIOT_MCAST,
|
list.put(mcast ? ACTION_SETCOIOT_PEER : ACTION_SETCOIOT_MCAST,
|
||||||
mcast ? "Set CoIoT Peer Mode" : "Set CoIoT Multicast Mode");
|
mcast ? "Set CoIoT Peer Mode" : "Set CoIoT Multicast Mode");
|
||||||
}
|
}
|
||||||
if (profile.isSensor && !profile.isMotion && (profile.settings.wifiSta != null)
|
if (profile.isSensor && !profile.isMotion && profile.settings.wifiSta != null
|
||||||
&& profile.settings.wifiSta.enabled) {
|
&& profile.settings.wifiSta.enabled) {
|
||||||
// FW 1.10+: Reset STA list, force WiFi rescan and connect to stringest AP
|
// FW 1.10+: Reset STA list, force WiFi rescan and connect to stringest AP
|
||||||
list.put(ACTION_RESSTA, "Reconnect WiFi");
|
list.put(ACTION_RESSTA, "Reconnect WiFi");
|
||||||
}
|
}
|
||||||
if (profile.settings.apRoaming != null) {
|
if (!gen2 && profile.settings.apRoaming != null) {
|
||||||
list.put(!profile.settings.apRoaming.enabled ? ACTION_ENAPROAMING : ACTION_DISAPROAMING,
|
list.put(!profile.settings.apRoaming.enabled ? ACTION_ENAPROAMING : ACTION_DISAPROAMING,
|
||||||
!profile.settings.apRoaming.enabled ? "Enable WiFi Roaming" : "Disable WiFi Roaming");
|
!profile.settings.apRoaming.enabled ? "Enable WiFi Roaming" : "Disable WiFi Roaming");
|
||||||
}
|
}
|
||||||
if (profile.settings.wifiRecoveryReboot != null) {
|
if (!gen2 && profile.settings.wifiRecoveryReboot != null) {
|
||||||
list.put(!profile.settings.wifiRecoveryReboot ? ACTION_ENWIFIREC : ACTION_DISWIFIREC,
|
list.put(!profile.settings.wifiRecoveryReboot ? ACTION_ENWIFIREC : ACTION_DISWIFIREC,
|
||||||
!profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery");
|
!profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery");
|
||||||
}
|
}
|
||||||
|
if (profile.settings.wifiAp != null && profile.settings.wifiAp.rangeExtender != null) {
|
||||||
|
list.put(!profile.settings.wifiAp.rangeExtender ? ACTION_ENRANGEEXT : ACTION_DISRANGEEXT,
|
||||||
|
!profile.settings.wifiAp.rangeExtender ? "Enable Range Extender" : "Disable Range Extender");
|
||||||
|
}
|
||||||
|
if (profile.settings.ethernet != null) {
|
||||||
|
list.put(!profile.settings.ethernet ? ACTION_ENETHERNET : ACTION_DISETHERNET,
|
||||||
|
!profile.settings.ethernet ? "Enable Ethernet" : "Disable Ethernet");
|
||||||
|
}
|
||||||
|
if (profile.settings.bluetooth != null) {
|
||||||
|
list.put(!profile.settings.bluetooth ? ACTION_ENBLUETOOTH : ACTION_DISBLUETOOTH,
|
||||||
|
!profile.settings.bluetooth ? "Enable Bluetooth" : "Disable Bluetooth");
|
||||||
|
}
|
||||||
|
|
||||||
boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
|
boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
|
||||||
list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
|
list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
|
||||||
|
|
||||||
list.put(ACTION_RESET, "-Factory Reset");
|
list.put(ACTION_RESET, "-Factory Reset");
|
||||||
if (profile.extFeatures) {
|
if (!gen2 && profile.extFeatures) {
|
||||||
list.put(ACTION_OTACHECK, "Check for Update");
|
list.put(ACTION_OTACHECK, "Check for Update");
|
||||||
boolean debug_enable = getBool(profile.settings.debugEnable);
|
boolean debug_enable = getBool(profile.settings.debugEnable);
|
||||||
list.put(!debug_enable ? ACTION_ENDEBUG : ACTION_DISDEBUG,
|
list.put(!debug_enable ? ACTION_ENDEBUG : ACTION_DISDEBUG,
|
||||||
|
|
|
@ -39,7 +39,6 @@ public class ShellyManagerConstants {
|
||||||
public static final String ACTION_SETCOIOT_PEER = "setcoiotpeer";
|
public static final String ACTION_SETCOIOT_PEER = "setcoiotpeer";
|
||||||
public static final String ACTION_SETCOIOT_MCAST = "setcoiotmcast";
|
public static final String ACTION_SETCOIOT_MCAST = "setcoiotmcast";
|
||||||
public static final String ACTION_SETTZ = "settz";
|
public static final String ACTION_SETTZ = "settz";
|
||||||
public static final String ACTION_SETNTP = "setntp";
|
|
||||||
public static final String ACTION_ENCLOUD = "encloud";
|
public static final String ACTION_ENCLOUD = "encloud";
|
||||||
public static final String ACTION_DISCLOUD = "discloud";
|
public static final String ACTION_DISCLOUD = "discloud";
|
||||||
public static final String ACTION_RES_STATS = "reset_stat";
|
public static final String ACTION_RES_STATS = "reset_stat";
|
||||||
|
@ -49,6 +48,12 @@ public class ShellyManagerConstants {
|
||||||
public static final String ACTION_DISWIFIREC = "diswifirec";
|
public static final String ACTION_DISWIFIREC = "diswifirec";
|
||||||
public static final String ACTION_ENAPROAMING = "enaproaming";
|
public static final String ACTION_ENAPROAMING = "enaproaming";
|
||||||
public static final String ACTION_DISAPROAMING = "disaproaming";
|
public static final String ACTION_DISAPROAMING = "disaproaming";
|
||||||
|
public static final String ACTION_ENRANGEEXT = "enrangeext";
|
||||||
|
public static final String ACTION_ENETHERNET = "enethernet";
|
||||||
|
public static final String ACTION_DISETHERNET = "disethernet";
|
||||||
|
public static final String ACTION_ENBLUETOOTH = "enbluetooth";
|
||||||
|
public static final String ACTION_DISBLUETOOTH = "disbluetooth";
|
||||||
|
public static final String ACTION_DISRANGEEXT = "disrangeext";
|
||||||
public static final String ACTION_OTACHECK = "otacheck";
|
public static final String ACTION_OTACHECK = "otacheck";
|
||||||
public static final String ACTION_ENDEBUG = "endebug";
|
public static final String ACTION_ENDEBUG = "endebug";
|
||||||
public static final String ACTION_DISDEBUG = "disdebug";
|
public static final String ACTION_DISDEBUG = "disdebug";
|
||||||
|
|
|
@ -33,9 +33,9 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||||
|
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
|
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
|
||||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
|
||||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||||
import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
|
import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
|
||||||
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
||||||
|
@ -122,7 +122,7 @@ public class ShellyManagerOtaPage extends ShellyManagerPage {
|
||||||
|
|
||||||
new Thread(() -> { // schedule asynchronous reboot
|
new Thread(() -> { // schedule asynchronous reboot
|
||||||
try {
|
try {
|
||||||
Shelly1HttpApi api = new Shelly1HttpApi(uid, config, httpClient);
|
ShellyApiInterface api = th.getApi();
|
||||||
ShellySettingsUpdate result = api.firmwareUpdate(updateUrl);
|
ShellySettingsUpdate result = api.firmwareUpdate(updateUrl);
|
||||||
String status = getString(result.status);
|
String status = getString(result.status);
|
||||||
logger.info("{}: {}", th.getThingName(), getMessage("fwupdate.initiated", status));
|
logger.info("{}: {}", th.getThingName(), getMessage("fwupdate.initiated", status));
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||||
properties.put(ATTRIBUTE_STATUS_ICON, ICON_ATTENTION);
|
properties.put(ATTRIBUTE_STATUS_ICON, ICON_ATTENTION);
|
||||||
}
|
}
|
||||||
if (!"unknown".equalsIgnoreCase(deviceType) && (status == ThingStatus.ONLINE)) {
|
if (!"unknown".equalsIgnoreCase(deviceType) && (status == ThingStatus.ONLINE)) {
|
||||||
properties.put(ATTRIBUTE_FIRMWARE_SEL, fillFirmwareHtml(uid, deviceType, profile.mode));
|
properties.put(ATTRIBUTE_FIRMWARE_SEL, fillFirmwareHtml(profile, uid, deviceType));
|
||||||
properties.put(ATTRIBUTE_ACTION_LIST, fillActionHtml(th, uid));
|
properties.put(ATTRIBUTE_ACTION_LIST, fillActionHtml(th, uid));
|
||||||
} else {
|
} else {
|
||||||
properties.put(ATTRIBUTE_FIRMWARE_SEL, "");
|
properties.put(ATTRIBUTE_FIRMWARE_SEL, "");
|
||||||
|
@ -132,7 +132,8 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||||
return new ShellyMgrResponse(fillAttributes(html, properties), HttpStatus.OK_200);
|
return new ShellyMgrResponse(fillAttributes(html, properties), HttpStatus.OK_200);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String fillFirmwareHtml(String uid, String deviceType, String mode) throws ShellyApiException {
|
private String fillFirmwareHtml(ShellyDeviceProfile profile, String uid, String deviceType)
|
||||||
|
throws ShellyApiException {
|
||||||
String html = "\n\t\t\t\t<select name=\"fwList\" id=\"fwList\" onchange=\"location = this.options[this.selectedIndex].value;\">\n";
|
String html = "\n\t\t\t\t<select name=\"fwList\" id=\"fwList\" onchange=\"location = this.options[this.selectedIndex].value;\">\n";
|
||||||
html += "\t\t\t\t\t<option value=\"\" selected disabled hidden>update to</option>\n";
|
html += "\t\t\t\t\t<option value=\"\" selected disabled hidden>update to</option>\n";
|
||||||
|
|
||||||
|
@ -140,20 +141,26 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||||
String bVersion = "";
|
String bVersion = "";
|
||||||
String updateUrl = SHELLY_MGR_FWUPDATE_URI + "?" + URLPARM_UID + "=" + urlEncode(uid);
|
String updateUrl = SHELLY_MGR_FWUPDATE_URI + "?" + URLPARM_UID + "=" + urlEncode(uid);
|
||||||
try {
|
try {
|
||||||
// Get current prod + beta version from original firmware repo
|
if (!profile.isGen2) { // currently there is no public firmware repo for Gen2
|
||||||
logger.debug("{}: Load firmware version list for device type {}", LOG_PREFIX, deviceType);
|
logger.debug("{}: Load firmware version list for device type {}", LOG_PREFIX, deviceType);
|
||||||
FwRepoEntry fw = getFirmwareRepoEntry(deviceType, mode);
|
FwRepoEntry fw = getFirmwareRepoEntry(deviceType, profile.mode);
|
||||||
|
|
||||||
pVersion = extractFwVersion(fw.version);
|
pVersion = extractFwVersion(fw.version);
|
||||||
|
bVersion = extractFwVersion(fw.betaVer);
|
||||||
|
} else {
|
||||||
|
pVersion = extractFwVersion(getString(profile.status.update.newVersion));
|
||||||
|
bVersion = extractFwVersion(getString(profile.status.update.betaVersion));
|
||||||
|
}
|
||||||
if (!pVersion.isEmpty()) {
|
if (!pVersion.isEmpty()) {
|
||||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWPROD + "\">Release "
|
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWPROD + "\">Release "
|
||||||
+ pVersion + "</option>\n";
|
+ pVersion + "</option>\n";
|
||||||
}
|
}
|
||||||
bVersion = extractFwVersion(fw.betaVer);
|
|
||||||
if (!bVersion.isEmpty()) {
|
if (!bVersion.isEmpty()) {
|
||||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWBETA + "\">Beta "
|
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWBETA + "\">Beta "
|
||||||
+ bVersion + "</option>\n";
|
+ bVersion + "</option>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!profile.isGen2) { // currently no online repo for Gen2
|
||||||
// Add those from Shelly Firmware Archive
|
// Add those from Shelly Firmware Archive
|
||||||
String json = httpGet(FWREPO_ARCH_URL + "?" + URLPARM_TYPE + "=" + deviceType);
|
String json = httpGet(FWREPO_ARCH_URL + "?" + URLPARM_TYPE + "=" + deviceType);
|
||||||
if (json.startsWith("[]")) {
|
if (json.startsWith("[]")) {
|
||||||
|
@ -171,14 +178,18 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||||
String version = getString(e.version);
|
String version = getString(e.version);
|
||||||
ShellyVersionDTO v = new ShellyVersionDTO();
|
ShellyVersionDTO v = new ShellyVersionDTO();
|
||||||
if (!version.equalsIgnoreCase(pVersion) && !version.equalsIgnoreCase(bVersion)
|
if (!version.equalsIgnoreCase(pVersion) && !version.equalsIgnoreCase(bVersion)
|
||||||
&& (v.compare(version, SHELLY_API_MIN_FWCOIOT) >= 0) || version.contains("master")) {
|
&& (v.compare(version, SHELLY_API_MIN_FWCOIOT) >= 0)
|
||||||
|
|| version.contains("master")) {
|
||||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + version
|
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + version
|
||||||
+ "\">" + version + "</option>\n";
|
+ "\">" + version + "</option>\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ShellyApiException e) {
|
}
|
||||||
|
} catch (
|
||||||
|
|
||||||
|
ShellyApiException e) {
|
||||||
logger.debug("{}: Unable to retrieve firmware list: {}", LOG_PREFIX, e.toString());
|
logger.debug("{}: Unable to retrieve firmware list: {}", LOG_PREFIX, e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,9 +293,9 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||||
if ((config.eventsCoIoT) && (profile.settings.coiot != null)) {
|
if ((config.eventsCoIoT) && (profile.settings.coiot != null)) {
|
||||||
if ((profile.settings.coiot.enabled != null) && !profile.settings.coiot.enabled) {
|
if ((profile.settings.coiot.enabled != null) && !profile.settings.coiot.enabled) {
|
||||||
result.put("CoIoT Status", "COIOT_DISABLED");
|
result.put("CoIoT Status", "COIOT_DISABLED");
|
||||||
} else if (stats.coiotMessages == 0) {
|
} else if (stats.protocolMessages == 0) {
|
||||||
result.put("CoIoT Discovery", "NO_COIOT_DISCOVERY");
|
result.put("CoIoT Discovery", "NO_COIOT_DISCOVERY");
|
||||||
} else if (stats.coiotMessages < 2) {
|
} else if (stats.protocolMessages < 2) {
|
||||||
result.put("CoIoT Multicast", "NO_COIOT_MULTICAST");
|
result.put("CoIoT Multicast", "NO_COIOT_MULTICAST");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,6 +261,9 @@ public class ShellyManagerPage {
|
||||||
properties.put(ATTRIBUTE_ACTIONS_SKIPPED,
|
properties.put(ATTRIBUTE_ACTIONS_SKIPPED,
|
||||||
profile.status.astats != null ? String.valueOf(profile.status.astats.skipped) : "n/a");
|
profile.status.astats != null ? String.valueOf(profile.status.astats.skipped) : "n/a");
|
||||||
properties.put(ATTRIBUTE_MAX_ITEMP, stats.maxInternalTemp > 0 ? stats.maxInternalTemp + " °C" : "n/a");
|
properties.put(ATTRIBUTE_MAX_ITEMP, stats.maxInternalTemp > 0 ? stats.maxInternalTemp + " °C" : "n/a");
|
||||||
|
if (stats.maxInternalTemp == 0) {
|
||||||
|
properties.replace(CHANNEL_DEVST_ITEMP, "n/a");
|
||||||
|
}
|
||||||
|
|
||||||
// Shelly H&T: When external power is connected the battery level is not valid
|
// Shelly H&T: When external power is connected the battery level is not valid
|
||||||
if (!profile.isHT || (getInteger(profile.settings.externalPower) == 0)) {
|
if (!profile.isHT || (getInteger(profile.settings.externalPower) == 0)) {
|
||||||
|
|
|
@ -16,12 +16,15 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||||
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.SHELLY_API_INVTEMP;
|
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.SHELLY_API_INVTEMP;
|
||||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import javax.measure.Unit;
|
import javax.measure.Unit;
|
||||||
|
|
||||||
|
@ -47,6 +50,7 @@ import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||||
import org.openhab.core.thing.type.ChannelKind;
|
import org.openhab.core.thing.type.ChannelKind;
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
import org.openhab.core.types.StateOption;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
@ -97,6 +101,20 @@ public class ShellyChannelDefinitions {
|
||||||
public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
|
public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
|
||||||
public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
|
public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
|
||||||
|
|
||||||
|
public class OptionEntry {
|
||||||
|
public ChannelTypeUID uid;
|
||||||
|
public String key;
|
||||||
|
public String value;
|
||||||
|
|
||||||
|
public OptionEntry(ChannelTypeUID uid, String key, String value) {
|
||||||
|
this.uid = uid;
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
|
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
|
@ -169,8 +187,6 @@ public class ShellyChannelDefinitions {
|
||||||
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
|
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
|
||||||
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
|
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
|
||||||
|
|
||||||
// RGBW2-color
|
|
||||||
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_LIGHT_POWER, "system:power", ITEMT_SWITCH))
|
|
||||||
// Power Meter
|
// Power Meter
|
||||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEMT_POWER))
|
.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_TOTALKWH, "meterTotal", ITEMT_ENERGY))
|
||||||
|
@ -182,7 +198,7 @@ public class ShellyChannelDefinitions {
|
||||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEMT_POWER))
|
.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_VOLTAGE, "meterVoltage", ITEMT_VOLT))
|
||||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
|
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
|
||||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_PERCENT))
|
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
|
||||||
|
|
||||||
// Sensors
|
// Sensors
|
||||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
|
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
|
||||||
|
@ -227,11 +243,11 @@ public class ShellyChannelDefinitions {
|
||||||
// TRV
|
// TRV
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_POSITION, "sensorPosition", ITEMT_DIMMER))
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_POSITION, "sensorPosition", ITEMT_DIMMER))
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_MODE, "controlMode", ITEMT_STRING))
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_MODE, "controlMode", ITEMT_STRING))
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SCHEDULE, "controlSchedule", ITEMT_SWITCH))
|
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_PROFILE, "controlProfile", ITEMT_STRING))
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_PROFILE, "controlProfile", ITEMT_STRING))
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SETTEMP, "targetTemp", ITEMT_TEMP))
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SETTEMP, "targetTemp", ITEMT_TEMP))
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BCONTROL, "boostControl", ITEMT_SWITCH))
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BCONTROL, "boostControl", ITEMT_SWITCH))
|
||||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BTIMER, "boostTimer", ITEMT_TIME));
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BTIMER, "boostTimer", ITEMT_TIME))
|
||||||
|
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SCHEDULE, "controlSchedule", ITEMT_SWITCH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
|
public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
|
||||||
|
@ -273,7 +289,8 @@ public class ShellyChannelDefinitions {
|
||||||
|
|
||||||
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
||||||
|
|
||||||
if (!profile.isSensor && !profile.isIX && getDouble(status.temperature) != SHELLY_API_INVTEMP) {
|
if (!profile.isSensor && !profile.isIX && status.temperature != null
|
||||||
|
&& status.temperature != SHELLY_API_INVTEMP) {
|
||||||
// Only some devices report the internal device temp
|
// Only some devices report the internal device temp
|
||||||
addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
|
addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
|
||||||
}
|
}
|
||||||
|
@ -311,19 +328,20 @@ public class ShellyChannelDefinitions {
|
||||||
|
|
||||||
if (profile.settings.relays != null) {
|
if (profile.settings.relays != null) {
|
||||||
ShellySettingsRelay rs = profile.settings.relays.get(idx);
|
ShellySettingsRelay rs = profile.settings.relays.get(idx);
|
||||||
boolean timer = rs.hasTimer != null || rstatus.hasTimer != null; // Dimmer 1/2 have
|
|
||||||
addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
|
addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
|
||||||
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
|
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
|
||||||
|
|
||||||
|
boolean timer = rs.hasTimer != null || rstatus.hasTimer != null; // Dimmer 1/2 have
|
||||||
|
addChannel(thing, add, timer, group, CHANNEL_TIMER_ACTIVE);
|
||||||
addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||||
addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
||||||
addChannel(thing, add, timer, group, CHANNEL_TIMER_ACTIVE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shelly 1/1PM Addon
|
// Shelly 1/1PM Addon
|
||||||
if (profile.settings.extTemperature != null) {
|
if (profile.status.extTemperature != null) {
|
||||||
addChannel(thing, add, profile.settings.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
addChannel(thing, add, profile.status.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
||||||
addChannel(thing, add, profile.settings.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
addChannel(thing, add, profile.status.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
||||||
addChannel(thing, add, profile.settings.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
addChannel(thing, add, profile.status.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
||||||
}
|
}
|
||||||
if (profile.settings.extHumidity != null) {
|
if (profile.settings.extHumidity != null) {
|
||||||
addChannel(thing, add, profile.settings.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
|
addChannel(thing, add, profile.settings.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
|
||||||
|
@ -344,7 +362,6 @@ public class ShellyChannelDefinitions {
|
||||||
ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
|
ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
|
||||||
addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||||
addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
||||||
|
|
||||||
ShellyShortLightStatus dss = dstatus.dimmers.get(idx);
|
ShellyShortLightStatus dss = dstatus.dimmers.get(idx);
|
||||||
addChannel(thing, add, dss != null && dss.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
|
addChannel(thing, add, dss != null && dss.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
|
||||||
}
|
}
|
||||||
|
@ -359,7 +376,6 @@ public class ShellyChannelDefinitions {
|
||||||
if (profile.settings.lights != null) {
|
if (profile.settings.lights != null) {
|
||||||
ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
|
ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
|
||||||
String whiteGroup = profile.isRGBW2 ? group : CHANNEL_GROUP_WHITE_CONTROL;
|
String whiteGroup = profile.isRGBW2 ? group : CHANNEL_GROUP_WHITE_CONTROL;
|
||||||
|
|
||||||
// Create power channel in color mode and brightness channel in white mode
|
// Create power channel in color mode and brightness channel in white mode
|
||||||
addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
|
addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
|
||||||
addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||||
|
@ -368,29 +384,31 @@ public class ShellyChannelDefinitions {
|
||||||
addChannel(thing, add, light.brightness != null, whiteGroup, CHANNEL_BRIGHTNESS);
|
addChannel(thing, add, light.brightness != null, whiteGroup, CHANNEL_BRIGHTNESS);
|
||||||
addChannel(thing, add, light.temp != null, whiteGroup, CHANNEL_COLOR_TEMP);
|
addChannel(thing, add, light.temp != null, whiteGroup, CHANNEL_COLOR_TEMP);
|
||||||
}
|
}
|
||||||
|
|
||||||
return add;
|
return add;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
|
public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||||
final ShellySettingsStatus status, String group) {
|
final ShellySettingsStatus status) {
|
||||||
Map<String, Channel> add = new LinkedHashMap<>();
|
Map<String, Channel> add = new LinkedHashMap<>();
|
||||||
if (status.inputs != null) {
|
if (status.inputs != null) {
|
||||||
// Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
|
// 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
|
// 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++) {
|
for (int i = 0; i < profile.numInputs; i++) {
|
||||||
String suffix = multi ? String.valueOf(i + 1) : "";
|
String group = profile.getInputGroup(i);
|
||||||
ShellyInputState input = status.inputs.get(i);
|
String suffix = profile.getInputSuffix(i); // multi ? String.valueOf(i + 1) : "";
|
||||||
addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
|
addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
|
||||||
|
addChannel(thing, add, true, group,
|
||||||
|
(!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
|
||||||
if (profile.inButtonMode(i)) {
|
if (profile.inButtonMode(i)) {
|
||||||
|
ShellyInputState input = status.inputs.get(i);
|
||||||
addChannel(thing, add, input.event != null, group, CHANNEL_STATUS_EVENTTYPE + suffix);
|
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, 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) {
|
} else if (status.input != null) {
|
||||||
// old RGBW2 firmware
|
// old RGBW2 firmware
|
||||||
|
String group = profile.getInputGroup(0);
|
||||||
addChannel(thing, add, true, group, CHANNEL_INPUT);
|
addChannel(thing, add, true, group, CHANNEL_INPUT);
|
||||||
addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
|
addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
|
||||||
}
|
}
|
||||||
|
@ -409,7 +427,7 @@ public class ShellyChannelDefinitions {
|
||||||
ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
|
ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
ShellySettingsGlobal settings = handler.getProfile().settings;
|
ShellySettingsGlobal settings = handler.getProfile().settings;
|
||||||
if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) {
|
if (getBool(settings.favoritesEnabled) && settings.favorites != null) {
|
||||||
addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
|
addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,7 +453,7 @@ public class ShellyChannelDefinitions {
|
||||||
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
|
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
|
||||||
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
|
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
|
||||||
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
|
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
|
||||||
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_EMETER_PFACTOR); // EM has no PF. but power
|
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR); // EM has no PF. but power
|
||||||
|
|
||||||
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
|
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
|
||||||
return newChannels;
|
return newChannels;
|
||||||
|
@ -498,7 +516,7 @@ public class ShellyChannelDefinitions {
|
||||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE);
|
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE);
|
||||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
||||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE);
|
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE);
|
||||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE); // TRV
|
addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery
|
// Battery
|
||||||
|
@ -550,6 +568,30 @@ public class ShellyChannelDefinitions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
||||||
|
List<StateOption> options = new ArrayList<>();
|
||||||
|
for (OptionEntry oe : stateOptions) {
|
||||||
|
if (oe.uid.equals(uid)) {
|
||||||
|
options.add(new StateOption(oe.key, oe.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addStateOption(String channelId, String key, String value) {
|
||||||
|
ChannelTypeUID uid = getChannelTypeUID(channelId);
|
||||||
|
stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearStateOptions(String channelId) {
|
||||||
|
ChannelTypeUID uid = getChannelTypeUID(channelId);
|
||||||
|
for (OptionEntry oe : stateOptions) {
|
||||||
|
if (oe.uid.equals(uid)) {
|
||||||
|
stateOptions.remove(oe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ShellyChannel {
|
public class ShellyChannel {
|
||||||
private final ShellyTranslationProvider messages;
|
private final ShellyTranslationProvider messages;
|
||||||
public String group = "";
|
public String group = "";
|
||||||
|
|
|
@ -13,17 +13,29 @@
|
||||||
|
|
||||||
package org.openhab.binding.shelly.internal.provider;
|
package org.openhab.binding.shelly.internal.provider;
|
||||||
|
|
||||||
|
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.BINDING_ID;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||||
|
import org.openhab.core.events.EventPublisher;
|
||||||
import org.openhab.core.thing.Channel;
|
import org.openhab.core.thing.Channel;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingRegistry;
|
||||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||||
|
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||||
import org.openhab.core.types.StateDescription;
|
import org.openhab.core.types.StateDescription;
|
||||||
|
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||||
|
import org.openhab.core.types.StateOption;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides the list of valid inputs for the input channel of a source.
|
* This class provides the list of valid inputs for the input channel of a source.
|
||||||
|
@ -32,27 +44,39 @@ import org.openhab.core.types.StateDescription;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class ShellyStateDescriptionProvider extends BaseDynamicStateDescriptionProvider implements ThingHandlerService {
|
@Component(service = { DynamicStateDescriptionProvider.class, ShellyStateDescriptionProvider.class })
|
||||||
private @Nullable ShellyThingInterface handler;
|
public class ShellyStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||||
|
private final ThingRegistry thingRegistry;
|
||||||
|
|
||||||
@Override
|
@Activate
|
||||||
public void setThingHandler(ThingHandler handler) {
|
public ShellyStateDescriptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||||
this.handler = (ShellyThingInterface) handler;
|
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||||
}
|
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService,
|
||||||
|
@Reference ThingRegistry thingRegistry) {
|
||||||
@Override
|
this.eventPublisher = eventPublisher;
|
||||||
public @Nullable ThingHandler getThingHandler() {
|
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||||
return (ThingHandler) handler;
|
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||||
|
this.thingRegistry = thingRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("null")
|
@SuppressWarnings("null")
|
||||||
@Override
|
@Override
|
||||||
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
|
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||||
@Nullable Locale locale) {
|
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||||
ChannelTypeUID uid = channel.getChannelTypeUID();
|
ChannelTypeUID uid = channel.getChannelTypeUID();
|
||||||
if (uid != null && handler != null) {
|
if (uid == null || !BINDING_ID.equals(uid.getBindingId()) || originalStateDescription == null) {
|
||||||
setStateOptions(channel.getUID(), handler.getStateOptions(uid));
|
return null;
|
||||||
}
|
}
|
||||||
return super.getStateDescription(channel, original, locale);
|
|
||||||
|
Thing thing = thingRegistry.get(channel.getUID().getThingUID());
|
||||||
|
ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
|
||||||
|
if (handler == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<StateOption> stateOptions = handler.getStateOptions(uid);
|
||||||
|
return stateOptions == null ? null
|
||||||
|
: StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(stateOptions).build()
|
||||||
|
.toStateDescription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config-description:config-descriptions
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<config-description uri="thing-type:shelly:relay-gen2">
|
||||||
|
<parameter name="deviceIp" type="text" required="true">
|
||||||
|
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||||
|
<context>network-address</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="password" type="text" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.password.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.password.description</description>
|
||||||
|
<context>password</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
|
||||||
|
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||||
|
<default>60</default>
|
||||||
|
<unitLabel>seconds</unitLabel>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
<config-description uri="thing-type:shelly:roller-gen2">
|
||||||
|
<parameter name="deviceIp" type="text" required="true">
|
||||||
|
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||||
|
<context>network-address</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="password" type="text" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.password.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.password.description</description>
|
||||||
|
<context>password</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoriteUP" type="integer" min="0" max="4" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.roller.favoriteUP.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.roller.favoriteUP.description</description>
|
||||||
|
<default>0</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoriteDOWN" type="integer" min="0" max="4" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.roller.favoriteDOWN.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.roller.favoriteDOWN.description</description>
|
||||||
|
<default>0</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
|
||||||
|
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||||
|
<default>60</default>
|
||||||
|
<unitLabel>seconds</unitLabel>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
<config-description uri="thing-type:shelly:battery-gen2">
|
||||||
|
<parameter name="deviceIp" type="text" required="true">
|
||||||
|
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||||
|
<context>network-address</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="password" type="text" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.password.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.password.description</description>
|
||||||
|
<context>password</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="lowBattery" type="integer" required="false">
|
||||||
|
<label>@text/thing-type.config.shelly.battery.lowBattery.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.battery.lowBattery.description</description>
|
||||||
|
<default>20</default>
|
||||||
|
<unitLabel>%</unitLabel>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="updateInterval" type="integer" min="60" required="true" unit="s">
|
||||||
|
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||||
|
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||||
|
<default>900</default>
|
||||||
|
<unitLabel>seconds</unitLabel>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</config-description:config-descriptions>
|
|
@ -17,9 +17,10 @@ message.config-status.error.missing-userid = No user ID in the Thing configurati
|
||||||
# Thing status descriptions
|
# Thing status descriptions
|
||||||
message.offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
|
message.offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
|
||||||
message.offline.conf-error-access-denied = Access denied, check user id and password.
|
message.offline.conf-error-access-denied = Access denied, check user id and password.
|
||||||
message.offline.conf-error-wrong-mode = Device is no longer in the configured device mode {0}, required {1}. Delete the thing and re-discover the device.
|
message.offline.conf-error-wrong-mode = Device is in device mode {0}, thing requires {1}. Delete the thing and re-discover the device.
|
||||||
|
message.offline.status-error-connect = Unable to connect to device - {0}
|
||||||
message.offline.status-error-timeout = Device is not reachable (API timeout)
|
message.offline.status-error-timeout = Device is not reachable (API timeout)
|
||||||
message.offline.status-error-unexpected-error = Unexpected error
|
message.offline.status-error-unexpected-error = Unexpected error: {0}
|
||||||
message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
|
message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
|
||||||
message.offline.status-error-watchdog = Device is not responding, seems to be unavailable.
|
message.offline.status-error-watchdog = Device is not responding, seems to be unavailable.
|
||||||
message.offline.status-error-restarted = The device has restarted and will be re-initialized.
|
message.offline.status-error-restarted = The device has restarted and will be re-initialized.
|
||||||
|
@ -27,8 +28,8 @@ message.offline.status-error-fwupgrade = Firmware upgrade in progress
|
||||||
|
|
||||||
# General messages
|
# General messages
|
||||||
message.versioncheck.failed = Unable to check firmware version: {0}
|
message.versioncheck.failed = Unable to check firmware version: {0}
|
||||||
message.versioncheck.beta = Device is running a Beta version: {0}/{1}.
|
message.versioncheck.beta = Device is running a Beta version: {0}/{1}
|
||||||
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1}, required minimal {3}.
|
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1}, minimal required {2}
|
||||||
message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1}
|
message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1}
|
||||||
message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT
|
message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT
|
||||||
message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
|
message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
|
||||||
|
@ -76,7 +77,7 @@ thing-type.shelly.shellysmoke.description = Shelly Smoke Sensor (battery powered
|
||||||
thing-type.shelly.shellyflood.description = Shelly Flood Sensor (battery powered)
|
thing-type.shelly.shellyflood.description = Shelly Flood Sensor (battery powered)
|
||||||
thing-type.shelly.shellysense.description = Shelly Sense (Motion Sensor and Remote IR Controller)
|
thing-type.shelly.shellysense.description = Shelly Sense (Motion Sensor and Remote IR Controller)
|
||||||
thing-type.shelly.shellybulb.description = Shelly Bulb (Dimmable RGB+W Bulb)
|
thing-type.shelly.shellybulb.description = Shelly Bulb (Dimmable RGB+W Bulb)
|
||||||
thing-type.shelly.shellybulbduo.description = Shelly Duo (Dimmable WW/CW Bulb)
|
thing-type.shelly.shellybulbduo.description = Shelly Duo (Dimmable RGB+W/CW Bulb)
|
||||||
thing-type.shelly.shellycolorbulb.description = Shelly Color Bulb (Dimmable RGB+W Bulb)
|
thing-type.shelly.shellycolorbulb.description = Shelly Color Bulb (Dimmable RGB+W Bulb)
|
||||||
thing-type.shelly.shellyvintage.description = Shelly Vintage (Dimmable Vintage Bulb)
|
thing-type.shelly.shellyvintage.description = Shelly Vintage (Dimmable Vintage Bulb)
|
||||||
thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW2 (LED Controller in Color Mode)
|
thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW2 (LED Controller in Color Mode)
|
||||||
|
@ -85,6 +86,24 @@ thing-type.shelly.shellymotion.description = Shelly Motion (Motion Detection (ba
|
||||||
thing-type.shelly.shellygas.description = Shelly Gas (Gas Sensor - Gas Leak Detection)
|
thing-type.shelly.shellygas.description = Shelly Gas (Gas Sensor - Gas Leak Detection)
|
||||||
thing-type.shelly.shellytrv.description = Shelly TRV (Radiator value, battery powered)
|
thing-type.shelly.shellytrv.description = Shelly TRV (Radiator value, battery powered)
|
||||||
thing-type.shelly.shellyix3.description = Shelly ix3 (Activation Device with 3 inputs)
|
thing-type.shelly.shellyix3.description = Shelly ix3 (Activation Device with 3 inputs)
|
||||||
|
thing-type.shelly.shellypludht.description = Shelly Plus HT - Temperature and Humidity Sensor
|
||||||
|
|
||||||
|
# Plus/Pro devices
|
||||||
|
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||||
|
thing-type.shelly.shellyplus1pm.description = Shelly Plus 1PM - Single Relay Switch with Power Meter
|
||||||
|
thing-type.shelly.shellyplus2-relay.description = Shelly Plus 2PM - Dual Relay Switch with Power Meter
|
||||||
|
thing-type.shelly.shellyplus2pm-roller.description = Shelly Plus 2PM - Roller Control with Power Meter
|
||||||
|
thing-type.shelly.shellyplusht.description = Shelly Plus HT - Humidity and Temperature sensor with display
|
||||||
|
thing-type.shelly.shellyplusi4.description = Shelly Plus i4 - 4xInput Device
|
||||||
|
thing-type.shelly.shellyplusi4dc.description = Shelly Plus i4DC - 4xDC Input Device
|
||||||
|
thing-type.shelly.shellypro1.description = Shelly Pro 1 - Single Relay Switch
|
||||||
|
thing-type.shelly.shellypro1pm.description = Shelly Pro 1PM - Single Relay Switch with Power Meter
|
||||||
|
thing-type.shelly.shellypro2-relay.description = Shelly Pro 2 - Dual Relay Switch
|
||||||
|
thing-type.shelly.shellypro2pm-relay.description= Shelly Pro 2PM - Dual Relay Switch with Power Meter
|
||||||
|
thing-type.shelly.shellypro2pm-roller.description = Shelly Pro 2PM - Roller Control with Power Meter
|
||||||
|
thing-type.shelly.shellypro3.description = Shelly Pro 3 - 3xRelay Switch
|
||||||
|
thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter
|
||||||
|
|
||||||
|
|
||||||
# Plus/Pro devices
|
# Plus/Pro devices
|
||||||
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||||
|
@ -180,11 +199,11 @@ channel-group-type.shelly.relayChannel3.label = Relay 3
|
||||||
channel-group-type.shelly.relayChannel4.label = Relay 4
|
channel-group-type.shelly.relayChannel4.label = Relay 4
|
||||||
channel-group-type.shelly.dimmerChannel.label = Dimmer
|
channel-group-type.shelly.dimmerChannel.label = Dimmer
|
||||||
channel-group-type.shelly.dimmerChannel.description = A Shelly Dimmer channel
|
channel-group-type.shelly.dimmerChannel.description = A Shelly Dimmer channel
|
||||||
channel-group-type.shelly.ix3Channel1.label = Input 1
|
channel-group-type.shelly.iXChannel1.label = Input 1
|
||||||
channel-group-type.shelly.ix3Channel2.label = Input 2
|
channel-group-type.shelly.iXChannel2.label = Input 2
|
||||||
channel-group-type.shelly.ix3Channel3.label = Input 3
|
channel-group-type.shelly.iXChannel3.label = Input 3
|
||||||
channel-group-type.shelly.ix3Channel4.label = Input 4
|
channel-group-type.shelly.iXChannel4.label = Input 3
|
||||||
channel-group-type.shelly.ix3Channel.description = Input Status
|
channel-group-type.shelly.iXChannel.description = Input Status
|
||||||
channel-group-type.shelly.rollerControl.label = Roller Control
|
channel-group-type.shelly.rollerControl.label = Roller Control
|
||||||
channel-group-type.shelly.rollerControl.description = Controlling the roller mode
|
channel-group-type.shelly.rollerControl.description = Controlling the roller mode
|
||||||
channel-group-type.shelly.meter.label = Power Meter
|
channel-group-type.shelly.meter.label = Power Meter
|
||||||
|
@ -200,9 +219,9 @@ channel-group-type.shelly.externalSensors.description = Temperatures from extern
|
||||||
channel-type.shelly.outputName.label = Output Name
|
channel-type.shelly.outputName.label = Output Name
|
||||||
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App
|
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App
|
||||||
channel-type.shelly.timerAutoOn.label = Auto-ON Timer
|
channel-type.shelly.timerAutoOn.label = Auto-ON Timer
|
||||||
channel-type.shelly.timerAutoOn.description = When the output of the relay is switched off, it will be switched off automatically after n seconds
|
channel-type.shelly.timerAutoOn.description = When the output of the relay is switched off, it will be switched back on automatically after n seconds
|
||||||
channel-type.shelly.timerAutoOff.label = Auto-OFF Timer
|
channel-type.shelly.timerAutoOff.label = Auto-OFF Timer
|
||||||
channel-type.shelly.timerAutoOff.description = When the output of the relay is switched on, it will be switched on automatically after n seconds
|
channel-type.shelly.timerAutoOff.description = When the output of the relay is switched on, it will be switched back off automatically after n seconds
|
||||||
channel-type.shelly.timerActive.label = Auto ON/OFF timer active
|
channel-type.shelly.timerActive.label = Auto ON/OFF timer active
|
||||||
channel-type.shelly.timerActive.description = ON: A timer is active, OFF: no timer active
|
channel-type.shelly.timerActive.description = ON: A timer is active, OFF: no timer active
|
||||||
channel-type.shelly.temperature.label = Temperature
|
channel-type.shelly.temperature.label = Temperature
|
||||||
|
@ -398,7 +417,7 @@ channel-type.shelly.boostTimer.label = Boost Timer
|
||||||
channel-type.shelly.boostTimer.description = Number of minutes to boost maximum power
|
channel-type.shelly.boostTimer.description = Number of minutes to boost maximum power
|
||||||
channel-type.shelly.batVoltage.label = Battery Voltage
|
channel-type.shelly.batVoltage.label = Battery Voltage
|
||||||
channel-type.shelly.batVoltage.description = Battery voltage (V)
|
channel-type.shelly.batVoltage.description = Battery voltage (V)
|
||||||
channel-type.shelly.charger.label = Charger Connected
|
channel-type.shelly.charger.label = External Power
|
||||||
channel-type.shelly.charger.description = ON: External power is connected
|
channel-type.shelly.charger.description = ON: External power is connected
|
||||||
channel-type.shelly.calibrated.label = Calibrated
|
channel-type.shelly.calibrated.label = Calibrated
|
||||||
channel-type.shelly.calibrated.description = ON: The device/sensor is calibrated
|
channel-type.shelly.calibrated.description = ON: The device/sensor is calibrated
|
||||||
|
@ -429,7 +448,7 @@ channel-type.shelly.lastEvent.state.option.L = Long push
|
||||||
channel-type.shelly.lastEvent.state.option.SL = Short-Long push
|
channel-type.shelly.lastEvent.state.option.SL = Short-Long push
|
||||||
channel-type.shelly.lastEvent.state.option.LS = Long-Short push
|
channel-type.shelly.lastEvent.state.option.LS = Long-Short push
|
||||||
channel-type.shelly.eventCount.label = Event Count
|
channel-type.shelly.eventCount.label = Event Count
|
||||||
channel-type.shelly.eventCount.description = Event Count
|
channel-type.shelly.eventCount.description = Number of input events received from device
|
||||||
channel-type.shelly.eventTrigger.label = Event
|
channel-type.shelly.eventTrigger.label = Event
|
||||||
channel-type.shelly.eventTrigger.description = Event Trigger (ROLLER_OPEN/ROLLER_CLOSE/ROLLER_STOP)
|
channel-type.shelly.eventTrigger.description = Event Trigger (ROLLER_OPEN/ROLLER_CLOSE/ROLLER_STOP)
|
||||||
channel-type.shelly.eventTrigger.option.ROLLER_OPEN = Roller is open
|
channel-type.shelly.eventTrigger.option.ROLLER_OPEN = Roller is open
|
||||||
|
@ -506,6 +525,18 @@ message.manager.action.setwifirec-enable = The device performs an auto-restart i
|
||||||
message.manager.action.setwifirec-disable = WiFi Recovery Mode will be disabled.
|
message.manager.action.setwifirec-disable = WiFi Recovery Mode will be disabled.
|
||||||
message.manager.action.setwifirec-confirm = WiFi Recovery Mode has been {0}.
|
message.manager.action.setwifirec-confirm = WiFi Recovery Mode has been {0}.
|
||||||
message.manager.action.setwifirec-failed = Unable to update setting for WiFi Recovery Mode: {0}
|
message.manager.action.setwifirec-failed = Unable to update setting for WiFi Recovery Mode: {0}
|
||||||
|
message.manager.action.setwifirangeext-enable = WiFi Range Extender will be enabled.
|
||||||
|
message.manager.action.setwifirangeext-disable = WiFi Range Extender will be disabled.
|
||||||
|
message.manager.action.setwifirangeext-confirm = WiFiRange Extender has been set, restart required: {0}
|
||||||
|
message.manager.action.setwifirangeext-failed = Unable to update setting for WiFi Range Extender: {0}
|
||||||
|
message.manager.action.setethernet-enable = Ethernet interface will be enabled, this requires a device reboot.
|
||||||
|
message.manager.action.setethernet-disable = Ethernet interface will be disabled, this may require a device reboot.
|
||||||
|
message.manager.action.setethernet-confirm = Ethernet interface has been {0}.
|
||||||
|
message.manager.action.setethernet-failed = Unable to update setting for Ethernet interface: {0}
|
||||||
|
message.manager.action.setbluetooth-enable = Bluetooth interface will be enabled, this requires a device reboot.
|
||||||
|
message.manager.action.setbluetooth-disable = Bluetooth interface will be disabled, this may require a device reboot.
|
||||||
|
message.manager.action.setbluetooth-confirm = Bluetooth interface has been {0}.
|
||||||
|
message.manager.action.setbluetooth-failed = Unable to update setting for Bluetooth interface: {0}
|
||||||
message.manager.action.aproaming-enable = WiFi Access Point Roaming will be enabled. Check product documentation for details.
|
message.manager.action.aproaming-enable = WiFi Access Point Roaming will be enabled. Check product documentation for details.
|
||||||
message.manager.action.aproaming-disable = WiFi Access Point Roaming will be disabled.
|
message.manager.action.aproaming-disable = WiFi Access Point Roaming will be disabled.
|
||||||
message.manager.action.aproaming-confirm = Unable to update setting WiFi Access Point Roaming: {0}
|
message.manager.action.aproaming-confirm = Unable to update setting WiFi Access Point Roaming: {0}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
<thing-type id="shellybulb">
|
<thing-type id="shellybulb">
|
||||||
<label>Shelly Bulb (SHBLB-1)</label>
|
<label>Shelly Bulb</label>
|
||||||
<description>@text/thing-type.shelly.shellybulb.description</description>
|
<description>@text/thing-type.shelly.shellybulb.description</description>
|
||||||
<category>Lightbulb</category>
|
<category>Lightbulb</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellybulbduo">
|
<thing-type id="shellybulbduo">
|
||||||
<label>Shelly Duo (SHBDUO-1)</label>
|
<label>Shelly Duo</label>
|
||||||
<description>@text/thing-type.shelly.shellybulbduo.description</description>
|
<description>@text/thing-type.shelly.shellybulbduo.description</description>
|
||||||
<category>Lightbulb</category>
|
<category>Lightbulb</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellycolorbulb">
|
<thing-type id="shellycolorbulb">
|
||||||
<label>Shelly Color Bulb (SHCB-1)</label>
|
<label>Shelly Color Bulb</label>
|
||||||
<description>@text/thing-type.shelly.shellycolorbulb.description</description>
|
<description>@text/thing-type.shelly.shellycolorbulb.description</description>
|
||||||
<category>ColorLight</category>
|
<category>ColorLight</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyvintage">
|
<thing-type id="shellyvintage">
|
||||||
<label>Shelly Vintage (SHVIN-1)</label>
|
<label>Shelly Vintage</label>
|
||||||
<description>@text/thing-type.shelly.shellyvintage.description</description>
|
<description>@text/thing-type.shelly.shellyvintage.description</description>
|
||||||
<category>Lightbulb</category>
|
<category>Lightbulb</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyrgbw2-color">
|
<thing-type id="shellyrgbw2-color">
|
||||||
<label>Shelly RGBW2 Color Mode (SHRGBW2)</label>
|
<label>Shelly RGBW2 Color Mode</label>
|
||||||
<description>@text/thing-type.shelly.shellyrgbw2-color.description</description>
|
<description>@text/thing-type.shelly.shellyrgbw2-color.description</description>
|
||||||
<category>ColorLight</category>
|
<category>ColorLight</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyrgbw2-white">
|
<thing-type id="shellyrgbw2-white">
|
||||||
<label>Shelly RGBW2 White Mode (SHRGBW2)</label>
|
<label>Shelly RGBW2 White Mode</label>
|
||||||
<description>@text/thing-type.shelly.shellyrgbw2-white.description</description>
|
<description>@text/thing-type.shelly.shellyrgbw2-white.description</description>
|
||||||
<category>Lightbulb</category>
|
<category>Lightbulb</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
<thing-type id="shelly1">
|
<thing-type id="shelly1">
|
||||||
<label>Shelly 1 (SHSW-1)</label>
|
<label>Shelly 1</label>
|
||||||
<description>@text/thing-type.shelly.shelly1.description</description>
|
<description>@text/thing-type.shelly.shelly1.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="relay" typeId="relayChannel"/>
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly1l">
|
<thing-type id="shelly1l">
|
||||||
<label>Shelly 1L (SHSW-L)</label>
|
<label>Shelly 1L</label>
|
||||||
<description>@text/thing-type.shelly.shelly1l.description</description>
|
<description>@text/thing-type.shelly.shelly1l.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="relay" typeId="relayChannel"/>
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly1pm">
|
<thing-type id="shelly1pm">
|
||||||
<label>Shelly 1PM (SHSW-PM)</label>
|
<label>Shelly 1PM</label>
|
||||||
<description>@text/thing-type.shelly.shelly1pm.description</description>
|
<description>@text/thing-type.shelly.shelly1pm.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="relay" typeId="relayChannel"/>
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyem">
|
<thing-type id="shellyem">
|
||||||
<label>Shelly EM (SHEM)</label>
|
<label>Shelly EM</label>
|
||||||
<description>@text/thing-type.shelly.shellyem.description</description>
|
<description>@text/thing-type.shelly.shellyem.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="meter1" typeId="meter">
|
<channel-group id="meter1" typeId="meter">
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
|
|
||||||
<!-- Shelly 3EM - device reports wrong service name: shellyem3, see README -->
|
<!-- Shelly 3EM - device reports wrong service name: shellyem3, see README -->
|
||||||
<thing-type id="shellyem3">
|
<thing-type id="shellyem3">
|
||||||
<label>Shelly 3EM (SHEM-3)</label>
|
<label>Shelly EM3</label>
|
||||||
<description>@text/thing-type.shelly.shellyem3.description</description>
|
<description>@text/thing-type.shelly.shellyem3.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="meter1" typeId="meter">
|
<channel-group id="meter1" typeId="meter">
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly2-relay">
|
<thing-type id="shelly2-relay">
|
||||||
<label>Shelly 2 Relay (SHSW-21)</label>
|
<label>Shelly 2 Relay</label>
|
||||||
<description>@text/thing-type.shelly.shelly2-relay.description</description>
|
<description>@text/thing-type.shelly.shelly2-relay.description</description>
|
||||||
|
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -104,22 +104,8 @@
|
||||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly2-roller">
|
|
||||||
<label>Shelly 2 Roller (SHSW-21)</label>
|
|
||||||
<description>@text/thing-type.shelly.shelly2-roller.description</description>
|
|
||||||
<category>Rollershutter</category>
|
|
||||||
<channel-groups>
|
|
||||||
<channel-group id="roller" typeId="rollerControl"/>
|
|
||||||
<channel-group id="meter" typeId="meter"/>
|
|
||||||
<channel-group id="device" typeId="deviceStatus"/>
|
|
||||||
</channel-groups>
|
|
||||||
|
|
||||||
<representation-property>serviceName</representation-property>
|
|
||||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
|
||||||
</thing-type>
|
|
||||||
|
|
||||||
<thing-type id="shelly25-relay">
|
<thing-type id="shelly25-relay">
|
||||||
<label>Shelly 2.5 Relay (SHSW-25)</label>
|
<label>Shelly 2.5 Relay</label>
|
||||||
<description>@text/thing-type.shelly.shelly25-relay.description</description>
|
<description>@text/thing-type.shelly.shelly25-relay.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="relay1" typeId="relayChannel">
|
<channel-group id="relay1" typeId="relayChannel">
|
||||||
|
@ -142,7 +128,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly25-roller">
|
<thing-type id="shelly25-roller">
|
||||||
<label>Shelly 2.5 Roller (SHSW-25)</label>
|
<label>Shelly 2.5 Roller</label>
|
||||||
<description>@text/thing-type.shelly.shelly25-roller.description</description>
|
<description>@text/thing-type.shelly.shelly25-roller.description</description>
|
||||||
<category>Rollershutter</category>
|
<category>Rollershutter</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -156,7 +142,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shelly4pro">
|
<thing-type id="shelly4pro">
|
||||||
<label>Shelly 4 Pro Relay (SHSW-44)</label>
|
<label>Shelly 4 Pro Relay</label>
|
||||||
<description>@text/thing-type.shelly.shelly4pro.description</description>
|
<description>@text/thing-type.shelly.shelly4pro.description</description>
|
||||||
|
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -192,7 +178,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyplug">
|
<thing-type id="shellyplug">
|
||||||
<label>Shelly Plug (SHPLG-1)</label>
|
<label>Shelly Plug</label>
|
||||||
<description>@text/thing-type.shelly.shellyplug.description</description>
|
<description>@text/thing-type.shelly.shellyplug.description</description>
|
||||||
<category>PowerOutlet</category>
|
<category>PowerOutlet</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -206,7 +192,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyplugs">
|
<thing-type id="shellyplugs">
|
||||||
<label>Shelly Plug-S (SHPLG-S)</label>
|
<label>Shelly Plug-S</label>
|
||||||
<description>@text/thing-type.shelly.shellyplugs.description</description>
|
<description>@text/thing-type.shelly.shellyplugs.description</description>
|
||||||
<category>PowerOutlet</category>
|
<category>PowerOutlet</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -220,7 +206,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyplugu1">
|
<thing-type id="shellyplugu1">
|
||||||
<label>Shelly Plug US (SHPLG-U1)</label>
|
<label>Shelly Plug US</label>
|
||||||
<description>@text/thing-type.shelly.shellyplugu1.description</description>
|
<description>@text/thing-type.shelly.shellyplugu1.description</description>
|
||||||
<category>PowerOutlet</category>
|
<category>PowerOutlet</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -234,7 +220,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyuni">
|
<thing-type id="shellyuni">
|
||||||
<label>Shelly UNI (SHUNI-1)</label>
|
<label>Shelly UNI</label>
|
||||||
<description>@text/thing-type.shelly.shellyuni.description</description>
|
<description>@text/thing-type.shelly.shellyuni.description</description>
|
||||||
|
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -253,7 +239,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellydimmer">
|
<thing-type id="shellydimmer">
|
||||||
<label>Shelly Dimmer (SHDM-1)</label>
|
<label>Shelly Dimmer</label>
|
||||||
<description>@text/thing-type.shelly.shellydimmer.description</description>
|
<description>@text/thing-type.shelly.shellydimmer.description</description>
|
||||||
<category>DimmableLight</category>
|
<category>DimmableLight</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -267,7 +253,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellydimmer2">
|
<thing-type id="shellydimmer2">
|
||||||
<label>Shelly Dimmer 2 (SHDM-2)</label>
|
<label>Shelly Dimmer 2</label>
|
||||||
<description>@text/thing-type.shelly.shellydimmer2.description</description>
|
<description>@text/thing-type.shelly.shellydimmer2.description</description>
|
||||||
<category>DimmableLight</category>
|
<category>DimmableLight</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -281,17 +267,17 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyix3">
|
<thing-type id="shellyix3">
|
||||||
<label>Shelly ix3 (SHIX3-1)</label>
|
<label>Shelly ix3</label>
|
||||||
<description>@text/thing-type.shelly.shellyix3.description</description>
|
<description>@text/thing-type.shelly.shellyix3.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="status1" typeId="ix3Channel">
|
<channel-group id="status1" typeId="ixChannel">
|
||||||
<label>@text/channel-group-type.shelly.ix3Channel1.label</label>
|
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||||
</channel-group>
|
</channel-group>
|
||||||
<channel-group id="status2" typeId="ix3Channel">
|
<channel-group id="status2" typeId="ixChannel">
|
||||||
<label>@text/channel-group-type.shelly.ix3Channel2.label</label>
|
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||||
</channel-group>
|
</channel-group>
|
||||||
<channel-group id="status3" typeId="ix3Channel">
|
<channel-group id="status3" typeId="ixChannel">
|
||||||
<label>@text/channel-group-type.shelly.ix3Channel3.label</label>
|
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||||
</channel-group>
|
</channel-group>
|
||||||
<channel-group id="device" typeId="deviceStatus"/>
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
</channel-groups>
|
</channel-groups>
|
||||||
|
@ -315,9 +301,9 @@
|
||||||
<description>@text/channel-group-type.shelly.dimmerChannel.description</description>
|
<description>@text/channel-group-type.shelly.dimmerChannel.description</description>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
<channel-group-type id="ix3Channel">
|
<channel-group-type id="ixChannel">
|
||||||
<label>@text/channel-group-type.shelly.ix3Channel.label</label>
|
<label>@text/channel-group-type.shelly.ixChannel.label</label>
|
||||||
<description>@text/channel-group-type.shelly.ix3Channel.description</description>
|
<description>@text/channel-group-type.shelly.ixChannel.description</description>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
<channel-group-type id="rollerControl">
|
<channel-group-type id="rollerControl">
|
||||||
|
@ -576,7 +562,7 @@
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>@text/channel-type.shelly.meterPowerFactor.label</label>
|
<label>@text/channel-type.shelly.meterPowerFactor.label</label>
|
||||||
<description>@text/channel-type.shelly.meterPowerFactor.description</description>
|
<description>@text/channel-type.shelly.meterPowerFactor.description</description>
|
||||||
<state readOnly="true" pattern="%.3f %unit%">
|
<state readOnly="true" pattern="%.3f">
|
||||||
</state>
|
</state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
<thing-type id="shellyht">
|
<thing-type id="shellyht">
|
||||||
<label>Shelly H&T (SHHT-1)</label>
|
<label>Shelly H&T</label>
|
||||||
<description>@text/thing-type.shelly.shellyht.description</description>
|
<description>@text/thing-type.shelly.shellyht.description</description>
|
||||||
<category>Sensor</category>
|
<category>Sensor</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellygas">
|
<thing-type id="shellygas">
|
||||||
<label>Shelly GAS (SHGS-1)</label>
|
<label>Shelly Gas</label>
|
||||||
<description>@text/thing-type.shelly.shellygas.description</description>
|
<description>@text/thing-type.shelly.shellygas.description</description>
|
||||||
<category>Sensor</category>
|
<category>Sensor</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellyflood">
|
<thing-type id="shellyflood">
|
||||||
<label>Shelly Flood (SHWT-1)</label>
|
<label>Shelly Flood</label>
|
||||||
<description>@text/thing-type.shelly.shellyflood.description</description>
|
<description>@text/thing-type.shelly.shellyflood.description</description>
|
||||||
<category>Sensor</category>
|
<category>Sensor</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellydw">
|
<thing-type id="shellydw">
|
||||||
<label>Shelly Door/Window (SHDW-1)</label>
|
<label>Shelly Door/Window</label>
|
||||||
<description>@text/thing-type.shelly.shellydw.description</description>
|
<description>@text/thing-type.shelly.shellydw.description</description>
|
||||||
<category>Sensor</category>
|
<category>Sensor</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellydw2">
|
<thing-type id="shellydw2">
|
||||||
<label>Shelly Door/Window (SHDW-2)</label>
|
<label>Shelly Door/Window</label>
|
||||||
<description>@text/thing-type.shelly.shellydw2.description</description>
|
<description>@text/thing-type.shelly.shellydw2.description</description>
|
||||||
<category>Sensor</category>
|
<category>Sensor</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellysense">
|
<thing-type id="shellysense">
|
||||||
<label>Shelly Sense (SHSEN-1)</label>
|
<label>Shelly Sense</label>
|
||||||
<description>@text/thing-type.shelly.shellysense.description</description>
|
<description>@text/thing-type.shelly.shellysense.description</description>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
<channel-group id="control" typeId="control"/>
|
<channel-group id="control" typeId="control"/>
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellybutton1">
|
<thing-type id="shellybutton1">
|
||||||
<label>Shelly Button 1 (SHBTN-1)</label>
|
<label>Shelly Button 1</label>
|
||||||
<description>@text/thing-type.shelly.shellybutton1.description</description>
|
<description>@text/thing-type.shelly.shellybutton1.description</description>
|
||||||
<category>WallSwitch</category>
|
<category>WallSwitch</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellybutton2">
|
<thing-type id="shellybutton2">
|
||||||
<label>Shelly Button 2 (SHBTN-2)</label>
|
<label>Shelly Button 2</label>
|
||||||
<description>@text/thing-type.shelly.shellybutton2.description</description>
|
<description>@text/thing-type.shelly.shellybutton2.description</description>
|
||||||
<category>WallSwitch</category>
|
<category>WallSwitch</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellymotion">
|
<thing-type id="shellymotion">
|
||||||
<label>Shelly Motion (SHMOS-01/SHMOS-02)</label>
|
<label>Shelly Motion</label>
|
||||||
<description>@text/thing-type.shelly.shellymotion.description</description>
|
<description>@text/thing-type.shelly.shellymotion.description</description>
|
||||||
<category>MotionDetector</category>
|
<category>MotionDetector</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<thing-type id="shellytrv">
|
<thing-type id="shellytrv">
|
||||||
<label>Shelly TRV (SHTRV-01)</label>
|
<label>Shelly TRV</label>
|
||||||
<description>@text/thing-type.shelly.shellytrv.description</description>
|
<description>@text/thing-type.shelly.shellytrv.description</description>
|
||||||
<category>RadiatorControl</category>
|
<category>RadiatorControl</category>
|
||||||
<channel-groups>
|
<channel-groups>
|
||||||
|
@ -210,7 +210,6 @@
|
||||||
<state readOnly="false"></state>
|
<state readOnly="false"></state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
|
||||||
<channel-group-type id="sensorData">
|
<channel-group-type id="sensorData">
|
||||||
<label>@text/channel-group-type.shelly.sensorData.label</label>
|
<label>@text/channel-group-type.shelly.sensorData.label</label>
|
||||||
<description>@text/channel-group-type.shelly.sensorData.description</description>
|
<description>@text/channel-group-type.shelly.sensorData.description</description>
|
||||||
|
@ -457,7 +456,7 @@
|
||||||
<item-type>Number:ElectricPotential</item-type>
|
<item-type>Number:ElectricPotential</item-type>
|
||||||
<label>@text/channel-type.shelly.sensorADC.label</label>
|
<label>@text/channel-type.shelly.sensorADC.label</label>
|
||||||
<description>@text/channel-type.shelly.sensorADC.description</description>
|
<description>@text/channel-type.shelly.sensorADC.description</description>
|
||||||
<state readOnly="true" pattern="%.0f %unit%">
|
<state readOnly="true" pattern="%.3f %unit%">
|
||||||
</state>
|
</state>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="shelly"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<thing-type id="shellyplus1">
|
||||||
|
<label>ShellyPlus 1</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplus1.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
<channel-group id="sensors" typeId="externalSensors"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellyplus1pm">
|
||||||
|
<label>ShellyPlus 1PM</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplus1pm.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
<channel-group id="meter" typeId="meter"/>
|
||||||
|
<channel-group id="sensors" typeId="externalSensors"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellyplus2pm-relay">
|
||||||
|
<label>ShellyPlus 2 Relay</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplus2-relay.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay1" typeId="relayChannel"/>
|
||||||
|
<channel-group id="meter1" typeId="meter"/>
|
||||||
|
<channel-group id="relay2" typeId="relayChannel"/>
|
||||||
|
<channel-group id="meter2" typeId="meter"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellyplus2pm-roller">
|
||||||
|
<label>ShellyPlus 2PM Roller</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplus2pm-roller.description</description>
|
||||||
|
<category>Rollershutter</category>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="roller" typeId="rollerControl"/>
|
||||||
|
<channel-group id="meter" typeId="meter"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:roller-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
|
||||||
|
<thing-type id="shellyplusi4">
|
||||||
|
<label>ShellyPlus i4</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplusi4.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="status1" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status2" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status3" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status4" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel4.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellyplusi4dc">
|
||||||
|
<label>ShellyPlus i4DC</label>
|
||||||
|
<description>@text/thing-type.shelly.shellyplusi4dc.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="status1" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status2" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status3" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="status4" typeId="ixChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.ixChannel4.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
|
||||||
|
<thing-type id="shellypro1">
|
||||||
|
<label>ShellyPro 1</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro1.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
<channel-group id="sensors" typeId="externalSensors"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellypro1pm">
|
||||||
|
<label>ShellyPro 1PM</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro1pm.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay" typeId="relayChannel"/>
|
||||||
|
<channel-group id="meter" typeId="meter"/>
|
||||||
|
<channel-group id="sensors" typeId="externalSensors"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellypro2-relay">
|
||||||
|
<label>ShellyPro 2 Relay</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro2-relay.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay1" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter1" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay2" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter2" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellypro2pm-relay">
|
||||||
|
<label>ShellyPro 2PM Relay</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro2pm-relay.description</description>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay1" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter1" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay2" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter2" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellypro2pm-roller">
|
||||||
|
<label>ShellyPro 2PM Roller</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro2pm-roller.description</description>
|
||||||
|
<category>Rollershutter</category>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="roller" typeId="rollerControl"/>
|
||||||
|
<channel-group id="meter" typeId="meter"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:roller-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<thing-type id="shellypro3">
|
||||||
|
<label>ShellyPro 3</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro3.description</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay1" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay2" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay3" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel3.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay4" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel4.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
|
||||||
|
<thing-type id="shellypro4pm">
|
||||||
|
<label>ShellyPro 4PM</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypro4pm.description</description>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="relay1" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter1" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay2" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter2" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter2.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay3" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel3.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter3" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter3.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="relay4" typeId="relayChannel">
|
||||||
|
<label>@text/channel-group-type.shelly.relayChannel4.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="meter4" typeId="meter">
|
||||||
|
<label>@text/channel-group-type.shelly.meter4.label</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="shelly"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<thing-type id="shellyplusht">
|
||||||
|
<label>ShellyPlus H&T</label>
|
||||||
|
<description>@text/thing-type.shelly.shellypludht.description</description>
|
||||||
|
<category>Sensor</category>
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="sensors" typeId="sensorData"/>
|
||||||
|
<channel-group id="battery" typeId="batteryStatus"/>
|
||||||
|
<channel-group id="device" typeId="deviceStatus"/>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serviceName</representation-property>
|
||||||
|
<config-description-ref uri="thing-type:shelly:battery-gen2"/>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
|
@ -101,6 +101,6 @@
|
||||||
<td align="right">${deviceRestarts}</td>
|
<td align="right">${deviceRestarts}</td>
|
||||||
<td align="right">${timeoutErrors}</td>
|
<td align="right">${timeoutErrors}</td>
|
||||||
<td align="right">${timeoutsRecovered}</td>
|
<td align="right">${timeoutsRecovered}</td>
|
||||||
<td align="right" title="CoIoT Status: ${coiotStatus}">${coiotMessages}</td>
|
<td align="right" title="CoIOT Status: ${coiotStatus}">${protocolMessages}</td>
|
||||||
<td align="right">${coiotErrors}</td>
|
<td align="right">${protocolErrors}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -55,6 +55,6 @@
|
||||||
<th>Device Restarts</th>
|
<th>Device Restarts</th>
|
||||||
<th>Timeout Errors</th>
|
<th>Timeout Errors</th>
|
||||||
<th>Timeouts Recovered</th>
|
<th>Timeouts Recovered</th>
|
||||||
<th>CoIOT Messages</th>
|
<th>Protocol Messages</th>
|
||||||
<th>CoIOT Errors</th>
|
<th>Protocol Errors</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
Loading…
Reference in New Issue