added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.shelly/.classpath
Normal file
32
bundles/org.openhab.binding.shelly/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.shelly/.project
Normal file
23
bundles/org.openhab.binding.shelly/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.shelly</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
13
bundles/org.openhab.binding.shelly/NOTICE
Normal file
13
bundles/org.openhab.binding.shelly/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
887
bundles/org.openhab.binding.shelly/README.md
Normal file
887
bundles/org.openhab.binding.shelly/README.md
Normal file
@@ -0,0 +1,887 @@
|
||||
|
||||
# Shelly Binding
|
||||
|
||||
This Binding integrates [Alterco Shelly devices](https://shelly.cloud).
|
||||
|
||||
Alterco provides a rich set of smart home devices. All of them are WiFi enabled (2,4GHz, IPv4 only) and provide a documented API.
|
||||
The binding is officially acknowledged by Alterco and openHAB is listed as a reference.
|
||||
Alterco directly supports this project.
|
||||
|
||||
The binding controls the devices independently from the Alterco Shelly Cloud (in fact it can be disabled).
|
||||
The binding co-exists with Shelly App for Smartphones, Shelly Web App, Shelly Cloud, mqqt and other 3rd party Apps.
|
||||
|
||||
The binding focuses on reporting the device status and device control.
|
||||
Initial setup and device configuration has to be performed using the Shelly Apps.
|
||||
The binding gets in sync with the next status refresh.
|
||||
|
||||
## Supported Devices
|
||||
|
||||
| Thing Type | Model | Vendor ID |
|
||||
|--------------------|--------------------------------------------------------|-----------|
|
||||
| shelly1 | Shelly Single Relay Switch | SHSW-1 |
|
||||
| shelly1pm | Shelly Single Relay Switch with integrated Power Meter | SHSW-PM |
|
||||
| shelly2-relay | Shelly Double Relay Switch in relay mode | SHSW-21 |
|
||||
| shelly2-roller | Shelly2 in Roller Mode | SHSW-21 |
|
||||
| shelly25-relay | Shelly 2.5 in Relay Switch | SHSW-25 |
|
||||
| shelly25-roller | Shelly 2.5 in Roller Mode | SHSW-25 |
|
||||
| shelly4pro | Shelly 4x Relay Switch | SHSW-44 |
|
||||
| shellydimmer | Shelly Dimmer | SHDM-1 |
|
||||
| shellydimmer2 | Shelly Dimmer2 | SHDM-2 |
|
||||
| shellyix3 | Shelly ix3 | SHIX3-1 |
|
||||
| shellyplug | Shelly Plug | SHPLG2-1 |
|
||||
| shellyplugs | Shelly Plug-S | SHPLG-S |
|
||||
| shellyem | Shelly EM with integrated Power Meters | SHEM |
|
||||
| shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 |
|
||||
| shellyrgbw2 | Shelly RGB Controller | SHRGBW2 |
|
||||
| shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 |
|
||||
| shellybulbduo | Shelly Duo (White Mode) | SHBDUO-1 |
|
||||
| shellyvintage | Shelly Vintage (White Mode) | SHVIN-1 |
|
||||
| shellyht | Shelly Sensor (temp+humidity) | SHHT-1 |
|
||||
| shellyflood | Shelly Flood Sensor | SHWT-1 |
|
||||
| shellysmoke | Shelly Smoke Sensor | |
|
||||
| shellygas | Shelly Gas Sensor | SHGS-1 |
|
||||
| shellydw | Shelly Door/Window | SHDW-1 |
|
||||
| shellydw2 | Shelly Door/Window 2 | SHDW-2 |
|
||||
| shellybutton1 | Shelly Button 1 | SHBTN-1 |
|
||||
| shellysense | Shelly Motion and IR Controller | SHSEN-1 |
|
||||
| shellydevice | A password protected Shelly device or an unknown type | |
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has the following configuration options:
|
||||
|
||||
| Parameter |Description |Mandatory|Default |
|
||||
|----------------|------------------------------------------------------------------|---------|------------------------------------------------|
|
||||
| defaultUserId |Default user id for HTTP authentication when not set in the Thing | no |admin |
|
||||
| defaultPassword|Default password for HTTP authentication when not set in the Thing| no |admin |
|
||||
| autoCoIoT |Auto-enable CoIoT events when firmware 1.6+ is enabled. | no |true |
|
||||
|
||||
The binding defaults to CoIoT events when firmware 1.6 or newer is detected. CoIoT provides near-realtime updates on device status changes.
|
||||
This mode also overrules event settings in the thing configuration.
|
||||
|
||||
Disabling this feature allows granular control, which event types will be used. This is also required when the Shelly devices are not located on the same IP subnet (e.g. using a VPN).
|
||||
In this case autoCoIoT should be disabled, CoIoT events will not work, because the underlying CoAP protocol is based on Multicast IP, which usually doesn't passes a VPN or routed network.
|
||||
|
||||
## Discovery
|
||||
|
||||
In general devices need to be powered to be discovered by the binding.
|
||||
The binding uses mDNS to discover the Shelly devices.
|
||||
They periodically announce their presence, which is used by the binding to find them on the local network.
|
||||
|
||||
Sometimes you need to run the manual discovery multiple times until you see all your devices.
|
||||
|
||||
## Firmware
|
||||
|
||||
The binding requires firmware version 1.7.0 or newer to enable all features.
|
||||
Some of the features are enabled dynamically or are not available depending on device type and firmware release.
|
||||
The Web UI of the Shelly device displays the current firmware version under Settings:Firmware and shows an update option when a newer version is available.
|
||||
|
||||
|Version|Notes |
|
||||
|-------|--------------------------------------------------------------------------------------------------|
|
||||
|1.5.7 |Minimum supported version. Older versions work in general, but have impacts to functionality (e.g. no events for battery powered devices). The binding displays a WARNING in the log if the firmware is older.|
|
||||
|1.6.x |First stable CoIoT implementation. AutoCoIoT is enabled when firmware version >= 1.6 is detected. |
|
||||
|1.7.x |Add additional status update values, fixes various issues |
|
||||
|1.8.0 |Brings CoIoT version 2, which fixes a lot issues and gaps of version 1. |
|
||||
|
||||
The current firmware version is reported in the Thing Properties.
|
||||
A dedicated channel (device#updateAvailable) indicates the availability of a newer firmware. Use the device's Web UI or the Shelly App to perform the update.
|
||||
|
||||
Once you have updated the device **you should delete and re-discover** the openHAB Thing.
|
||||
Battery powered devices need to wake up by pressing the button.
|
||||
This makes sure that the Thing is correctly initialized and all supported channels are created. openHAB will kill the item linkage.
|
||||
At a minimum you should restart the binding on the openHAB console if you don't want to re-discover the things.
|
||||
|
||||
### Password Protected Devices
|
||||
|
||||
The Shelly devices can be configured to require authorization through a user id and password.
|
||||
In this case you need to set these values in the Thing configuration after approving the Inbox entry.
|
||||
|
||||
If you have multiple devices protected by the same credentials it's recommended to set the default user id and password in the binding configuration BEFORE running the discovery.
|
||||
In this case the binding could directly access the device to retrieve the required information using those credentials.
|
||||
Otherwise a thing of type shellyprotected is created in the Inbox and you could set the credentials while adding the thing. In this case the credentials are persisted as part of the thing configuration.
|
||||
|
||||
### Important for battery powered devices
|
||||
|
||||
Make sure to wake up battery powered devices, so that they show up on the network.
|
||||
The device has a push button inside. Open the case, press that button and the LED starts flashing.
|
||||
Wait a moment and then start the discovery. The device should show up in the Inbox and can be added.
|
||||
Sometimes you need to run the discovery multiple times.
|
||||
|
||||
### Dynamic creation of channels
|
||||
|
||||
The Shelly series of devices has many combinations of relays, meters (different versions), sensors etc. For this the binding creates various channels dynamically based on the status information provided by the device at initialization time.
|
||||
If a channel is missing make sure the thing was discovered correctly and is ONLINE. If a channel is missing delete the thing and re-discover it.
|
||||
|
||||
### Thing Status
|
||||
|
||||
The binding sets the following thing status depending on the device status:
|
||||
|
||||
| Status |Description |
|
||||
|--------------|------------------------------------------------------------------|
|
||||
| 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.|
|
||||
| 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 stays. |
|
||||
|
||||
`Battery powered devices:`
|
||||
If the device is in sleep mode and can't be reached by the binding, the thing will change into UNKNOWN state.
|
||||
Once the device wakes up, the thing will perform initialization and the state will change to ONLINE.
|
||||
|
||||
The first time a device is discovered and initialized successfully, the binding will be able to perform auto-initialization when OH is restarted. Waking up the device triggers the event URL and/or CoIoT packet, which is processed by the binding and triggers initialization. Once a device is initialized, it is no longer necessary to manually wake it up after an openHAB restart unless you change the battery. In this case press the button and run the discovery again.
|
||||
|
||||
### Device Watchdog
|
||||
|
||||
The binding supervises availability of the device once it becomes ONLINE by sending periodic status requests to the device. The watchdog is restarted when the device is responding properly.
|
||||
|
||||
Communication errors are handled depending on the device type:
|
||||
|
||||
- regular power devices change to OFFLINE, because this status indicates an error
|
||||
- battery powered devices stay ONLINE, because usually the device is in sleep mode
|
||||
|
||||
The binding also monitors that the device is responding at least once within a given time period.
|
||||
The period is computed depending on the device type and configuration:
|
||||
|
||||
- battery powered devices: <sleepPeriod from device config> + 10min, usually 12h+10min=730min
|
||||
- else, if CoIoT is enabled: 3*<update Period from device settings>+10sec, usually3*15+10=45sec
|
||||
- else 2*60+10sec = 130sec
|
||||
|
||||
Once the timer expires the device switches to OFFFLINE and the bindings starts to re-initialize the device periodically.
|
||||
|
||||
You could also create a rule to catch those status changes or device alarms (see rule examples).
|
||||
|
||||
## Trouble Shooting
|
||||
|
||||
### Network Settings
|
||||
|
||||
Shelly devices do only support IPv4.
|
||||
This implies that the openHAB host system has IPv4 bound to the network interface.
|
||||
The binding is only able to discover devices on the local subnet.
|
||||
Add things manually with the given IP if you have a routed network in between or using a VPN connection.
|
||||
|
||||
The binding enables CoIoT protocol by default if the device is running firmware 1.6 or newer.
|
||||
CoIoT is based on CoAP and uses a UDP based signaling using IP Multicast (224.0.1.187, port 5683).
|
||||
Again if the device is not on the same local IP subnet you need special router/switch configurations to utilized CoAP via IP Multicast.
|
||||
Otherwise disable the Auto-CoIoT feature in the binding config (not the thing config), disable CoIoT events in the thing configuration and enable sensors events (http callback).
|
||||
Nevertheless in this setup the binding can communicate the device, but you are loosing the benefits of CoIoT.
|
||||
|
||||
Refer to openHAB's general documentation when running openHAB in a docker container. Enabling mDNS discovery has additional setup requirements.
|
||||
|
||||
### Re-discover when IP address has changed
|
||||
|
||||
Important: The IP address should not be changed after the device is added to openHAB.
|
||||
|
||||
This can be achieved by
|
||||
|
||||
- assigning a static IP address (recommended for battery powered devices) or
|
||||
- using DHCP and setup the router to always assign the same IP address to the device
|
||||
|
||||
When the IP address changes for a device you need to delete the Thing and then re-discover the device.
|
||||
In this case channel linkage gets lost and you need to re-link the channels/items.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
|Parameter |Description |Mandatory|Default |
|
||||
|------------------|--------------------------------------------------------------|---------|--------------------------------------------------|
|
||||
|deviceIp |IP address of the Shelly device | yes |none |
|
||||
|userId |The user id used for HTTP authentication | no |none |
|
||||
|password |Password for HTTP authentication* | no |none |
|
||||
|lowBattery |Threshold for battery level. Set alert when level is below. | no |20 (=20%), only for battery powered devices |
|
||||
|updateInterval |Interval for the background status check in seconds. | no |1h for battery powered devices, 60s for all others|
|
||||
|eventsButton |true: register event "trigger when a button is pushed" | no |false |
|
||||
|eventsPush |true: register event "trigger on short and long push" | no |false |
|
||||
|eventsSwitch |true: register event "trigger of switching the relay output" | no |true |
|
||||
|eventsSensorReport|true: register event "posted updated sensor data" | no |true for sensor devices |
|
||||
|eventsCoIoT |true: Listen for CoIoT/COAP events | no |true for battery devices, false for others |
|
||||
|
||||
|
||||
### General Notes
|
||||
|
||||
- channels `input` and `input1`/`input2` get only updated with firmware 1.5.6+.
|
||||
- channel button: Short push and long push events require firmware version 1.5.6+.
|
||||
- Use the channel `rollerpos` only if you need the inverted roller position, otherwise use the `control` channel with item type `Number`
|
||||
- The different devices have different types of power meters, which are mapped in different sets of channels.
|
||||
|
||||
Every device has a channel group `device` with the following channels:
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------------|--------|---------|---------------------------------------------------------------------------------|
|
||||
|device |deviceName |String |yes |Device name as configured in the Shelly App |
|
||||
| |uptime |Number |yes |Number of seconds since the device was powered up |
|
||||
| |wifiSignal |Number |yes |WiFi signal strength (4=excellent, 3=good, 2=not string, 1=unreliable, 0=none) |
|
||||
| |innerTemp |Number |yes |Internal device temperature (when provided by the device) |
|
||||
| |selfTest |String |yes |Result from device self-test (pending/not_completed/running/completed/unknown) |
|
||||
| |alarm |Trigger |yes |Self-Test result not_completed/completed/running/pending |
|
||||
| |accumulatedWatts |Number |yes |Accumulated power in W of the device (including all meters) |
|
||||
| |accumulatedTotal |Number |yes |Accumulated total power in kw/h of the device (including all meters) |
|
||||
| |accumulatedReturned|Number |yes |Accumulated returned power in kw/h of the device (including all meters) |
|
||||
| |heartBeat |DateTime|yes |Timestamp of the last successful device communication |
|
||||
| |updateAvailable |Switch |yes |ON: A firmware update is available (use Shelly App to perform update) |
|
||||
| |statusLed |Switch |r/w |ON: Status LED is disabled, OFF: LED enabled |
|
||||
| |powerLed |Switch |r/w |ON: Power LED is disabled, OFF: LED enabled |
|
||||
|
||||
Availability of channels is depending on the device type.
|
||||
The binding detects many of those channels on-the-fly (when Thing changes to ONLINE state) and adjusts the Thing's channel structure.
|
||||
The device must be discovered and ONLINE to successfully complete this process.
|
||||
The accumulated channels are only available for devices with more than 1 meter. accumulatedReturned only for the EM and 3EM.
|
||||
The LED channels are available for the Plug-S with firmware 1.6x and for various other devices with firmware 1.8 or newer. The binding detects them automatically.
|
||||
|
||||
## Events
|
||||
|
||||
### Action URLs vs. CoIoT
|
||||
|
||||
Depending on the firmware release the Shelly devices supports 2 different mechanims to report sensor updates or events.
|
||||
|
||||
1. Action URLs
|
||||
|
||||
Usually the binding polls the device to update the status and maps the returned values to the various channels.
|
||||
In addition the binding can register so-called Action URLs. Those a events triggered by the device to report special events.
|
||||
You need to disable autoCoIoT in the binding configuration to make specific selections for the Action events.
|
||||
|
||||
The following event types could be registered when enabled in the thing configuration:
|
||||
|
||||
|Event Type |Description |
|
||||
|------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
|eventsButton |This event is triggered when the device is in button mode. The device reports the ON/OFF status oh the button. |
|
||||
|eventsSwitch |This event reports the status of the relay output. This could change by the button or API calls. |
|
||||
|eventsPush |The device reports the short/longpush events when in button mode momentary, momentary_on_release or detached. |
|
||||
|eventsSensorReport|Sensor devices (like H&T) provide sensor updates when this action URL is enabled. |
|
||||
|
||||
Important: The binding defaults to CoIoT when firmware 1.6 or newer is detected.
|
||||
This has significant experience improvements and also prevents interfering with other applications, because the device only supports one set of Action URLs.
|
||||
|
||||
2. CoIoT / CoAP
|
||||
|
||||
Starting with version 1.6 the devices reports most status values via the CoIoT protocol.
|
||||
CoIoT provides near-realtime updates and better event support.
|
||||
Firmware 1.7 adds additional status values, also supported by the binding.
|
||||
Version 1.8 introduces CoIoT version 2, which fixes various issues with version 1 and provides almost all relevant status updates.
|
||||
|
||||
If there is no specific reason you should enable CoIoT. See Network Settings for more information.
|
||||
|
||||
Enable the autoCoIoT option in the binding configuration or eventsCoIoT is the thing configuration to activate CoIoT.
|
||||
|
||||
### Button events
|
||||
|
||||
Various devices signal an event when the physical button is pressed.
|
||||
This could be a switch connected to the SW input of the relay or the Button 1.
|
||||
|
||||
The following trigger types are sent:
|
||||
|
||||
|Event Type |Description |
|
||||
|-------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
|SHORT_PRESSED |The button was pressed once for a short time |
|
||||
|DOUBLE_PRESSED |The button was pressed twice with short delay |
|
||||
|TRIPLE_PRESSED |The button was pressed three times with short delay |
|
||||
|LONG_PRESSED |The button was pressed for a longer time |
|
||||
|SHORT_LONG_PRESSED |A short followed by a long button push |
|
||||
|LONG_SHORT_PRESSED |A long followed by a short button push |
|
||||
|
||||
Check the channel definitions for the various devices to see if the device supports those events.
|
||||
You could use the Shelly App to set the timing for those events.
|
||||
|
||||
### Alarms
|
||||
|
||||
The binding provides health monitoring functions for the device.
|
||||
When an alarm condition is detected the channel alarm gets triggered and provides one of the following alarm types:
|
||||
|
||||
A new alarm will be triggered on a new condition or every 5 minutes if the condition persists.
|
||||
|
||||
### Non-battery powered devices
|
||||
|
||||
|Event Type|Description|
|
||||
|------------|-----------------------------------------------------------------------------------------------------------------|
|
||||
|RESTARTED |The device has been restarted. This could be an indicator for a firmware problem. |
|
||||
|WEAK_SIGNAL |An alarm is triggered when RSSI is < -80, which indicates an unstable connection. |
|
||||
|OVER_TEMP |The device is overheating, check installation and housing. |
|
||||
|OVER_LOAD |An over load condition has been detected, e.g. from the roller motor. |
|
||||
|OVER_POWER |Maximum allowed power was exceeded. The relay was turned off. |
|
||||
|LOAD_ERROR |Device reported a load problem, so far Dimmer only. |
|
||||
|
||||
### Sensors
|
||||
|
||||
|Event Type|Description|
|
||||
|------------|-----------------------------------------------------------------------------------------------------------------|
|
||||
|POWERON |Device was powered on. |
|
||||
|PERIODIC |Periodic wakeup. |
|
||||
|BUTTON |Button was pressed, e.g. to wake up the device. |
|
||||
|SENSOR |Wake-up due to updated sensor data. |
|
||||
|ALARM |Alarm condition was detected, check status, could be OPENED for the DW, flood alarm, smoke alarm |
|
||||
|BATTERY |Device reported an update to the battery status. |
|
||||
|TEMP_UNDER |Below "temperature under" threshold |
|
||||
|TEMP_OVER |Above "temperature over" threshold |
|
||||
|
||||
|
||||
```
|
||||
rule "Shelly Alarm"
|
||||
when
|
||||
Channel "shelly:shelly1:XXXXXX:device#alarm" triggered
|
||||
then
|
||||
logInfo("Shelly", "n alarm condition was detected:" + receivedEvent.toString())
|
||||
end
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
Depending on the device type and firmware release channels might be not available or
|
||||
|
||||
### Shelly 1 (thing-type: shelly1)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |output |Switch |r/w |Controls the relay's output channel (on/off) |
|
||||
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|sensors |temperature1 |Number |yes |Temperature value of external sensor #1 (if connected to temp/hum addon) |
|
||||
| |temperature2 |Number |yes |Temperature value of external sensor #2 (if connected to temp/hum addon) |
|
||||
| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) |
|
||||
| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) |
|
||||
|
||||
### Shelly 1PM (thing-type: shelly1pm)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |output |Switch |r/w |Controls the relay's output channel (on/off) |
|
||||
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts for a round minute, 1 minute ago |
|
||||
| |lastPower2 |Number |yes |Energy consumption in Watts for a round minute, 2 minutes ago |
|
||||
| |lastPower3 |Number |yes |Energy consumption in Watts for a round minute, 3 minutes 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 |
|
||||
|sensors |temperature1 |Number |yes |Temperature value of external sensor #1 (if connected to temp/hum addon) |
|
||||
| |temperature2 |Number |yes |Temperature value of external sensor #2 (if connected to temp/hum addon) |
|
||||
| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) |
|
||||
| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) |
|
||||
|
||||
### Shelly EM (thing-type: shellyem)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |output |Switch |r/w |Controls the relay's output channel (on/off) |
|
||||
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter1 |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
|
||||
| |returnedKWH |Number |yes |Total returned energy, kw/h |
|
||||
| |reactiveWatts|Number |yes |Instantaneous reactive power, Watts |
|
||||
| |voltage |Number |yes |RMS voltage, Volts |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||
|meter2 |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
|
||||
| |returnedKWH |Number |yes |Total returned energy, kw/h |
|
||||
| |reactiveWatts|Number |yes |Instantaneous reactive power, Watts |
|
||||
| |voltage |Number |yes |RMS voltage, Volts |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||
|
||||
### Shelly 3EM (thing-type: shellyem3)
|
||||
|
||||
Please note: The product is called Shelly 3EM whereas the device propagates the service under shellyem3.
|
||||
The thing id is derived from the service name, so that's the reason why the thing is named shelly**em3** and not shelly3em.
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |output |Switch |r/w |Controls the relay's output channel (on/off) |
|
||||
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter1 |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
|
||||
| |returnedKWH |Number |yes |Total returned energy, kw/h |
|
||||
| |reactiveWatts|Number |yes |Instantaneous reactive power, Watts |
|
||||
| |voltage |Number |yes |RMS voltage, Volts |
|
||||
| |current |Number |yes |Current in A |
|
||||
| |powerFactor |Number |yes |Power Factor |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||
|meter2 |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
|
||||
| |returnedKWH |Number |yes |Total returned energy, kw/h |
|
||||
| |reactiveWatts|Number |yes |Instantaneous reactive power, Watts |
|
||||
| |voltage |Number |yes |RMS voltage, Volts |
|
||||
| |current |Number |yes |Current in A |
|
||||
| |powerFactor |Number |yes |Power Factor |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||
|meter3 |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |totalKWH |Number |yes |Total energy consumption in Watts since the device powered up (resets on restart)|
|
||||
| |returnedKWH |Number |yes |Total returned energy, kw/h |
|
||||
| |reactiveWatts|Number |yes |Instantaneous reactive power, Watts |
|
||||
| |voltage |Number |yes |RMS voltage, Volts |
|
||||
| |current |Number |yes |Current in A |
|
||||
| |powerFactor |Number |yes |Power Factor |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last measurement |
|
||||
|
||||
|
||||
### Shelly 2 - relay mode thing-type: shelly2-relay)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay1 |output |Switch |r/w |Relay #1: Controls the relay's output channel (on/off) |
|
||||
| |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 with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |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) |
|
||||
| |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 with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts 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 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 |
|
||||
| |rollerpos |Number |r/w |Roller position: 100%=open...0%=closed; gets updated when the roller stops, see Notes |
|
||||
| |state |String |yes |Roller state: open/close/stop |
|
||||
| |stopReason |String |yes |Last stop reasons: normal, safety_switch or obstacle |
|
||||
| |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 |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 |
|
||||
|
||||
The roller positioning calibration has to be performed using the Shelly App before the position can be set in percent.
|
||||
|
||||
### Shelly 2.5 - relay mode (thing-type:shelly25-relay)
|
||||
|
||||
The Shelly 2.5 includes 2 meters, one for each channel.
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay1 | | | |See group relay1 for Shelly 2 |
|
||||
|relay2 | | | |See group relay1 for Shelly 2 |
|
||||
|meter1 | | | |See group meter1 for Shelly 2 |
|
||||
|meter2 | | | |See group meter1 for Shelly 2 |
|
||||
|
||||
### Shelly 2.5 - roller mode (thing-type: shelly25-roller)
|
||||
|
||||
The Shelly 2.5 includes 2 meters, one for each channel.
|
||||
However, it doesn't make sense to differ power consumption for the roller moving up vs. moving down.
|
||||
For this the binding aggregates the power consumption of both relays and includes the values in "meter1".
|
||||
|
||||
|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 |
|
||||
| |event |Trigger |yes |Roller event/trigger with payload ROLLER_OPEN / ROLLER_CLOSE / ROLLER_STOP |
|
||||
|meter | | | |See group meter1 for Shelly 2 |
|
||||
|
||||
The roller positioning calibration has to be performed using the Shelly App before the position can be set in percent.
|
||||
|
||||
### Shelly4 Pro (thing-type: shelly4pro)
|
||||
|
||||
The Shelly 4Pro provides 4 relays and 4 power meters.
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay1 | | | |See group relay1 for Shelly 2 |
|
||||
|relay2 | | | |See group relay1 for Shelly 2 |
|
||||
|relay3 | | | |See group relay1 for Shelly 2 |
|
||||
|relay4 | | | |See group relay1 for Shelly 2 |
|
||||
|meter1 | | | |See group meter1 for Shelly 2 |
|
||||
|meter2 | | | |See group meter1 for Shelly 2 |
|
||||
|meter3 | | | |See group meter1 for Shelly 2 |
|
||||
|meter4 | | | |See group meter1 for Shelly 2 |
|
||||
|
||||
### Shelly Plug-S (thing-type: shellyplugs)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay | | | |See group relay1 for Shelly 2 |
|
||||
|meter | | | |See group meter1 for Shelly 2 |
|
||||
|
||||
### Shelly Dimmer (thing-type: shellydimmer)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |brightness |Dimmer |r/w |Currently selected brightness. |
|
||||
| |input1 |Switch |yes |State of Input 1 (S1) |
|
||||
| |input2 |Switch |yes |State of Input 2 (S2) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds |
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts for a round minute, 1 minute ago |
|
||||
| |lastPower2 |Number |yes |Energy consumption in Watts for a round minute, 2 minutes ago |
|
||||
| |lastPower3 |Number |yes |Energy consumption in Watts for a round minute, 3 minutes 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 Dimmer2 (thing-type: shellydimmer2)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|---------------------------------------------------------------------------------|
|
||||
|relay |brightness |Dimmer |r/w |Currently selected brightness. |
|
||||
| |input1 |Switch |yes |State of Input 1 (S1) |
|
||||
| |input2 |Switch |yes |State of Input 2 (S2) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds |
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |outputName |String |yes |Logical name of this relay output as configured in the Shelly App |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts for a round minute, 1 minute ago |
|
||||
| |lastPower2 |Number |yes |Energy consumption in Watts for a round minute, 2 minutes ago |
|
||||
| |lastPower3 |Number |yes |Energy consumption in Watts for a round minute, 3 minutes 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 |
|
||||
|
||||
The Dimmer should be calibrated using the Shelly App.
|
||||
|
||||
### Shelly ix3 (thing-type: shellyix3)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|status |input1 |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: SHORT_PRESSED, DOUBLE_PRESSED, TRIPLE_PRESSED, LONG_PRESSED, SHORT_LONG_PRESSED or LONG_SHORT_PRESSED |
|
||||
| |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
|
||||
| |eventCount |Number |yes |Number of button events |
|
||||
|
||||
### Shelly Bulb (thing-type: shellybulb)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |power |Switch |r/w |Switch light ON/OFF |
|
||||
| |mode |Switch |r/w |Color mode: color or white |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|color | | | |Color settings: only valid in COLOR mode |
|
||||
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, bight not white |
|
||||
| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode |
|
||||
| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" |
|
||||
| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |green |Dimmer |r/w |Green brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |blue |Dimmer |r/w |Blue brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |white |Dimmer |r/w |White brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |gain |Dimmer |r/w |Gain setting: 0..100% or 0..100 |
|
||||
| |effect |Number |r/w |Puts the light into effect mode: 0..6) |
|
||||
| | | | | 0=No effect, 1=Meteor Shows, 2=Gradual Change, 3=Breath |
|
||||
| | | | | 4=Flash, 5=On/Off Gradual, 6=Red/Green Change |
|
||||
|white | | | |Color settings: only valid in WHITE mode |
|
||||
| |temperature |Number |r/w |color temperature (K): 0..100% or 3000..6500 |
|
||||
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
|
||||
|
||||
#### Shelly Duo (thing-type: shellybulbduo)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|white | | | |Color settings: only valid in WHITE mode |
|
||||
| |temperature |Number |r/w |color temperature (K): 0..100% or 2700..6500 |
|
||||
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts for a round minute, 1 minute ago |
|
||||
| |lastPower2 |Number |yes |Energy consumption in Watts for a round minute, 2 minutes ago |
|
||||
| |lastPower3 |Number |yes |Energy consumption in Watts for a round minute, 3 minutes 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 Vintage (thing-type: shellyvintage)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|white | | | |Color settings: only valid in WHITE mode |
|
||||
| |brightness |Dimmer | |Brightness: 0..100% or 0..100 |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
| |lastPower1 |Number |yes |Energy consumption in Watts for a round minute, 1 minute ago |
|
||||
| |lastPower2 |Number |yes |Energy consumption in Watts for a round minute, 2 minutes ago |
|
||||
| |lastPower3 |Number |yes |Energy consumption in Watts for a round minute, 3 minutes 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 RGBW2 in Color Mode (thing-type: shellyrgbw2-color)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |power |Switch |r/w |Switch light ON/OFF |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|color | | | |Color settings: only valid in COLOR mode |
|
||||
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, bight not white |
|
||||
| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode |
|
||||
| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" |
|
||||
| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |green |Dimmer |r/w |Green brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |blue |Dimmer |r/w |Blue brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |white |Dimmer |r/w |White brightness: 0..100% or 0..255 (control only the red channel) |
|
||||
| |gain |Dimmer |r/w |Gain setting: 0..100% or 0..100 |
|
||||
| |effect |Number |r/w |Puts the light into effect mode: 0..3) |
|
||||
| | | | |0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts |
|
||||
|
||||
### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |input |Switch |yes |State of Input |
|
||||
|channel1 |brightness |Dimmer |r/w |Channel 1: Brightness: 0..100, control power state with ON/OFF |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|channel2 |brightness |Dimmer |r/w |Channel 2: Brightness: 0..100, control power state with ON/OFF |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|channel3 |brightness |Dimmer |r/w |Channel 3: Brightness: 0..100, control power state with ON/OFF |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|channel4 |brightness |Dimmer |r/w |Channel 5: Brightness: 0..100, control power state with ON/OFF |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED or LONG_PRESSED (FW 1.5.6+) |
|
||||
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds|
|
||||
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds|
|
||||
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|
||||
|meter |currentWatts |Number |yes |Current power consumption in Watts (all channels) |
|
||||
|
||||
Please note that the settings of channel group color are only valid in color mode and vice versa for white mode.
|
||||
The current firmware doesn't support the timestamp report for the meters.
|
||||
The binding emulates this by using the system time on every update.
|
||||
|
||||
In white mode each RGBW2 channel is defined as DimmableLight.
|
||||
This means that the brightness channel has 2 functions
|
||||
|
||||
- Sending ON/OFF (OnOffType) to power on/off the channel
|
||||
- Sending a Number to set the brightness (percentage 0..100)
|
||||
|
||||
Sending brightness 0 will automatically turn off the channel if it's currently on.
|
||||
Sending brightness > 0 will automatically turn on the channel if it's currently off.
|
||||
You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see example rules.
|
||||
|
||||
### Shelly H&T (thing-type: shellyht)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit |
|
||||
| |humidity |Number |yes |Relative humidity in % |
|
||||
| |charger |Number |yes |ON: USB charging cable is |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |voltage |Number |yes |Voltage of the battery |
|
||||
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|
||||
|
||||
### Shelly Flood (thing type: shellyflood)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit |
|
||||
| |flood |Switch |yes |ON: Flooding condition detected, OFF: no flooding |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |voltage |Number |yes |Voltage of the battery |
|
||||
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|
||||
|
||||
### Shelly Door/Window (thing type: shellydw)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|sensors |state |Contact |yes |OPEN: Contact is open, CLOSED: Contact is closed |
|
||||
| |lux |Number |yes |Brightness in Lux |
|
||||
| |illumination |String |yes |Current illumination: dark/twilight/bright |
|
||||
| |titl |Number |yes |Tilt in ° (angle), -1 indicates that the sensor is not calibrated |
|
||||
| |vibration |Switch |yes |ON: Vibration detected |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
| |lastError |String |yes |Last device error. |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |voltage |Number |yes |Voltage of the battery |
|
||||
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|
||||
|
||||
### Shelly Button 1 (thing type: shellybutton1)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|status |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
|
||||
| |eventCount |Number |yes |Number of button events |
|
||||
| |input |Switch |yes |ON: Input/Button is powered, see General Notes on Channels |
|
||||
| |button |Trigger |yes |Event trigger with payload SHORT_PRESSED, DOUBLE_PRESSED... |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any value changed) |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |voltage |Number |yes |Voltage of the battery |
|
||||
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|
||||
|
||||
You should calibrate the sensor using the Shelly App to get information on the tilt status.
|
||||
|
||||
### Shelly Smoke(thing type: shellysmoke)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|sensors |temperature |Number |yes |Temperature, unit is reported by tempUnit |
|
||||
| |smoke |Number |yes |ON: Smoke detected |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
| |lastError |String |yes |Last device error. |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |voltage |Number |yes |Voltage of the battery |
|
||||
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|
||||
|
||||
### Shelly Smoke(thing type: shellygas)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|sensors |ppm |Number |yes |Gas concentration (ppm) |
|
||||
| |sensorState |String |yes |Sensor state: unknown/warmup/normal/fault |
|
||||
| |alarmState |String |yes |Alarm state: unknown/none/mild/heavy/test |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
|
||||
### Shelly Sense (thing-type: shellysense)
|
||||
|
||||
|Group |Channel |Type |read-only|Description |
|
||||
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|
||||
|control |key |String |r/w |Send a IR key to the sense. There a 3 different types supported |
|
||||
| | | | |Stored key: send the key code defined by the App , e.g. 123_1_up |
|
||||
| | | | |Pronto hex: send a Pronto Code in ex format, e.g. 0000 006C 0022 ... |
|
||||
| | | | |Pronto base64: in base64 format, will be send 1:1 to the Sense |
|
||||
| |motionTime |Number |r/w |Define the number of seconds when the Sense should report motion |
|
||||
| |motionLED |Switch |r/w |Control the motion LED: ON when motion is detected or OFF |
|
||||
| |charger |Switch |yes |ON: charger connected, OFF: charger not connected. |
|
||||
|sensors |temperature |Number |yes |Temperature in °C |
|
||||
| |humidity |Number |yes |Relative humidity in % |
|
||||
| |lux |Number |yes |Brightness in Lux |
|
||||
| |motion |Switch |yes |ON: Motion detected, OFF: No motion (check also motionTimer) |
|
||||
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|
||||
|battery |batteryLevel |Number |yes |Battery Level in % |
|
||||
| |batteryAlert |Switch |yes |Low battery alert |
|
||||
|
||||
## Full Example
|
||||
|
||||
### shelly.things
|
||||
|
||||
```
|
||||
/* Shelly 2.5 Roller */
|
||||
Thing shelly:shelly25-roller:XXXXX1 "Shelly 25 Roller XXXXX1" @ "Home Theater" [deviceIp="x.x.x.x", userId="", password=""]
|
||||
Thing shelly:shelly25-roller:XXXXX2 "Shelly 25 Roller XXXXX2" @ "Living Room" [deviceIp="x.x.x.x", userId="admin", password="secret"]
|
||||
|
||||
|
||||
/* Shelly 2.5 Relays */
|
||||
Thing shelly:shelly25-relay:XXXXX3 "Shelly 25 Relay XXXXX3" @ "Hall Way" [deviceIp="x.x.x.x", userId="", password=""]
|
||||
Thing shelly:shelly25-relay:XXXXX4 "Shelly 25 Relay XXXXX4" @ "Dining Room" [deviceIp="x.x.x.x", userId="", password=""]
|
||||
Thing shelly:shelly25-relay:XXXXX5 "Shelly 25 Relay XXXXX5" @ "Bed Room" [deviceIp="x.x.x.x", userId="", password=""]
|
||||
|
||||
/* Other *
|
||||
Thing shelly:shellyht:e01691 "ShellyChimenea" @ "lowerground" [ deviceIp="10.0.55.101", userId="", password="", lowBattery=15 , eventsCoIoT=true ]
|
||||
Thing shelly:shellyht:e01681 "ShellyDormitorio" @ "upperground" [ deviceIp="10.0.55.102", userId="", password="", lowBattery=15 , eventsCoIoT=true ]
|
||||
Thing shelly:shellyflood:XXXXXX "ShellyFlood" @ "cellar" [ deviceIp="10.0.0.103", userId="", password="", lowBattery=15, eventsSwitch=true, eventsButton=true, eventsCoIoT=true ]
|
||||
|
||||
```
|
||||
|
||||
### shelly.items
|
||||
|
||||
```
|
||||
/* Relays */
|
||||
Switch Shelly_XXXXX3_Relay "Garage Light" {channel="shelly:shelly1:XXXXX3:relay#output"}
|
||||
Number Shelly_XXXXX3_AutoOnTimer "Garage Light Auto On Timer" {channel="shelly:shelly1:XXXXX3:relay#autoOn"}
|
||||
Number Shelly_XXXXX3_AutoOffTimer "Garage Light Auto Off Timer" {channel="shelly:shelly1:BA2F18:relay#autoOff"}
|
||||
Switch Shelly_XXXXX3_Relay "Garage Light" {channel="shelly:shelly1:XXXXX3:relay#output"}
|
||||
Switch Shelly_XXXXX3_Input "Garage Switch (Input)" {channel="shelly:shelly1:XXXXX3:relay#input"}
|
||||
|
||||
/* Sensors */
|
||||
Number ShellyHT_Dormitorio_Temp "Dormitorio Temperature" <temperature> {channel="shelly:shellyht:e01681:sensors#temperature"}
|
||||
Number ShellyHT_Dormitorio_Humid "Dormitorio Humidity" <humidity> {channel="shelly:shellyht:e01681:sensors#humidity"}
|
||||
Number ShellyHT_Dormitorio_Batt "Dormitorio Battery" <battery> {channel="shelly:shellyht:e01681:battery#batteryLevel"}
|
||||
Number ShellyHT_Chimenea_Temp "Chimenea Temperature" <temperature> {channel="shelly:shellyht:e01691:sensors#temperature"}
|
||||
Number ShellyHT_Chimenea_Humid "Chimenea Humidity" <humidity> {channel="shelly:shellyht:e01691:sensors#humidity"}
|
||||
Number ShellyHT_Chimenea_Batt "Chimenea Battery" <battery> {channel="shelly:shellyht:e01691:battery#batteryLevel"}
|
||||
Number ShellyF_Sotano_Temp "Sotano Temperature" <temperature> {channel="shelly:shellyflood:764fe0:sensors#temperature"}
|
||||
Number ShellyF_Sotano_Batt "Sotano Battery" <battery> {channel="shelly:shellyflood:764fe0:battery#batteryLevel"}
|
||||
Switch ShellyF_Sotano_Flood "Sotano Flood Alarm" <alarm> {channel="shelly:shellyflood:764fe0:sensors#flood"}
|
||||
|
||||
/* Dimmer */
|
||||
Switch DimmerSwitch "Light on/off" {channel="shelly:shellydimmer:XXX:relay#brightness"}
|
||||
Dimmer DimmerBrightness "Garage Light Brightness" {channel="shelly:shellydimmer:XXX:relay#brightness"}
|
||||
Dimmer DimmerIncDec "Garage Light +/-" {channel="shelly:shellydimmer:XXX:relay#brightness"}
|
||||
|
||||
Number Shelly_Power "Bath Room Light Power" {channel="shelly:shelly1:XXXXXX:meter#currentWatts"} /* Power Meter */
|
||||
|
||||
```
|
||||
|
||||
### shelly.rules
|
||||
|
||||
reading colors from color picker:
|
||||
|
||||
```
|
||||
import org.openhab.core.library.types.*
|
||||
|
||||
rule "Get input change from garage light"
|
||||
when
|
||||
Item Shelly_XXXXX3_Input changed to ON
|
||||
then
|
||||
logInfo("Garage", "Light input is ON")
|
||||
BackDoorLight.sendCommand(ON)
|
||||
end
|
||||
|
||||
rule "Momentary Switch events"
|
||||
when
|
||||
Channel "shelly:shellydevice:XXXXXX:relay1#button" triggered SHORT_PRESSED
|
||||
then
|
||||
logInfo("Relay", "A short push was detected")
|
||||
end
|
||||
|
||||
|
||||
rule "Shelly alarms"
|
||||
when
|
||||
Channel "shelly:shellydevice:XXXXXX:device#alarm" triggered or
|
||||
Channel "shelly:shelly25-roller:XXXXXX:device#alarm" triggered
|
||||
then
|
||||
if (receivedEvent !== null) { // A (channel) event triggered the rule
|
||||
eventSource = receivedEvent.getChannel().asString
|
||||
eventType = receivedEvent.getEvent()
|
||||
...
|
||||
}
|
||||
end
|
||||
|
||||
rule "Color changed"
|
||||
when
|
||||
Item ShellyColor changed
|
||||
then
|
||||
var HSBType hsbValue = ShellyColor.state as HSBType
|
||||
var int redValue = hsbValue.red.intValue
|
||||
var int greenValue = hsbValue.green.intValue
|
||||
var int blueValue = hsbValue.blue.intValue
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
### shelly.sitemap
|
||||
|
||||
```
|
||||
sitemap demo label="Home"
|
||||
{
|
||||
Frame label="Dimmer" {
|
||||
Switch item=DimmerSwitch
|
||||
Slider item=DimmerBrightness
|
||||
SetPoint item=DimmerIncDec
|
||||
|
||||
Number item=ShellyHT_Dormitorio_Temp
|
||||
Number item=ShellyHT_Chimenea_Humid
|
||||
Number item=ShellyF_Sotano_Batt
|
||||
Number item=Shelly_Power
|
||||
}
|
||||
}
|
||||
```
|
||||
17
bundles/org.openhab.binding.shelly/pom.xml
Normal file
17
bundles/org.openhab.binding.shelly/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<artifactId>org.openhab.binding.shelly</artifactId>
|
||||
<name>openHAB Add-ons :: Bundles :: Shelly Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.shelly-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-shelly" description="Shelly Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-coap</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.shelly/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBindingConstants {
|
||||
|
||||
public static final String VENDOR = "Shelly";
|
||||
public static final String BINDING_ID = "shelly";
|
||||
public static final String SYSTEM_ID = "system";
|
||||
|
||||
// Type names
|
||||
public static final String THING_TYPE_SHELLY1_STR = "shelly1";
|
||||
public static final String THING_TYPE_SHELLY1PM_STR = "shelly1pm";
|
||||
public static final String THING_TYPE_SHELLYEM_STR = "shellyem";
|
||||
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
|
||||
public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch";
|
||||
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_RELAY_STR = "shelly25-relay";
|
||||
public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller";
|
||||
public static final String THING_TYPE_SHELLY4PRO_STR = "shelly4pro";
|
||||
public static final String THING_TYPE_SHELLYPLUG_STR = "shellyplug";
|
||||
public static final String THING_TYPE_SHELLYPLUGS_STR = "shellyplugs";
|
||||
public static final String THING_TYPE_SHELLYDIMMER_STR = "shellydimmer";
|
||||
public static final String THING_TYPE_SHELLYDIMMER2_STR = "shellydimmer2";
|
||||
public static final String THING_TYPE_SHELLYIX3_STR = "shellyix3";
|
||||
public static final String THING_TYPE_SHELLYBULB_STR = "shellybulb";
|
||||
public static final String THING_TYPE_SHELLYDUO_STR = "shellybulbduo";
|
||||
public static final String THING_TYPE_SHELLYVINTAGE_STR = "shellyvintage";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_PREFIX = "shellyrgbw2";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = "shellyrgbw2-color";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = "shellyrgbw2-white";
|
||||
public static final String THING_TYPE_SHELLYHT_STR = "shellyht";
|
||||
public static final String THING_TYPE_SHELLYSMOKE_STR = "shellysmoke";
|
||||
public static final String THING_TYPE_SHELLYGAS_STR = "shellygas";
|
||||
public static final String THING_TYPE_SHELLYFLOOD_STR = "shellyflood";
|
||||
public static final String THING_TYPE_SHELLYDOORWIN_STR = "shellydw";
|
||||
public static final String THING_TYPE_SHELLYDOORWIN2_STR = "shellydw2";
|
||||
public static final String THING_TYPE_SHELLYEYE_STR = "shellyseye";
|
||||
public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
|
||||
public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1";
|
||||
public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
|
||||
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
|
||||
|
||||
// Device Types
|
||||
public static final String SHELLYDT_1 = "SHSW-1";
|
||||
public static final String SHELLYDT_1PM = "SHSW-PM";
|
||||
public static final String SHELLYDT_SHPLG = "SHPLG-1";
|
||||
public static final String SHELLYDT_SHPLG_S = "SHPLG-S";
|
||||
public static final String SHELLYDT_SHELLY2 = "SHSW-21";
|
||||
public static final String SHELLYDT_SHELLY25 = "SHSW-25";
|
||||
public static final String SHELLYDT_SHPRO = "SHSW-44";
|
||||
public static final String SHELLYDT_EM = "SHEM";
|
||||
public static final String SHELLYDT_3EM = "SHEM-3";
|
||||
public static final String SHELLYDT_HT = "SHHT-1";
|
||||
public static final String SHELLYDT_DW = "SHDW-1";
|
||||
public static final String SHELLYDT_DW2 = "SHDW-2";
|
||||
public static final String SHELLYDT_SENSE = "SHSEN-1";
|
||||
public static final String SHELLYDT_GAS = "SHGS-1";
|
||||
public static final String SHELLYDT_DIMMER = "SHDM-1";
|
||||
public static final String SHELLYDT_DIMMER2 = "SHDM-2";
|
||||
public static final String SHELLYDT_IX3 = "SHIX3-1";
|
||||
public static final String SHELLYDT_BULB = "SHBLB-1";
|
||||
public static final String SHELLYDT_DUO = "SHBDUO-1";
|
||||
public static final String SHELLYDT_VINTAGE = "SHVIN-1";
|
||||
public static final String SHELLYDT_RGBW2 = "SHRGBW2";
|
||||
public static final String SHELLYDT_BUTTON1 = "SHBTN-1";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY1PM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1PM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYEM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
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,
|
||||
THING_TYPE_SHELLY25_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY25_ROLLER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY4PRO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY4PRO_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPLUG = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUG_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPLUGS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUGS_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDIMMER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER2 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDIMMER2_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYIX3 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYIX3_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBULB = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBULB_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDUO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYDUO_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYVINTAGE = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYVINTAGE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYHT = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYHT_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYSENSE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSENSE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYSMOKE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSMOKE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYGAS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYGAS_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYFLOOD = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYFLOOD_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDOORWIN_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN2 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBUTTON1 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYBUTTON1_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYEYE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEYE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_COLOR = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYRGBW2_COLOR_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_WHITE = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYRGBW2_WHITE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPROTECTED = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPROTECTED_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYUNKNOWN = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYUNKNOWN_STR);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM,
|
||||
THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER, THING_TYPE_SHELLY25_RELAY,
|
||||
THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
|
||||
THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2,
|
||||
THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE,
|
||||
THING_TYPE_SHELLYRGBW2_COLOR, THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT,
|
||||
THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE, THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS,
|
||||
THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN, THING_TYPE_SHELLYDOORWIN2,
|
||||
THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
// Thing Configuration Properties
|
||||
public static final String CONFIG_DEVICEIP = "deviceIp";
|
||||
public static final String CONFIG_HTTP_USERID = "userId";
|
||||
public static final String CONFIG_HTTP_PASSWORD = "password";
|
||||
public static final String CONFIG_UPDATE_INTERVAL = "updateInterval";
|
||||
|
||||
public static final String PROPERTY_SERVICE_NAME = "serviceName";
|
||||
public static final String PROPERTY_DEV_NAME = "deviceName";
|
||||
public static final String PROPERTY_DEV_TYPE = "deviceType";
|
||||
public static final String PROPERTY_DEV_MODE = "deviceMode";
|
||||
public static final String PROPERTY_HWREV = "deviceHwRev";
|
||||
public static final String PROPERTY_HWBATCH = "deviceHwBatch";
|
||||
public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod";
|
||||
public static final String PROPERTY_NUM_RELAYS = "numberRelays";
|
||||
public static final String PROPERTY_NUM_ROLLERS = "numberRollers";
|
||||
public static final String PROPERTY_NUM_METER = "numberMeters";
|
||||
public static final String PROPERTY_LAST_ACTIVE = "lastActive";
|
||||
public static final String PROPERTY_WIFI_NETW = "wifiNetwork";
|
||||
public static final String PROPERTY_UPDATE_STATUS = "updateStatus";
|
||||
public static final String PROPERTY_UPDATE_AVAILABLE = "updateAvailable";
|
||||
public static final String PROPERTY_UPDATE_CURR_VERS = "updateCurrentVersion";
|
||||
public static final String PROPERTY_UPDATE_NEW_VERS = "updateNewVersion";
|
||||
public static final String PROPERTY_COAP_DESCR = "coapDeviceDescr";
|
||||
public static final String PROPERTY_COAP_VERSION = "coapVersion";
|
||||
public static final String PROPERTY_STATS_TIMEOUTS = "statsTimeoutErrors";
|
||||
public static final String PROPERTY_STATS_TRECOVERED = "statsTimeoutsRecovered";
|
||||
public static final String PROPERTY_COIOTAUTO = "coiotAutoEnable";
|
||||
public static final String PROPERTY_COIOTREFRESH = "coiotAutoRefresh";
|
||||
|
||||
// Relay
|
||||
public static final String CHANNEL_GROUP_RELAY_CONTROL = "relay";
|
||||
public static final String CHANNEL_OUTPUT_NAME = "outputName";
|
||||
public static final String CHANNEL_OUTPUT = "output";
|
||||
public static final String CHANNEL_INPUT = "input";
|
||||
public static final String CHANNEL_INPUT1 = "input1";
|
||||
public static final String CHANNEL_INPUT2 = "input2";
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
|
||||
public static final String CHANNEL_TIMER_AUTOON = "autoOn";
|
||||
public static final String CHANNEL_TIMER_AUTOOFF = "autoOff";
|
||||
public static final String CHANNEL_TIMER_ACTIVE = "timerActive";
|
||||
|
||||
// Roller
|
||||
public static final String CHANNEL_GROUP_ROL_CONTROL = "roller";
|
||||
public static final String CHANNEL_ROL_CONTROL_CONTROL = "control";
|
||||
public static final String CHANNEL_ROL_CONTROL_POS = "rollerpos";
|
||||
public static final String CHANNEL_ROL_CONTROL_TIMER = "timer";
|
||||
public static final String CHANNEL_ROL_CONTROL_STATE = "state";
|
||||
public static final String CHANNEL_ROL_CONTROL_STOPR = "stopReason";
|
||||
|
||||
// Dimmer
|
||||
public static final String CHANNEL_GROUP_DIMMER_CONTROL = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
|
||||
// Power meter
|
||||
public static final String CHANNEL_GROUP_METER = "meter";
|
||||
public static final String CHANNEL_METER_CURRENTWATTS = "currentWatts";
|
||||
public static final String CHANNEL_METER_LASTMIN = "lastPower";
|
||||
public static final String CHANNEL_METER_LASTMIN1 = CHANNEL_METER_LASTMIN + "1";
|
||||
public static final String CHANNEL_METER_TOTALKWH = "totalKWH";
|
||||
public static final String CHANNEL_EMETER_TOTALRET = "returnedKWH";
|
||||
public static final String CHANNEL_EMETER_REACTWATTS = "reactiveWatts";
|
||||
public static final String CHANNEL_EMETER_VOLTAGE = "voltage";
|
||||
public static final String CHANNEL_EMETER_CURRENT = "current";
|
||||
public static final String CHANNEL_EMETER_PFACTOR = "powerFactor";
|
||||
|
||||
public static final String CHANNEL_GROUP_SENSOR = "sensors";
|
||||
public static final String CHANNEL_SENSOR_TEMP = "temperature";
|
||||
public static final String CHANNEL_SENSOR_HUM = "humidity";
|
||||
public static final String CHANNEL_SENSOR_LUX = "lux";
|
||||
public static final String CHANNEL_SENSOR_PPM = "ppm";
|
||||
public static final String CHANNEL_SENSOR_ILLUM = "illumination";
|
||||
public static final String CHANNEL_SENSOR_VIBRATION = "vibration";
|
||||
public static final String CHANNEL_SENSOR_TILT = "tilt";
|
||||
public static final String CHANNEL_SENSOR_FLOOD = "flood";
|
||||
public static final String CHANNEL_SENSOR_SMOKE = "smoke";
|
||||
public static final String CHANNEL_SENSOR_CONTACT = "state";
|
||||
public static final String CHANNEL_SENSOR_VALVE = "valve";
|
||||
public static final String CHANNEL_SENSOR_SSTATE = "status"; // Shelly Gas
|
||||
public static final String CHANNEL_SENSOR_ALARM_STATE = "alarmState";
|
||||
public static final String CHANNEL_SENSOR_MOTION = "motion";
|
||||
public static final String CHANNEL_SENSOR_ERROR = "lastError";
|
||||
|
||||
// External sensors for Shelly1/1PM
|
||||
public static final String CHANNEL_ESENDOR_TEMP1 = CHANNEL_SENSOR_TEMP + "1";
|
||||
public static final String CHANNEL_ESENDOR_TEMP2 = CHANNEL_SENSOR_TEMP + "2";
|
||||
public static final String CHANNEL_ESENDOR_TEMP3 = CHANNEL_SENSOR_TEMP + "3";
|
||||
public static final String CHANNEL_ESENDOR_HUMIDITY = CHANNEL_SENSOR_HUM;
|
||||
|
||||
public static final String CHANNEL_GROUP_SENSE_CONTROL = "control";
|
||||
public static final String CHANNEL_SENSE_KEY = "key";
|
||||
|
||||
public static final String CHANNEL_GROUP_BATTERY = "battery";
|
||||
public static final String CHANNEL_SENSOR_BAT_LEVEL = "batteryLevel";
|
||||
public static final String CHANNEL_SENSOR_BAT_LOW = "lowBattery";
|
||||
|
||||
public static final String CHANNEL_GROUP_LIGHT_CONTROL = "control";
|
||||
public static final String CHANNEL_LIGHT_COLOR_MODE = "mode";
|
||||
public static final String CHANNEL_LIGHT_POWER = "power";
|
||||
public static final String CHANNEL_LIGHT_DEFSTATE = "defaultState";
|
||||
public static final String CHANNEL_GROUP_LIGHT_CHANNEL = "channel";
|
||||
|
||||
// Bulb/RGBW2 in color mode
|
||||
public static final String CHANNEL_GROUP_COLOR_CONTROL = "color";
|
||||
public static final String CHANNEL_COLOR_PICKER = "hsb";
|
||||
public static final String CHANNEL_COLOR_FULL = "full";
|
||||
public static final String CHANNEL_COLOR_RED = "red";
|
||||
public static final String CHANNEL_COLOR_GREEN = "green";
|
||||
public static final String CHANNEL_COLOR_BLUE = "blue";
|
||||
public static final String CHANNEL_COLOR_WHITE = "white";
|
||||
public static final String CHANNEL_COLOR_GAIN = "gain";
|
||||
public static final String CHANNEL_COLOR_EFFECT = "effect";
|
||||
|
||||
// Bulb/RGBW2/Dup in White Mode
|
||||
public static final String CHANNEL_GROUP_WHITE_CONTROL = "white";
|
||||
public static final String CHANNEL_COLOR_TEMP = "temperature";
|
||||
|
||||
// Device Status
|
||||
public static final String CHANNEL_GROUP_DEV_STATUS = "device";
|
||||
public static final String CHANNEL_DEVST_NAME = "deviceName";
|
||||
public static final String CHANNEL_DEVST_UPTIME = "uptime";
|
||||
public static final String CHANNEL_DEVST_HEARTBEAT = "heartBeat";
|
||||
public static final String CHANNEL_DEVST_RSSI = "wifiSignal";
|
||||
public static final String CHANNEL_DEVST_ITEMP = "internalTemp";
|
||||
public static final String CHANNEL_DEVST_WAKEUP = "wakeupReason";
|
||||
public static final String CHANNEL_DEVST_ALARM = "alarm";
|
||||
public static final String CHANNEL_DEVST_ACCUWATTS = "accumulatedWatts";
|
||||
public static final String CHANNEL_DEVST_ACCUTOTAL = "accumulatedWTotal";
|
||||
public static final String CHANNEL_DEVST_ACCURETURNED = "accumulatedReturned";
|
||||
public static final String CHANNEL_DEVST_CHARGER = "charger";
|
||||
public static final String CHANNEL_DEVST_UPDATE = "updateAvailable";
|
||||
public static final String CHANNEL_DEVST_SELFTTEST = "selfTest";
|
||||
|
||||
public static final String CHANNEL_LED_STATUS_DISABLE = "statusLed";
|
||||
public static final String CHANNEL_LED_POWER_DISABLE = "powerLed";
|
||||
// Button/xi3
|
||||
public static final String CHANNEL_GROUP_STATUS = "status";
|
||||
public static final String CHANNEL_STATUS_EVENTTYPE = "lastEvent";
|
||||
public static final String CHANNEL_STATUS_EVENTCOUNT = "eventCount";
|
||||
|
||||
// General
|
||||
public static final String CHANNEL_LAST_UPDATE = "lastUpdate";
|
||||
public static final String CHANNEL_EVENT_TRIGGER = "event";
|
||||
public static final String CHANNEL_BUTTON_TRIGGER = "button";
|
||||
|
||||
public static final String SERVICE_TYPE = "_http._tcp.local.";
|
||||
public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+
|
||||
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+
|
||||
|
||||
// Alarm types/messages
|
||||
public static final String ALARM_TYPE_NONE = "NONE";
|
||||
public static final String ALARM_TYPE_RESTARTED = "RESTARTED";
|
||||
public static final String ALARM_TYPE_OVERTEMP = "OVERTEMP";
|
||||
public static final String ALARM_TYPE_OVERPOWER = "OVERPOWER";
|
||||
public static final String ALARM_TYPE_OVERLOAD = "OVERLOAD";
|
||||
public static final String ALARM_TYPE_LOADERR = "LOAD_ERROR";
|
||||
public static final String ALARM_TYPE_LOW_BATTERY = "LOW_BATTERY";
|
||||
|
||||
// Event types
|
||||
public static final String EVENT_TYPE_RELAY = "relay";
|
||||
public static final String EVENT_TYPE_ROLLER = "roller";
|
||||
public static final String EVENT_TYPE_LIGHT = "light";
|
||||
public static final String EVENT_TYPE_SENSORDATA = "report";
|
||||
|
||||
// URI for the EventServlet
|
||||
public static final String SHELLY_CALLBACK_URI = "/shelly/event";
|
||||
|
||||
public static final int DIM_STEPSIZE = 5;
|
||||
|
||||
// Formatting: Number of scaling digits
|
||||
public static final int DIGITS_NONE = 0;
|
||||
public static final int DIGITS_WATT = 1;
|
||||
public static final int DIGITS_KWH = 3;
|
||||
public static final int DIGITS_VOLT = 1;
|
||||
public static final int DIGITS_TEMP = 1;
|
||||
public static final int DIGITS_LUX = 1;
|
||||
public static final int DIGITS_PERCENT = 1;
|
||||
|
||||
public static final int SHELLY_API_TIMEOUT_MS = 5000;
|
||||
public static final int UPDATE_STATUS_INTERVAL_SECONDS = 3; // check for updates every x sec
|
||||
public static final int UPDATE_SKIP_COUNT = 20; // update every x triggers or when a key was pressed
|
||||
public static final int UPDATE_MIN_DELAY = 15;// update every x triggers or when a key was pressed
|
||||
public static final int UPDATE_SETTINGS_INTERVAL_SECONDS = 60; // check for updates every x sec
|
||||
public static final int HEALTH_CHECK_INTERVAL_SEC = 300; // Health check interval, 5min
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyUtils;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.HttpServiceUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* The {@link ShellyHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ThingHandlerFactory.class, ShellyHandlerFactory.class }, configurationPid = "binding.shelly")
|
||||
public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyHandlerFactory.class);
|
||||
private final HttpClient httpClient;
|
||||
private final ShellyTranslationProvider messages;
|
||||
private final ShellyCoapServer coapServer;
|
||||
private final Set<ShellyBaseHandler> deviceListeners = new ConcurrentHashSet<>();
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
|
||||
private String localIP = "";
|
||||
private int httpPort = -1;
|
||||
|
||||
/**
|
||||
* Activate the bundle: save properties
|
||||
*
|
||||
* @param componentContext
|
||||
* @param configProperties set of properties from cfg (use same names as in
|
||||
* thing config)
|
||||
*/
|
||||
@Activate
|
||||
public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService,
|
||||
@Reference LocaleProvider localeProvider, @Reference TranslationProvider i18nProvider,
|
||||
@Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
|
||||
Map<String, Object> configProperties) {
|
||||
logger.debug("Activate Shelly HandlerFactory");
|
||||
super.activate(componentContext);
|
||||
|
||||
messages = new ShellyTranslationProvider(bundleContext.getBundle(), i18nProvider, localeProvider);
|
||||
localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
|
||||
if (localIP.isEmpty()) {
|
||||
logger.warn("{}", messages.get("message.init.noipaddress"));
|
||||
}
|
||||
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
httpPort = HttpServiceUtil.getHttpServicePort(componentContext.getBundleContext());
|
||||
if (httpPort == -1) {
|
||||
httpPort = 8080;
|
||||
}
|
||||
logger.debug("Using OH HTTP port {}", httpPort);
|
||||
|
||||
this.coapServer = new ShellyCoapServer();
|
||||
|
||||
// Save bindingConfig & pass it to all registered listeners
|
||||
bindingConfig.updateFromProperties(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
String thingType = thingTypeUID.getId();
|
||||
ShellyBaseHandler handler = null;
|
||||
|
||||
if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyProtectedHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort,
|
||||
httpClient);
|
||||
} else if (thingType.equals(THING_TYPE_SHELLYBULB.getId()) || thingType.equals(THING_TYPE_SHELLYDUO.getId())
|
||||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR.getId())
|
||||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE.getId())) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyLightHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyRelayHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
deviceListeners.add(handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
logger.debug("Unable to create Thing Handler instance!");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove handler of things.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof ShellyBaseHandler) {
|
||||
deviceListeners.remove(thingHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch event to registered devices.
|
||||
*
|
||||
* @param deviceName
|
||||
* @param componentIndex Index of component, e.g. 2 for relay2
|
||||
* @param eventType Type of event, e.g. light
|
||||
* @param parameters Input parameters from URL, e.g. on sensor reports
|
||||
*/
|
||||
public void onEvent(String ipAddress, String deviceName, String componentIndex, String eventType,
|
||||
Map<String, String> parameters) {
|
||||
logger.trace("{}: Dispatch event to thing handler", deviceName);
|
||||
for (ShellyBaseHandler listener : deviceListeners) {
|
||||
if (listener.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) {
|
||||
// event processed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShellyBindingConfiguration getBindingConfig() {
|
||||
return bindingConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link CarNetException} implements an extension to the standard Exception class. This allows to keep also the
|
||||
* result of the last API call (e.g. including the http status code in the message).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyApiException extends Exception {
|
||||
private static final long serialVersionUID = -5809459454769761821L;
|
||||
|
||||
private ShellyApiResult apiResult = new ShellyApiResult();
|
||||
private static String NONE = "none";
|
||||
|
||||
public ShellyApiException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
|
||||
public ShellyApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ShellyApiException(ShellyApiResult res) {
|
||||
super(NONE);
|
||||
apiResult = res;
|
||||
}
|
||||
|
||||
public ShellyApiException(String message, Exception exception) {
|
||||
super(message, exception);
|
||||
}
|
||||
|
||||
public ShellyApiException(ShellyApiResult result, Exception exception) {
|
||||
super(exception);
|
||||
apiResult = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return isEmpty() ? "" : nonNullString(super.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String message = nonNullString(super.getMessage());
|
||||
String cause = getCauseClass().toString();
|
||||
if (!isEmpty()) {
|
||||
if (isUnknownHost()) {
|
||||
String[] string = message.split(": "); // java.net.UnknownHostException: api.rach.io
|
||||
message = MessageFormat.format("Unable to connect to {0} (Unknown host / Network down / Low signal)",
|
||||
string[1]);
|
||||
} else if (isMalformedURL()) {
|
||||
message = MessageFormat.format("Invalid URL: {0}", apiResult.getUrl());
|
||||
} else if (isTimeout()) {
|
||||
message = MessageFormat.format("Device unreachable or API Timeout ({0})", apiResult.getUrl());
|
||||
} else {
|
||||
message = MessageFormat.format("{0} ({1})", message, cause);
|
||||
}
|
||||
} else {
|
||||
message = apiResult.toString();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean isApiException() {
|
||||
return getCauseClass() == ShellyApiException.class;
|
||||
}
|
||||
|
||||
public boolean isTimeout() {
|
||||
Class<?> extype = !isEmpty() ? getCauseClass() : null;
|
||||
return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
|
||||
|| (extype == InterruptedException.class) || getMessage().toLowerCase().contains("timeout"));
|
||||
}
|
||||
|
||||
public boolean isHttpAccessUnauthorized() {
|
||||
return apiResult.isHttpAccessUnauthorized();
|
||||
}
|
||||
|
||||
public boolean isUnknownHost() {
|
||||
return getCauseClass() == MalformedURLException.class;
|
||||
}
|
||||
|
||||
public boolean isMalformedURL() {
|
||||
return getCauseClass() == UnknownHostException.class;
|
||||
}
|
||||
|
||||
public boolean isJSONException() {
|
||||
return getCauseClass() == JsonSyntaxException.class;
|
||||
}
|
||||
|
||||
public ShellyApiResult getApiResult() {
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return nonNullString(super.getMessage()).equals(NONE);
|
||||
}
|
||||
|
||||
private static String nonNullString(@Nullable String s) {
|
||||
return s != null ? s : "";
|
||||
}
|
||||
|
||||
private Class<?> getCauseClass() {
|
||||
Throwable cause = getCause();
|
||||
if (getCause() != null) {
|
||||
return cause.getClass();
|
||||
}
|
||||
return ShellyApiException.class;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpStatus.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
|
||||
/**
|
||||
* The {@link ShellyApiResult} wraps up the API result and provides some more information like url, http code, received
|
||||
* response etc.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyApiResult {
|
||||
public String url = "";
|
||||
public String method = "";
|
||||
public String response = "";
|
||||
public int httpCode = -1;
|
||||
public String httpReason = "";
|
||||
|
||||
public ShellyApiResult() {
|
||||
}
|
||||
|
||||
public ShellyApiResult(String method, String url) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public ShellyApiResult(ContentResponse contentResponse) {
|
||||
fillFromResponse(contentResponse);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return !url.isEmpty() ? method + " " + url : "";
|
||||
}
|
||||
|
||||
public String getHttpResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getUrl() + " > " + getHttpResponse();
|
||||
}
|
||||
|
||||
public boolean isHttpOk() {
|
||||
return httpCode == OK_200;
|
||||
}
|
||||
|
||||
public boolean isHttpAccessUnauthorized() {
|
||||
return (httpCode == UNAUTHORIZED_401 || response.contains(SHELLY_APIERR_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
public boolean isHttpTimeout() {
|
||||
return httpCode == -1 || response.toUpperCase().contains(SHELLY_APIERR_TIMEOUT.toLowerCase());
|
||||
}
|
||||
|
||||
public boolean isHttpServerError() {
|
||||
return httpCode == INTERNAL_SERVER_ERROR_500;
|
||||
}
|
||||
|
||||
public boolean isNotCalibrtated() {
|
||||
return getHttpResponse().contains(SHELLY_APIERR_NOT_CALIBRATED);
|
||||
}
|
||||
|
||||
private void fillFromResponse(@Nullable ContentResponse contentResponse) {
|
||||
if (contentResponse != null) {
|
||||
String r = contentResponse.getContentAsString();
|
||||
response = r != null ? r : "";
|
||||
httpCode = contentResponse.getStatus();
|
||||
httpReason = contentResponse.getReason();
|
||||
|
||||
Request request = contentResponse.getRequest();
|
||||
if (request != null) {
|
||||
url = request.getURI().toString();
|
||||
method = request.getMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
|
||||
* call. This is used to be more dynamic in controlling the device, but also to overcome some issues in the API (e.g.
|
||||
* RGBW2 returns "no meter" even it has one)
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyDeviceProfile {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class);
|
||||
|
||||
public boolean initialized = false; // true when initialized
|
||||
|
||||
public String thingName = "";
|
||||
public String deviceType = "";
|
||||
|
||||
public String settingsJson = "";
|
||||
public ShellySettingsGlobal settings = new ShellySettingsGlobal();
|
||||
public ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
|
||||
public String hostname = "";
|
||||
public String mode = "";
|
||||
public boolean discoverable = true;
|
||||
|
||||
public String hwRev = "";
|
||||
public String hwBatchId = "";
|
||||
public String mac = "";
|
||||
public String fwId = "";
|
||||
public String fwVersion = "";
|
||||
public String fwDate = "";
|
||||
|
||||
public boolean hasRelays = false; // true if it has at least 1 power meter
|
||||
public int numRelays = 0; // number of relays/outputs
|
||||
public int numRollers = 0; // number of Rollers, usually 1
|
||||
public boolean isRoller = false; // true for Shelly2 in roller mode
|
||||
public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
|
||||
|
||||
public int numMeters = 0;
|
||||
public boolean isEMeter = false; // true for ShellyEM/3EM
|
||||
|
||||
public boolean isLight = false; // true if it is a Shelly Bulb/RGBW2
|
||||
public boolean isBulb = false; // true only if it is a Bulb
|
||||
public boolean isDuo = false; // true only if it is a Duo
|
||||
public boolean isRGBW2 = false; // true only if it a a RGBW2
|
||||
public boolean inColor = false; // true if bulb/rgbw2 is in color mode
|
||||
|
||||
public boolean isSensor = false; // true for HT & Smoke
|
||||
public boolean hasBattery = false; // true if battery device
|
||||
public boolean isSense = false; // true if thing is a Shelly Sense
|
||||
public boolean isHT = false; // true for H&T
|
||||
public boolean isDW = false; // true for Door Window sensor
|
||||
public boolean isButton = false; // true for a Shelly Button 1
|
||||
public boolean isIX3 = false; // true for a Shelly IX
|
||||
|
||||
public int minTemp = 0; // Bulb/Duo: Min Light Temp
|
||||
public int maxTemp = 0; // Bulb/Duo: Max Light Temp
|
||||
|
||||
public int updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||
|
||||
public Map<String, String> irCodes = new HashMap<>(); // Sense: list of stored IR codes
|
||||
|
||||
public ShellyDeviceProfile() {
|
||||
}
|
||||
|
||||
public ShellyDeviceProfile initialize(String thingType, String json) throws ShellyApiException {
|
||||
Gson gson = new Gson();
|
||||
|
||||
initialized = false;
|
||||
|
||||
try {
|
||||
initFromThingType(thingType);
|
||||
settingsJson = json;
|
||||
settings = gson.fromJson(json, ShellySettingsGlobal.class);
|
||||
} catch (IllegalArgumentException | JsonSyntaxException e) {
|
||||
throw new ShellyApiException(
|
||||
thingName + ": Unable to transform settings JSON " + e.toString() + ", json='" + json + "'", e);
|
||||
}
|
||||
|
||||
// General settings
|
||||
deviceType = getString(settings.device.type);
|
||||
mac = getString(settings.device.mac);
|
||||
hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty()
|
||||
? settings.device.hostname.toLowerCase()
|
||||
: "shelly-" + mac.toUpperCase().substring(6, 11);
|
||||
mode = !getString(settings.mode).isEmpty() ? getString(settings.mode).toLowerCase() : "";
|
||||
hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : "";
|
||||
hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : "";
|
||||
fwDate = substringBefore(settings.fw, "/");
|
||||
fwVersion = substringBetween(settings.fw, "/", "@");
|
||||
fwId = substringAfter(settings.fw, "@");
|
||||
discoverable = (settings.discoverable == null) || settings.discoverable;
|
||||
|
||||
inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
|
||||
|
||||
numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0;
|
||||
if ((numRelays > 0) && (settings.relays == null)) {
|
||||
numRelays = 0;
|
||||
}
|
||||
isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
|
||||
hasRelays = (numRelays > 0) || isDimmer;
|
||||
numRollers = getInteger(settings.device.numRollers);
|
||||
|
||||
isEMeter = settings.emeters != null;
|
||||
numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
|
||||
if ((numMeters == 0) && isLight) {
|
||||
// RGBW2 doesn't report, but has one
|
||||
numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
|
||||
}
|
||||
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
|
||||
|
||||
if (settings.sleepMode != null) {
|
||||
// Sensor, usally 12h
|
||||
updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
|
||||
: settings.sleepMode.period * 3600; // hours
|
||||
updatePeriod += 600; // give 10min extra
|
||||
} else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
|
||||
// Derive from CoAP update interval, usually 2*15+5s=50sec -> 70sec
|
||||
updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 3 * getInteger(settings.coiot.updatePeriod)) + 10;
|
||||
} else {
|
||||
updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean containsEventUrl(String eventType) {
|
||||
return containsEventUrl(settingsJson, eventType);
|
||||
}
|
||||
|
||||
public boolean containsEventUrl(String json, String eventType) {
|
||||
String settings = json.toLowerCase();
|
||||
return settings.contains((eventType + SHELLY_EVENTURL_SUFFIX).toLowerCase());
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public void initFromThingType(String name) {
|
||||
String thingType = (name.contains("-") ? substringBefore(name, "-") : name).toLowerCase().trim();
|
||||
if (thingType.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
|
||||
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR);
|
||||
isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
|
||||
isLight = isBulb || isDuo || isRGBW2;
|
||||
if (isLight) {
|
||||
minTemp = isBulb ? MIN_COLOR_TEMP_BULB : MIN_COLOR_TEMP_DUO;
|
||||
maxTemp = isBulb ? MAX_COLOR_TEMP_BULB : MAX_COLOR_TEMP_DUO;
|
||||
}
|
||||
|
||||
boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
|
||||
boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
|
||||
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
|
||||
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
|
||||
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
|
||||
isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
|
||||
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
|
||||
isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isSense;
|
||||
hasBattery = isHT || isFlood || isDW || isSmoke || isButton; // we assume that Sense is connected to // the
|
||||
// charger
|
||||
}
|
||||
|
||||
public String getControlGroup(int i) {
|
||||
if (i < 0) {
|
||||
logger.debug("{}: Invalid index {} for getControlGroup()", thingName, i);
|
||||
return "";
|
||||
}
|
||||
int idx = i + 1;
|
||||
if (isDimmer) {
|
||||
return CHANNEL_GROUP_DIMMER_CONTROL;
|
||||
} else if (isRoller) {
|
||||
return numRollers == 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
|
||||
} else if (hasRelays) {
|
||||
return numRelays == 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
|
||||
} else if (isLight) {
|
||||
return numRelays == 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
|
||||
} else if (isButton) {
|
||||
return CHANNEL_GROUP_STATUS;
|
||||
} else if (isSensor) {
|
||||
return CHANNEL_GROUP_SENSOR;
|
||||
}
|
||||
|
||||
// e.g. ix3
|
||||
return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx;
|
||||
}
|
||||
|
||||
public String getInputGroup(int i) {
|
||||
int idx = i + 1; // group names are 1-based
|
||||
if (isRGBW2) {
|
||||
return CHANNEL_GROUP_LIGHT_CONTROL;
|
||||
} else if (isIX3) {
|
||||
return CHANNEL_GROUP_STATUS + idx;
|
||||
} else if (isButton) {
|
||||
return CHANNEL_GROUP_STATUS;
|
||||
} else if (isRoller) {
|
||||
return numRelays <= 2 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
|
||||
} else {
|
||||
// Device has 1 input per relay: 0=off, 1+2 depend on switch mode
|
||||
return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
|
||||
}
|
||||
}
|
||||
|
||||
public String getInputChannel(int i) {
|
||||
int idx = i + 1; // channel names are 1-based
|
||||
if (isRGBW2 || isIX3) {
|
||||
return CHANNEL_INPUT; // RGBW2 has only 1 channel
|
||||
} else if (hasRelays) {
|
||||
return CHANNEL_INPUT + idx;
|
||||
}
|
||||
return CHANNEL_INPUT;
|
||||
}
|
||||
|
||||
public boolean inButtonMode(int idx) {
|
||||
if (idx < 0) {
|
||||
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
|
||||
return false;
|
||||
}
|
||||
String btnType = "";
|
||||
if (isButton) {
|
||||
return true;
|
||||
} else if (isIX3) {
|
||||
if ((settings.inputs != null) && (idx >= 0) && (idx < settings.inputs.size())) {
|
||||
ShellySettingsInput input = settings.inputs.get(idx);
|
||||
btnType = input.btnType;
|
||||
}
|
||||
} else if (isDimmer) {
|
||||
if ((settings.dimmers != null) && (idx >= 0) && (idx < settings.dimmers.size())) {
|
||||
ShellySettingsDimmer dimmer = settings.dimmers.get(idx);
|
||||
btnType = dimmer.btnType;
|
||||
}
|
||||
} else if ((settings.relays != null) && (idx >= 0) && (idx < settings.relays.size())) {
|
||||
ShellySettingsRelay relay = settings.relays.get(idx);
|
||||
btnType = relay.btnType;
|
||||
}
|
||||
|
||||
if (btnType.equals(SHELLY_BTNT_MOMENTARY) || btnType.equals(SHELLY_BTNT_MOM_ON_RELEASE)
|
||||
|| btnType.equals(SHELLY_BTNT_DETACHED) || btnType.equals(SHELLY_BTNT_ONE_BUTTON)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
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.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ShellyEventServlet} implements a servlet. which is called by the Shelly device to signnal events (button,
|
||||
* 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
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL, immediate = true)
|
||||
public class ShellyEventServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 549582869577534569L;
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
|
||||
|
||||
private final HttpService httpService;
|
||||
private final ShellyHandlerFactory handlerFactory;
|
||||
|
||||
@Activate
|
||||
public ShellyEventServlet(@Reference HttpService httpService, @Reference ShellyHandlerFactory handlerFactory,
|
||||
Map<String, Object> config) {
|
||||
this.httpService = httpService;
|
||||
this.handlerFactory = handlerFactory;
|
||||
try {
|
||||
httpService.registerServlet(SHELLY_CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
|
||||
logger.debug("ShellyEventServlet started at '{}'", SHELLY_CALLBACK_URI);
|
||||
} catch (NamespaceException | ServletException | IllegalArgumentException e) {
|
||||
logger.warn("Could not start CallbackServlet", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
httpService.unregister(SHELLY_CALLBACK_URI);
|
||||
logger.debug("ShellyEventServlet stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
|
||||
throws ServletException, IOException, IllegalArgumentException {
|
||||
String path = "";
|
||||
String deviceName = "";
|
||||
String index = "";
|
||||
String type = "";
|
||||
|
||||
if ((request == null) || (resp == null)) {
|
||||
logger.debug("request or resp must not be null!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
path = 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/roller/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
||||
deviceName = substringBetween(path, "/event/", "/").toLowerCase();
|
||||
if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
|
||||
|| path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
|
||||
index = substringAfterLast(path, "/").toLowerCase();
|
||||
type = substringBetween(path, deviceName + "/", "/" + index);
|
||||
} else {
|
||||
index = "";
|
||||
type = substringAfterLast(path, "/").toLowerCase();
|
||||
}
|
||||
logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
|
||||
for (Map.Entry<String, String[]> p : parameters.entrySet()) {
|
||||
parms.put(p.getKey(), p.getValue()[0]);
|
||||
|
||||
}
|
||||
handlerFactory.onEvent(ipAddress, deviceName, index, type, parms);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Exception processing callback: path={}; index={}, type={}, parameters={}", deviceName,
|
||||
path, index, type, request.getParameterMap().toString());
|
||||
} finally {
|
||||
resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
resp.getWriter().write("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySendKeyList;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySenseKeyCode;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDevice;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLight;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/**
|
||||
* {@link ShellyHttpApi} 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
|
||||
public class ShellyHttpApi {
|
||||
public static final String HTTP_HEADER_AUTH = "Authorization";
|
||||
public static final String HTTP_AUTH_TYPE_BASIC = "Basic";
|
||||
public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyHttpApi.class);
|
||||
private final HttpClient httpClient;
|
||||
private ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
private String thingName;
|
||||
private final Gson gson = new Gson();
|
||||
private int timeoutErrors = 0;
|
||||
private int timeoutsRecovered = 0;
|
||||
|
||||
private ShellyDeviceProfile profile = new ShellyDeviceProfile();
|
||||
|
||||
public ShellyHttpApi(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
this.thingName = thingName;
|
||||
setConfig(thingName, config);
|
||||
profile.initFromThingType(thingName);
|
||||
}
|
||||
|
||||
public void setConfig(String thingName, ShellyThingConfiguration config) {
|
||||
this.thingName = thingName;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public ShellySettingsDevice getDevInfo() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the device profile
|
||||
*
|
||||
* @param thingType Type of DEVICE as returned from the thing properties (based on discovery)
|
||||
* @return Initialized ShellyDeviceProfile
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||
String json = request(SHELLY_URL_SETTINGS);
|
||||
if (json.contains("\"type\":\"SHDM-")) {
|
||||
logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName);
|
||||
json = fixDimmerJson(json);
|
||||
}
|
||||
|
||||
// Map settings to device profile for Light and Sense
|
||||
profile.initialize(thingType, json);
|
||||
|
||||
// 2nd level initialization
|
||||
profile.thingName = profile.hostname;
|
||||
if (profile.isLight && (profile.numMeters == 0)) {
|
||||
logger.debug("{}: Get number of meters from light status", thingName);
|
||||
ShellyStatusLight status = getLightStatus();
|
||||
profile.numMeters = status.meters != null ? status.meters.size() : 0;
|
||||
}
|
||||
if (profile.isSense) {
|
||||
profile.irCodes = getIRCodeList();
|
||||
logger.debug("{}: Sense stored key list loaded, {} entries.", thingName, profile.irCodes.size());
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return profile.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get generic device settings/status. Json returned from API will be mapped to a Gson object
|
||||
*
|
||||
* @return Device settings/status as ShellySettingsStatus object
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public ShellySettingsStatus getStatus() throws ShellyApiException {
|
||||
String json = "";
|
||||
try {
|
||||
json = request(SHELLY_URL_STATUS);
|
||||
// Dimmer2 returns invalid json type for loaderror :-(
|
||||
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,");
|
||||
json = json.replace("\"loaderror\":1,", "\"loaderror\":true,");
|
||||
ShellySettingsStatus status = gson.fromJson(json, ShellySettingsStatus.class);
|
||||
status.json = json;
|
||||
return status;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new ShellyApiException("Unable to parse JSON: " + json, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class);
|
||||
}
|
||||
|
||||
public ShellyShortLightStatus setRelayTurn(Integer id, String turnMode) throws ShellyApiException {
|
||||
return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
|
||||
ShellyShortLightStatus.class);
|
||||
}
|
||||
|
||||
public void setBrightness(Integer id, Integer brightness, boolean autoOn) throws ShellyApiException {
|
||||
String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : "";
|
||||
request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness.toString());
|
||||
}
|
||||
|
||||
public ShellyControlRoller getRollerStatus(Integer rollerIndex) throws ShellyApiException {
|
||||
String uri = SHELLY_URL_CONTROL_ROLLER + "/" + rollerIndex.toString() + "/pos";
|
||||
return callApi(uri, ShellyControlRoller.class);
|
||||
}
|
||||
|
||||
public void setRollerTurn(Integer relayIndex, String turnMode) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=" + turnMode);
|
||||
}
|
||||
|
||||
public void setRollerPos(Integer relayIndex, Integer position) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=to_pos&roller_pos="
|
||||
+ position.toString());
|
||||
}
|
||||
|
||||
public void setRollerTimer(Integer relayIndex, Integer timer) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?timer=" + timer.toString());
|
||||
}
|
||||
|
||||
public ShellyShortLightStatus getLightStatus(Integer index) throws ShellyApiException {
|
||||
return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class);
|
||||
}
|
||||
|
||||
public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
|
||||
ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class);
|
||||
if (profile.isSense) {
|
||||
// complete reported data, map C to F or vice versa: C=(F - 32) * 0.5556;
|
||||
status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value
|
||||
: ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(getDouble(status.tmp.value))
|
||||
.doubleValue();
|
||||
status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value
|
||||
: Units.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT).convert(getDouble(status.tmp.value))
|
||||
.doubleValue();
|
||||
}
|
||||
if ((status.charger == null) && (status.externalPower != null)) {
|
||||
// SHelly H&T uses external_power, Sense uses charger
|
||||
status.charger = status.externalPower != 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setTimer(Integer index, String timerName, Double value) throws ShellyApiException {
|
||||
String type = SHELLY_CLASS_RELAY;
|
||||
if (profile.isRoller) {
|
||||
type = SHELLY_CLASS_ROLLER;
|
||||
} else if (profile.isLight) {
|
||||
type = SHELLY_CLASS_LIGHT;
|
||||
}
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "="
|
||||
+ ((Integer) value.intValue()).toString();
|
||||
request(uri);
|
||||
}
|
||||
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
|
||||
}
|
||||
|
||||
public ShellySettingsLight getLightSettings() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class);
|
||||
}
|
||||
|
||||
public ShellyStatusLight getLightStatus() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class);
|
||||
}
|
||||
|
||||
public void setLightSetting(String parm, String value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change between White and Color Mode
|
||||
*
|
||||
* @param mode
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setLightMode(String mode) throws ShellyApiException {
|
||||
if (!mode.isEmpty() && !profile.mode.equals(mode)) {
|
||||
setLightSetting(SHELLY_API_MODE, mode);
|
||||
profile.mode = mode;
|
||||
profile.inColor = profile.isLight && profile.mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single light parameter
|
||||
*
|
||||
* @param lightIndex Index of the light, usually 0 for Bulb and 0..3 for RGBW2.
|
||||
* @param parm Name of the parameter (see API spec)
|
||||
* @param value The value
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setLightParm(Integer lightIndex, String parm, String value) throws ShellyApiException {
|
||||
// Bulb, RGW2: /<color mode>/<light id>?parm?value
|
||||
// Dimmer: /light/<light id>?parm=value
|
||||
request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value);
|
||||
}
|
||||
|
||||
public void setLightParms(Integer lightIndex, Map<String, String> parameters) throws ShellyApiException {
|
||||
String url = getControlUriPrefix(lightIndex) + "?";
|
||||
int i = 0;
|
||||
for (String key : parameters.keySet()) {
|
||||
if (i > 0) {
|
||||
url = url + "&";
|
||||
}
|
||||
url = url + key + "=" + parameters.get(key);
|
||||
i++;
|
||||
}
|
||||
request(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the IR Code list from the Shelly Sense device. The list could be customized by the user. It defines the
|
||||
* symbolic key code, which gets
|
||||
* map into a PRONTO code
|
||||
*
|
||||
* @return Map of key codes
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public Map<String, String> getIRCodeList() throws ShellyApiException {
|
||||
String result = request(SHELLY_URL_LIST_IR);
|
||||
// take pragmatic approach to make the returned JSon into named arrays for Gson parsing
|
||||
String keyList = substringAfter(result, "[");
|
||||
keyList = substringBeforeLast(keyList, "]");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("\",\""), "\", \"name\": \"");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} ");
|
||||
String json = "{\"key_codes\" : [" + keyList + "] }";
|
||||
|
||||
ShellySendKeyList codes = gson.fromJson(json, ShellySendKeyList.class);
|
||||
Map<String, String> list = new HashMap<>();
|
||||
for (ShellySenseKeyCode key : codes.keyCodes) {
|
||||
list.put(key.id, key.name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a IR key code to the Shelly Sense.
|
||||
*
|
||||
* @param keyCode A keyCoud could be a symbolic name (as defined in the key map on the device) or a PRONTO Code in
|
||||
* plain or hex64 format
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
|
||||
String type = "";
|
||||
if (profile.irCodes.containsKey(keyCode)) {
|
||||
type = SHELLY_IR_CODET_STORED;
|
||||
} else if ((keyCode.length() > 4) && keyCode.contains(" ")) {
|
||||
type = SHELLY_IR_CODET_PRONTO;
|
||||
} else {
|
||||
type = SHELLY_IR_CODET_PRONTO_HEX;
|
||||
}
|
||||
String url = SHELLY_URL_SEND_IR + "?type=" + type;
|
||||
if (type.equals(SHELLY_IR_CODET_STORED)) {
|
||||
url = url + "&" + "id=" + keyCode;
|
||||
} else if (type.equals(SHELLY_IR_CODET_PRONTO)) {
|
||||
String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8));
|
||||
if (code == null) {
|
||||
throw new IllegalArgumentException("Unable to BASE64 encode the pronto code: " + keyCode);
|
||||
}
|
||||
url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code;
|
||||
} else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) {
|
||||
url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode;
|
||||
}
|
||||
request(url);
|
||||
}
|
||||
|
||||
public void setSenseSetting(String setting, String value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + setting + "=" + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set event callback URLs. Depending on the device different event types are supported. In fact all of them will be
|
||||
* redirected to the binding's servlet and act as a trigger to schedule a status update
|
||||
*
|
||||
* @param ShellyApiException
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setActionURLs() throws ShellyApiException {
|
||||
setRelayEvents();
|
||||
setDimmerEvents();
|
||||
setSensorEventUrls();
|
||||
}
|
||||
|
||||
private void setRelayEvents() throws ShellyApiException {
|
||||
if (profile.settings.relays != null) {
|
||||
int num = profile.isRoller ? profile.numRollers : profile.numRelays;
|
||||
for (int i = 0; i < num; i++) {
|
||||
setEventUrls(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDimmerEvents() throws ShellyApiException {
|
||||
if (profile.settings.dimmers != null) {
|
||||
for (int i = 0; i < profile.settings.dimmers.size(); i++) {
|
||||
setEventUrls(i);
|
||||
}
|
||||
} else if (profile.isLight) {
|
||||
setEventUrls(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sensor Action URLs
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void setSensorEventUrls() throws ShellyApiException, ShellyApiException {
|
||||
if (profile.isSensor) {
|
||||
logger.debug("{}: Set Sensor Reporting URL", thingName);
|
||||
setEventUrl(config.eventsSensorReport, SHELLY_EVENT_SENSORREPORT, SHELLY_EVENT_DARK, SHELLY_EVENT_TWILIGHT,
|
||||
SHELLY_EVENT_FLOOD_DETECTED, SHELLY_EVENT_FLOOD_GONE, SHELLY_EVENT_OPEN, SHELLY_EVENT_CLOSE,
|
||||
SHELLY_EVENT_VIBRATION, SHELLY_EVENT_ALARM_MILD, SHELLY_EVENT_ALARM_HEAVY, SHELLY_EVENT_ALARM_OFF,
|
||||
SHELLY_EVENT_TEMP_OVER, SHELLY_EVENT_TEMP_UNDER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/delete Relay/Roller/Dimmer Action URLs
|
||||
*
|
||||
* @param index Device Index (0-based)
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void setEventUrls(Integer index) throws ShellyApiException {
|
||||
if (profile.isRoller) {
|
||||
setEventUrl(EVENT_TYPE_ROLLER, 0, config.eventsRoller, SHELLY_EVENT_ROLLER_OPEN, SHELLY_EVENT_ROLLER_CLOSE,
|
||||
SHELLY_EVENT_ROLLER_STOP);
|
||||
} else if (profile.isDimmer) {
|
||||
// 2 set of URLs
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsButton, SHELLY_EVENT_BTN1_ON, SHELLY_EVENT_BTN1_OFF,
|
||||
SHELLY_EVENT_BTN2_ON, SHELLY_EVENT_BTN2_OFF);
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH1, SHELLY_EVENT_LONGPUSH1,
|
||||
SHELLY_EVENT_SHORTPUSH2, SHELLY_EVENT_LONGPUSH2);
|
||||
|
||||
// Relay output
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
} else if (profile.hasRelays) {
|
||||
// Standard relays: btn_xxx, out_xxx, short/longpush URLs
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsButton, SHELLY_EVENT_BTN_ON, SHELLY_EVENT_BTN_OFF);
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH, SHELLY_EVENT_LONGPUSH);
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
} else if (profile.isLight) {
|
||||
// Duo, Bulb
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEventUrl(boolean enabled, String... eventTypes) throws ShellyApiException {
|
||||
if (config.localIp.isEmpty()) {
|
||||
throw new ShellyApiException(thingName + ": Local IP address was not detected, can't build Callback URL");
|
||||
}
|
||||
for (String eventType : eventTypes) {
|
||||
if (profile.containsEventUrl(eventType)) {
|
||||
// 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 urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + eclass + urlParm;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||
if (!enabled && !profile.settingsJson.contains(testUrl)) {
|
||||
// Don't set URL to null when the current one doesn't point to this OH
|
||||
// Don't interfere with a 3rd party App
|
||||
continue;
|
||||
}
|
||||
if (!profile.settingsJson.contains(testUrl)) {
|
||||
// Current Action URL is != new URL
|
||||
logger.debug("{}: Set new url for event type {}: {}", thingName, eventType, newUrl);
|
||||
request(SHELLY_URL_SETTINGS + "?" + mkEventUrl(eventType) + "=" + urlEncode(newUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setEventUrl(String deviceClass, Integer index, boolean enabled, String... eventTypes)
|
||||
throws ShellyApiException {
|
||||
for (String eventType : eventTypes) {
|
||||
if (profile.containsEventUrl(eventType)) {
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
|
||||
if (!enabled && !profile.settingsJson.contains(test)) {
|
||||
// Don't set URL to null when the current one doesn't point to this OH
|
||||
// Don't interfere with a 3rd party App
|
||||
continue;
|
||||
}
|
||||
test = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||
if (!profile.settingsJson.contains(test)) {
|
||||
// Current Action URL is != new URL
|
||||
logger.debug("{}: Set URL for type {} to {}", thingName, eventType, newUrl);
|
||||
request(SHELLY_URL_SETTINGS + "/" + deviceClass + "/" + index + "?" + mkEventUrl(eventType) + "="
|
||||
+ urlEncode(newUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String mkEventUrl(String eventType) {
|
||||
return eventType + SHELLY_EVENTURL_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit GET request and return response, check for invalid responses
|
||||
*
|
||||
* @param uri: URI (e.g. "/settings")
|
||||
*/
|
||||
public <T> T callApi(String uri, Class<T> classOfT) throws ShellyApiException {
|
||||
try {
|
||||
String json = request(uri);
|
||||
return gson.fromJson(json, classOfT);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new ShellyApiException("Unable to convert JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String request(String uri) throws ShellyApiException {
|
||||
ShellyApiResult apiResult = new ShellyApiResult();
|
||||
int retries = 3;
|
||||
boolean timeout = false;
|
||||
while (retries > 0) {
|
||||
try {
|
||||
apiResult = innerRequest(HttpMethod.GET, uri);
|
||||
if (timeout) {
|
||||
logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered,
|
||||
apiResult.getUrl());
|
||||
timeoutsRecovered++;
|
||||
}
|
||||
return apiResult.response; // successful
|
||||
} catch (ShellyApiException e) {
|
||||
if ((!e.isTimeout() && !apiResult.isHttpServerError()) || profile.hasBattery || (retries == 0)) {
|
||||
// Sensor in sleep mode or API exception for non-battery device or retry counter expired
|
||||
throw e; // non-timeout exception
|
||||
}
|
||||
|
||||
timeout = true;
|
||||
retries--;
|
||||
timeoutErrors++; // count the retries
|
||||
logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString());
|
||||
}
|
||||
}
|
||||
throw new ShellyApiException("Inconsistent API result or Timeout"); // successful
|
||||
}
|
||||
|
||||
private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException {
|
||||
Request request = null;
|
||||
String url = "http://" + config.deviceIp + uri;
|
||||
ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url);
|
||||
|
||||
try {
|
||||
request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
if (!config.userId.isEmpty()) {
|
||||
String value = config.userId + ":" + config.password;
|
||||
request.header(HTTP_HEADER_AUTH,
|
||||
HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes()));
|
||||
}
|
||||
request.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON);
|
||||
logger.trace("{}: HTTP {} for {}", thingName, method, url);
|
||||
|
||||
// Do request and get response
|
||||
ContentResponse contentResponse = request.send();
|
||||
apiResult = new ShellyApiResult(contentResponse);
|
||||
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
||||
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
||||
|
||||
// validate response, API errors are reported as Json
|
||||
if (contentResponse.getStatus() != HttpStatus.OK_200) {
|
||||
throw new ShellyApiException(apiResult);
|
||||
}
|
||||
if (response == null || response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
|
||||
throw new ShellyApiException("Unexpected response: " + response);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
|
||||
ShellyApiException ex = new ShellyApiException(apiResult, e);
|
||||
if (!ex.isTimeout()) { // will be handled by the caller
|
||||
logger.trace("{}: API call returned exception", thingName, ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
public String getControlUriPrefix(Integer id) {
|
||||
String uri = "";
|
||||
if (profile.isLight || profile.isDimmer) {
|
||||
if (profile.isDuo || profile.isDimmer) {
|
||||
// Duo + Dimmer
|
||||
uri = SHELLY_URL_CONTROL_LIGHT;
|
||||
} else {
|
||||
// Bulb + RGBW2
|
||||
uri = "/" + (profile.inColor ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
|
||||
}
|
||||
} else {
|
||||
// Roller, Relay
|
||||
uri = SHELLY_URL_CONTROL_RELEAY;
|
||||
}
|
||||
uri = uri + "/" + id;
|
||||
return uri;
|
||||
}
|
||||
|
||||
public int getTimeoutErrors() {
|
||||
return timeoutErrors;
|
||||
}
|
||||
|
||||
public int getTimeoutsRecovered() {
|
||||
return timeoutsRecovered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapListener} describes the listening interface to process Coap responses
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyCoIoTInterface {
|
||||
public int getVersion();
|
||||
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
|
||||
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTProtocol} implements common functions for the CoIoT implementations
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTProtocol.class);
|
||||
protected final String thingName;
|
||||
protected final ShellyBaseHandler thingHandler;
|
||||
protected final ShellyDeviceProfile profile;
|
||||
protected final Map<String, CoIotDescrBlk> blkMap;
|
||||
protected final Map<String, CoIotDescrSen> sensorMap;
|
||||
|
||||
// Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish
|
||||
// between a real update or just a repeated status on periodic updates
|
||||
protected int lastCfgCount = -1;
|
||||
protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine
|
||||
protected String[] inputEvent = { "", "", "", "", "", "", "", "" };
|
||||
|
||||
public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
this.thingName = thingName;
|
||||
this.thingHandler = thingHandler;
|
||||
this.blkMap = blkMap;
|
||||
this.sensorMap = sensorMap;
|
||||
this.profile = thingHandler.getProfile();
|
||||
}
|
||||
|
||||
protected boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// Process status information and convert into channel updates
|
||||
// Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
// String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
// : CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
int rIndex = getIdFromBlk(sen);
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "b": // BatteryLevel +
|
||||
updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
|
||||
toQuantityType(s.value, DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "h" /* Humidity */:
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
|
||||
toQuantityType(s.value, DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "m" /* Motion */:
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "l": // Luminosity +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
|
||||
toQuantityType(s.value, DIGITS_LUX, SmartHomeUnits.LUX));
|
||||
break;
|
||||
case "s": // CatchAll
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "state": // Relay status +
|
||||
case "output":
|
||||
updatePower(profile, updates, rIndex, sen, s, sensorUpdates);
|
||||
break;
|
||||
case "input":
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
case "brightness":
|
||||
// already handled by state/output
|
||||
break;
|
||||
case "overtemp": // ++
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "position":
|
||||
// work around: Roller reports 101% instead max 100
|
||||
double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(SHELLY_MAX_ROLLER_POS - pos, SmartHomeUnits.PERCENT));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(pos, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "flood":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "vibration": // DW with FW1.6.5+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "luminositylevel": // +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM, getStringType(s.valueStr));
|
||||
break;
|
||||
case "charger": // Sense
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
// RGBW2/Bulb
|
||||
case "red":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_RED,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "green":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GREEN,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "blue":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_BLUE,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "white":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_WHITE,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "gain":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GAIN,
|
||||
ShellyColorUtils.toPercent((int) s.value, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
|
||||
break;
|
||||
case "sensorerror": // +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr));
|
||||
break;
|
||||
default:
|
||||
// Unknown
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown type
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
|
||||
updates.put(mkChannelId(group, channel), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleInput(CoIotDescrSen sen, CoIotSensor s, String rGroup, Map<String, State> updates) {
|
||||
int idx = getSensorNumber(sen.desc, sen.id) - 1;
|
||||
String iGroup = profile.getInputGroup(idx);
|
||||
String iChannel = profile.getInputChannel(idx);
|
||||
updateChannel(updates, iGroup, iChannel, s.value == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
}
|
||||
|
||||
protected void handleInputEvent(CoIotDescrSen sen, String type, Integer count, Map<String, State> updates) {
|
||||
int idx = getSensorNumber(sen.desc, sen.id) - 1;
|
||||
String group = profile.getInputGroup(idx);
|
||||
if (count == -1) {
|
||||
// event type
|
||||
updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE, new StringType(type));
|
||||
inputEvent[idx] = type;
|
||||
} else {
|
||||
// event count
|
||||
updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(count));
|
||||
if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1)) || (count != lastEventCount[idx]))) {
|
||||
if (profile.isButton || (lastEventCount[idx] != -1)) { // skip the first one if binding was restarted
|
||||
thingHandler.triggerButton(group, inputEvent[idx]);
|
||||
}
|
||||
lastEventCount[idx] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Handles the combined updated of the brightness channel:
|
||||
* brightness$Switch is the OnOffType (power state)
|
||||
* brightness&Value is the brightness value
|
||||
*
|
||||
* @param profile Device profile, required to select the channel group and name
|
||||
* @param updates List of updates. updatePower will add brightness$Switch and brightness&Value if changed
|
||||
* @param id Sensor id from the update
|
||||
* @param sen Sensor description from the update
|
||||
* @param s New sensor value
|
||||
* @param allUpdatesList of updates. This is required, because we need to update both values at the same time
|
||||
*/
|
||||
protected void updatePower(ShellyDeviceProfile profile, Map<String, State> updates, int id, CoIotDescrSen sen,
|
||||
CoIotSensor s, List<CoIotSensor> allUpdates) {
|
||||
String group = "";
|
||||
String channel = CHANNEL_BRIGHTNESS;
|
||||
String checkL = ""; // RGBW-white uses 4 different Power, Brightness, VSwitch values
|
||||
if (profile.isLight || profile.isDimmer) {
|
||||
if (profile.isBulb || profile.inColor) {
|
||||
group = CHANNEL_GROUP_LIGHT_CONTROL;
|
||||
channel = CHANNEL_LIGHT_POWER;
|
||||
} else if (profile.isDuo) {
|
||||
group = CHANNEL_GROUP_WHITE_CONTROL;
|
||||
} else if (profile.isDimmer) {
|
||||
group = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
} else if (profile.isRGBW2) {
|
||||
group = CHANNEL_GROUP_LIGHT_CHANNEL + id;
|
||||
checkL = String.valueOf(id - 1); // id is 1-based, L is 0-based
|
||||
logger.trace("{}: updatePower() for L={}", thingName, checkL);
|
||||
}
|
||||
|
||||
// We need to update brigthtess and on/off state at the same time to avoid "flipping brightness slider" in
|
||||
// the UI
|
||||
Double brightness = -1.0;
|
||||
Double power = -1.0;
|
||||
for (CoIotSensor update : allUpdates) {
|
||||
CoIotDescrSen d = fixDescription(sensorMap.get(update.id), blkMap);
|
||||
if (!checkL.isEmpty() && !d.links.equals(checkL)) {
|
||||
// continue until we find the correct one
|
||||
continue;
|
||||
}
|
||||
if (d.desc.equalsIgnoreCase("brightness")) {
|
||||
brightness = new Double(update.value);
|
||||
} else if (d.desc.equalsIgnoreCase("output") || d.desc.equalsIgnoreCase("state")) {
|
||||
power = new Double(update.value);
|
||||
}
|
||||
}
|
||||
if (power != -1) {
|
||||
updateChannel(updates, group, channel + "$Switch", power == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
if (brightness != -1) {
|
||||
updateChannel(updates, group, channel + "$Value",
|
||||
toQuantityType(power == 1 ? brightness : 0, DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
} else if (profile.hasRelays) {
|
||||
group = profile.numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + id;
|
||||
updateChannel(updates, group, CHANNEL_OUTPUT, s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
} else if (profile.isSensor) {
|
||||
// Sensor state
|
||||
if (profile.isDW) { // Door Window has item type Contact
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
s.value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
|
||||
} else {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find index of Input id, which is required to map to channel name
|
||||
*
|
||||
* @parm sensorDesc D field from sensor update
|
||||
* @param sensorId The id from the sensor update
|
||||
* @return Index of found entry (+1 will be the suffix for the channel name) or null if sensorId is not found
|
||||
*/
|
||||
protected int getSensorNumber(String sensorDesc, String sensorId) {
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, CoIotDescrSen> se : sensorMap.entrySet()) {
|
||||
CoIotDescrSen sen = se.getValue();
|
||||
if (sen.desc.equalsIgnoreCase(sensorDesc)) {
|
||||
idx++; // iterate from input1..2..n
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId) && blkMap.containsKey(sen.links)) {
|
||||
int id = getIdFromBlk(sen);
|
||||
if (id != -1) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected int getIdFromBlk(CoIotDescrSen sen) {
|
||||
int idx = -1;
|
||||
if (blkMap.containsKey(sen.links)) {
|
||||
CoIotDescrBlk blk = blkMap.get(sen.links);
|
||||
String desc = blk.desc.toLowerCase();
|
||||
if (desc.startsWith(SHELLY_CLASS_RELAY) || desc.startsWith(SHELLY_CLASS_ROLLER)
|
||||
|| desc.startsWith(SHELLY_CLASS_EMETER)) {
|
||||
if (desc.contains("_")) { // CoAP v2
|
||||
idx = Integer.parseInt(substringAfter(desc, "_"));
|
||||
} else { // CoAP v1
|
||||
if (desc.substring(0, 5).equalsIgnoreCase(SHELLY_CLASS_RELAY)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_RELAY));
|
||||
}
|
||||
if (desc.substring(0, 6).equalsIgnoreCase(SHELLY_CLASS_ROLLER)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_ROLLER));
|
||||
}
|
||||
if (desc.substring(0, SHELLY_CLASS_EMETER.length()).equalsIgnoreCase(SHELLY_CLASS_EMETER)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_EMETER));
|
||||
}
|
||||
}
|
||||
idx = idx + 1; // make it 1-based (sen.L is 0-based)
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get matching sensorId for updates on "External Temperature" - there might be more than 1 sensor.
|
||||
*
|
||||
* @param sensorId sensorId to map into a channel index
|
||||
* @return Index of the corresponding channel (e.g. 0 build temperature1, 1->temperagture2...)
|
||||
*/
|
||||
protected int getExtTempId(String sensorId) {
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, CoIotDescrSen> se : sensorMap.entrySet()) {
|
||||
CoIotDescrSen sen = se.getValue();
|
||||
if (sen.desc.equalsIgnoreCase("external_temperature") || sen.desc.equalsIgnoreCase("external temperature c")
|
||||
|| (sen.desc.equalsIgnoreCase("extTemp") && !sen.unit.equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT))) {
|
||||
idx++; // iterate from temperature1..2..n
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected ShellyDeviceProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 1
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion1.class);
|
||||
|
||||
public ShellyCoIoTVersion1(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
super(thingName, thingHandler, blkMap, sensorMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return ShellyCoapJSonDTO.COIOT_VERSION_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// first check the base implementation
|
||||
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
// process by the base class
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process status information and convert into channel updates
|
||||
Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "t": // Temperature +
|
||||
Double value = getDouble(s.value);
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "temperature": // Sensor Temp
|
||||
if (getString(getProfile().settings.temperatureUnits)
|
||||
.equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT)) {
|
||||
value = ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(getDouble(s.value))
|
||||
.doubleValue();
|
||||
}
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "temperature f": // Device Temp -> ignore (we use C only)
|
||||
break;
|
||||
case "temperature c": // Device Temp in C
|
||||
// Device temperature
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "external temperature f": // Shelly 1/1PM external temp sensors
|
||||
// ignore F, we use C only
|
||||
break;
|
||||
case "external temperature c": // Shelly 1/1PM external temp sensors
|
||||
case "external_temperature":
|
||||
int idx = getExtTempId(sen.id);
|
||||
if (idx > 0) {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP + idx,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
} else {
|
||||
logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type,
|
||||
sen.desc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("{}: Unknown temperatur type: {}", thingName, sen.desc);
|
||||
}
|
||||
break;
|
||||
case "p": // Power/Watt
|
||||
// 3EM uses 1-based meter IDs, other 0-based
|
||||
String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
|
||||
: CHANNEL_GROUP_METER + (profile.isEMeter ? sen.links : rIndex);
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
break;
|
||||
case "s" /* CatchAll */:
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "overtemp":
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "energy counter 0 [w-min]":
|
||||
updateChannel(updates, rGroup, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
break;
|
||||
case "energy counter 1 [w-min]":
|
||||
case "energy counter 2 [w-min]":
|
||||
// we don't use them
|
||||
break;
|
||||
case "energy counter total [w-h]": // 3EM reports W/h
|
||||
case "energy counter total [w-min]":
|
||||
Double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000;
|
||||
updateChannel(updates, rGroup, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(total, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
case "voltage":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
break;
|
||||
case "current":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
break;
|
||||
case "pf":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||
break;
|
||||
case "position":
|
||||
// work around: Roller reports 101% instead max 100
|
||||
double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(SHELLY_MAX_ROLLER_POS - pos, SmartHomeUnits.PERCENT));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(pos, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "input event": // Shelly Button 1
|
||||
handleInputEvent(sen, getString(s.valueStr), -1, updates);
|
||||
break;
|
||||
case "input event counter": // Shelly Button 1/ix3
|
||||
handleInputEvent(sen, "", getInteger((int) s.value), updates);
|
||||
break;
|
||||
case "flood":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "tilt": // DW with FW1.6.5+ //+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT,
|
||||
toQuantityType(s.value, DIGITS_NONE, SmartHomeUnits.DEGREE_ANGLE));
|
||||
break;
|
||||
case "vibration": // DW with FW1.6.5+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "temp": // Shelly Bulb
|
||||
case "colortemperature": // Shelly Duo
|
||||
updateChannel(updates,
|
||||
profile.inColor ? CHANNEL_GROUP_COLOR_CONTROL : CHANNEL_GROUP_WHITE_CONTROL,
|
||||
CHANNEL_COLOR_TEMP,
|
||||
ShellyColorUtils.toPercent((int) s.value, profile.minTemp, profile.maxTemp));
|
||||
break;
|
||||
case "sensor state": // Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "alarm state": // Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
|
||||
getStringType(s.valueStr));
|
||||
break;
|
||||
case "self-test state":// Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
|
||||
getStringType(s.valueStr));
|
||||
break;
|
||||
case "concentration":// Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value));
|
||||
break;
|
||||
case "sensorerror":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr));
|
||||
break;
|
||||
default:
|
||||
// Unknown
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown type
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Depending on the device type and firmware release there are significant bugs or incosistencies in the CoIoT
|
||||
* Device Description returned by the discovery request. Shelly is even not following it's own speicifcation. All of
|
||||
* that has been reported to Shelly and acknowledged. Firmware 1.6 brought significant improvements. However, the
|
||||
* old mapping stays in to support older firmware releases.
|
||||
*
|
||||
* @param sen Sensor description received from device
|
||||
* @return fixed Sensor description (sen)
|
||||
*/
|
||||
@Override
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
// Shelly1: reports null descr+type "Switch" -> map to S
|
||||
// Shelly1PM: reports null descr+type "Overtemp" -> map to O
|
||||
// Shelly1PM: reports null descr+type "W" -> add description
|
||||
// Shelly1PM: reports temp senmsors without desc -> add description
|
||||
// Shelly Dimmer: sensors are reported without descriptions -> map to S
|
||||
// SHelly Sense: multiple issues: Description should not be lower case, invalid type for Motion and Battery
|
||||
// Shelly Sense: Battery is reported with Desc "battery", but type "H" instead of "B"
|
||||
// Shelly Sense: Motion is reported with Desc "battery", but type "H" instead of "B"
|
||||
// Shelly Bulb: Colors are coded with Type="Red" etc. rather than Type="S" and color as Descr
|
||||
// Shelly RGBW2 is reporting Brightness, Power, VSwitch for each channel, but all with L=0
|
||||
if (sen.desc == null) {
|
||||
sen.desc = "";
|
||||
}
|
||||
String desc = sen.desc.toLowerCase();
|
||||
|
||||
// RGBW2 reports Power_0, Power_1, Power_2, Power_3; same for VSwitch and Brightness, all of them linkted to L:0
|
||||
// we break it up to Power with L:0, Power with L:1...
|
||||
if (desc.contains("_") && (desc.contains("power") || desc.contains("vswitch") || desc.contains("brightness"))) {
|
||||
String newDesc = substringBefore(sen.desc, "_");
|
||||
String newLink = substringAfter(sen.desc, "_");
|
||||
sen.desc = newDesc;
|
||||
sen.links = newLink;
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
// auto-insert a matching blk entry
|
||||
CoIotDescrBlk blk = new CoIotDescrBlk();
|
||||
CoIotDescrBlk blk0 = blkMap.get("0"); // blk 0 is always there
|
||||
blk.id = sen.links;
|
||||
blk.desc = blk0.desc + "_" + blk.id;
|
||||
blkMap.put(blk.id, blk);
|
||||
}
|
||||
}
|
||||
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "w": // old devices/firmware releases use "W", new ones "P"
|
||||
sen.type = "P";
|
||||
sen.desc = "Power";
|
||||
break;
|
||||
case "tc":
|
||||
sen.type = "T";
|
||||
sen.desc = "Temperature C";
|
||||
break;
|
||||
case "tf":
|
||||
sen.type = "T";
|
||||
sen.desc = "Temperature F";
|
||||
break;
|
||||
case "overtemp":
|
||||
sen.type = "S";
|
||||
sen.desc = "Overtemp";
|
||||
break;
|
||||
case "relay0":
|
||||
case "switch":
|
||||
case "vswitch":
|
||||
sen.type = "S";
|
||||
sen.desc = "State";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "motion": // fix acc to spec it's T=M
|
||||
sen.type = "M";
|
||||
sen.desc = "Motion";
|
||||
break;
|
||||
case "battery": // fix: type is B not H
|
||||
sen.type = "B";
|
||||
sen.desc = "Battery";
|
||||
break;
|
||||
case "overtemp":
|
||||
sen.type = "S";
|
||||
sen.desc = "Overtemp";
|
||||
break;
|
||||
case "relay0":
|
||||
case "switch":
|
||||
case "vswitch":
|
||||
sen.type = "S";
|
||||
sen.desc = "State";
|
||||
break;
|
||||
case "e cnt 0 [w-min]": // 4 Pro
|
||||
case "e cnt 1 [w-min]":
|
||||
case "e cnt 2 [w-min]":
|
||||
case "e cnt total [w-min]": // 4 Pro
|
||||
sen.desc = sen.desc.toLowerCase().replace("e cnt", "energy counter");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (sen.desc.isEmpty()) {
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "p":
|
||||
sen.desc = "Power";
|
||||
break;
|
||||
case "T":
|
||||
sen.desc = "Temperature";
|
||||
break;
|
||||
case "input":
|
||||
sen.type = "S";
|
||||
sen.desc = "Input";
|
||||
break;
|
||||
case "output":
|
||||
sen.type = "S";
|
||||
sen.desc = "Output";
|
||||
break;
|
||||
case "brightness":
|
||||
sen.type = "S";
|
||||
sen.desc = "Brightness";
|
||||
break;
|
||||
case "red":
|
||||
case "green":
|
||||
case "blue":
|
||||
case "white":
|
||||
case "gain":
|
||||
case "temp": // Bulb: Color temperature
|
||||
sen.desc = sen.type;
|
||||
sen.type = "S";
|
||||
break;
|
||||
case "vswitch":
|
||||
// it seems that Shelly tends to break their own spec: T is the description and D is no longer
|
||||
// included -> map D to sen.T and set CatchAll for T
|
||||
sen.desc = sen.type;
|
||||
sen.type = "S";
|
||||
break;
|
||||
// Default: set no description
|
||||
// (there are no T values defined in the CoIoT spec)
|
||||
case "tostate":
|
||||
default:
|
||||
sen.desc = "";
|
||||
}
|
||||
}
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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
|
||||
*/
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 1
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 2
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion2.class);
|
||||
|
||||
public ShellyCoIoTVersion2(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
super(thingName, thingHandler, blkMap, sensorMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return ShellyCoapJSonDTO.COIOT_VERSION_2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// first check the base implementation
|
||||
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
// process by the base class
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process status information and convert into channel updates
|
||||
// Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
int rIndex = getIdFromBlk(sen);
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
|
||||
: CHANNEL_GROUP_METER + (profile.isEMeter ? getIdFromBlk(sen) : rIndex);
|
||||
|
||||
boolean processed = true;
|
||||
double value = getDouble(s.value);
|
||||
String reason = "";
|
||||
switch (sen.id) {
|
||||
case "3103": // H, humidity, 0-100 percent, unknown 999
|
||||
case "3106": // L, luminosity, lux, U32, -1
|
||||
case "3109": // S, tilt, 0-180deg, -1
|
||||
case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown
|
||||
case "3111": // B, battery, 0-100%, unknown -1
|
||||
case "3112": // S, charger, 0/1
|
||||
case "3115": // S, sensorError, 0/1
|
||||
case "5101": // S, brightness, 1-100%
|
||||
// processed by base handler
|
||||
break;
|
||||
case "6109": // P, overpowerValue, W, U32
|
||||
case "9101":
|
||||
// Relay: S, mode, relay/roller or
|
||||
// Dimmer: S, mode, color/white
|
||||
// skip, could check against thing mode...
|
||||
break;
|
||||
|
||||
case "1101": // S, output, 0/1
|
||||
updatePower(profile, updates, rIndex, sen, s, sensorUpdates);
|
||||
break;
|
||||
case "1102": // roler_0: S, roller, open/close/stop -> roller state
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "1103": // roller_0: S, rollerPos, 0-100, unknown -1
|
||||
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min((int) value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "1105": // S, valvle, closed/opened/not_connected/failure/closing/opening/checking or unbknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr));
|
||||
break;
|
||||
|
||||
case "2101": // Input_0: S, input, 0/1
|
||||
case "2201": // Input_1: S, input, 0/1
|
||||
case "2301": // Input_2: S, input, 0/1
|
||||
case "2401": // Input_3: S, input, 0/1
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
case "2102": // Input_0: EV, inputEvent, S/SS/SSS/L
|
||||
case "2202": // Input_1: EV, inputEvent
|
||||
case "2302": // Input_2: EV, inputEvent
|
||||
case "2402": // Input_3: EV, inputEvent
|
||||
handleInputEvent(sen, getString(s.valueStr), -1, updates);
|
||||
break;
|
||||
case "2103": // EVC, inputEventCnt, U16
|
||||
case "2203": // EVC, inputEventCnt, U16
|
||||
case "2303": // EVC, inputEventCnt, U16
|
||||
case "2403": // EVC, inputEventCnt, U16
|
||||
handleInputEvent(sen, "", getInteger((int) s.value), updates);
|
||||
break;
|
||||
case "3101": // sensor_0: T, extTemp, C, -55/125; unknown 999
|
||||
case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999
|
||||
case "3301": // sensor_2: T, extTemp, C, -55/125; unknown 999
|
||||
int idx = getExtTempId(sen.id);
|
||||
if (idx >= 0) {
|
||||
// H&T, Fllod, DW only have 1 channel, 1/1PM with Addon have up to to 3 sensors
|
||||
String channel = profile.isSensor ? CHANNEL_SENSOR_TEMP : CHANNEL_SENSOR_TEMP + idx;
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, channel,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
} else {
|
||||
logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc);
|
||||
}
|
||||
break;
|
||||
case "3104": // T, deviceTemp, Celsius -40/300; 999=unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "3102": // sensor_0: T, extTemp, F, -67/257, unknown 999
|
||||
case "3202": // sensor_1: T, extTemp, F, -67/257, unknown 999
|
||||
case "3302": // sensor_2: T, extTemp, F, -67/257, unknown 999
|
||||
case "3105": // T, deviceTemp, Fahrenheit -40/572
|
||||
// skip, we use only C
|
||||
break;
|
||||
|
||||
case "3107": // C, Gas concentration, U16
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value));
|
||||
break;
|
||||
case "3108": // DW: S, dwIsOpened, 0/1, -1=unknown
|
||||
if (value != -1) {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
|
||||
} else {
|
||||
logger.debug("{}: Sensor error reported, check device, battery and installation", thingName);
|
||||
}
|
||||
break;
|
||||
case "3113": // S, sensorOp, warmup/normal/fault
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "3114": // S, selfTest, not_completed/completed/running/pending
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST, getStringType(s.valueStr));
|
||||
break;
|
||||
case "3117": // S, extInput, 0/1
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
|
||||
case "4101": // relay_0: P, power, W
|
||||
case "4201": // relay_1: P, power, W
|
||||
case "4301": // relay_2: P, power, W
|
||||
case "4401": // relay_3: P, power, W
|
||||
case "4105": // emeter_0: P, power, W
|
||||
case "4205": // emeter_1: P, power, W
|
||||
case "4305": // emeter_2: P, power, W
|
||||
case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1
|
||||
case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
break;
|
||||
|
||||
case "4103": // relay_0: E, energy, Wmin, U32
|
||||
case "4203": // relay_1: E, energy, Wmin, U32
|
||||
case "4303": // relay_2: E, energy, Wmin, U32
|
||||
case "4403": // relay_3: E, energy, Wmin, U32
|
||||
case "4104": // roller_0: E, rollerEnergy, Wmin, U32, -1
|
||||
case "4204": // roller_0: E, rollerEnergy, Wmin, U32, -1
|
||||
case "4106": // emeter_0: E, energy, Wh, U32
|
||||
case "4206": // emeter_1: E, energy, Wh, U32
|
||||
case "4306": // emeter_2: E, energy, Wh, U32
|
||||
double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000;
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(total, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
|
||||
case "4107": // emeter_0: E, energyReturned, Wh, U32, -1
|
||||
case "4207": // emeter_1: E, energyReturned, Wh, U32, -1
|
||||
case "4307": // emeter_2: E, energyReturned, Wh, U32, -1
|
||||
updateChannel(updates, mGroup, CHANNEL_EMETER_TOTALRET,
|
||||
toQuantityType(getDouble(s.value) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
|
||||
case "4108": // emeter_0: V, voltage, 0-265V, U32, -1
|
||||
case "4208": // emeter_1: V, voltage, 0-265V, U32, -1
|
||||
case "4308": // emeter_2: V, voltage, 0-265V, U32, -1
|
||||
updateChannel(updates, mGroup, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
break;
|
||||
|
||||
case "4109": // emeter_0: A, current, 0/120A, -1
|
||||
case "4209": // emeter_1: A, current, 0/120A, -1
|
||||
case "4309": // emeter_2: A, current, 0/120A, -1
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
break;
|
||||
|
||||
case "4110": // emeter_0: S, powerFactor, 0/1, -1
|
||||
case "4210": // emeter_1: S, powerFactor, 0/1, -1
|
||||
case "4310": // emeter_2: S, powerFactor, 0/1, -1
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||
break;
|
||||
|
||||
case "6101": // A, overtemp, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "6102": // relay_0: A, overpower, 0/1
|
||||
case "6202": // relay_1: A, overpower, 0/1
|
||||
case "6302": // relay_2: A, overpower, 0/1
|
||||
case "6402": // relay_3: A, overpower, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERPOWER, true);
|
||||
}
|
||||
break;
|
||||
case "6104": // relay_0: A, loadError, 0/1
|
||||
case "6204": // relay_1: A, loadError, 0/1
|
||||
case "6304": // relay_2: A, loadError, 0/1
|
||||
case "6404": // relay_3: A, loadError, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_LOADERR, true);
|
||||
}
|
||||
break;
|
||||
case "6103": // roller_0: A, rollerStopReason, normal/safety_switch/obstacle/overpower
|
||||
reason = getString(s.valueStr);
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STOPR, getStringType(reason));
|
||||
if (!reason.isEmpty() && !reason.equalsIgnoreCase(SHELLY_API_STOPR_NORMAL)) {
|
||||
thingHandler.postEvent("ROLLER_" + reason.toUpperCase(), true);
|
||||
}
|
||||
case "6106": // A, flood, 0/1, -1
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "6108": // A, gas, none/mild/heavy/test or unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "6110": // A, vibration, 0/1, -1=unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "9102": // EV, wakeupEvent, battery/button/periodic/poweron/sensor/ext_power, "unknown"=unknown
|
||||
thingHandler.updateWakeupReason(s.valueArray);
|
||||
break;
|
||||
case "9103": // EVC, cfgChanged, U16
|
||||
if ((lastCfgCount != -1) && (lastCfgCount != s.value)) {
|
||||
thingHandler.requestUpdates(1, true); // refresh config
|
||||
}
|
||||
lastCfgCount = (int) s.value;
|
||||
break;
|
||||
|
||||
default:
|
||||
processed = false;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.californium.core.CoapClient;
|
||||
import org.eclipse.californium.core.coap.CoAP.Code;
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
|
||||
import org.eclipse.californium.core.coap.CoAP.Type;
|
||||
import org.eclipse.californium.core.coap.MessageObserverAdapter;
|
||||
import org.eclipse.californium.core.coap.Option;
|
||||
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
||||
import org.eclipse.californium.core.coap.Request;
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescrTypeAdapter;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescription;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotGenericSensorList;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapHandler} handles the CoIoT/CoAP registration and events.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoapHandler implements ShellyCoapListener {
|
||||
private static final byte[] EMPTY_BYTE = new byte[0];
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoapHandler.class);
|
||||
private final ShellyBaseHandler thingHandler;
|
||||
private ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
private final GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
private final Gson gson;
|
||||
private String thingName;
|
||||
|
||||
private boolean coiotBound = false;
|
||||
private ShellyCoIoTInterface coiot;
|
||||
private int coiotVers = -1;
|
||||
|
||||
private final ShellyCoapServer coapServer;
|
||||
private @Nullable CoapClient statusClient;
|
||||
private Request reqDescription = new Request(Code.GET, Type.CON);
|
||||
private Request reqStatus = new Request(Code.GET, Type.CON);
|
||||
private boolean discovering = false;
|
||||
|
||||
private int lastSerial = -1;
|
||||
private String lastPayload = "";
|
||||
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
|
||||
private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>();
|
||||
private final ShellyDeviceProfile profile;
|
||||
|
||||
public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) {
|
||||
this.thingHandler = thingHandler;
|
||||
this.thingName = thingHandler.thingName;
|
||||
this.coapServer = coapServer;
|
||||
this.coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); // Default
|
||||
|
||||
gsonBuilder.registerTypeAdapter(CoIotDevDescription.class, new CoIotDevDescrTypeAdapter());
|
||||
gsonBuilder.registerTypeAdapter(CoIotGenericSensorList.class, new CoIotSensorTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
profile = thingHandler.getProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CoAP access, send discovery packet and start Status server
|
||||
*
|
||||
* @parm thingName Thing name derived from Thing Type/hostname
|
||||
* @parm config ShellyThingConfiguration
|
||||
* @thows ShellyApiException
|
||||
*/
|
||||
public synchronized void start(String thingName, ShellyThingConfiguration config) throws ShellyApiException {
|
||||
try {
|
||||
this.thingName = thingName;
|
||||
this.config = config;
|
||||
if (isStarted()) {
|
||||
logger.trace("{}: CoAP Listener was already started", thingName);
|
||||
stop();
|
||||
}
|
||||
|
||||
logger.debug("{}: Starting CoAP Listener", thingName);
|
||||
coapServer.start(config.localIp, this);
|
||||
statusClient = new CoapClient(completeUrl(config.deviceIp, COLOIT_URI_DEVSTATUS))
|
||||
.setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint());
|
||||
discover();
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("{}: CoAP Exception", thingName, e);
|
||||
throw new ShellyApiException("Unknown Host: " + config.deviceIp, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return statusClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an inbound Response (or mapped Request): decode CoAP options. handle discovery result or status updates
|
||||
*
|
||||
* @param response The Response packet
|
||||
*/
|
||||
@Override
|
||||
public void processResponse(@Nullable Response response) {
|
||||
if (response == null) {
|
||||
return; // other device instance
|
||||
}
|
||||
String ip = response.getSourceContext().getPeerAddress().toString();
|
||||
if (!ip.contains(config.deviceIp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String payload = "";
|
||||
String devId = "";
|
||||
String uri = "";
|
||||
// int validity = 0;
|
||||
int serial = -1;
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
|
||||
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
|
||||
}
|
||||
if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
|
||||
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getCode() == ResponseCode.CONTENT) {
|
||||
payload = response.getPayloadString();
|
||||
List<Option> options = response.getOptions().asSortedList();
|
||||
int i = 0;
|
||||
while (i < options.size()) {
|
||||
Option opt = options.get(i);
|
||||
switch (opt.getNumber()) {
|
||||
case OptionNumberRegistry.URI_PATH:
|
||||
uri = COLOIT_URI_BASE + opt.getStringValue();
|
||||
break;
|
||||
case COIOT_OPTION_GLOBAL_DEVID:
|
||||
devId = opt.getStringValue();
|
||||
String sVersion = substringAfterLast(devId, "#");
|
||||
int iVersion = Integer.parseInt(sVersion);
|
||||
if (coiotBound && (coiotVers != iVersion)) {
|
||||
logger.debug(
|
||||
"{}: CoIoT versopm has changed from {} to {}, maybe the firmware was upgraded",
|
||||
thingName, coiotVers, iVersion);
|
||||
thingHandler.reinitializeThing();
|
||||
coiotBound = false;
|
||||
}
|
||||
if (!coiotBound) {
|
||||
thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion);
|
||||
logger.debug("{}: CoIoT Version {} detected", thingName, iVersion);
|
||||
if (iVersion == COIOT_VERSION_1) {
|
||||
coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap);
|
||||
} else if (iVersion == COIOT_VERSION_2) {
|
||||
coiot = new ShellyCoIoTVersion2(thingName, thingHandler, blkMap, sensorMap);
|
||||
} else {
|
||||
logger.warn("{}: Unsupported CoAP version detected: {}", thingName, sVersion);
|
||||
return;
|
||||
}
|
||||
coiotVers = iVersion;
|
||||
coiotBound = true;
|
||||
}
|
||||
break;
|
||||
case COIOT_OPTION_STATUS_VALIDITY:
|
||||
// validity = o.getIntegerValue();
|
||||
break;
|
||||
case COIOT_OPTION_STATUS_SERIAL:
|
||||
serial = opt.getIntegerValue();
|
||||
break;
|
||||
default:
|
||||
logger.debug("{} ({}): COAP option {} with value {} skipped", thingName, devId,
|
||||
opt.getNumber(), opt.getValue());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// If we received a CoAP message successful the thing must be online
|
||||
thingHandler.setThingOnline();
|
||||
|
||||
// The device changes the serial on every update, receiving a message with the same serial is a
|
||||
// duplicate, excep for battery devices! Those reset the serial every time when they wake-up
|
||||
if ((serial == lastSerial) && payload.equals(lastPayload)
|
||||
&& (!profile.hasBattery || ((serial & 0xFF) != 0))) {
|
||||
logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
|
||||
return;
|
||||
}
|
||||
|
||||
// fixed malformed JSON :-(
|
||||
payload = fixJSON(payload);
|
||||
|
||||
if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC) || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
|
||||
handleDeviceDescription(devId, payload);
|
||||
} else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
|
||||
|| (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
|
||||
handleStatusUpdate(devId, payload, serial);
|
||||
}
|
||||
} else {
|
||||
// error handling
|
||||
logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, response.getCode(),
|
||||
response.getPayloadString());
|
||||
}
|
||||
|
||||
if (!discovering) {
|
||||
// Observe Status Updates
|
||||
reqStatus = sendRequest(reqStatus, config.deviceIp, COLOIT_URI_DEVSTATUS, Type.NON);
|
||||
discovering = true;
|
||||
}
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
|
||||
resetSerial();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a CoIoT device description message. This includes definitions on device units (Relay0, Relay1, Sensors
|
||||
* etc.) as well as a definition of sensors and actors. This information needs to be stored allowing to map ids from
|
||||
* status updates to the device units and matching the correct thing channel.
|
||||
*
|
||||
* @param devId The device id reported in the CoIoT message.
|
||||
* @param payload Device desciption in JSon format, example:
|
||||
* {"blk":[{"I":0,"D":"Relay0"}],"sen":[{"I":112,"T":"Switch","R":"0/1","L":0}],"act":[{"I":211,"D":"Switch","L":0,"P":[{"I":2011,"D":"ToState","R":"0/1"}]}]}
|
||||
*/
|
||||
private void handleDeviceDescription(String devId, String payload) {
|
||||
logger.debug("{}: CoIoT Device Description for {}: {}", thingName, devId, payload);
|
||||
|
||||
try {
|
||||
boolean valid = true;
|
||||
|
||||
// Decode Json
|
||||
CoIotDevDescription descr = gson.fromJson(payload, CoIotDevDescription.class);
|
||||
for (int i = 0; i < descr.blk.size(); i++) {
|
||||
CoIotDescrBlk blk = descr.blk.get(i);
|
||||
logger.debug("{}: id={}: {}", thingName, blk.id, blk.desc);
|
||||
if (!blkMap.containsKey(blk.id)) {
|
||||
blkMap.put(blk.id, blk);
|
||||
} else {
|
||||
blkMap.replace(blk.id, blk);
|
||||
}
|
||||
if ((blk.type != null) && !blk.type.isEmpty()) {
|
||||
// in fact it is a sen entry - that's vioaling the Spec
|
||||
logger.trace("{}: fix: auto-create sensor definition for id {}/{}!", thingName, blk.id,
|
||||
blk.desc);
|
||||
CoIotDescrSen sen = new CoIotDescrSen();
|
||||
sen.id = blk.id;
|
||||
sen.desc = blk.desc;
|
||||
sen.type = blk.type;
|
||||
sen.range = blk.range;
|
||||
sen.links = blk.links;
|
||||
valid &= addSensor(sen);
|
||||
}
|
||||
}
|
||||
|
||||
// Save to thing properties
|
||||
thingHandler.updateProperties(PROPERTY_COAP_DESCR, payload);
|
||||
|
||||
logger.debug("{}: Adding {} sensor definitions", thingName, descr.sen.size());
|
||||
if (descr.sen != null) {
|
||||
for (int i = 0; i < descr.sen.size(); i++) {
|
||||
valid &= addSensor(descr.sen.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
logger.debug(
|
||||
"{}: Incompatible device description detected for CoIoT version {} (id length mismatch), discarding!",
|
||||
thingName, coiot.getVersion());
|
||||
thingHandler.updateProperties(PROPERTY_COAP_DESCR, "");
|
||||
discover();
|
||||
return;
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("{}: Unable to parse CoAP Device Description! JSON={}", thingName, payload);
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
logger.warn("{}: Unable to parse CoAP Device Description! JSON={}", thingName, payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sensor to the sensor table
|
||||
*
|
||||
* @param sen CoIotDescrSen of the sensor
|
||||
*/
|
||||
private synchronized boolean addSensor(CoIotDescrSen sen) {
|
||||
logger.debug("{}: id {}: {}, Type={}, Range={}, Links={}", thingName, sen.id, sen.desc, sen.type, sen.range,
|
||||
sen.links);
|
||||
// CoIoT version 2 changes from 3 digit IDs to 4 digit IDs
|
||||
// We need to make sure that the persisted device description matches,
|
||||
// otherwise the stored one is discarded and a new discovery is triggered
|
||||
// This happens on firmware up/downgrades (version 1.8 brings CoIoT v2 with 4 digit IDs)
|
||||
int vers = coiot.getVersion();
|
||||
if (((vers == COIOT_VERSION_1) && (sen.id.length() > 3))
|
||||
|| ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CoIotDescrSen fixed = coiot.fixDescription(sen, blkMap);
|
||||
if (!sensorMap.containsKey(fixed.id)) {
|
||||
sensorMap.put(sen.id, fixed);
|
||||
} else {
|
||||
sensorMap.replace(sen.id, fixed);
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException e) { // depending on firmware release the CoAP device
|
||||
// description is buggy
|
||||
logger.debug("{}: Unable to decode sensor definition -> skip", thingName, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
private void handleStatusUpdate(String devId, String payload, int serial) {
|
||||
logger.debug("{}: CoIoT Sensor data {} (serial={})", thingName, payload, serial);
|
||||
if (blkMap.isEmpty()) {
|
||||
// send discovery packet
|
||||
resetSerial();
|
||||
discover();
|
||||
|
||||
// try to uses description from last initialization
|
||||
String savedDescr = thingHandler.getProperty(PROPERTY_COAP_DESCR);
|
||||
if (savedDescr.isEmpty()) {
|
||||
logger.debug("{}: Device description not yet received, trigger auto-initialization", thingName);
|
||||
return;
|
||||
}
|
||||
|
||||
// simulate received device description to create element table
|
||||
logger.debug("{}: Device description for {} restored: {}", thingName, devId, savedDescr);
|
||||
handleDeviceDescription(devId, savedDescr);
|
||||
}
|
||||
|
||||
// Parse Json,
|
||||
CoIotGenericSensorList list = gson.fromJson(fixJSON(payload), CoIotGenericSensorList.class);
|
||||
if (list.generic == null) {
|
||||
logger.debug("{}: Sensor list has invalid format! Payload: {}", devId, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
List<CoIotSensor> sensorUpdates = list.generic;
|
||||
Map<String, State> updates = new TreeMap<String, State>();
|
||||
logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size());
|
||||
int failed = 0;
|
||||
for (int i = 0; i < sensorUpdates.size(); i++) {
|
||||
try {
|
||||
CoIotSensor s = sensorUpdates.get(i);
|
||||
if (!sensorMap.containsKey(s.id)) {
|
||||
logger.debug("{}: Invalid id in sensor description: {}, index {}", thingName, s.id, i);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
CoIotDescrSen sen = sensorMap.get(s.id);
|
||||
// find matching sensor definition from device description, use the Link ID as index
|
||||
sen = coiot.fixDescription(sen, blkMap);
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
logger.debug("{}: Invalid CoAP description: sen.links({}", thingName, getString(sen.links));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
logger.debug("{}: Unable to find BLK for link {} from sen.id={}", thingName, sen.links, sen.id);
|
||||
continue;
|
||||
}
|
||||
CoIotDescrBlk element = blkMap.get(sen.links);
|
||||
logger.trace("{}: Sensor value[{}]: id={}, Value={} ({}, Type={}, Range={}, Link={}: {})", thingName,
|
||||
i, s.id, getString(s.valueStr).isEmpty() ? s.value : s.valueStr, sen.desc, sen.type, sen.range,
|
||||
sen.links, element.desc);
|
||||
|
||||
if (!coiot.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
logger.debug("{}: CoIoT data for id {}, type {}/{} not processed, value={}; payload={}", thingName,
|
||||
sen.id, sen.type, sen.desc, s.value, payload);
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
// even the processing of one value failed we continue with the next one (sometimes this is caused by
|
||||
// buggy formats provided by the device
|
||||
logger.debug("{}: Unable to process data from sensor[{}], devId={}, payload={}", thingName, i, devId,
|
||||
payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
int updated = 0;
|
||||
for (Map.Entry<String, State> u : updates.entrySet()) {
|
||||
updated += thingHandler.updateChannel(u.getKey(), u.getValue(), false) ? 1 : 0;
|
||||
}
|
||||
if (updated > 0) {
|
||||
logger.debug("{}: {} channels updated from CoIoT status, serial={}", thingName, updated, serial);
|
||||
if (profile.isSensor || profile.isRoller) {
|
||||
// CoAP is currently lacking the lastUpdate info, so we use host timestamp
|
||||
thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
// Old firmware release are lacking various status values, which are not updated using CoIoT.
|
||||
// In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most
|
||||
// of the values are available
|
||||
if ((!thingHandler.autoCoIoT && (thingHandler.scheduledUpdates <= 1))
|
||||
|| (thingHandler.autoCoIoT && !profile.isLight && !profile.hasBattery)) {
|
||||
thingHandler.requestUpdates(1, false);
|
||||
}
|
||||
} else {
|
||||
if (failed == sensorUpdates.size()) {
|
||||
logger.debug("{}: Device description problem detected, re-discover", thingName);
|
||||
coiotBound = false;
|
||||
discover();
|
||||
}
|
||||
}
|
||||
|
||||
// Remember serial, new packets with same serial will be ignored
|
||||
lastSerial = serial;
|
||||
lastPayload = payload;
|
||||
}
|
||||
|
||||
private void discover() {
|
||||
reqDescription = sendRequest(reqDescription, config.deviceIp, COLOIT_URI_DEVDESC, Type.CON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix malformed JSON - stupid, but the devices sometimes return malformed JSON with then causes a
|
||||
* JsonSyntaxException
|
||||
*
|
||||
* @param json to be checked/fixed
|
||||
*/
|
||||
private static String fixJSON(String payload) {
|
||||
String json = payload;
|
||||
json = json.replace("}{", "},{");
|
||||
json = json.replace("][", "],[");
|
||||
json = json.replace("],,[", "],[");
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new request (Discovery to get Device Description). Before a pending
|
||||
* request will be canceled.
|
||||
*
|
||||
* @param request The current request (this will be canceled an a new one will
|
||||
* be created)
|
||||
* @param ipAddress Device's IP address
|
||||
* @param uri The URI we are calling (CoIoT = /cit/d or /cit/s)
|
||||
* @param con true: send as CON, false: send as NON
|
||||
* @return new packet
|
||||
*/
|
||||
private Request sendRequest(@Nullable Request request, String ipAddress, String uri, Type con) {
|
||||
if ((request != null) && !request.isCanceled()) {
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
resetSerial();
|
||||
return newRequest(ipAddress, uri, con).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new Request structure. A message observer will be added to get the
|
||||
* callback when a response has been received.
|
||||
*
|
||||
* @param ipAddress IP address of the device
|
||||
* @param uri URI to be addressed
|
||||
* @param uri The URI we are calling (CoIoT = /cit/d or /cit/s)
|
||||
* @param con true: send as CON, false: send as NON
|
||||
* @return new packet
|
||||
*/
|
||||
|
||||
private Request newRequest(String ipAddress, String uri, Type con) {
|
||||
// We need to build our own Request to set an empty Token
|
||||
Request request = new Request(Code.GET, con);
|
||||
request.setURI(completeUrl(ipAddress, uri));
|
||||
request.setToken(EMPTY_BYTE);
|
||||
request.addMessageObserver(new MessageObserverAdapter() {
|
||||
@Override
|
||||
public void onResponse(@Nullable Response response) {
|
||||
processResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
logger.debug("{}: CoAP Request was canceled", thingName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout() {
|
||||
logger.debug("{}: CoAP Request timed out", thingName);
|
||||
}
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset serial and payload used to detect duplicate messages, which have to be ignored.
|
||||
* We can't rely that the device manages serials correctly all the time. There are firmware releases sending updated
|
||||
* sensor information with the serial from the last packet, which is wrong. We bypass this problem by comparing also
|
||||
* the payload.
|
||||
*/
|
||||
private void resetSerial() {
|
||||
lastSerial = -1;
|
||||
lastPayload = "";
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return coiotVers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending requests and shutdown the client
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (isStarted()) {
|
||||
logger.debug("{}: Stopping CoAP Listener", thingName);
|
||||
coapServer.stop(this);
|
||||
if (statusClient != null) {
|
||||
statusClient.shutdown();
|
||||
statusClient = null;
|
||||
}
|
||||
if (!reqDescription.isCanceled()) {
|
||||
reqDescription.cancel();
|
||||
}
|
||||
if (!reqStatus.isCanceled()) {
|
||||
reqStatus.cancel();
|
||||
}
|
||||
}
|
||||
resetSerial();
|
||||
coiotBound = false;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private static String completeUrl(String ipAddress, String uri) {
|
||||
return "coap://" + ipAddress + ":" + COIOT_PORT + uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapJSonDTO} helps the CoIoT Json into Java objects
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
public class ShellyCoapJSonDTO {
|
||||
// Coap
|
||||
public static final int COIOT_VERSION_1 = 1;
|
||||
public static final int COIOT_VERSION_2 = 2;
|
||||
|
||||
public static final int COIOT_PORT = 5683;
|
||||
public static final String COAP_MULTICAST_ADDRESS = "224.0.1.187";
|
||||
|
||||
public static final String COLOIT_URI_BASE = "/cit/";
|
||||
public static final String COLOIT_URI_DEVDESC = COLOIT_URI_BASE + "d";
|
||||
public static final String COLOIT_URI_DEVSTATUS = COLOIT_URI_BASE + "s";
|
||||
|
||||
public static final int COIOT_OPTION_GLOBAL_DEVID = 3332;
|
||||
public static final int COIOT_OPTION_STATUS_VALIDITY = 3412;
|
||||
public static final int COIOT_OPTION_STATUS_SERIAL = 3420;
|
||||
|
||||
public static final String COIOT_TAG_BLK = "blk";
|
||||
public static final String COIOT_TAG_SEN = "sen";
|
||||
public static final String COIOT_TAG_ACT = "act";
|
||||
public static final String COIOT_TAG_GENERIC = "G";
|
||||
|
||||
public static class CoIotDescrBlk {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
|
||||
// Sometimes sen entries are part of the blk array - not conforming the Spec!
|
||||
@SerializedName("T")
|
||||
public String type; // Type
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
}
|
||||
|
||||
public static class CoIotDescrSen {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("T")
|
||||
public String type; // Type
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
@SerializedName("U")
|
||||
public String unit; // Unit
|
||||
}
|
||||
|
||||
public static class CoIotDescrP {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
}
|
||||
|
||||
public static class CoIotDescrAct {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
@SerializedName("P")
|
||||
public List<CoIotDescrP> pTag; // ?
|
||||
}
|
||||
|
||||
public static class CoIotDevDescription {
|
||||
public List<CoIotDescrBlk> blk;
|
||||
public List<CoIotDescrSen> sen;
|
||||
// public List<CoIotDescrAct> act;
|
||||
|
||||
public CoIotDevDescription() {
|
||||
blk = new ArrayList<>();
|
||||
sen = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class CoIotSensor {
|
||||
@SerializedName("index")
|
||||
public String id; // id
|
||||
public double value; // value
|
||||
public String valueStr; // value
|
||||
public List<Object> valueArray;
|
||||
}
|
||||
|
||||
public static class CoIotGenericSensorList {
|
||||
@SerializedName("G")
|
||||
public List<CoIotSensor> generic;
|
||||
|
||||
public CoIotGenericSensorList() {
|
||||
generic = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CoIotDevDescrTypeAdapter extends TypeAdapter<CoIotDevDescription> {
|
||||
@Override
|
||||
public CoIotDevDescription read(final JsonReader in) throws IOException {
|
||||
CoIotDevDescription descr = new CoIotDevDescription();
|
||||
|
||||
/*
|
||||
* parse JSON like
|
||||
* "blk": [
|
||||
* { "I": 0, "D": "Relay0"},
|
||||
* { "I": 1, "D": "Sensors"} ],
|
||||
* "sen": [
|
||||
* { "I": 111, "T": "P", "D": "Power","R": "0/3500","L": 0},
|
||||
* { "I": 112,"T": "S","D": "Switch","R": "0/1","L": 0}
|
||||
* ]
|
||||
*/
|
||||
in.beginObject();
|
||||
String name = in.nextName();
|
||||
if (name.equalsIgnoreCase(COIOT_TAG_BLK)) {
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotDescrBlk blk = new CoIotDescrBlk();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
switch (in.nextName().toUpperCase()) {
|
||||
case "I":
|
||||
blk.id = in.nextString();
|
||||
break;
|
||||
case "D":
|
||||
blk.desc = in.nextString();
|
||||
break;
|
||||
default:
|
||||
// skip data
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
descr.blk.add(blk);
|
||||
}
|
||||
in.endArray();
|
||||
name = in.nextName();
|
||||
}
|
||||
|
||||
if (name.equalsIgnoreCase(COIOT_TAG_SEN)) {
|
||||
/*
|
||||
* parse sensor list, e.g.
|
||||
* "sen":[
|
||||
* { "I":111,"T":"Red","R":"0/255","L":0},
|
||||
* { "I":121,"T":"Green","R":"0/255","L":0},
|
||||
* ]
|
||||
*/
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotDescrSen sen = new CoIotDescrSen();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String tag = in.nextName();
|
||||
switch (tag.toUpperCase()) {
|
||||
case "I":
|
||||
sen.id = in.nextString();
|
||||
break;
|
||||
case "D":
|
||||
sen.desc = in.nextString();
|
||||
break;
|
||||
case "T":
|
||||
sen.type = in.nextString();
|
||||
break;
|
||||
case "R":
|
||||
JsonToken token = in.peek();
|
||||
if (token == JsonToken.BEGIN_ARRAY) {
|
||||
// must be v2: an array
|
||||
in.beginArray();
|
||||
sen.range = "";
|
||||
while (in.hasNext()) {
|
||||
String value = in.nextString();
|
||||
sen.range += sen.range.isEmpty() ? value : ";" + value;
|
||||
}
|
||||
in.endArray();
|
||||
} else {
|
||||
sen.range = in.nextString();
|
||||
}
|
||||
break;
|
||||
case "L":
|
||||
sen.links = String.valueOf(in.nextInt());
|
||||
break;
|
||||
case "U": // New in CoAPv2: unit"
|
||||
sen.unit = in.nextString();
|
||||
break;
|
||||
default:
|
||||
// skip data
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
descr.sen.add(sen);
|
||||
}
|
||||
|
||||
in.endArray();
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return descr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final JsonWriter out, final CoIotDevDescription descr) throws IOException {
|
||||
out.beginObject();
|
||||
if (descr != null) {
|
||||
out.name(COIOT_TAG_BLK).beginArray();
|
||||
for (int i = 0; i < descr.blk.size(); i++) {
|
||||
CoIotDescrBlk blk = descr.blk.get(i);
|
||||
out.beginArray();
|
||||
out.value(blk.id);
|
||||
out.value(blk.desc);
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
|
||||
out.name(COIOT_TAG_SEN).beginArray();
|
||||
for (int i = 0; i < descr.sen.size(); i++) {
|
||||
// Create element, e.g. {“I”:66, “D”:“lux”, “T”:“L”, “R”:“0/100000”, “L”:1},
|
||||
CoIotDescrSen sen = descr.sen.get(i);
|
||||
out.beginArray();
|
||||
out.value(sen.id);
|
||||
out.value(sen.desc);
|
||||
out.value(sen.type);
|
||||
out.value(sen.range);
|
||||
out.value(sen.links);
|
||||
if (sen.unit != null) {
|
||||
out.value(sen.unit);
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CoIotSensorTypeAdapter extends TypeAdapter<CoIotGenericSensorList> {
|
||||
@Override
|
||||
public CoIotGenericSensorList read(final JsonReader in) throws IOException {
|
||||
CoIotGenericSensorList list = new CoIotGenericSensorList();
|
||||
|
||||
in.beginObject();
|
||||
String generic = in.nextName();
|
||||
if (generic.equals(COIOT_TAG_GENERIC)) {
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotSensor sensor = new CoIotSensor();
|
||||
in.beginArray();
|
||||
in.nextInt(); // alway 0
|
||||
sensor.id = Integer.toString(in.nextInt());
|
||||
JsonToken token = in.peek();
|
||||
if (token == JsonToken.STRING) {
|
||||
// handle as string
|
||||
sensor.valueStr = in.nextString();
|
||||
sensor.value = -1;
|
||||
} else if (token == JsonToken.NUMBER) {
|
||||
// handle as double
|
||||
sensor.value = in.nextDouble();
|
||||
sensor.valueStr = "";
|
||||
} else if (token == JsonToken.BEGIN_ARRAY) {
|
||||
sensor.valueArray = new ArrayList<>();
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
if (in.peek() == JsonToken.STRING) {
|
||||
sensor.valueArray.add(in.nextString());
|
||||
} else {
|
||||
// skip
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endArray();
|
||||
}
|
||||
in.endArray();
|
||||
list.generic.add(sensor);
|
||||
}
|
||||
in.endArray();
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final JsonWriter out, final CoIotGenericSensorList o) throws IOException {
|
||||
CoIotGenericSensorList sensors = o;
|
||||
out.beginObject();
|
||||
if (sensors != null) {
|
||||
out.name(COIOT_TAG_GENERIC).beginArray();
|
||||
for (int i = 0; i < sensors.generic.size(); i++) {
|
||||
out.beginArray();
|
||||
out.value(0);
|
||||
out.value(sensors.generic.get(i).id);
|
||||
out.value(sensors.generic.get(i).value);
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapListener} describes the listening interface to process Coap responses
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyCoapListener {
|
||||
public void processResponse(@Nullable Response response);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.COIOT_PORT;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.californium.core.CoapResource;
|
||||
import org.eclipse.californium.core.CoapServer;
|
||||
import org.eclipse.californium.core.coap.CoAP;
|
||||
import org.eclipse.californium.core.coap.CoAP.Code;
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
|
||||
import org.eclipse.californium.core.coap.Request;
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.californium.core.network.CoapEndpoint;
|
||||
import org.eclipse.californium.core.network.Exchange;
|
||||
import org.eclipse.californium.core.network.config.NetworkConfig;
|
||||
import org.eclipse.californium.elements.UdpMulticastConnector;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapServer} implements the UDP listener and status event processor (for /cit/s messages)
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoapServer {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoapServer.class);
|
||||
|
||||
boolean started = false;
|
||||
private CoapEndpoint statusEndpoint = new CoapEndpoint.Builder().build();
|
||||
private @Nullable UdpMulticastConnector statusConnector;
|
||||
private final CoapServer server = new CoapServer(NetworkConfig.getStandard(), COIOT_PORT);;
|
||||
private final Set<ShellyCoapListener> coapListeners = new ConcurrentHashSet<>();
|
||||
|
||||
protected class ShellyStatusListener extends CoapResource {
|
||||
private ShellyCoapServer listener;
|
||||
|
||||
public ShellyStatusListener(String uri, ShellyCoapServer listener) {
|
||||
super(uri, true);
|
||||
getAttributes().setTitle("ShellyCoapListener");
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(@Nullable final Exchange exchange) {
|
||||
if (exchange != null) {
|
||||
Request request = exchange.getRequest();
|
||||
Code code = exchange.getRequest().getCode();
|
||||
switch (code) {
|
||||
case CUSTOM_30:
|
||||
listener.processResponse(createResponse(request));
|
||||
break;
|
||||
default:
|
||||
super.handleRequest(exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void start(String localIp, ShellyCoapListener listener) throws UnknownHostException {
|
||||
if (!started) {
|
||||
logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, COIOT_PORT);
|
||||
NetworkConfig nc = NetworkConfig.getStandard();
|
||||
InetAddress localAddr = InetAddress.getByName(localIp);
|
||||
InetSocketAddress localPort = new InetSocketAddress(COIOT_PORT);
|
||||
|
||||
// Join the multicast group on the selected network interface
|
||||
statusConnector = new UdpMulticastConnector(localAddr, localPort, CoAP.MULTICAST_IPV4); // bind UDP listener
|
||||
statusEndpoint = new CoapEndpoint.Builder().setNetworkConfig(nc).setConnector(statusConnector).build();
|
||||
server.addEndpoint(statusEndpoint);
|
||||
CoapResource cit = new ShellyStatusListener("cit", this);
|
||||
CoapResource s = new ShellyStatusListener("s", this);
|
||||
cit.add(s);
|
||||
server.add(cit);
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (!coapListeners.contains(listener)) {
|
||||
coapListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processResponse(Response response) {
|
||||
coapListeners.forEach(listener -> listener.processResponse(response));
|
||||
}
|
||||
|
||||
public static Response createResponse(Request request) {
|
||||
Response response = Response.createResponse(request, ResponseCode.CONTENT);
|
||||
response.setType(request.getType());
|
||||
response.setSourceContext(request.getSourceContext());
|
||||
response.setMID(request.getMID());
|
||||
response.setOptions(request.getOptions());
|
||||
response.setPayload(request.getPayload());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CoapEndpoint getEndpoint() {
|
||||
return statusEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending requests and shutdown the client
|
||||
*/
|
||||
public void stop(ShellyCoapListener listener) {
|
||||
coapListeners.remove(listener);
|
||||
if (coapListeners.isEmpty()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stop() {
|
||||
if (started) {
|
||||
// Last listener
|
||||
server.stop();
|
||||
statusEndpoint.stop();
|
||||
coapListeners.clear();
|
||||
started = false;
|
||||
logger.debug("CoAP Listener stopped");
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Dictionary;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShellyBindingConfiguration} class contains fields mapping binding configuration parameters.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBindingConfiguration {
|
||||
// Binding Configuration Properties
|
||||
public static final String CONFIG_DEF_HTTP_USER = "defaultUserId";
|
||||
public static final String CONFIG_DEF_HTTP_PWD = "defaultPassword";
|
||||
public static final String CONFIG_AUTOCOIOT = "autoCoIoT";
|
||||
|
||||
public String defaultUserId = ""; // default for http basic user id
|
||||
public String defaultPassword = ""; // default for http basic auth password
|
||||
public boolean autoCoIoT = true;
|
||||
|
||||
public void updateFromProperties(Map<String, Object> properties) {
|
||||
for (Map.Entry<String, Object> e : properties.entrySet()) {
|
||||
if (e.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
switch (e.getKey()) {
|
||||
case CONFIG_DEF_HTTP_USER:
|
||||
defaultUserId = (String) e.getValue();
|
||||
break;
|
||||
case CONFIG_DEF_HTTP_PWD:
|
||||
defaultPassword = (String) e.getValue();
|
||||
break;
|
||||
case CONFIG_AUTOCOIOT:
|
||||
autoCoIoT = (boolean) e.getValue();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFromProperties(Dictionary<String, Object> properties) {
|
||||
List<String> keys = Collections.list(properties.keys());
|
||||
Map<String, Object> dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), properties::get));
|
||||
updateFromProperties(dictCopy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShellyThingConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyThingConfiguration {
|
||||
public String deviceIp = ""; // ip address of thedevice
|
||||
public String userId = ""; // userid for http basic auth
|
||||
public String password = ""; // password for http basic auth
|
||||
|
||||
public int updateInterval = 60; // schedule interval for the update job
|
||||
public int lowBattery = 15; // threshold for battery value
|
||||
public boolean brightnessAutoOn = true; // true: turn on device if brightness > 0 is set
|
||||
|
||||
public boolean eventsButton = false; // true: register for Relay btn_xxx events
|
||||
public boolean eventsSwitch = true; // true: register for device out_xxx events
|
||||
public boolean eventsPush = true; // true: register for short/long push events
|
||||
public boolean eventsRoller = true; // true: register for short/long push events
|
||||
public boolean eventsSensorReport = true; // true: register for sensor events
|
||||
public boolean eventsCoIoT = false; // true: use CoIoT events (based on COAP)
|
||||
|
||||
public String localIp = ""; // local ip addresses used to create callback url
|
||||
public String localPort = "8080";
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.discovery;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
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.ShellyApiResult;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.cm.Configuration;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class identifies Shelly devices by their mDNS service information.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = MDNSDiscoveryParticipant.class, immediate = true)
|
||||
public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyDiscoveryParticipant.class);
|
||||
private final ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
|
||||
private final ShellyTranslationProvider messages;
|
||||
private final HttpClient httpClient;
|
||||
private final ConfigurationAdmin configurationAdmin;
|
||||
|
||||
/**
|
||||
* OSGI Service Activation
|
||||
*
|
||||
* @param componentContext
|
||||
* @param localeProvider
|
||||
*/
|
||||
@Activate
|
||||
public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
|
||||
@Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
|
||||
@Reference TranslationProvider i18nProvider, ComponentContext componentContext) {
|
||||
logger.debug("Activating ShellyDiscovery service");
|
||||
this.configurationAdmin = configurationAdmin;
|
||||
this.messages = new ShellyTranslationProvider(componentContext.getBundleContext().getBundle(), i18nProvider,
|
||||
localeProvider);
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
bindingConfig.updateFromProperties(componentContext.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SERVICE_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process updates to Binding Config
|
||||
*
|
||||
* @param componentContext
|
||||
*/
|
||||
@Modified
|
||||
protected void modified(final ComponentContext componentContext) {
|
||||
logger.debug("Shelly Binding Configuration refreshed");
|
||||
bindingConfig.updateFromProperties(componentContext.getProperties());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DiscoveryResult createResult(final ServiceInfo service) {
|
||||
String name = service.getName().toLowerCase(); // Duao: Name starts with" Shelly" rather than "shelly"
|
||||
if (!name.startsWith("shelly")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String address = "";
|
||||
try {
|
||||
String mode = "";
|
||||
String model = "unknown";
|
||||
String deviceName = "";
|
||||
ThingUID thingUID = null;
|
||||
ShellyDeviceProfile profile = null;
|
||||
Map<String, Object> properties = new TreeMap<>();
|
||||
|
||||
name = service.getName().toLowerCase();
|
||||
address = service.getHostAddress();
|
||||
if ((address == null) || address.isEmpty()) {
|
||||
logger.trace("{}: Shelly device discovered with empty IP address (service-name={})", name, service);
|
||||
return null;
|
||||
}
|
||||
String thingType = service.getQualifiedName().contains(SERVICE_TYPE) && name.contains("-")
|
||||
? substringBeforeLast(name, "-")
|
||||
: name;
|
||||
logger.debug("{}: Shelly device discovered: IP-Adress={}, type={}", name, address, thingType);
|
||||
|
||||
// Get device settings
|
||||
Configuration serviceConfig = configurationAdmin.getConfiguration("binding.shelly");
|
||||
if (serviceConfig.getProperties() != null) {
|
||||
bindingConfig.updateFromProperties(serviceConfig.getProperties());
|
||||
}
|
||||
|
||||
ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
config.deviceIp = address;
|
||||
config.userId = bindingConfig.defaultUserId;
|
||||
config.password = bindingConfig.defaultPassword;
|
||||
|
||||
try {
|
||||
ShellyHttpApi api = new ShellyHttpApi(name, config, httpClient);
|
||||
|
||||
profile = api.getDeviceProfile(thingType);
|
||||
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
|
||||
deviceName = getString(profile.settings.name);
|
||||
model = getString(profile.settings.device.type);
|
||||
mode = profile.mode;
|
||||
|
||||
properties = ShellyBaseHandler.fillDeviceProperties(profile);
|
||||
logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
|
||||
profile.deviceType, mode.isEmpty() ? "<standard>" : mode, deviceName);
|
||||
|
||||
// get thing type from device name
|
||||
thingUID = ShellyThingCreator.getThingUID(name, model, mode, false);
|
||||
} catch (ShellyApiException e) {
|
||||
ShellyApiResult result = e.getApiResult();
|
||||
if (result.isHttpAccessUnauthorized()) {
|
||||
logger.info("{}: {}", name, messages.get("discovery.protected", address));
|
||||
|
||||
// create shellyunknown thing - will be changed during thing initialization with valid credentials
|
||||
thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
|
||||
} else {
|
||||
logger.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
|
||||
logger.debug("{}: Discovery failed", name, e);
|
||||
}
|
||||
} catch (IllegalArgumentException e) { // maybe some format description was buggy
|
||||
logger.debug("{}: Discovery failed!", name, e);
|
||||
}
|
||||
|
||||
if (thingUID != null) {
|
||||
addProperty(properties, CONFIG_DEVICEIP, address);
|
||||
addProperty(properties, PROPERTY_MODEL_ID, model);
|
||||
addProperty(properties, PROPERTY_SERVICE_NAME, name);
|
||||
addProperty(properties, PROPERTY_DEV_NAME, deviceName);
|
||||
addProperty(properties, PROPERTY_DEV_TYPE, thingType);
|
||||
addProperty(properties, PROPERTY_DEV_MODE, mode);
|
||||
|
||||
logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
|
||||
String thingLabel = deviceName.isEmpty() ? name + " - " + address
|
||||
: deviceName + " (" + name + "@" + address + ")";
|
||||
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(thingLabel)
|
||||
.withRepresentationProperty(name).build();
|
||||
}
|
||||
} catch (IOException | NullPointerException e) {
|
||||
// maybe some format description was buggy
|
||||
logger.debug("{}: Exception on processing serviceInfo '{}'", name, service.getNiceTextString(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addProperty(Map<String, Object> properties, String key, @Nullable String value) {
|
||||
properties.put(key, value != null ? value : "");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ThingUID getThingUID(@Nullable ServiceInfo service) throws IllegalArgumentException {
|
||||
logger.debug("ServiceInfo {}", service);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("service must not be null!");
|
||||
}
|
||||
String serviceName = service.getName();
|
||||
if (serviceName == null) {
|
||||
throw new IllegalArgumentException("serviceName must not be null!");
|
||||
}
|
||||
serviceName = serviceName.toLowerCase();
|
||||
if (!serviceName.contains(VENDOR.toLowerCase())) {
|
||||
logger.debug("Not a " + VENDOR + " device!");
|
||||
return null;
|
||||
}
|
||||
return ShellyThingCreator.getThingUID(serviceName, "", "", false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.discovery;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyThingCreator} maps the device id into the thing type id
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyThingCreator {
|
||||
private static final Map<String, String> THING_TYPE_MAPPING = new LinkedHashMap<>();
|
||||
static {
|
||||
// mapping by thing type
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_1PM, THING_TYPE_SHELLY1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_1, THING_TYPE_SHELLY1_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_3EM, THING_TYPE_SHELLY3EM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_EM, THING_TYPE_SHELLYEM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_GAS, THING_TYPE_SHELLYGAS_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DW, THING_TYPE_SHELLYDOORWIN_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DW2, THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DUO, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BULB, THING_TYPE_SHELLYBULB_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_VINTAGE, THING_TYPE_SHELLYVINTAGE_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER, THING_TYPE_SHELLYDIMMER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER2, THING_TYPE_SHELLYDIMMER2_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_IX3, THING_TYPE_SHELLYIX3_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BUTTON1, THING_TYPE_SHELLYBUTTON1_STR);
|
||||
|
||||
// mapping by thing type
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1_STR, THING_TYPE_SHELLY1_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1PM_STR, THING_TYPE_SHELLY1PM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY4PRO_STR, THING_TYPE_SHELLY4PRO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER2_STR, THING_TYPE_SHELLYDIMMER2_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER_STR, THING_TYPE_SHELLYDIMMER_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYIX3_STR, THING_TYPE_SHELLYIX3_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY3EM_STR, THING_TYPE_SHELLY3EM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEM_STR, THING_TYPE_SHELLYEM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYVINTAGE_STR, THING_TYPE_SHELLYVINTAGE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBULB_STR, THING_TYPE_SHELLYBULB_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYHT_STR, THING_TYPE_SHELLYHT_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSMOKE_STR, THING_TYPE_SHELLYSMOKE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYGAS_STR, THING_TYPE_SHELLYGAS_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYFLOOD_STR, THING_TYPE_SHELLYFLOOD_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDOORWIN_STR, THING_TYPE_SHELLYDOORWIN_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDOORWIN2_STR, THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSENSE_STR, THING_TYPE_SHELLYSENSE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEYE_STR, THING_TYPE_SHELLYEYE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR);
|
||||
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR);
|
||||
}
|
||||
|
||||
public static ThingUID getThingUID(String serviceName, String deviceType, String mode, boolean unknown) {
|
||||
String devid = substringAfterLast(serviceName, "-");
|
||||
if (devid.isEmpty()) {
|
||||
throw new IllegalArgumentException("serviceName has improper format: " + serviceName);
|
||||
}
|
||||
return new ThingUID(!unknown ? getThingTypeUID(serviceName, deviceType, mode)
|
||||
: getThingTypeUID(THING_TYPE_SHELLYPROTECTED_STR + "-" + devid, deviceType, mode), devid);
|
||||
}
|
||||
|
||||
public static ThingTypeUID getThingTypeUID(String serviceName, String deviceType, String mode) {
|
||||
return new ThingTypeUID(BINDING_ID, getThingType(serviceName, deviceType, mode));
|
||||
}
|
||||
|
||||
public static ThingTypeUID getUnknownTTUID() {
|
||||
return new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPROTECTED_STR);
|
||||
}
|
||||
|
||||
public static String getThingType(String hostname, String deviceType, String mode) {
|
||||
String name = hostname.toLowerCase();
|
||||
String type = substringBefore(name, "-").toLowerCase();
|
||||
String devid = substringAfterLast(name, "-");
|
||||
if ((devid == null) || (type == null)) {
|
||||
throw new IllegalArgumentException("Invalid device name format: " + hostname);
|
||||
}
|
||||
|
||||
// First check for special handling
|
||||
if (name.startsWith(THING_TYPE_SHELLY25_PREFIX)) { // Shelly v2.5
|
||||
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
|
||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY2_RELAY_STR : THING_TYPE_SHELLY2_ROLLER_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLYPLUG_STR)) {
|
||||
// shellyplug-s needs to be mapped to shellyplugs to follow the schema
|
||||
// for the thing types: <thing type>-<mode>
|
||||
if (name.startsWith(THING_TYPE_SHELLYPLUGS_STR) || name.contains("-s")) {
|
||||
return THING_TYPE_SHELLYPLUGS_STR;
|
||||
}
|
||||
return THING_TYPE_SHELLYPLUG_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX)) {
|
||||
return mode.equals(SHELLY_MODE_COLOR) ? THING_TYPE_SHELLYRGBW2_COLOR_STR : THING_TYPE_SHELLYRGBW2_WHITE_STR;
|
||||
}
|
||||
|
||||
// Check general mapping
|
||||
if (!deviceType.isEmpty() && THING_TYPE_MAPPING.containsKey(deviceType)) {
|
||||
return THING_TYPE_MAPPING.get(deviceType);
|
||||
}
|
||||
if (THING_TYPE_MAPPING.containsKey(type)) {
|
||||
return THING_TYPE_MAPPING.get(type);
|
||||
}
|
||||
return THING_TYPE_SHELLYUNKNOWN_STR;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
|
||||
* added on the first thing status update
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyChannelDefinitionsDTO {
|
||||
|
||||
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
|
||||
|
||||
// shortcuts to avoid line breaks (make code more readable)
|
||||
private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
|
||||
private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
|
||||
private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
|
||||
private static final String CHGR_METER = CHANNEL_GROUP_METER;
|
||||
private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
|
||||
private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
|
||||
|
||||
public static final String ITEM_TYPE_NUMBER = "Number";
|
||||
public static final String ITEM_TYPE_STRING = "String";
|
||||
public static final String ITEM_TYPE_SWITCH = "Switch";
|
||||
public static final String ITEM_TYPE_CONTACT = "Contact";
|
||||
public static final String ITEM_TYPE_DATETIME = "DateTime";
|
||||
public static final String ITEM_TYPE_TEMP = "Number:Temperature";
|
||||
public static final String ITEM_TYPE_LUX = "Number:Illuminance";
|
||||
public static final String ITEM_TYPE_POWER = "Number:Power";
|
||||
public static final String ITEM_TYPE_ENERGY = "Number:Energy";
|
||||
public static final String ITEM_TYPE_VOLT = "Number:ElectricPotential";
|
||||
public static final String ITEM_TYPE_AMP = "Number:ElectricPotential";
|
||||
public static final String ITEM_TYPE_PERCENT = "Number:Dimensionless";
|
||||
public static final String ITEM_TYPE_ANGLE = "Number:Angle";
|
||||
|
||||
public static final String PREFIX_GROUP = "definitions.shelly.group.";
|
||||
public static final String PREFIX_CHANNEL = "channel-type.shelly.";
|
||||
public static final String SUFFIX_LABEL = ".label";
|
||||
public static final String SUFFIX_DESCR = ".description";
|
||||
|
||||
public ShellyChannelDefinitionsDTO(ShellyTranslationProvider m) {
|
||||
// Device: Internal Temp
|
||||
CHANNEL_DEFINITIONS
|
||||
// Device
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEM_TYPE_DATETIME))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEM_TYPE_SWITCH))
|
||||
|
||||
// Relay
|
||||
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEM_TYPE_STRING))
|
||||
|
||||
// Roller
|
||||
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEM_TYPE_STRING))
|
||||
|
||||
// RGBW2
|
||||
.add(new ShellyChannel(m, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
|
||||
|
||||
// Power Meter
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// EMeter
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEM_TYPE_VOLT))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEM_TYPE_AMP))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEM_TYPE_NUMBER))
|
||||
|
||||
// Sensors
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEM_TYPE_PERCENT))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEM_TYPE_LUX))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEM_TYPE_CONTACT))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEM_TYPE_ANGLE))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// Button/ix3
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "eventType", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system.button", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// Addon with external sensors
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEM_TYPE_PERCENT))
|
||||
|
||||
// Battery
|
||||
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level",
|
||||
ITEM_TYPE_PERCENT))
|
||||
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEM_TYPE_SWITCH));
|
||||
}
|
||||
|
||||
public static ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
|
||||
String group = substringBefore(channelName, "#");
|
||||
String channel = substringAfter(channelName, "#");
|
||||
if (group.contains(CHANNEL_GROUP_METER)) {
|
||||
group = CHANNEL_GROUP_METER; // map meter1..n to meter
|
||||
} else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
|
||||
group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
|
||||
} else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
|
||||
group = CHANNEL_GROUP_LIGHT_CHANNEL;
|
||||
} else if (group.contains(CHANNEL_GROUP_STATUS)) {
|
||||
group = CHANNEL_GROUP_STATUS; // map status1..n to meter
|
||||
}
|
||||
String channelId = group + "#" + channel;
|
||||
return CHANNEL_DEFINITIONS.get(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*
|
||||
* @return ArrayList<Channel> of channels to be added to the thing
|
||||
*/
|
||||
public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellySettingsStatus status) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
|
||||
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
||||
|
||||
if (!profile.isSensor) {
|
||||
// Only some devices report the internal device temp
|
||||
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
|
||||
CHANNEL_DEVST_ITEMP);
|
||||
}
|
||||
|
||||
// RGBW2
|
||||
addChannel(thing, add, status.input != null, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT);
|
||||
|
||||
// If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
|
||||
boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
|
||||
&& !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
|
||||
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
|
||||
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
|
||||
addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
|
||||
|
||||
if (profile.settings.ledPowerDisable != null) {
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
|
||||
}
|
||||
if (profile.settings.ledStatusDisable != null) {
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
|
||||
}
|
||||
return add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*
|
||||
* @return ArrayList<Channel> of channels to be added to the thing
|
||||
*/
|
||||
public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellyStatusRelay relay, int idx) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
String group = profile.getControlGroup(idx);
|
||||
|
||||
ShellySettingsRelay rs = profile.settings.relays.get(idx);
|
||||
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
|
||||
|
||||
// Shelly 1/1PM Addon
|
||||
if (relay.extTemperature != null) {
|
||||
addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
||||
addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
||||
addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
||||
}
|
||||
if (relay.extHumidity != null) {
|
||||
addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
|
||||
}
|
||||
|
||||
return add;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
addChannel(thing, add, roller.state != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
|
||||
return add;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
|
||||
addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
|
||||
if (meter.counters != null) {
|
||||
addChannel(thing, newChannels, meter.counters[0] != null, group, CHANNEL_METER_LASTMIN1);
|
||||
}
|
||||
addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
|
||||
String group) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
|
||||
addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
|
||||
addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
|
||||
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
|
||||
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
|
||||
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
|
||||
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
|
||||
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellyStatusSensor sdata) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
|
||||
// Sensor data
|
||||
addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
|
||||
addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
|
||||
addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
|
||||
if (sdata.accel != null) {
|
||||
addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_VIBRATION);
|
||||
addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
|
||||
}
|
||||
addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
|
||||
addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
|
||||
addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_ILLUM);
|
||||
addChannel(thing, newChannels, sdata.contact != null && sdata.contact.state != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_CONTACT);
|
||||
addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
|
||||
addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
|
||||
addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
|
||||
addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
|
||||
|
||||
// Gas
|
||||
if (sdata.gasSensor != null) {
|
||||
addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
|
||||
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_SSTATE);
|
||||
addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
|
||||
CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
|
||||
addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
|
||||
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_ALARM_STATE);
|
||||
}
|
||||
|
||||
// Battery
|
||||
if (sdata.bat != null) {
|
||||
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
|
||||
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
|
||||
}
|
||||
|
||||
addChannel(thing, newChannels, true, profile.getControlGroup(0), CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
|
||||
String channelName) throws IllegalArgumentException {
|
||||
if (supported) {
|
||||
final String channelId = group + "#" + channelName;
|
||||
final ShellyChannel channelDef = getDefinition(channelId);
|
||||
final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
|
||||
final ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
|
||||
? new ChannelTypeUID(channelDef.typeId)
|
||||
: new ChannelTypeUID(BINDING_ID, channelDef.typeId);
|
||||
Channel channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
|
||||
newChannels.put(channelId, channel);
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellyChannel {
|
||||
private final ShellyTranslationProvider messages;
|
||||
public String group = "";
|
||||
public String groupLabel = "";
|
||||
public String groupDescription = "";
|
||||
|
||||
public String channel = "";
|
||||
public String label = "";
|
||||
public String description = "";
|
||||
public String itemType = "";
|
||||
public String typeId = "";
|
||||
public String category = "";
|
||||
public Set<String> tags = new HashSet<>();
|
||||
|
||||
public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
|
||||
String itemType, String... category) {
|
||||
this.messages = messages;
|
||||
this.group = group;
|
||||
this.channel = channel;
|
||||
this.itemType = itemType;
|
||||
this.typeId = typeId;
|
||||
|
||||
groupLabel = getText(PREFIX_GROUP + group + SUFFIX_LABEL);
|
||||
groupDescription = getText(PREFIX_GROUP + group + SUFFIX_DESCR);
|
||||
label = getText(PREFIX_CHANNEL + channel + SUFFIX_LABEL);
|
||||
description = getText(PREFIX_CHANNEL + channel + SUFFIX_DESCR);
|
||||
}
|
||||
|
||||
public String getChanneId() {
|
||||
return group + "#" + channel;
|
||||
}
|
||||
|
||||
private String getText(String key) {
|
||||
String text = messages.get(key);
|
||||
return text != null ? text : "";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChannelMap {
|
||||
private final Map<String, ShellyChannel> map = new LinkedHashMap<>();
|
||||
|
||||
private ChannelMap add(ShellyChannel def) {
|
||||
map.put(def.getChanneId(), def);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShellyChannel get(String channelName) throws IllegalArgumentException {
|
||||
ShellyChannel def = null;
|
||||
if (channelName.contains("#")) {
|
||||
def = map.get(channelName);
|
||||
}
|
||||
for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
|
||||
if (entry.getValue().channel.contains("#" + channelName)) {
|
||||
def = entry.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (def == null) {
|
||||
throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* The {@link ShellyColorUtils} provides some utility functions around RGBW handling.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyColorUtils {
|
||||
OnOffType power = OnOffType.OFF;
|
||||
String mode = "";
|
||||
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
int white = 0;
|
||||
PercentType percentRed = new PercentType(0);
|
||||
PercentType percentGreen = new PercentType(0);
|
||||
PercentType percentBlue = new PercentType(0);
|
||||
PercentType percentWhite = new PercentType(0);
|
||||
|
||||
int gain = 0;
|
||||
int brightness = 0;
|
||||
int temp = 0;
|
||||
int minTemp = 0;
|
||||
int maxTemp = 0;
|
||||
PercentType percentGain = new PercentType(0);
|
||||
PercentType percentBrightness = new PercentType(0);
|
||||
PercentType percentTemp = new PercentType(0);
|
||||
Integer effect = 0;
|
||||
|
||||
public ShellyColorUtils() {
|
||||
}
|
||||
|
||||
public ShellyColorUtils(ShellyColorUtils col) {
|
||||
minTemp = col.minTemp;
|
||||
maxTemp = col.maxTemp;
|
||||
setRed(col.red);
|
||||
setGreen(col.green);
|
||||
setBlue(col.blue);
|
||||
setWhite(col.white);
|
||||
setGain(col.gain);
|
||||
setBrightness(col.brightness);
|
||||
setTemp(col.temp);
|
||||
}
|
||||
|
||||
void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
void setMinMaxTemp(int min, int max) {
|
||||
minTemp = min;
|
||||
maxTemp = max;
|
||||
}
|
||||
|
||||
boolean setRGBW(int red, int green, int blue, int white) {
|
||||
setRed(red);
|
||||
setGreen(green);
|
||||
setBlue(blue);
|
||||
setWhite(white);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean setRed(int value) {
|
||||
boolean changed = red != value;
|
||||
red = value;
|
||||
percentRed = toPercent(red);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setGreen(int value) {
|
||||
boolean changed = green != value;
|
||||
green = value;
|
||||
percentGreen = toPercent(green);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setBlue(int value) {
|
||||
boolean changed = blue != value;
|
||||
blue = value;
|
||||
percentBlue = toPercent(blue);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setWhite(int value) {
|
||||
boolean changed = white != value;
|
||||
white = value;
|
||||
percentWhite = toPercent(white);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setBrightness(int value) {
|
||||
boolean changed = brightness != value;
|
||||
brightness = value;
|
||||
percentBrightness = toPercent(brightness, SHELLY_MIN_BRIGHTNESS, SHELLY_MAX_BRIGHTNESS);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setGain(int value) {
|
||||
boolean changed = gain != value;
|
||||
gain = value;
|
||||
percentGain = toPercent(gain, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setTemp(int value) {
|
||||
boolean changed = temp != value;
|
||||
temp = value;
|
||||
percentTemp = toPercent(temp, minTemp, maxTemp);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setEffect(int value) {
|
||||
boolean changed = effect != value;
|
||||
effect = value;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public HSBType toHSB() {
|
||||
return HSBType.fromRGB(red, green, blue);
|
||||
}
|
||||
|
||||
public Integer[] fromRGBW(String rgbwString) {
|
||||
Integer values[] = new Integer[4];
|
||||
values[0] = values[1] = values[2] = values[3] = -1;
|
||||
try {
|
||||
String rgbw[] = rgbwString.split(",");
|
||||
for (int i = 0; i < rgbw.length; i++) {
|
||||
values[i] = Integer.parseInt(rgbw[i]);
|
||||
}
|
||||
} catch (NullPointerException e) { // might be a format problem
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to convert fullColor value: " + rgbwString + ", " + e.getMessage());
|
||||
}
|
||||
if (values[0] != -1) {
|
||||
setRed(values[0]);
|
||||
}
|
||||
if (values[1] != -1) {
|
||||
setGreen(values[1]);
|
||||
}
|
||||
if (values[2] != -1) {
|
||||
setBlue(values[2]);
|
||||
}
|
||||
if (values[3] != -1) {
|
||||
setWhite(values[3]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public static PercentType toPercent(Integer value) {
|
||||
return toPercent(value, 0, SHELLY_MAX_COLOR);
|
||||
}
|
||||
|
||||
public static PercentType toPercent(Integer _value, Integer min, Integer max) {
|
||||
Double range = max.doubleValue() - min.doubleValue();
|
||||
Double value = _value.doubleValue();
|
||||
value = value < min ? min.doubleValue() : value;
|
||||
value = value > max ? max.doubleValue() : value;
|
||||
Double percent = 0.0;
|
||||
if (range > 0) {
|
||||
percent = new Double(Math.round((value - min) / range * 100));
|
||||
}
|
||||
return new PercentType(new BigDecimal(percent));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/***
|
||||
* The{@link ShellyComponents} implements updates for supplemental components
|
||||
* Meter will be used by Relay + Light; Sensor is part of H&T, Flood, Door Window, Sense
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyComponents {
|
||||
|
||||
/**
|
||||
* Update device status
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
*/
|
||||
public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status));
|
||||
}
|
||||
|
||||
Integer rssi = getInteger(status.wifiSta.rssi);
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
|
||||
toQuantityType(new Double(getLong(status.uptime)), DIGITS_NONE, SmartHomeUnits.SECOND));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
|
||||
if (status.tmp != null) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
} else if (status.temperature != null) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
}
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
|
||||
|
||||
return false; // device status never triggers update
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Meter channel
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*/
|
||||
public static boolean updateMeters(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
|
||||
ShellyDeviceProfile profile = thingHandler.getProfile();
|
||||
|
||||
double accumulatedWatts = 0.0;
|
||||
double accumulatedTotal = 0.0;
|
||||
double accumulatedReturned = 0.0;
|
||||
|
||||
boolean updated = false;
|
||||
// Devices without power meters get no updates
|
||||
// We need to differ
|
||||
// Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device
|
||||
// Meter and EMeter have a different set of channels
|
||||
if ((profile.numMeters > 0) && ((status.meters != null) || (status.emeters != null))) {
|
||||
if (!profile.isRoller && !profile.isRGBW2) {
|
||||
thingHandler.logger.trace("{}: Updating {} {}meter(s)", thingHandler.thingName, profile.numMeters,
|
||||
!profile.isEMeter ? "standard " : "e-");
|
||||
|
||||
// In Relay mode we map eacher meter to the matching channel group
|
||||
int m = 0;
|
||||
if (!profile.isEMeter) {
|
||||
for (ShellySettingsMeter meter : status.meters) {
|
||||
Integer meterIndex = m + 1;
|
||||
if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag
|
||||
// correctly in white mode
|
||||
String groupName = "";
|
||||
if (profile.numMeters > 1) {
|
||||
groupName = CHANNEL_GROUP_METER + meterIndex.toString();
|
||||
} else {
|
||||
groupName = CHANNEL_GROUP_METER;
|
||||
}
|
||||
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
// skip for Shelly Bulb: JSON has a meter, but values don't get updated
|
||||
if (!profile.isBulb) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createMeterChannels(thingHandler.getThing(), meter, groupName));
|
||||
}
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(meter.power), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
accumulatedWatts += getDouble(meter.power);
|
||||
|
||||
// convert Watt/Min to kw/h
|
||||
if (meter.total != null) {
|
||||
double kwh = getDouble(meter.total) / 60 / 1000;
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(kwh, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
accumulatedTotal += kwh;
|
||||
}
|
||||
if (meter.counters != null) {
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
}
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp)));
|
||||
}
|
||||
m++;
|
||||
}
|
||||
} else {
|
||||
for (ShellySettingsEMeter emeter : status.emeters) {
|
||||
Integer meterIndex = m + 1;
|
||||
if (getBool(emeter.isValid)) {
|
||||
String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString()
|
||||
: CHANNEL_GROUP_METER;
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createEMeterChannels(thingHandler.getThing(), emeter, groupName));
|
||||
}
|
||||
|
||||
// convert Watt/Hour tok w/h
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(emeter.power), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH, toQuantityType(
|
||||
getDouble(emeter.total) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET, toQuantityType(
|
||||
getDouble(emeter.totalReturned) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_REACTWATTS,
|
||||
toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(emeter.voltage), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
|
||||
if (emeter.current != null) {
|
||||
// Shelly
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(emeter.current), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_PFACTOR,
|
||||
getDecimal(emeter.pf));
|
||||
}
|
||||
|
||||
accumulatedWatts += getDouble(emeter.power);
|
||||
accumulatedTotal += getDouble(emeter.total) / 1000;
|
||||
accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
m++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In Roller Mode we accumulate all meters to a single set of meters
|
||||
thingHandler.logger.trace("{}: Updating Meter (accumulated)", thingHandler.thingName);
|
||||
double currentWatts = 0.0;
|
||||
double totalWatts = 0.0;
|
||||
double lastMin1 = 0.0;
|
||||
long timestamp = 0l;
|
||||
String groupName = CHANNEL_GROUP_METER;
|
||||
for (ShellySettingsMeter meter : status.meters) {
|
||||
if (meter.isValid) {
|
||||
currentWatts += getDouble(meter.power);
|
||||
totalWatts += getDouble(meter.total);
|
||||
if (meter.counters != null) {
|
||||
lastMin1 += getDouble(meter.counters[0]);
|
||||
}
|
||||
if (getLong(meter.timestamp) > timestamp) {
|
||||
timestamp = getLong(meter.timestamp); // newest one
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create channels for 1 Meter
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(getDouble(lastMin1), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
|
||||
// convert totalWatts into kw/h
|
||||
totalWatts = totalWatts / (60.0 * 10000.0);
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(currentWatts), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(getDouble(totalWatts), DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
getTimestamp(getString(profile.settings.timezone), timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
if (updated && !profile.isRoller && !profile.isRGBW2) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
|
||||
toQuantityType(accumulatedWatts, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
|
||||
toQuantityType(accumulatedTotal, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCURETURNED,
|
||||
toQuantityType(accumulatedReturned, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sensor channel
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
|
||||
throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = thingHandler.getProfile();
|
||||
|
||||
boolean updated = false;
|
||||
if (profile.isSensor || profile.hasBattery || profile.isSense) {
|
||||
ShellyStatusSensor sdata = thingHandler.api.getSensorStatus();
|
||||
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
|
||||
thingHandler.updateChannelDefinitions(
|
||||
ShellyChannelDefinitionsDTO.createSensorChannels(thingHandler.getThing(), profile, sdata));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateWakeupReason(sdata.actReasons);
|
||||
|
||||
if ((sdata.contact != null) && sdata.contact.isValid) {
|
||||
// Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
getString(sdata.contact.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
|
||||
getStringType(sdata.sensorError));
|
||||
if (changed) {
|
||||
thingHandler.postEvent(sdata.sensorError, true);
|
||||
}
|
||||
updated |= changed;
|
||||
}
|
||||
if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) {
|
||||
thingHandler.logger.trace("{}: Updating temperature", thingHandler.thingName);
|
||||
Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS)
|
||||
? getDouble(sdata.tmp.tC)
|
||||
: getDouble(sdata.tmp.tF);
|
||||
if (getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)) {
|
||||
// convert Fahrenheit to Celsius
|
||||
temp = ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(temp).doubleValue();
|
||||
}
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
|
||||
toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (sdata.hum != null) {
|
||||
thingHandler.logger.trace("{}: Updating humidity", thingHandler.thingName);
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
|
||||
toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
if ((sdata.lux != null) && getBool(sdata.lux.isValid)) {
|
||||
// “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true},
|
||||
thingHandler.logger.trace("{}: Updating lux", thingHandler.thingName);
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
|
||||
toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, SmartHomeUnits.LUX));
|
||||
if (sdata.lux.illumination != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM,
|
||||
getStringType(sdata.lux.illumination));
|
||||
}
|
||||
}
|
||||
if (sdata.accel != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT, toQuantityType(
|
||||
getDouble(sdata.accel.tilt.doubleValue()), DIGITS_NONE, SmartHomeUnits.DEGREE_ANGLE));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
getInteger(sdata.accel.vibration) == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
if (sdata.flood != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
getOnOff(sdata.flood));
|
||||
}
|
||||
if (sdata.smoke != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE,
|
||||
getOnOff(sdata.smoke));
|
||||
}
|
||||
if (sdata.gasSensor != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
|
||||
getStringType(sdata.gasSensor.selfTestState));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
|
||||
getStringType(sdata.gasSensor.alarmState));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE,
|
||||
getStringType(sdata.gasSensor.sensorState));
|
||||
}
|
||||
if ((sdata.concentration != null) && sdata.concentration.isValid) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
|
||||
getDecimal(sdata.concentration.ppm));
|
||||
}
|
||||
|
||||
if (sdata.bat != null) { // no update for Sense
|
||||
thingHandler.logger.trace("{}: Updating battery", thingHandler.thingName);
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
|
||||
toQuantityType(getDouble(sdata.bat.value), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
|
||||
getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF);
|
||||
updated |= changed;
|
||||
if (changed && getDouble(sdata.bat.value) < thingHandler.config.lowBattery) {
|
||||
thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
|
||||
}
|
||||
}
|
||||
if (sdata.motion != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
|
||||
getOnOff(sdata.motion));
|
||||
}
|
||||
if (sdata.charger != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
|
||||
getOnOff(sdata.charger));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateInputs(status);
|
||||
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link DeviceUpdateListener} can register on the {@link TradfriGatewayHandler} to be informed about details about
|
||||
* devices.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyDeviceListener {
|
||||
|
||||
/**
|
||||
* This method is called when new device information is received.
|
||||
*/
|
||||
public boolean onEvent(String ipAddress, String deviceName, String deviceIndex, String eventType,
|
||||
Map<String, String> parameters);
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLightChannel;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyLightHandler} handles light (Bulb, Duo and RGBW2) specific commands and status. All other commands
|
||||
* will be routet of the ShellyBaseHandler.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyLightHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyLightHandler.class);
|
||||
private final Map<Integer, ShellyColorUtils> channelColors;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
channelColors = new TreeMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Thing is using {}", this.getClass());
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws IllegalArgumentException {
|
||||
String groupName = getString(channelUID.getGroupId());
|
||||
if (groupName.isEmpty()) {
|
||||
throw new IllegalArgumentException("Empty groupName");
|
||||
}
|
||||
|
||||
int lightId = getLightIdFromGroup(groupName);
|
||||
logger.trace("{}: Execute command {} on channel {}, lightId={}", thingName, command, channelUID.getAsString(),
|
||||
lightId);
|
||||
|
||||
try {
|
||||
ShellyColorUtils oldCol = getCurrentColors(lightId);
|
||||
oldCol.mode = profile.mode;
|
||||
ShellyColorUtils col = new ShellyColorUtils(oldCol);
|
||||
|
||||
boolean update = true;
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
default: // non-bulb commands will be handled by the generic handler
|
||||
return false;
|
||||
|
||||
case CHANNEL_LIGHT_POWER:
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
api.setLightParm(lightId, SHELLY_LIGHT_TURN,
|
||||
command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = (OnOffType) command;
|
||||
requestUpdates(1, false);
|
||||
update = false;
|
||||
break;
|
||||
case CHANNEL_LIGHT_COLOR_MODE:
|
||||
logger.debug("{}: Select color mode {}", thingName, command);
|
||||
col.setMode((OnOffType) command == OnOffType.ON ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
|
||||
break;
|
||||
case CHANNEL_COLOR_PICKER:
|
||||
logger.debug("{}: Update colors from color picker", thingName);
|
||||
update = handleColorPicker(profile, lightId, col, command);
|
||||
break;
|
||||
case CHANNEL_COLOR_FULL:
|
||||
logger.debug("{}: Set colors to {}", thingName, command);
|
||||
handleFullColor(col, command);
|
||||
break;
|
||||
case CHANNEL_COLOR_RED:
|
||||
col.setRed(setColor(lightId, SHELLY_COLOR_RED, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_GREEN:
|
||||
col.setGreen(setColor(lightId, SHELLY_COLOR_GREEN, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_BLUE:
|
||||
col.setBlue(setColor(lightId, SHELLY_COLOR_BLUE, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_WHITE:
|
||||
col.setWhite(setColor(lightId, SHELLY_COLOR_WHITE, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_GAIN:
|
||||
col.setGain(setColor(lightId, SHELLY_COLOR_GAIN, command, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
|
||||
break;
|
||||
case CHANNEL_BRIGHTNESS: // only in white mode
|
||||
if (profile.inColor && !profile.isBulb) {
|
||||
logger.debug("{}: Not in white mode, brightness not available", thingName);
|
||||
break;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
if (command instanceof OnOffType) { // Switch
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
ShellyShortLightStatus light = api.setRelayTurn(lightId,
|
||||
command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = getOnOff(light.ison);
|
||||
col.setBrightness(light.brightness);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", col.power);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(new Double(col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE,
|
||||
SmartHomeUnits.PERCENT));
|
||||
update = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
Float percent = ((PercentType) command).floatValue();
|
||||
value = percent.intValue(); // 0..100% = 0..100
|
||||
logger.debug("{}: Set brightness to {}%/{}", thingName, percent, value);
|
||||
} else if (command instanceof DecimalType) {
|
||||
value = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set brightness to {} (Integer)", thingName, value);
|
||||
}
|
||||
if (value == 0) {
|
||||
logger.debug("{}: Brightness=0 -> switch light OFF", thingName);
|
||||
api.setRelayTurn(lightId, SHELLY_API_OFF);
|
||||
update = false;
|
||||
} else {
|
||||
if (command instanceof IncreaseDecreaseType) {
|
||||
ShellyShortLightStatus light = api.getLightStatus(lightId);
|
||||
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
|
||||
value = Math.min(light.brightness + DIM_STEPSIZE, 100);
|
||||
} else {
|
||||
value = Math.max(light.brightness - DIM_STEPSIZE, 0);
|
||||
}
|
||||
logger.trace("{}: Change brightness from {} to {}", thingName, light.brightness, value);
|
||||
}
|
||||
|
||||
validateRange("brightness", value, 0, 100);
|
||||
logger.debug("{}: Changing brightness from {} to {}", thingName, oldCol.brightness, value);
|
||||
col.setBrightness(value);
|
||||
}
|
||||
updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_POWER,
|
||||
value > 0 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
|
||||
case CHANNEL_COLOR_TEMP:
|
||||
Integer temp = -1;
|
||||
if (command instanceof PercentType) {
|
||||
logger.debug("{}: Set color temp to {}%", thingName, ((PercentType) command).floatValue());
|
||||
Float percent = ((PercentType) command).floatValue() / 100;
|
||||
temp = new DecimalType(col.minTemp + ((col.maxTemp - col.minTemp)) * percent).intValue();
|
||||
logger.debug("{}: Converted color-temp {}% to {}K (from Percent to Integer)", thingName,
|
||||
percent, temp);
|
||||
} else if (command instanceof DecimalType) {
|
||||
temp = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set color temp to {}K (Integer)", thingName, temp);
|
||||
}
|
||||
validateRange(CHANNEL_COLOR_TEMP, temp, col.minTemp, col.maxTemp);
|
||||
col.setTemp(temp);
|
||||
col.brightness = -1;
|
||||
break;
|
||||
|
||||
case CHANNEL_COLOR_EFFECT:
|
||||
Integer effect = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set color effect to {}", thingName, effect);
|
||||
validateRange("effect", effect, SHELLY_MIN_EFFECT, SHELLY_MAX_EFFECT);
|
||||
col.setEffect(effect.intValue());
|
||||
}
|
||||
|
||||
if (update) {
|
||||
// check for switching color mode
|
||||
if (profile.isBulb && !col.mode.isEmpty() && !col.mode.equals(oldCol.mode)) {
|
||||
logger.debug("{}: Color mode changed from {} to {}, set new mode", thingName, oldCol.mode,
|
||||
col.mode);
|
||||
api.setLightMode(col.mode);
|
||||
}
|
||||
|
||||
// send changed colors to the device
|
||||
sendColors(profile, lightId, oldCol, col, config.brightnessAutoOn);
|
||||
}
|
||||
return true;
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to handle command: {}", thingName, e.toString());
|
||||
return false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Unable to handle command", thingName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleColorPicker(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils col,
|
||||
Command command) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
if (command instanceof HSBType) {
|
||||
HSBType hsb = (HSBType) command;
|
||||
|
||||
logger.debug("HSB-Info={}, Hue={}, getRGB={}, toRGB={}/{}/{}", hsb, hsb.getHue(),
|
||||
String.format("0x%08X", hsb.getRGB()), hsb.toRGB()[0], hsb.toRGB()[1], hsb.toRGB()[2]);
|
||||
if (hsb.toString().contains("360,")) {
|
||||
logger.trace("{}: need to fix the Hue value (360->0)", thingName);
|
||||
HSBType fixHue = new HSBType(new DecimalType(0), hsb.getSaturation(), hsb.getBrightness());
|
||||
hsb = fixHue;
|
||||
}
|
||||
|
||||
col.setRed(getColorFromHSB(hsb.getRed()));
|
||||
col.setBlue(getColorFromHSB(hsb.getBlue()));
|
||||
col.setGreen(getColorFromHSB(hsb.getGreen()));
|
||||
col.setBrightness(getColorFromHSB(hsb.getBrightness(), BRIGHTNESS_FACTOR));
|
||||
// white, gain and temp are not part of the HSB color scheme
|
||||
updated = true;
|
||||
} else if (command instanceof PercentType) {
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
col.brightness = SHELLY_MAX_BRIGHTNESS * ((PercentType) command).intValue();
|
||||
updated = true;
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
api.setLightParm(lightId, SHELLY_LIGHT_TURN,
|
||||
(OnOffType) command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = (OnOffType) command;
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
logger.debug("{}: {} brightness by {}", thingName, command, SHELLY_DIM_STEPSIZE);
|
||||
PercentType percent = (PercentType) super.getChannelValue(CHANNEL_GROUP_COLOR_CONTROL,
|
||||
CHANNEL_BRIGHTNESS);
|
||||
if (percent != null) {
|
||||
int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
|
||||
int newBrightness = currentBrightness;
|
||||
if (command == IncreaseDecreaseType.DECREASE) {
|
||||
newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
|
||||
} else {
|
||||
newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
|
||||
}
|
||||
col.brightness = newBrightness;
|
||||
updated = currentBrightness != newBrightness;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean handleFullColor(ShellyColorUtils col, Command command) throws IllegalArgumentException {
|
||||
String color = command.toString().toLowerCase();
|
||||
if (color.contains(",")) {
|
||||
col.fromRGBW(color);
|
||||
} else if (color.equals(SHELLY_COLOR_RED)) {
|
||||
col.setRGBW(SHELLY_MAX_COLOR, 0, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_GREEN)) {
|
||||
col.setRGBW(0, SHELLY_MAX_COLOR, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_BLUE)) {
|
||||
col.setRGBW(0, 0, SHELLY_MAX_COLOR, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_YELLOW)) {
|
||||
col.setRGBW(SHELLY_MAX_COLOR, SHELLY_MAX_COLOR, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_WHITE)) {
|
||||
col.setRGBW(0, 0, 0, SHELLY_MAX_COLOR);
|
||||
col.setMode(SHELLY_MODE_WHITE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid full color selection: " + color);
|
||||
}
|
||||
col.setMode(color.equals(SHELLY_MODE_WHITE) ? SHELLY_MODE_WHITE : SHELLY_MODE_COLOR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ShellyColorUtils getCurrentColors(int lightId) {
|
||||
ShellyColorUtils col;
|
||||
if (!channelColors.containsKey(lightId)) {
|
||||
col = new ShellyColorUtils(); // create a new entry
|
||||
col.setMinMaxTemp(profile.minTemp, profile.maxTemp);
|
||||
channelColors.put(lightId, col);
|
||||
logger.trace("{}: Colors entry created for lightId {}", thingName, lightId);
|
||||
} else {
|
||||
col = channelColors.get(lightId);
|
||||
logger.trace(
|
||||
"{}: Colors loaded for lightId {}: power={}, RGBW={}/{}/{}/{}, gain={}, brightness={}, color temp={} (min={}, max={}",
|
||||
thingName, lightId, col.power, col.red, col.green, col.blue, col.white, col.gain, col.brightness,
|
||||
col.temp, col.minTemp, col.maxTemp);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeviceStatus(ShellySettingsStatus genericStatus) throws ShellyApiException {
|
||||
if (!profile.isInitialized()) {
|
||||
logger.debug("{}: Device not yet initialized!", thingName);
|
||||
return false;
|
||||
}
|
||||
if (!profile.isLight) {
|
||||
logger.debug("{}: ERROR: Device is not a light. but class ShellyHandlerLight is called!", thingName);
|
||||
}
|
||||
|
||||
ShellyStatusLight status = api.getLightStatus();
|
||||
logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.mode,
|
||||
status.lights.size());
|
||||
|
||||
// In white mode we have multiple channels
|
||||
int lightId = 0;
|
||||
boolean updated = false;
|
||||
for (ShellyStatusLightChannel light : status.lights) {
|
||||
Integer channelId = lightId + 1;
|
||||
String controlGroup = buildControlGroupName(profile, channelId);
|
||||
// The bulb has a combined channel set for color or white mode
|
||||
// The RGBW2 uses 2 different thing types: color=1 channel, white=4 channel
|
||||
if (profile.isBulb) {
|
||||
updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_COLOR_MODE, getOnOff(profile.inColor));
|
||||
}
|
||||
|
||||
ShellyColorUtils col = getCurrentColors(lightId);
|
||||
col.power = getOnOff(light.ison);
|
||||
|
||||
// Channel control/timer
|
||||
// ShellyStatusLightChannel light = status.lights.get(i);
|
||||
updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
|
||||
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(light.autoOn));
|
||||
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(light.autoOff));
|
||||
updated |= updateInputs(controlGroup, genericStatus, lightId);
|
||||
if (getBool(light.overpower)) {
|
||||
postEvent(ALARM_TYPE_OVERPOWER, false);
|
||||
}
|
||||
|
||||
if (profile.inColor) {
|
||||
logger.trace("{}: update color settings", thingName);
|
||||
col.setRGBW(getInteger(light.red), getInteger(light.green), getInteger(light.blue),
|
||||
getInteger(light.white));
|
||||
col.setGain(getInteger(light.gain));
|
||||
col.setEffect(getInteger(light.effect));
|
||||
|
||||
String colorGroup = CHANNEL_GROUP_COLOR_CONTROL;
|
||||
logger.trace("{}: Update channels for group {}: RGBW={}/{}/{}, in %:{}%/{}%/{}%, white={}%, gain={}%",
|
||||
thingName, colorGroup, col.red, col.green, col.blue, col.percentRed, col.percentGreen,
|
||||
col.percentBlue, col.percentWhite, col.percentGain);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_RED, col.percentRed);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_GREEN, col.percentGreen);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_BLUE, col.percentBlue);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_WHITE, col.percentWhite);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_GAIN, col.percentGain);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_EFFECT, new DecimalType(col.effect));
|
||||
setFullColor(colorGroup, col);
|
||||
|
||||
logger.trace("{}: update {}.color picker", thingName, colorGroup);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_PICKER, col.toHSB());
|
||||
}
|
||||
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
String whiteGroup = buildWhiteGroupName(profile, channelId);
|
||||
col.setBrightness(getInteger(light.brightness));
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Switch", col.power);
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : new Double(0),
|
||||
DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
|
||||
if ((profile.isBulb || profile.isDuo) && (light.temp != null)) {
|
||||
col.setTemp(getInteger(light.temp));
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_COLOR_TEMP, col.percentTemp);
|
||||
logger.trace("{}: update {}.color picker", thingName, whiteGroup);
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_COLOR_PICKER, col.toHSB());
|
||||
}
|
||||
}
|
||||
|
||||
// continue with next light
|
||||
lightId++;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
private Integer setColor(Integer lightId, String colorName, Command command, Integer minValue, Integer maxValue)
|
||||
throws ShellyApiException, IllegalArgumentException {
|
||||
Integer value = -1;
|
||||
logger.debug("{}: Set {} to {} ({})", thingName, colorName, command, command.getClass());
|
||||
if (command instanceof PercentType) {
|
||||
PercentType percent = (PercentType) command;
|
||||
Double v = new Double(maxValue) * percent.doubleValue() / 100.0;
|
||||
value = v.intValue();
|
||||
logger.debug("{}: Value for {} is in percent: {}%={}", thingName, colorName, percent, value);
|
||||
} else if (command instanceof DecimalType) {
|
||||
value = ((DecimalType) command).intValue();
|
||||
logger.debug("Value for {} is a number: {}", colorName, value);
|
||||
} else if (command instanceof OnOffType) {
|
||||
value = ((OnOffType) command).equals(OnOffType.ON) ? SHELLY_MAX_COLOR : SHELLY_MIN_COLOR;
|
||||
logger.debug("{}: Value for {} of type OnOff was converted to {}", thingName, colorName, value);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid value type for " + colorName + ": " + value + " / type " + value.getClass());
|
||||
}
|
||||
validateRange(colorName, value, minValue, maxValue);
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
private Integer setColor(Integer lightId, String colorName, Command command, Integer maxValue)
|
||||
throws ShellyApiException, IllegalArgumentException {
|
||||
return setColor(lightId, colorName, command, 0, maxValue);
|
||||
}
|
||||
|
||||
private void setFullColor(String colorGroup, ShellyColorUtils col) {
|
||||
if ((col.red == SHELLY_MAX_COLOR) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_YELLOW));
|
||||
} else if ((col.red == SHELLY_MAX_COLOR) && (col.green == 0) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_RED));
|
||||
} else if ((col.red == 0) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_GREEN));
|
||||
} else if ((col.red == 0) && (col.green == 0) && (col.blue == SHELLY_MAX_COLOR)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_BLUE));
|
||||
} else if ((col.red == 0) && (col.green == 0) && (col.blue == 0) && (col.white == SHELLY_MAX_COLOR)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_WHITE));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendColors(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils oldCol,
|
||||
ShellyColorUtils newCol, boolean autoOn) throws ShellyApiException {
|
||||
// boolean updated = false;
|
||||
Integer channelId = lightId + 1;
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
|
||||
logger.trace(
|
||||
"{}: New color settings for channel {}: RGB {}/{}/{}, white={}, gain={}, brightness={}, color-temp={}",
|
||||
thingName, channelId, newCol.red, newCol.green, newCol.blue, newCol.white, newCol.gain,
|
||||
newCol.brightness, newCol.temp);
|
||||
if (autoOn && (newCol.brightness >= 0)) {
|
||||
parms.put(SHELLY_LIGHT_TURN, profile.inColor || newCol.brightness > 0 ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
}
|
||||
if (profile.inColor) {
|
||||
if (oldCol.red != newCol.red || oldCol.green != newCol.green || oldCol.blue != newCol.blue
|
||||
|| oldCol.white != newCol.white) {
|
||||
logger.debug("{}: Setting RGBW to {}/{}/{}/{}", thingName, newCol.red, newCol.green, newCol.blue,
|
||||
newCol.white);
|
||||
parms.put(SHELLY_COLOR_RED, String.valueOf(newCol.red));
|
||||
parms.put(SHELLY_COLOR_GREEN, String.valueOf(newCol.green));
|
||||
parms.put(SHELLY_COLOR_BLUE, String.valueOf(newCol.blue));
|
||||
parms.put(SHELLY_COLOR_WHITE, String.valueOf(newCol.white));
|
||||
}
|
||||
}
|
||||
if ((!profile.inColor) && (oldCol.temp != newCol.temp)) {
|
||||
logger.debug("{}: Setting color temp to {}", thingName, newCol.temp);
|
||||
parms.put(SHELLY_COLOR_TEMP, String.valueOf(newCol.temp));
|
||||
}
|
||||
if (oldCol.gain != newCol.gain) {
|
||||
logger.debug("{}: Setting gain to {}", thingName, newCol.gain);
|
||||
parms.put(SHELLY_COLOR_GAIN, String.valueOf(newCol.gain));
|
||||
}
|
||||
if ((newCol.brightness >= 0) && (!profile.inColor || profile.isBulb)
|
||||
&& (oldCol.brightness != newCol.brightness)) {
|
||||
logger.debug("{}: Setting brightness to {}", thingName, newCol.brightness);
|
||||
parms.put(SHELLY_COLOR_BRIGHTNESS, String.valueOf(newCol.brightness));
|
||||
}
|
||||
if (!oldCol.effect.equals(newCol.effect)) {
|
||||
logger.debug("{}: Setting effect to {}", thingName, newCol.effect);
|
||||
parms.put(SHELLY_COLOR_EFFECT, newCol.effect.toString());
|
||||
}
|
||||
if (parms.size() > 0) {
|
||||
logger.debug("{}: Send light settings: {}", thingName, parms);
|
||||
api.setLightParms(lightId, parms);
|
||||
updateCurrentColors(lightId, newCol);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrentColors(int lightId, ShellyColorUtils col) {
|
||||
channelColors.replace(lightId, col);
|
||||
logger.debug("{}: Colors updated for lightId {}: RGBW={}/{}/{}/{}, Sat/Gain={}, Bright={}, Temp={} ", thingName,
|
||||
lightId, col.red, col.green, col.blue, col.white, col.gain, col.brightness, col.temp);
|
||||
}
|
||||
|
||||
private Integer getColorFromHSB(PercentType colorPercent) {
|
||||
return getColorFromHSB(colorPercent, new Double(SATURATION_FACTOR));
|
||||
}
|
||||
|
||||
private Integer getColorFromHSB(PercentType colorPercent, Double factor) {
|
||||
Double value = new Double(Math.round(colorPercent.doubleValue() * factor));
|
||||
logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, value.intValue(),
|
||||
factor);
|
||||
return value.intValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyProtectedHandler} implements a dummy handler for password protected devices.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyProtectedHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyProtectedHandler.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyProtectedHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/***
|
||||
* The{@link ShellyRelayHandler} handles light (bulb+rgbw2) specific commands and status. All other commands will be
|
||||
* handled by the generic thing handler.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyRelayHandler.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyRelayHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws ShellyApiException {
|
||||
// Process command
|
||||
String groupName = getString(channelUID.getGroupId());
|
||||
Integer rIndex = 0;
|
||||
if (groupName.startsWith(CHANNEL_GROUP_RELAY_CONTROL)
|
||||
&& groupName.length() > CHANNEL_GROUP_RELAY_CONTROL.length()) {
|
||||
rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_RELAY_CONTROL)) - 1;
|
||||
} else if (groupName.startsWith(CHANNEL_GROUP_ROL_CONTROL)
|
||||
&& groupName.length() > CHANNEL_GROUP_ROL_CONTROL.length()) {
|
||||
rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_ROL_CONTROL)) - 1;
|
||||
}
|
||||
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
default:
|
||||
return false;
|
||||
|
||||
case CHANNEL_OUTPUT:
|
||||
if (!profile.isRoller) {
|
||||
// extract relay number of group name (relay0->0, relay1->1...)
|
||||
logger.debug("{}: Set relay output to {}", thingName, command);
|
||||
api.setRelayTurn(rIndex, command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
} else {
|
||||
logger.debug("{}: Device is in roller mode, channel command {} ignored", thingName, channelUID);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_BRIGHTNESS: // e.g.Dimmer, Duo
|
||||
handleBrightness(command, rIndex);
|
||||
break;
|
||||
|
||||
case CHANNEL_ROL_CONTROL_POS:
|
||||
case CHANNEL_ROL_CONTROL_CONTROL:
|
||||
logger.debug("{}: Roller command/position {}", thingName, command);
|
||||
handleRoller(command, groupName, rIndex,
|
||||
channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
|
||||
|
||||
// request updates the next 45sec to update roller position after it stopped
|
||||
requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
||||
break;
|
||||
|
||||
case CHANNEL_TIMER_AUTOON:
|
||||
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command));
|
||||
break;
|
||||
case CHANNEL_TIMER_AUTOOFF:
|
||||
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* PaperUI Control has a combined Slider for Brightness combined with On/Off
|
||||
* Brightness channel has 2 functions: Switch On/Off (OnOnType) and setting brightness (PercentType)
|
||||
* There is some more logic in the control. When brightness is set to 0 the control sends also an OFF command
|
||||
* When current brightness is 0 and slider will be moved the new brightness will be set, but also a ON command is
|
||||
* send.
|
||||
*
|
||||
* @param command
|
||||
* @param index
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void handleBrightness(Command command, Integer index) throws ShellyApiException {
|
||||
Integer value = -1;
|
||||
if (command instanceof PercentType) { // Dimmer
|
||||
value = ((PercentType) command).intValue();
|
||||
} else if (command instanceof DecimalType) { // Number
|
||||
value = ((DecimalType) command).intValue();
|
||||
} else if (command instanceof OnOffType) { // Switch
|
||||
logger.debug("{}: Switch output {}", thingName, command);
|
||||
updateBrightnessChannel(index, (OnOffType) command, value);
|
||||
return;
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
ShellyShortLightStatus light = api.getLightStatus(index);
|
||||
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
|
||||
value = Math.min(light.brightness + DIM_STEPSIZE, 100);
|
||||
} else {
|
||||
value = Math.max(light.brightness - DIM_STEPSIZE, 0);
|
||||
}
|
||||
logger.debug("{}: Increase/Decrease brightness from {} to {}", thingName, light.brightness, value);
|
||||
}
|
||||
validateRange("brightness", value, 0, 100);
|
||||
|
||||
// Switch light off on brightness = 0
|
||||
if (value == 0) {
|
||||
logger.debug("{}: Brightness=0 -> switch output OFF", thingName);
|
||||
updateBrightnessChannel(index, OnOffType.OFF, 0);
|
||||
} else {
|
||||
logger.debug("{}: Setting dimmer brightness to {}", thingName, value);
|
||||
updateBrightnessChannel(index, OnOffType.ON, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBrightnessChannel(int lightId, OnOffType power, int brightness) throws ShellyApiException {
|
||||
if (brightness > 0) {
|
||||
api.setBrightness(lightId, brightness, config.brightnessAutoOn);
|
||||
} else {
|
||||
api.setRelayTurn(lightId, power == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
}
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
|
||||
new Double(power == OnOffType.ON ? brightness : 0), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeviceStatus(ShellySettingsStatus status) throws ShellyApiException {
|
||||
// map status to channels
|
||||
boolean updated = false;
|
||||
updated |= updateRelays(status);
|
||||
updated |= updateDimmers(status);
|
||||
updated |= updateLed(status);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Roller Commands
|
||||
*
|
||||
* @param command from handleCommand()
|
||||
* @param groupName relay, roller...
|
||||
* @param index relay number
|
||||
* @param isControl true: is the Rollershutter channel, false: rollerpos channel
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void handleRoller(Command command, String groupName, Integer index, boolean isControl)
|
||||
throws ShellyApiException {
|
||||
Integer position = -1;
|
||||
|
||||
if ((command instanceof UpDownType) || (command instanceof OnOffType)) {
|
||||
ShellyControlRoller rstatus = api.getRollerStatus(index);
|
||||
|
||||
if (!getString(rstatus.state).isEmpty() && !getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_STOP)) {
|
||||
boolean up = command instanceof UpDownType && (UpDownType) command == UpDownType.UP;
|
||||
boolean down = command instanceof UpDownType && (UpDownType) command == UpDownType.DOWN;
|
||||
if ((up && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
|
||||
|| (down && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
|
||||
logger.debug("{}: Roller is already moving ({}), ignore command {}", thingName,
|
||||
getString(rstatus.state), command);
|
||||
requestUpdates(1, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (((command instanceof UpDownType) && UpDownType.UP.equals(command))
|
||||
|| ((command instanceof OnOffType) && OnOffType.ON.equals(command))) {
|
||||
logger.debug("{}: Open roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||
position = SHELLY_MAX_ROLLER_POS;
|
||||
|
||||
}
|
||||
if (((command instanceof UpDownType) && UpDownType.DOWN.equals(command))
|
||||
|| ((command instanceof OnOffType) && OnOffType.OFF.equals(command))) {
|
||||
logger.debug("{}: Closing roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
||||
position = SHELLY_MIN_ROLLER_POS;
|
||||
}
|
||||
} else if ((command instanceof StopMoveType) && StopMoveType.STOP.equals(command)) {
|
||||
logger.debug("{}: Stop roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_STOP);
|
||||
} else {
|
||||
logger.debug("{}: Set roller to position {}", thingName, command);
|
||||
if (command instanceof PercentType) {
|
||||
PercentType p = (PercentType) command;
|
||||
position = p.intValue();
|
||||
} else if (command instanceof DecimalType) {
|
||||
DecimalType d = (DecimalType) command;
|
||||
position = d.intValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid value type for roller control/posiution" + command.getClass().toString());
|
||||
}
|
||||
|
||||
// take position from RollerShutter control and map to Shelly positon (OH:
|
||||
// 0=closed, 100=open; Shelly 0=open, 100=closed)
|
||||
// take position 1:1 from position channel
|
||||
position = isControl ? SHELLY_MAX_ROLLER_POS - position : position;
|
||||
validateRange("roller position", position, SHELLY_MIN_ROLLER_POS, SHELLY_MAX_ROLLER_POS);
|
||||
|
||||
logger.debug("{}: Changing roller position to {}", thingName, 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));
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
|
||||
} else {
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*/
|
||||
private void createRelayChannels(ShellyStatusRelay relay, int idx) {
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRelayChannels(getThing(), profile, relay, idx));
|
||||
}
|
||||
}
|
||||
|
||||
private void createRollerChannels(ShellyControlRoller roller) {
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRollerChannels(getThing(), roller));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Relay/Roller channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
// Check for Relay in Standard Mode
|
||||
if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) {
|
||||
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
||||
|
||||
int i = 0;
|
||||
ShellyStatusRelay rstatus = api.getRelayStatus(i);
|
||||
for (ShellyShortStatusRelay relay : rstatus.relays) {
|
||||
createRelayChannels(rstatus, i);
|
||||
if ((relay.isValid == null) || relay.isValid) {
|
||||
String groupName = profile.getControlGroup(i);
|
||||
ShellySettingsRelay rs = profile.settings.relays.get(i);
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(rs.name));
|
||||
|
||||
if (getBool(relay.overpower)) {
|
||||
postEvent(ALARM_TYPE_OVERPOWER, false);
|
||||
}
|
||||
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT, getOnOff(relay.ison));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_ACTIVE, getOnOff(relay.hasTimer));
|
||||
if (rstatus.extTemperature != null) {
|
||||
// Shelly 1/1PM support up to 3 external sensors
|
||||
// for whatever reason those are not represented as an array, but 3 elements
|
||||
if (rstatus.extTemperature.sensor1 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP1, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor1.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (rstatus.extTemperature.sensor2 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP2, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor2.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (rstatus.extTemperature.sensor3 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP3, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor3.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
}
|
||||
if ((rstatus.extHumidity != null) && (rstatus.extHumidity.sensor1 != null)) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, toQuantityType(
|
||||
getDouble(rstatus.extHumidity.sensor1.hum), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
// Update Auto-ON/OFF timer
|
||||
ShellySettingsRelay rsettings = profile.settings.relays.get(i);
|
||||
if (rsettings != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
|
||||
toQuantityType(getDouble(rsettings.autoOn), SmartHomeUnits.SECOND));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
|
||||
toQuantityType(getDouble(rsettings.autoOff), SmartHomeUnits.SECOND));
|
||||
}
|
||||
|
||||
// Update input(s) state
|
||||
updated |= updateInputs(groupName, status, i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Relay in Roller Mode
|
||||
if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
|
||||
logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
|
||||
int i = 0;
|
||||
|
||||
for (ShellySettingsRoller roller : status.rollers) {
|
||||
if (roller.isValid) {
|
||||
ShellyControlRoller control = api.getRollerStatus(i);
|
||||
Integer relayIndex = i + 1;
|
||||
String groupName = profile.numRollers > 1 ? CHANNEL_GROUP_ROL_CONTROL + relayIndex.toString()
|
||||
: CHANNEL_GROUP_ROL_CONTROL;
|
||||
|
||||
createRollerChannels(control);
|
||||
|
||||
if (control.name != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(control.name));
|
||||
}
|
||||
|
||||
String state = getString(control.state);
|
||||
if (state.equals(SHELLY_ALWD_ROLLER_TURN_STOP)) { // only valid in stop state
|
||||
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), SmartHomeUnits.PERCENT));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(new Double(pos), SmartHomeUnits.PERCENT));
|
||||
scheduledUpdates = 1; // one more poll and then stop
|
||||
}
|
||||
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR, getStringType(control.stopReason));
|
||||
updated |= updateInputs(groupName, status, i);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Relay/Roller channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public boolean updateDimmers(ShellySettingsStatus orgStatus) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
if (profile.isDimmer) {
|
||||
// We need to fixup the returned Json: The dimmer returns light[] element, which is ok, but it doesn't have
|
||||
// the same structure as lights[] from Bulb,RGBW2 and Duo. The tag gets replaced by dimmers[] so that Gson
|
||||
// maps to a different structure (ShellyShortLight).
|
||||
Gson gson = new Gson();
|
||||
ShellySettingsStatus dstatus = gson.fromJson(ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
|
||||
ShellySettingsStatus.class);
|
||||
|
||||
logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
|
||||
int l = 0;
|
||||
for (ShellyShortLightStatus dimmer : dstatus.dimmers) {
|
||||
Integer r = l + 1;
|
||||
String groupName = profile.numRelays <= 1 ? CHANNEL_GROUP_DIMMER_CONTROL
|
||||
: CHANNEL_GROUP_DIMMER_CONTROL + r.toString();
|
||||
|
||||
// On a status update we map a dimmer.ison = false to brightness 0 rather than the device's brightness
|
||||
// and send a OFF status to the same channel.
|
||||
// When the device's brightness is > 0 we send the new value to the channel and a ON command
|
||||
if (dimmer.ison) {
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.ON);
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
|
||||
new Double(getInteger(dimmer.brightness)), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
} else {
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.OFF);
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(new Double(0), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
|
||||
if (dsettings != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
|
||||
toQuantityType(getDouble(dsettings.autoOn), SmartHomeUnits.SECOND));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
|
||||
toQuantityType(getDouble(dsettings.autoOff), SmartHomeUnits.SECOND));
|
||||
}
|
||||
|
||||
updated |= updateInputs(groupName, orgStatus, l);
|
||||
l++;
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update LED channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*/
|
||||
public boolean updateLed(ShellySettingsStatus status) {
|
||||
boolean updated = false;
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_STATUS_DISABLE,
|
||||
getOnOff(profile.settings.ledStatusDisable));
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_POWER_DISABLE,
|
||||
getOnOff(profile.settings.ledPowerDisable));
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.shelly.internal.util;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.mkChannelId;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyChannelCache} implements a caching layer for channel updates.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyChannelCache {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyChannelCache.class);
|
||||
|
||||
private final ShellyBaseHandler thingHandler;
|
||||
private final Map<String, State> channelData = new ConcurrentHashMap<>();
|
||||
private String thingName = "";
|
||||
private boolean enabled = false;
|
||||
|
||||
public ShellyChannelCache(ShellyBaseHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
setThingName(thingHandler.thingName);
|
||||
}
|
||||
|
||||
public void setThingName(String thingName) {
|
||||
this.thingName = thingName;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public synchronized void disable() {
|
||||
clear();
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update one channel. Use Channel Cache to avoid unnecessary updates (and avoid
|
||||
* messing up the log with those updates)
|
||||
*
|
||||
* @param channelId Channel id
|
||||
* @param value Value (State)
|
||||
* @param forceUpdate true: ignore cached data, force update; false check cache of changed data
|
||||
* @return true, if successful
|
||||
*/
|
||||
public boolean updateChannel(String channelId, State newValue, Boolean forceUpdate) {
|
||||
try {
|
||||
State current = null;
|
||||
if (channelData.containsKey(channelId)) {
|
||||
current = channelData.get(channelId);
|
||||
}
|
||||
if (!enabled || forceUpdate || (current == null) || !current.equals(newValue)) {
|
||||
if ((current != null) && current.getClass().isEnum() && (current == newValue)) {
|
||||
return false; // special case for OnOffType
|
||||
}
|
||||
// For channels that support multiple types (like brightness) a suffix is added
|
||||
// this gets removed to get the channelId for updateState
|
||||
thingHandler.publishState(channelId, newValue);
|
||||
if (current == null) {
|
||||
channelData.put(channelId, newValue);
|
||||
} else {
|
||||
channelData.replace(channelId, newValue);
|
||||
}
|
||||
logger.debug("{}: Channel {} updated with {} (type {}).", thingName, channelId, newValue,
|
||||
newValue.getClass());
|
||||
return true;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Unable to update channel {} with {} (type {}): {} ({})", thingName, channelId, newValue,
|
||||
newValue.getClass(), ShellyUtils.getMessage(e), e.getClass());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean updateChannel(String group, String channel, State value) {
|
||||
return updateChannel(mkChannelId(group, channel), value, false);
|
||||
}
|
||||
|
||||
public boolean updateChannel(String channelId, State value) {
|
||||
return updateChannel(channelId, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the Channel Cache
|
||||
*
|
||||
* @param group Channel Group
|
||||
* @param channel Channel Name
|
||||
* @return the data from that channel
|
||||
*/
|
||||
|
||||
public State getValue(String group, String channel) {
|
||||
return getValue(mkChannelId(group, channel));
|
||||
}
|
||||
|
||||
public State getValue(String channelId) {
|
||||
if (channelData.containsKey(channelId)) {
|
||||
return channelData.get(channelId);
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
public void resetChannel(String channelId) {
|
||||
channelData.remove(channelId);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
channelData.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.util;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
/**
|
||||
* {@link ShellyTranslationProvider} provides i18n message lookup
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyTranslationProvider {
|
||||
|
||||
private final Bundle bundle;
|
||||
private final TranslationProvider i18nProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
|
||||
public ShellyTranslationProvider(Bundle bundle, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
|
||||
this.bundle = bundle;
|
||||
this.i18nProvider = i18nProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public ShellyTranslationProvider(final ShellyTranslationProvider other) {
|
||||
this.bundle = other.bundle;
|
||||
this.i18nProvider = other.i18nProvider;
|
||||
this.localeProvider = other.localeProvider;
|
||||
}
|
||||
|
||||
public @Nullable String get(String key, @Nullable Object... arguments) {
|
||||
return getText(key.contains("@text/") || key.contains(".shelly.") ? key : "message." + key, arguments);
|
||||
}
|
||||
|
||||
public @Nullable String getText(String key, @Nullable Object... arguments) {
|
||||
try {
|
||||
Locale locale = localeProvider.getLocale();
|
||||
return i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "Unable to load message for key " + key;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getDefaultText(String key) {
|
||||
return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.util;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* {@link ShellyUtils} provides general utility functions
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyUtils {
|
||||
public static String mkChannelId(String group, String channel) {
|
||||
return group + "#" + channel;
|
||||
}
|
||||
|
||||
public static String getString(@Nullable String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
public static String substringBefore(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.indexOf(pattern);
|
||||
if (pos > 0) {
|
||||
return string.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringBeforeLast(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.lastIndexOf(pattern);
|
||||
if (pos > 0) {
|
||||
return string.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringAfter(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.indexOf(pattern);
|
||||
if (pos != -1) {
|
||||
return string.substring(pos + pattern.length());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringAfterLast(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.lastIndexOf(pattern);
|
||||
if (pos != -1) {
|
||||
return string.substring(pos + pattern.length());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringBetween(@Nullable String string, String begin, String end) {
|
||||
if (string != null) {
|
||||
int s = string.indexOf(begin);
|
||||
if (s != -1) {
|
||||
// The end tag might be included before the start tag, e.g.
|
||||
// when using "http://" and ":" to get the IP from http://192.168.1.1:8081/xxx
|
||||
// therefore make it 2 steps
|
||||
String result = string.substring(s + begin.length());
|
||||
return substringBefore(result, end);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String getMessage(Exception e) {
|
||||
String message = e.getMessage();
|
||||
return message != null ? message : "";
|
||||
}
|
||||
|
||||
public static Integer getInteger(@Nullable Integer value) {
|
||||
return (value != null ? (Integer) value : 0);
|
||||
}
|
||||
|
||||
public static Long getLong(@Nullable Long value) {
|
||||
return (value != null ? (Long) value : 0);
|
||||
}
|
||||
|
||||
public static Double getDouble(@Nullable Double value) {
|
||||
return (value != null ? (Double) value : 0);
|
||||
}
|
||||
|
||||
public static Boolean getBool(@Nullable Boolean value) {
|
||||
return (value != null ? (Boolean) value : false);
|
||||
}
|
||||
|
||||
// as State
|
||||
|
||||
public static StringType getStringType(@Nullable String value) {
|
||||
return new StringType(value != null ? value : "");
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Double value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Integer value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Long value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static Double getNumber(Command command) throws IllegalArgumentException {
|
||||
if (command instanceof DecimalType) {
|
||||
return ((DecimalType) command).doubleValue();
|
||||
}
|
||||
if (command instanceof QuantityType) {
|
||||
return ((QuantityType<?>) command).doubleValue();
|
||||
}
|
||||
throw new IllegalArgumentException("Unable to convert number");
|
||||
}
|
||||
|
||||
public static OnOffType getOnOff(@Nullable Boolean value) {
|
||||
return (value != null ? value ? OnOffType.ON : OnOffType.OFF : OnOffType.OFF);
|
||||
}
|
||||
|
||||
public static OnOffType getOnOff(int value) {
|
||||
return value == 0 ? OnOffType.OFF : OnOffType.ON;
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable Double value, int digits, Unit<?> unit) {
|
||||
if (value == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
BigDecimal bd = new BigDecimal(value.doubleValue());
|
||||
return toQuantityType(bd.setScale(digits, BigDecimal.ROUND_HALF_UP), unit);
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
|
||||
return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable PercentType value, Unit<?> unit) {
|
||||
return value == null ? UnDefType.NULL : toQuantityType(value.toBigDecimal(), unit);
|
||||
}
|
||||
|
||||
public static void validateRange(String name, Integer value, int min, int max) {
|
||||
if ((value < min) || (value > max)) {
|
||||
throw new IllegalArgumentException("Value " + name + " is out of range (" + min + "-" + max + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public static String urlEncode(String input) throws ShellyApiException {
|
||||
try {
|
||||
return URLEncoder.encode(input, StandardCharsets.UTF_8.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ShellyApiException(
|
||||
"Unsupported encoding format: " + StandardCharsets.UTF_8.toString() + ", input=" + input, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Long now() {
|
||||
return System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
|
||||
public static DateTimeType getTimestamp() {
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(now()), ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
public static DateTimeType getTimestamp(String zone, long timestamp) {
|
||||
try {
|
||||
if (timestamp == 0) {
|
||||
return getTimestamp();
|
||||
}
|
||||
ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
|
||||
ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
|
||||
int delta = zdt.getOffset().getTotalSeconds();
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp - delta), zoneId));
|
||||
} catch (DateTimeException e) {
|
||||
// Unable to convert device's timezone, use system one
|
||||
return getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getLightIdFromGroup(String groupName) {
|
||||
if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
|
||||
return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1;
|
||||
}
|
||||
return 0; // only 1 light, e.g. bulb or rgbw2 in color mode
|
||||
}
|
||||
|
||||
public static String buildControlGroupName(ShellyDeviceProfile profile, Integer channelId) {
|
||||
return profile.isBulb || profile.isDuo || profile.inColor ? CHANNEL_GROUP_LIGHT_CONTROL
|
||||
: CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
|
||||
}
|
||||
|
||||
public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
|
||||
return profile.isBulb || profile.isDuo && !profile.inColor ? CHANNEL_GROUP_WHITE_CONTROL
|
||||
: CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
|
||||
}
|
||||
|
||||
public static DecimalType mapSignalStrength(int dbm) {
|
||||
int strength = -1;
|
||||
if (dbm > -60) {
|
||||
strength = 4;
|
||||
} else if (dbm > -70) {
|
||||
strength = 3;
|
||||
} else if (dbm > -80) {
|
||||
strength = 2;
|
||||
} else if (dbm > -90) {
|
||||
strength = 1;
|
||||
} else {
|
||||
strength = 0;
|
||||
}
|
||||
return new DecimalType(strength);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.util;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ShellyVersionDTO} compares 2 version strings.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyVersionDTO {
|
||||
private class VersionTokenizer {
|
||||
private final String versionString;
|
||||
private final int length;
|
||||
|
||||
private int position;
|
||||
private int number;
|
||||
private String suffix = "";
|
||||
|
||||
public VersionTokenizer(@Nullable String versionString) {
|
||||
if (versionString == null) {
|
||||
throw new IllegalArgumentException("versionString is null");
|
||||
}
|
||||
|
||||
this.versionString = versionString;
|
||||
length = versionString.length();
|
||||
}
|
||||
|
||||
private boolean moveNext() {
|
||||
number = 0;
|
||||
suffix = "";
|
||||
|
||||
// No more characters
|
||||
if (position >= length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (position < length) {
|
||||
char c = versionString.charAt(position);
|
||||
if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
number = number * 10 + (c - '0');
|
||||
position++;
|
||||
}
|
||||
|
||||
int suffixStart = position;
|
||||
|
||||
while (position < length) {
|
||||
char c = versionString.charAt(position);
|
||||
if (c == '.') {
|
||||
break;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
suffix = versionString.substring(suffixStart, position);
|
||||
|
||||
if (position < length) {
|
||||
position++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
private String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(String s1, String s2) {
|
||||
return compare(s1, s2) == 0;
|
||||
}
|
||||
|
||||
public int compare(String version1, String version2) {
|
||||
VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
|
||||
VersionTokenizer tokenizer2 = new VersionTokenizer(version2);
|
||||
|
||||
int number1 = 0, number2 = 0;
|
||||
String suffix1 = "", suffix2 = "";
|
||||
|
||||
while (tokenizer1.moveNext()) {
|
||||
if (!tokenizer2.moveNext()) {
|
||||
do {
|
||||
number1 = tokenizer1.getNumber();
|
||||
suffix1 = tokenizer1.getSuffix();
|
||||
if (number1 != 0 || suffix1.length() != 0) {
|
||||
// Version one is longer than number two, and non-zero
|
||||
return 1;
|
||||
}
|
||||
} while (tokenizer1.moveNext());
|
||||
|
||||
// Version one is longer than version two, but zero
|
||||
return 0;
|
||||
}
|
||||
|
||||
number1 = tokenizer1.getNumber();
|
||||
suffix1 = tokenizer1.getSuffix();
|
||||
number2 = tokenizer2.getNumber();
|
||||
suffix2 = tokenizer2.getSuffix();
|
||||
|
||||
if (number1 < number2) {
|
||||
// Number one is less than number two
|
||||
return -1;
|
||||
}
|
||||
if (number1 > number2) {
|
||||
// Number one is greater than number two
|
||||
return 1;
|
||||
}
|
||||
|
||||
boolean empty1 = suffix1.length() == 0;
|
||||
boolean empty2 = suffix2.length() == 0;
|
||||
|
||||
if (empty1 && empty2) {
|
||||
continue;
|
||||
} // No suffixes
|
||||
if (empty1) {
|
||||
return 1;
|
||||
} // First suffix is empty (1.2 > 1.2b)
|
||||
if (empty2) {
|
||||
return -1;
|
||||
} // Second suffix is empty (1.2a < 1.2)
|
||||
|
||||
// Lexical comparison of suffixes
|
||||
int result = suffix1.compareTo(suffix2);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
while (tokenizer2.moveNext()) {
|
||||
number2 = tokenizer2.getNumber();
|
||||
suffix2 = tokenizer2.getSuffix();
|
||||
if (number2 != 0 || suffix2.length() != 0) {
|
||||
// Version one is longer than version two, and non-zero
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Version two is longer than version one, but zero
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean checkBeta(@Nullable String version) {
|
||||
if (version == null) {
|
||||
return false;
|
||||
}
|
||||
return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master")
|
||||
|| (version.toLowerCase().contains("-rc"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="shelly" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Shelly Binding</name>
|
||||
<description>This binding supports the Shelly series of devices.</description>
|
||||
<author>Markus Michels</author>
|
||||
|
||||
<config-description>
|
||||
<parameter name="defaultUserId" type="text">
|
||||
<default>admin</default>
|
||||
<label>Default User</label>
|
||||
<description>Default userId to access protected Shelly devices.</description>
|
||||
</parameter>
|
||||
<parameter name="defaultPassword" type="text">
|
||||
<default>admin</default>
|
||||
<label>Default Password</label>
|
||||
<description>Default password to access protected Shelly devices.</description>
|
||||
</parameter>
|
||||
<parameter name="autoCoIoT" type="boolean">
|
||||
<default>true</default>
|
||||
<label>Auto-enable CoIoT</label>
|
||||
<description>True: Enable CoIoT events by default when firmware 1.6+ is detected</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,298 @@
|
||||
<?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">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsButton" type="boolean" required="false">
|
||||
<label>Enable Button Events</label>
|
||||
<description>True if the binding should register to get the Buttons Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsPush" type="boolean" required="false">
|
||||
<label>Enable Push Events (short/long)</label>
|
||||
<description>True if the binding should register to get the Short/Long Push Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:roller">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsRoller" type="boolean" required="false">
|
||||
<label>Enable Roller Events (Roller only)</label>
|
||||
<description>True if the binding should register to get Roller Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:shelly:dimmer">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness>0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsButton" type="boolean" required="false">
|
||||
<label>Enable Button Events</label>
|
||||
<description>True if the binding should register to get the Buttons Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsPush" type="boolean" required="false">
|
||||
<label>Enable Push Events (short/long)</label>
|
||||
<description>True if the binding should register to get the Short/Long Push Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:light">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness > 0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:rgbw2">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness > 0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
|
||||
<config-description uri="thing-type:shelly:battery">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User ID</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="lowBattery" type="integer" required="false">
|
||||
<label>Low Battery Threshold</label>
|
||||
<description>Threshold for the battery level, alert will be signed when battery level is below. Default: 20%</description>
|
||||
<default>20</default>
|
||||
<unitLabel>%</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="eventsSensorReport" type="boolean" required="false">
|
||||
<label>Enable Sensor Events</label>
|
||||
<description>True: Register event URL for sensor updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>3600</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:basic">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User ID</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsSensorReport" type="boolean" required="false">
|
||||
<label>Enable Sensor Events</label>
|
||||
<description>True: Register event URL for sensor updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>3600</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,56 @@
|
||||
discovery.failed# Config status messages
|
||||
config-status.error.missing-device-ip=IP address of the Shelly device is missing.
|
||||
|
||||
# Thing status descriptions
|
||||
offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
|
||||
offline.conf-error-access-denied = Access denied, check user id and password.
|
||||
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.
|
||||
offline.status-error-timeout = Device is not reachable (API timeout).
|
||||
offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
|
||||
offline.status-error-watchdog = Device is not responding, seems to be unavailable.
|
||||
offline.status-error-restarted = The device has restarted and will be re-initialized.
|
||||
|
||||
message.versioncheck.failed = Unable to check firmware version: {0}
|
||||
message.versioncheck.beta = Device is running a Beta version: {0}/{1} ({2}),make sure this is newer than {3} release build.
|
||||
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1} ({2}), required minimal {3}.
|
||||
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.roller.calibrating = Device is not calibrated, use Shelly App to perform initial roller calibration.
|
||||
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.protected = Device is password protected, enter correct credentials in thing configuration.
|
||||
message.command.failed = ERROR: Unable to process command {0} for channel {1}
|
||||
message.command.init = Thing not yet initialized, command {0} triggers initialization
|
||||
message.statusupdate.failed = Unable to update status
|
||||
message.event.triggered = Event triggered: {0}
|
||||
message.coap.init.failed = Unable to start CoIoT: {0}
|
||||
message.discovery.disabled = Device is marked as non-discoverable -> skip
|
||||
message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
|
||||
message.discovery.failed = Device discovery of device with IP address {0} failed: {1}
|
||||
|
||||
# Device
|
||||
channel-type.shelly.deviceName.label = Device Name
|
||||
channel-type.shelly.deviceName.description = Symbolic Device Name as configured in the Shelly App.
|
||||
|
||||
# Relay, external sensors
|
||||
channel-type.shelly.outputName.label = Output Name
|
||||
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App.
|
||||
channel-type.shelly.temperature1.label = Temperature 1
|
||||
channel-type.shelly.temperature1.description = Temperature of external Sensor #1
|
||||
channel-type.shelly.temperature2.label = Temperature 2
|
||||
channel-type.shelly.temperature2.description = Temperature of external Sensor #2
|
||||
channel-type.shelly.temperature3.label = Temperature 3
|
||||
channel-type.shelly.temperature3.description = Temperature of external Sensor #3
|
||||
channel-type.shelly.humidity.label = Humidity
|
||||
channel-type.shelly.humidity.description = Relative humidity (0..100%)
|
||||
|
||||
# Roller
|
||||
channel-type.shelly.rollerState.label = State
|
||||
channel-type.shelly.rollerState.description = State of the roller (open/closed/stopped).
|
||||
|
||||
|
||||
# LED disable
|
||||
channel-type.shelly.ledPowerDisable.label = Disable Power LED
|
||||
channel-type.shelly.ledPowerDisable.description = ON: The power status LED will be decativated
|
||||
channel-type.shelly.ledStatusDisable.label = Disable Status LED
|
||||
channel-type.shelly.ledStatusDisable.description = ON: The WiFi status LED will be decativated
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
# binding
|
||||
binding.shelly.name = Shelly Binding
|
||||
binding.shelly.description = Dieses Binding integriert Shelly-Komponenten, die über WiFi gesteuert werden können.
|
||||
|
||||
# Config status messages
|
||||
config-status.error.missing-deviceip=Die IP-Adresse des Shelly Gerätes ist nicht konfiguriert.
|
||||
|
||||
# Thing status descriptions
|
||||
offline.conf-error-no-credentials = Gerät ist passwortgeschützt, aber es sind keine Anmeldedaten konfiguriert.
|
||||
offline.conf-error-access-denied = Der Zugriff wurde verweigert. überprüfen Sie die konfigurierten Zugangsdaten, oder setzen diese entsprechend (analog zur Shelly App).
|
||||
offline.conf-error-wrong-mode = Das Gerät befindet sich nicht mehr in der erwarteten Betriebsart. Löschen Sie das Gerät und führen Sie eine erneute Geräteerkennung durch.
|
||||
offline.status-error-timeout = Das Gerät ist nicht erreichbar (API Timeout).
|
||||
offline.status-error-unexpected-api-result = Es trat ein unerwartetes Problem beim API-Zugriff auf. Überprüfen Sie die Logdatei für genauere Informationen.
|
||||
offline.status-error-watchdog = Das Gerät antwortet nicht und ist vermutlich nicht mehr verfügbar.
|
||||
offline.status-error-restarted = Das Gerät wurde neu gestartet und wird erneut initialisiert.
|
||||
|
||||
# Status error messages
|
||||
config-status.error.missing-userid = Keine Benutzerkennung in der Thing Konfiguration
|
||||
|
||||
# General messages
|
||||
message.versioncheck.failed = Firmware-Version konnte nicht geprüft werden: {0}
|
||||
message.versioncheck.beta = Es wurde eine Betaversion erkannt: {0}/{1} ({2}), bitte sicherstellen, dass diese neuer ist als Version {3} (Release Build).
|
||||
message.versioncheck.tooold = ACHTUNG: Eine alter Firmware wurde erkannt: {0}/{1} ({2}), minimal erforderlich {3}.
|
||||
message.versioncheck.update = INFO: Eine neue Firmwareversion ist verfügbar, aktuell: {0}, neu: {1}
|
||||
message.versioncheck.autocoiot = INFO: Die Firmware unterstützt die Anforderung, Auto-CoIoT wurde aktiviert.
|
||||
message.init.noipaddress = Es konnte keine lokale IP-Adresse ermittelt werden. Bitte sicherstellen, dass IPv4 aktiviert ist und das richtige Interface in der openHAB Netzwerk-Konfiguration ausgewählt ist.
|
||||
message.init.protected = Das Gerät ist passwortgeschützt, die Zugangsdaten müssen in der Thing Konfiguration hinterlegt werden.
|
||||
message.command.failed = FEHLER: Der Befehl {0} für Kanal {1} kann nicht verarbeitet werden
|
||||
message.command.init = Thing aktuell nicht initialisiert, der Befehl {0} führt zur Initialisierung
|
||||
message.statusupdate.failed = Status konnte nicht aktualisiert werden
|
||||
message.event.triggered = Event erzeugt: {0}
|
||||
message.coap.init.failed = CoAP/CoIoT konnte nicht gestartet werden: {0}
|
||||
message.discovery.disabled = Das Gerät ist als "nicht erkennen" markiert und wird nicht übernommen.
|
||||
message.discovery.protected = Das Gerät mit der IP-Adresse {0} ist zugriffsgeschützt und keine Zugangsdaten konfiguriert.
|
||||
message.discovery.failed = Erkennung des Gerätes mit der IP-Adresse {0} ist fehlgeschlagen
|
||||
message.roller.calibrating = Das Gerät ist nicht kalibriert. Es ist eine Kalibrierung mit der Shelly App erforderlich.
|
||||
|
||||
# thing types
|
||||
thing-type.shelly.shelly1.label = Shelly1 (SHSW-1)
|
||||
thing-type.shelly.shelly1.description = Shelly 1 (1 Relay)
|
||||
thing-type.shelly.shelly1pm.label = Shelly 1PM (SHSW-PM)
|
||||
thing-type.shelly.shelly1pm.description = Shelly 1PM mit 1xRelais und Strommesser
|
||||
thing-type.shelly.shellyem.label = Shelly EM (SHEM)
|
||||
thing-type.shelly.shellyem.description = Shelly EM zur Energiemessung
|
||||
thing-type.shelly.shellyem3.label = Shelly 3EM (SHEM-3)
|
||||
thing-type.shelly.shellyem3.description = Shelly 3EM zur Energiemessung
|
||||
thing-type.shelly.shelly2-relay.label = Shelly2 Relay (SHSW-21)
|
||||
thing-type.shelly.shelly2-relay.description = Shelly2 im Relais-Modus (2xRelais, Strommesser)
|
||||
thing-type.shelly.shelly2-roller.label = Shelly2 Relay (SHSW-21)
|
||||
thing-type.shelly.shelly2-roller.description = Shelly2 im Rollladen-Modus (1xRollladen, Strommesser)
|
||||
thing-type.shelly.shelly25-relay.label = Shelly2.5 Relay (SHSW-25)
|
||||
thing-type.shelly.shelly25-relay.description = Shelly2.5 im Relais-Modus (2xRelais, 2 Strommesser)
|
||||
thing-type.shelly.shelly25-roller.label = Shelly2.5 Roller (SHSW-25)
|
||||
thing-type.shelly.shelly25-roller.description = Shelly2.5 im Rollladen-Modus (1xRollladen, Strommesser)
|
||||
thing-type.shelly.shelly4pro.label = Shelly4 Pro Relay (SHSW-4)
|
||||
thing-type.shelly.shelly4pro.description = Shelly 4 Pro mit 4 Relais und Strommessern
|
||||
thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
|
||||
thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
|
||||
thing-type.shelly.shellyplugs.label = Shelly Plug-S (SHPLG-S)
|
||||
thing-type.shelly.shellyplugs.description = Shelly Plug-S als schaltbare Steckdose
|
||||
thing-type.shelly.shellydimmer.label = Shelly Dimmer (SHDM-1)
|
||||
thing-type.shelly.shellydimmer.description = Shelly mit Dimmer-Funktion
|
||||
thing-type.shelly.shellydimmer2.label = Shelly Dimmer (SHDM-2)
|
||||
thing-type.shelly.shellydimmer2.description = Shelly mit Dimmer-Funktion, 2. Generation
|
||||
thing-type.shelly.shellybutton1.label = Shelly Button 1 (SHBTN-1)
|
||||
thing-type.shelly.shellybutton1.description = Shelly Button 1 (batteriebetrieben)
|
||||
thing-type.shelly.shellyht.label = Shelly H&T (SHHT-1)
|
||||
thing-type.shelly.shellyht.description = Shelly H&T Sensor
|
||||
thing-type.shelly.shellydw.label = Shelly Door/Window Sensor (SHDW-1)
|
||||
thing-type.shelly.shellydw.description = Shelly Tür/Fenstersensor (batteriebetrieben)
|
||||
thing-type.shelly.shellydw2.label = Shelly Door/Window Sensor 2 (SHDW-2)
|
||||
thing-type.shelly.shellydw2.description = Shelly Tür/Fenstersensor (batteriebetrieben)
|
||||
thing-type.shelly.shellysmoke.label = Shelly Smoke
|
||||
thing-type.shelly.shellysmoke.description = Shelly Rauchmelder
|
||||
thing-type.shelly.shellyflood.label = Shelly Flood (SHWT-1)
|
||||
thing-type.shelly.shellyflood.description = Shelly Wassermelder
|
||||
thing-type.shelly.shellysense.label = Shelly Sense (SHSEN-1)
|
||||
thing-type.shelly.shellysense.description = Shelly Bewegungsmelder
|
||||
thing-type.shelly.shellybulb.label = Shelly Bulb (SHBLB-1)
|
||||
thing-type.shelly.shellybulb.description = Shelly Glühbirne weiß/Farbe
|
||||
thing-type.shelly.shellybulbduo.label = Shelly Duo (SHBDUO-1)
|
||||
thing-type.shelly.shellybulbduo.description = Shelly Duo Glühbirne
|
||||
thing-type.shelly.shellyvintage.label = Shelly Vintage (SHVIN-1)
|
||||
thing-type.shelly.shellyvintage.description = Shelly Vintage Glühbirne
|
||||
thing-type.shelly.shellyrgbw2-color.label = Shelly RGBW2 Color Mode (SHRGBW2)
|
||||
thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW-Controller im Farbmodus
|
||||
thing-type.shelly.shellyrgbw2-white.label = Shelly RGBW2 White Mode (SHRGBW2)
|
||||
thing-type.shelly.shellyrgbw2-white.description = Shelly RGBW-Controller im Weiß-Modus, 4 Streifen
|
||||
|
||||
# thing config - generic
|
||||
thing-type.config.shelly.generic.userId.label = Benutzer
|
||||
thing-type.config.shelly.generic.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.generic.password.label = Passwort
|
||||
thing-type.config.shelly.generic.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.generic.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.generic.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.generic.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.generic.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.generic.eventsButton.label = Button Events
|
||||
thing-type.config.shelly.generic.eventsButton.description = Aktiviert die Button Action URLS
|
||||
thing-type.config.shelly.generic.eventsPush.label = Push Events
|
||||
thing-type.config.shelly.generic.eventsPush.description = Aktiviert die Push Button Action URLS
|
||||
thing-type.config.shelly.generic.eventsSwitch.label = Output Events
|
||||
thing-type.config.shelly.generic.eventsSwitch.description = Aktiviert die Output Action URLS
|
||||
thing-type.config.shelly.generic.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.generic.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.generic.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.generic.updateInterval.description = Intervall für die Hintergundaktualisierung
|
||||
|
||||
# thing config - light
|
||||
thing-type.config.shelly.light.userId.label = Benutzer
|
||||
thing-type.config.shelly.light.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.light.password.label = Passwort
|
||||
thing-type.config.shelly.light.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.light.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.light.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.light.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.light.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.light.brightnessAutoOn.label = Helligkeit Auto-EIN
|
||||
thing-type.config.shelly.light.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
|
||||
thing-type.config.shelly.light.eventsButton.label = Button Events
|
||||
thing-type.config.shelly.light.eventsButton.description = Aktiviert die Button Action URLS
|
||||
thing-type.config.shelly.light.eventsPush.label = Push Events
|
||||
thing-type.config.shelly.light.eventsPush.description = Aktiviert die Push Button Action URLS
|
||||
thing-type.config.shelly.light.eventsSwitch.label = Output Events
|
||||
thing-type.config.shelly.light.eventsSwitch.description = Aktiviert die Output Action URLS
|
||||
thing-type.config.shelly.light.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.light.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.light.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.light.updateInterval.description = Intervall für die Hintergrundaktualisierung
|
||||
|
||||
# thing config - battery
|
||||
thing-type.config.shelly.battery.userId.label = Benutzer
|
||||
thing-type.config.shelly.battery.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.battery.password.label = Passwort
|
||||
thing-type.config.shelly.battery.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.battery.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.battery.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.battery.eventsSensorReport.label = Sensor Events
|
||||
thing-type.config.shelly.battery.eventsSensorReport.description = Aktiviert die Sensor Action URLS
|
||||
thing-type.config.shelly.battery.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.battery.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.battery.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.battery.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.battery.lowBattery.label = Batterieladung niedrig (%)
|
||||
thing-type.config.shelly.battery.lowBattery.description = Ein Alarm wird ausgelöst, wenn das Gerät eine Batterieladung kleiner diesem Schwellwert meldet. Default: 20%
|
||||
thing-type.config.shelly.battery.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.battery.updateInterval.description = Intervall für die Hintergundaktualisierung
|
||||
|
||||
# thing config - unknown
|
||||
thing-type.config.shellydevice.userId.label = Benutzer
|
||||
thing-type.config.shellydevice.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shellydevice.password.label = Passwort
|
||||
thing-type.config.shellydevice.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shellydevice.deviceIp.label = IP Adresse
|
||||
thing-type.config.shellydevice.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
|
||||
# channel-groups
|
||||
thing-type.shelly.shelly1.group.relay.label = Relais
|
||||
thing-type.shelly.shelly1.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly1.group.sensors.label = Externe Sensoren
|
||||
thing-type.shelly.shelly1.group.sensors.description = Temperaturwerte der externen Sensoren (nur wenn angeschlossen)
|
||||
thing-type.shelly.shelly1.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly1.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly1pm.group.relay.label = Relais
|
||||
thing-type.shelly.shelly1pm.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly1pm.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly1pm.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly1pm.group.sensors.label = Externe Sensoren
|
||||
thing-type.shelly.shelly1pm.group.sensors.description = Werte der externen Sensoren (nur wenn angeschlossen)
|
||||
thing-type.shelly.shelly1pm.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly1pm.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyem.group.relay.label = Relais
|
||||
thing-type.shelly.shellyem.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyem.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shellyem.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shellyem.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyem.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyem3.group.relay.label = Relais
|
||||
thing-type.shelly.shellyem3.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyem3.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shellyem3.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shellyem3.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.meter3.label = Verbrauch 3
|
||||
thing-type.shelly.shellyem3.group.meter3.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyem3.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly2-relay.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly2-relay.group.relay1.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly2-relay.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly2-relay.group.relay2.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly2-relay.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly2-relay.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly2-relay.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly2-relay.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly2-roller.group.roller.label = Rollladen
|
||||
thing-type.shelly.shelly2-roller.group.roller.description = Rollladensteuerung und Status
|
||||
thing-type.shelly.shelly2-roller.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly2-roller.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly2-roller.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly2-roller.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly25-relay.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly25-relay.group.relay1.description = Relais Ein-/Ausgänge und -Funktionen
|
||||
thing-type.shelly.shelly25-relay.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly25-relay.group.relay2.description = Relais Ein-/Ausgänge und -Funktionen
|
||||
thing-type.shelly.shelly25-relay.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shelly25-relay.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-relay.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shelly25-relay.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-relay.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly25-relay.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly25-roller.group.roller.label = Rollladen
|
||||
thing-type.shelly.shelly25-roller.group.roller.description = Rollladensteuerung und Status
|
||||
thing-type.shelly.shelly25-roller.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly25-roller.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-roller.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly25-roller.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly4pro.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly4pro.group.relay1.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly4pro.group.relay2.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay3.label = Relais 3
|
||||
thing-type.shelly.shelly4pro.group.relay3.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay4.label = Relais 4
|
||||
thing-type.shelly.shelly4pro.group.relay4.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shelly4pro.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shelly4pro.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter3.label = Verbrauch 3
|
||||
thing-type.shelly.shelly4pro.group.meter3.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter4.label = Verbrauch 4
|
||||
thing-type.shelly.shelly4pro.group.meter4.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly4pro.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyplug.group.relay.label = Relais
|
||||
thing-type.shelly.shellyplug.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyplug.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyplug.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyplug.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyplug.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyplugs.group.relay.label = Relais
|
||||
thing-type.shelly.shellyplugs.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyplugs.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyplugs.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyplugs.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyplugs.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydimmer.group.relay.label = Relais
|
||||
thing-type.shelly.shellydimmer.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellydimmer.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellydimmer.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellydimmer.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydimmer.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydimmer2.group.relay.label = Relais
|
||||
thing-type.shelly.shellydimmer2.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellydimmer2.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellydimmer2.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellydimmer2.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydimmer2.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyix3.group.status1.label = Eingang #1
|
||||
thing-type.shelly.shellyix3.group.status1.description = Status Informationen zum Eingang 1
|
||||
thing-type.shelly.shellyix3.group.status2.label = Eingang #2
|
||||
thing-type.shelly.shellyix3.group.status2.description = Status Informationen zum Eingang 2
|
||||
thing-type.shelly.shellyix3.group.status3.label = Eingang #3
|
||||
thing-type.shelly.shellyix3.group.status3.description = Status Informationen zum Eingang 3
|
||||
thing-type.shelly.shellyix3.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyix3.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.status.label = Taster-Status
|
||||
thing-type.shelly.shellybutton1.group.status.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.battery.label = Batteriestatus
|
||||
thing-type.shelly.shellybutton1.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellybutton1.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybulb.group.control.label = Steuerung
|
||||
thing-type.shelly.shellybulb.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellybulb.group.color.label = Farbmodus
|
||||
thing-type.shelly.shellybulb.group.color.description = Einstellungen für den Farbmodus
|
||||
thing-type.shelly.shellybulb.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellybulb.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellybulb.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybulb.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybulbduo.group.control.label = Steuerung
|
||||
thing-type.shelly.shellybulbduo.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellybulbduo.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellybulbduo.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellybulbduo.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellybulbduo.group.meter.description = Verbrauchswerte
|
||||
thing-type.shelly.shellybulbduo.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybulbduo.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyvintage.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyvintage.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellyvintage.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellyvintage.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellyvintage.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyvintage.group.meter.description = Verbrauchswerte
|
||||
thing-type.shelly.shellyvintage.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyvintage.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-color.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyrgbw2-color.group.control.description = Steuerung des Controllers
|
||||
thing-type.shelly.shellyrgbw2-color.group.color.label = Farben
|
||||
thing-type.shelly.shellyrgbw2-color.group.color.description = Farbwerte und Profile
|
||||
thing-type.shelly.shellyrgbw2-color.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyrgbw2-color.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyrgbw2-color.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-color.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-white.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyrgbw2-white.group.control.description = Lichtsteuerung
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel1.label = Kanal 1
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel1.description = Steuerung für Kanal 1
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel2.label = Kanal 2
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel2.description = Steuerung für Kanal 2
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel3.label = Kanal 3
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel3.description = Steuerung für Kanal 3
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel4.label = Kanal 4
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel4.description = Steuerung für Kanal 4
|
||||
thing-type.shelly.shellyrgbw2-white.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyrgbw2-white.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyrgbw2-white.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-white.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyht.group.sensors.label = Sensorwerte
|
||||
thing-type.shelly.shellyht.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellyht.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellyht.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellyht.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyht.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyflood.group.sensors.label = Sensorwerte
|
||||
thing-type.shelly.shellyflood.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellyflood.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellyflood.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellyflood.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyflood.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydw.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellydw.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellydw.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellydw.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellydw.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydw.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydw2.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellydw2.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellydw2.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellydw2.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellydw2.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydw2.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellysmoke.sensors.device.label = Sensordaten
|
||||
thing-type.shelly.shellysmoke.sensors.device.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellysmoke.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellysmoke.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellysmoke.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellysmoke.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellysense.sensors.device.label = Sensordaten
|
||||
thing-type.shelly.shellysense.sensors.device.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellysense.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellysense.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellygas.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellygas.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellygas.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellygas.group.device.description = Informationen zum Gerätestatus
|
||||
|
||||
|
||||
# channels
|
||||
channel-type.shelly.outputName.label = Ausgangsname
|
||||
channel-type.shelly.outputName.description = Name des Relais-Ausgangs/Kanals (Konfiguration innerhalb der Shelly App)
|
||||
channel-type.shelly.timerAutoOn.label = Auto-EIN Timer
|
||||
channel-type.shelly.timerAutoOn.description = Wenn das Relais ausgeschaltet wird, wird dieses automatisch wieder nach n Sekunden eingeschaltet
|
||||
channel-type.shelly.timerAutoOff.label = Auto-AUS Timer
|
||||
channel-type.shelly.timerAutoOff.description = Wenn das Relais eingeschaltet wird, wird dieses automatisch wieder nach n Sekunden ausgeschaltet
|
||||
channel-type.shelly.timerActive.label = Timer aktiv
|
||||
channel-type.shelly.timerActive.description = ON: Auto-On/Off Timer ist aktiv
|
||||
channel-type.shelly.temperature1.label = Temperatur 1
|
||||
channel-type.shelly.temperature1.description = Temperatur des externen Sensors #1
|
||||
channel-type.shelly.temperature2.label = Temperatur 2
|
||||
channel-type.shelly.temperature2.description = Temperatur des externen Sensors #2
|
||||
channel-type.shelly.temperature3.label = Temperatur 3
|
||||
channel-type.shelly.temperature3.description = Temperatur des externen Sensors #3
|
||||
channel-type.shelly.humidity.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.humidity.description = Relative Luftfeuchtigkeit (0..100%)
|
||||
channel-type.shelly.rollerShutter.label = Steuerung (0=offen, 100=geschlossen)
|
||||
channel-type.shelly.rollerShutter.description = Steuerung für den Rollladen: UP, DOWN, STOP, Position (0=offen, 100=geschlossen)
|
||||
channel-type.shelly.rollerPosition.label = Position (100=offen, 0=zu)
|
||||
channel-type.shelly.rollerPosition.description = Invertierte Position des Rollladen: 100=offen, 0=zu
|
||||
channel-type.shelly.rollerState.label = Status
|
||||
channel-type.shelly.rollerState.description = Zustand des Rollladen (open/closed/stopped).
|
||||
channel-type.shelly.rollerState.state.option.open = öffnet
|
||||
channel-type.shelly.rollerState.state.option.close = schließt
|
||||
channel-type.shelly.rollerState.state.option.stop = gestoppt
|
||||
channel-type.shelly.rollerStop.label = Stoppgrund
|
||||
channel-type.shelly.rollerStop.description = Letzter Grund für das Stoppen des Rollladens (normal=normaler Stopp, safety_switch=Sicherheits-Stopp, obstacle=Rollladen verhakt (Widerstand erkannt)
|
||||
channel-type.shelly.inputState.label = Eingang
|
||||
channel-type.shelly.inputState.description = Status des Relais-Eingangs
|
||||
channel-type.shelly.inputState1.label = Eingang 1
|
||||
channel-type.shelly.inputState1.description = Status des Relais-Eingangs 1
|
||||
channel-type.shelly.inputState2.label = Eingang 2
|
||||
channel-type.shelly.inputState2.description = Status des Relais-Eingangs 2
|
||||
channel-type.shelly.inputState3.label = Eingang 3
|
||||
channel-type.shelly.inputState3.description = Status des Relais-Eingangs 3
|
||||
channel-type.shelly.dimmerBrightness.label = Helligkeit
|
||||
channel-type.shelly.dimmerBrightness.description = Helligkeit (0-100%, 0=aus)
|
||||
channel-type.shelly.whiteBrightness.label = Helligkeit
|
||||
channel-type.shelly.whiteBrightness.description = Helligkeit (0-100%, 0=aus)
|
||||
channel-type.shelly.meterWatts.label = Leistung
|
||||
channel-type.shelly.meterWatts.description = Aktueller Stromverbrauch in Watt
|
||||
channel-type.shelly.meterAccuWatts.label = Kumulierter Verbrauch
|
||||
channel-type.shelly.meterAccuWatts.description = Kumulierter Verbrauch in Watt
|
||||
channel-type.shelly.meterAccuTotal.label = Kumulierter Gesamtverbrauch
|
||||
channel-type.shelly.meterAccuTotal.description = Kumulierter Gesamtverbrauch in kW/h
|
||||
channel-type.shelly.meterAccuReturned.label = Kumulierter Einspeisung
|
||||
channel-type.shelly.meterAccuReturned.description = Kumulierter Einspeisung in kW/h
|
||||
channel-type.shelly.meterCurrent.label = Stromstärke
|
||||
channel-type.shelly.meterCurrent.description = Aktuelle gemessene Stromstärke
|
||||
channel-type.shelly.meterTotal.label = Gesamtverbrauch
|
||||
channel-type.shelly.meterTotal.description = Gesamtverbrauch seit Neustart in kW/h
|
||||
channel-type.shelly.meterReturned.label = Einspeisung
|
||||
channel-type.shelly.meterReturned.description = Einspeisung in kW/h
|
||||
channel-type.shelly.meterVoltage.label = Spannung
|
||||
channel-type.shelly.meterVoltage.description = Spannung in Volt
|
||||
channel-type.shelly.meterPowerFactor.label = Stromfaktor
|
||||
channel-type.shelly.meterPowerFactor.description = Faktor für Strom bei Photovoltaik
|
||||
channel-type.shelly.meterReactive.label = Rückstrom
|
||||
channel-type.shelly.meterReactive.description = Aktueller Stromverbrauch (Watt) des Rückstroms
|
||||
channel-type.shelly.lastPower1.label = Schnitt letzte Min
|
||||
channel-type.shelly.lastPower1.description = Durchschnittsverbrauch der letzten Minute, 60s in Watt/Min
|
||||
channel-type.shelly.timestamp.label = Letzte Aktualisierung
|
||||
channel-type.shelly.timestamp.description = Zeitstempel der letzten Aktualisierung (lokale Zeitzone)
|
||||
channel-type.shelly.ledStatusDisable.label = Status-LED aus
|
||||
channel-type.shelly.ledStatusDisable.description = ON: Die Status-LED am Gerät ist deaktiviert
|
||||
channel-type.shelly.ledPowerDisable.label = Betriebs-LED aus
|
||||
channel-type.shelly.ledPowerDisable.description = ON: Die Betriebsanzeige (LED) am Gerät ist deaktiviert
|
||||
channel-type.shelly.colorMode.label = Farbmodus
|
||||
channel-type.shelly.colorMode.description = Betriebsart: ON: Farbe, OFF: Weiß
|
||||
channel-type.shelly.colorFull.label = Voll-Farbe
|
||||
channel-type.shelly.colorFull.description = Ausgewählte Farbe (red/green/blue/yellow/white) wird auf volle Intensität gesetzt (255)
|
||||
channel-type.shelly.colorFull.state.option.red = Rot
|
||||
channel-type.shelly.colorFull.state.option.green = Grün
|
||||
channel-type.shelly.colorFull.state.option.blue = Blau
|
||||
channel-type.shelly.colorFull.state.option.yellow = Gelb
|
||||
channel-type.shelly.colorFull.state.option.white = Weiß
|
||||
channel-type.shelly.colorRed.label = Rot
|
||||
channel-type.shelly.colorRed.description = Rot-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorGreen.label = Grün
|
||||
channel-type.shelly.colorGreen.description = Grün-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorBlue.label = Blau
|
||||
channel-type.shelly.colorBlue.description = Blau-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorWhite.label = Weiß
|
||||
channel-type.shelly.colorWhite.description = Weiß-Anteil des RGBW-Wertes (0-255)
|
||||
channel-type.shelly.colorGain.label = Verstärkung
|
||||
channel-type.shelly.colorGain.description = Verstärkung des Farbwertes (1-100%)
|
||||
channel-type.shelly.whiteTemp.label = Lichttemperatur
|
||||
channel-type.shelly.whiteTemp.description = Temperatur des Weißlichtes (Bulb/RGBW2: 3000..6500K; Duo: 2700K..6500K)
|
||||
channel-type.shelly.colorEffectBulb.label = Lichteffekt
|
||||
channel-type.shelly.colorEffectBulb.description = Lichteffekt: 0: keiner, 1: Meteoritenregen, 2: Verlauf, 3: Atmen, 4: Blitzen, 5: ‹bergang ein/aus, 6: Rot/Grün-Wechsel
|
||||
channel-type.shelly.colorEffectBulb.option.0 = Aus
|
||||
channel-type.shelly.colorEffectBulb.option.1 = Meteoritenregen
|
||||
channel-type.shelly.colorEffectBulb.option.2 = Verlauf
|
||||
channel-type.shelly.colorEffectBulb.option.3 = Atmen
|
||||
channel-type.shelly.colorEffectBulb.option.4 = Blitzen
|
||||
channel-type.shelly.colorEffectBulb.option.5 = Übergang ein/aus
|
||||
channel-type.shelly.colorEffectBulb.option.6 = Rot/Grün-Wechsel
|
||||
channel-type.shelly.colorEffectRGBW2.label = Farbeffekt
|
||||
channel-type.shelly.colorEffectRGBW2.description = Lichteffekt: 0: keiner, 1: Meteoritenregen, 2: Farbverlauf, 3: Blitzen
|
||||
channel-type.shelly.colorEffectRGBW2.option.0 = Aus
|
||||
channel-type.shelly.colorEffectRGBW2.option.1 = Meteoritenregen
|
||||
channel-type.shelly.colorEffectRGBW2.option.2 = Farbverlauf
|
||||
channel-type.shelly.colorEffectRGBW2.option.3 = Blitzen
|
||||
channel-type.shelly.sensorTemp.label = Temperatur
|
||||
channel-type.shelly.sensorTemp.description = Aktuelle Temperatur des Sensors in ∞C
|
||||
channel-type.shelly.sensorExtTemp.label = Temperatur
|
||||
channel-type.shelly.sensorExtTemp.description = Aktuelle Temperatur des externen Sensors in ∞C
|
||||
channel-type.shelly.sensorExtHum.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.sensorExtHum.description = Relative Luftfeuchtigkeit in %
|
||||
channel-type.shelly.sensorHumidity.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.sensorHumidity.description = Relative Luftfeuchtigkeit in %
|
||||
channel-type.shelly.sensorFlood.label = Wasseralarm
|
||||
channel-type.shelly.sensorFlood.description = Alarm: Es wurde Wasser erkannt
|
||||
channel-type.shelly.sensorSmoke.label = Rauchalarm
|
||||
channel-type.shelly.sensorSmoke.description = Alarm: Es wurde Rauch erkannt
|
||||
channel-type.shelly.sensorLux.label = Helligkeit
|
||||
channel-type.shelly.sensorLux.description = Helligkeit in Lux
|
||||
channel-type.shelly.sensorIllumination.label = Tageslicht
|
||||
channel-type.shelly.sensorIllumination.description = Erkanntes Tageslicht (bright=taghell, twilight=Dämmerung, dark=Abend/Nacht)
|
||||
channel-type.shelly.sensorIllumination.state.option.dark = Dunkel
|
||||
channel-type.shelly.sensorIllumination.state.option.twilight = Dämmerung
|
||||
channel-type.shelly.sensorIllumination.state.option.bright = Hell
|
||||
channel-type.shelly.sensorIllumination.state.option.unknown = Unbekannt
|
||||
channel-type.shelly.sensorIllumination.description = Angabe zum erkannten Tageslichtwert
|
||||
channel-type.shelly.sensorPPM.label = Gas-Konzentration
|
||||
channel-type.shelly.sensorPPM.description = Gemessene Konzentration in PPM
|
||||
channel-type.shelly.sensorTilt.label = Öffnungswinkel
|
||||
channel-type.shelly.sensorTilt.description = Öffnungswinkel in Grad (erfordert Kalibrierung in der App)
|
||||
channel-type.shelly.sensorVibration.label = Vibration
|
||||
channel-type.shelly.sensorVibration.description = ON: Sensor hat eine Vibration erkannt
|
||||
channel-type.shelly.sensorValve.label = Ventil
|
||||
channel-type.shelly.sensorValve.description = Gibt den Status des Ventils an, sofern eines angeschlossen ist.
|
||||
channel-type.shelly.sensorValve.state.option.closed = geschlossen
|
||||
channel-type.shelly.sensorValve.state.option.opened = geöffnet
|
||||
channel-type.shelly.sensorValve.state.option.not_connected = nicht angeschlossen
|
||||
channel-type.shelly.sensorValve.state.option.failure = Störung
|
||||
channel-type.shelly.sensorValve.state.option.closing = schließt
|
||||
channel-type.shelly.sensorValve.state.option.opening = öffnet
|
||||
channel-type.shelly.sensorValve.state.option.checking = Test läuft
|
||||
channel-type.shelly.sensorValve.state.option.UNKNOWN = unbekannt
|
||||
channel-type.shelly.sensorCharger.label = Ladegerät
|
||||
channel-type.shelly.sensorCharger.description = ON: Ein Ladegerät ist angeschlossen
|
||||
channel-type.shelly.sensorContact.label = Kontakt
|
||||
channel-type.shelly.sensorContact.description = Status des Sensors (OPEN=offen, CLOSED=geschlossen)
|
||||
channel-type.shelly.sensorContact.state.option.OPEN = geöffnet
|
||||
channel-type.shelly.sensorContact.state.option.CLOSED = geschlossen
|
||||
channel-type.shelly.sensorState.label = Gerätestatus
|
||||
channel-type.shelly.sensorState.description = Status der Betriebsbereitschaft (warmup/normal/fault)
|
||||
channel-type.shelly.sensorState.state.option.warmup= Initialisierung
|
||||
channel-type.shelly.sensorState.state.option.normal= Normal
|
||||
channel-type.shelly.sensorState.state.option.fault= Fehler
|
||||
channel-type.shelly.sensorState.state.option.unknown= Unbekannt
|
||||
channel-type.shelly.sensorWakeup.label = Aufwachgrund
|
||||
channel-type.shelly.sensorWakeup.description = Grund des letzten Aufweckens (button/battery/perodice/powerdown/sensor/alarm/ext_power)
|
||||
channel-type.shelly.sensorWakeup.state.option.button = Taste
|
||||
channel-type.shelly.sensorWakeup.state.option.battery = Batterie
|
||||
channel-type.shelly.sensorWakeup.state.option.periodic = Intervall
|
||||
channel-type.shelly.sensorWakeup.state.option.poweron = Betrieb
|
||||
channel-type.shelly.sensorWakeup.state.option.sensor = Statusänderung
|
||||
channel-type.shelly.sensorWakeup.state.option.alarm = Alarm
|
||||
channel-type.shelly.sensorWakeup.state.option.ext_power = Ladegerät verbunden
|
||||
channel-type.shelly.batVoltage.label = Batteriespannung
|
||||
channel-type.shelly.batVoltage.description = Batteriespannung in Volt (V)
|
||||
channel-type.shelly.charger.label = Ladegerät
|
||||
channel-type.shelly.charger.description = ON: Ein Ladegerät ist angeschlossen
|
||||
channel-type.shelly.senseKey.label = IR-Code
|
||||
channel-type.shelly.senseKey.description = IR Code senden im Pronto- oder HEX64-Format
|
||||
channel-type.shelly.deviceName.label = Gerätename
|
||||
channel-type.shelly.deviceName.description = Symbolischer Name des Gerätes (Konfiguration über Shelly App)
|
||||
channel-type.shelly.uptime.label = Laufzeit
|
||||
channel-type.shelly.uptime.description = Anzahl Sekunden seit dem das Gerät mit Strom versorgt wurde
|
||||
channel-type.shelly.heartBeat.label = Letzte Aktivität
|
||||
channel-type.shelly.heartBeat.description = Zeitpunkt der letzten Aktivität. Hierbei kann es sich um einen erfolgreichen API-Aufruf, oder Sensor-Aktualisierung handeln. Dies schließt eine erfolgreiche Netzwerk-Kommunikation ein (WiFi + IP).
|
||||
channel-type.shelly.updateAvailable.label = Firmwareaktualisierung vorhanden
|
||||
channel-type.shelly.updateAvailable.description = ON: Es ist eine neuere Firmwareversion verfügbar (kann mit der Shelly App durchgef¸hrt werden)
|
||||
channel-type.shelly.deviceTemp.label = Gerätetemperatur
|
||||
channel-type.shelly.deviceTemp.description = Temperatur im Gerät. Hohe Temperaturen deuten ggf. auch ein Hitzestau/Installationsproblem hin.
|
||||
channel-type.shelly.lastUpdate.label = Letzte Aktualisierung
|
||||
channel-type.shelly.lastUpdate.description = Zeitstempel der letzten Aktualisierung (lokale Zeitzone)
|
||||
channel-type.shelly.lastEvent.label = Letztes Ereignis
|
||||
channel-type.shelly.lastEvent.description = Typ des letzten Ereignisses (S=kurz, SS=2xkurz, SSS=3xkurz, L=lang)
|
||||
channel-type.shelly.lastEvent.state.option.S = 1x kurz
|
||||
channel-type.shelly.lastEvent.state.option.SS = 2x kurz
|
||||
channel-type.shelly.lastEvent.state.option.SSS = 3x kurz
|
||||
channel-type.shelly.lastEvent.state.option.L = lang
|
||||
channel-type.shelly.lastEvent.state.option.SL = kurz-lang
|
||||
channel-type.shelly.lastEvent.state.option.LS = lang-kurz
|
||||
channel-type.shelly.eventCount.label = Ereigniszähler
|
||||
channel-type.shelly.eventCount.description = Anzahl der empfangenen Ereignisse.
|
||||
channel-type.shelly.eventTrigger.label = Ereignis
|
||||
channel-type.shelly.eventTrigger.description = Signalisiert Ereignisse (Trigger): ROLLER_OPEN=Rollladen geöffnet, ROLLER_CLOSE=Rollladen geschlossen, ROLLER_STOP=Rollladen angehalten
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_OPEN = Rollladen geöffnet
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_CLOSE = Rollladen geschlossen
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_STOP = Rollladen gestoppt
|
||||
channel-type.shelly.alarmTrigger.description = Signalisiert Alarme (Trigger): NONE=kein Alarm, WEAK_SIGNAL=Schlechte WiFi-Verbindung, RESTARTED=Gerät neu gestartet, OVERTEMP=Überhitzung, OVERLOAD=Überlast, OVERPOWER=Maximale Last überschritten, LOAD_ERROR=Lastfehler, LOW_BATTERY=Batterie schwach
|
||||
channel-type.shelly.alarmTrigger.option.NONE = kein Alarm
|
||||
channel-type.shelly.alarmTrigger.option.WEAK_SIGNAL = Schlechte WiFi-Verbindung
|
||||
channel-type.shelly.alarmTrigger.option.RESTARTED = Gerät neu gestartet
|
||||
channel-type.shelly.alarmTrigger.option.OVERTEMP = Überhitzung
|
||||
channel-type.shelly.alarmTrigger.option.OVERLOAD = Überlast
|
||||
channel-type.shelly.alarmTrigger.option.OVERPOWER = Maximale Last überschritten
|
||||
channel-type.shelly.alarmTrigger.option.LOAD_ERROR = Lastfehler
|
||||
channel-type.shelly.alarmTrigger.option.LOW_BATTERY = Batterieladung schwach
|
||||
channel-type.shelly.alarmState.label = Alarmstatus
|
||||
channel-type.shelly.alarmState.description = Typ des Alarms (unknown/none/mild/heavy/test)
|
||||
channel-type.shelly.alarmState.state.option.unknown = Unbekannt
|
||||
channel-type.shelly.alarmState.state.option.none = Kein Alarm
|
||||
channel-type.shelly.alarmState.state.option.mild = Leichte Konzentration
|
||||
channel-type.shelly.alarmState.state.option.heavy = Hohe Konzentration
|
||||
channel-type.shelly.alarmState.state.option.test = Testknopf gedrückt
|
||||
channel-type.shelly.selfTest.label = Selbsttest
|
||||
channel-type.shelly.selfTest.description = Status des Selbsttests
|
||||
channel-type.shelly.selfTest.state.option.pending = ausstehend
|
||||
channel-type.shelly.selfTest.state.option.not_completed = Nicht abgeschlossen
|
||||
channel-type.shelly.selfTest.state.option.running = Test läuft
|
||||
channel-type.shelly.selfTest.state.option.completed = abgeschlossen
|
||||
channel-type.shelly.selfTest.state.option.unknown = unbekannt
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?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">
|
||||
|
||||
<channel-group-type id="deviceStatus">
|
||||
<label>Device Status</label>
|
||||
<description>Information about the device status</description>
|
||||
<channels>
|
||||
<channel id="alarm" typeId="alarmTrigger"/>
|
||||
<channel id="wifiSignal" typeId="system.signal-strength"/>
|
||||
<channel id="uptime" typeId="uptime"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="alarmTrigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Alarm</label>
|
||||
<description>Alarm Trigger, e.g. weak WiFi Signal detected or over heating.</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="NONE">None</option>
|
||||
<option value="WEAK_SIGNAL">Weak WiFi</option>
|
||||
<option value="RESTARTED">Device restarted</option>
|
||||
<option value="OVERTEMP">Device is overheating</option>
|
||||
<option value="OVERLOAD">Device is overloaded</option>
|
||||
<option value="OVERPOWER">Device is over max power</option>
|
||||
<option value="LOAD_ERROR">Load error</option>
|
||||
<option value="LOW_BATTERY">Low battery</option>
|
||||
<option value="BATTERY">Wakeup by battery</option>
|
||||
<option value="POWERON">Device was powered on</option>
|
||||
<option value="BUTTON">Button was pressed</option>
|
||||
<option value="SENSOR">Sensor data updated</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
<channel-type id="sensorWakeup" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Wakeup Reason</label>
|
||||
<description>Last reason, which woke up the device</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="button">Button</option>
|
||||
<option value="battery">Battery</option>
|
||||
<option value="periodic">Periodic</option>
|
||||
<option value="poweron">Power-On</option>
|
||||
<option value="sensor">Sensor</option>
|
||||
<option value="alarm">Alarm</option>
|
||||
<option value="ext_power">Charger connected</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="deviceName">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.shelly.deviceName.label</label>
|
||||
<description>@text/channel-type.shelly.deviceName.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="charger" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Charger Connected</label>
|
||||
<description>ON: Device is charging, OFF: No charger connected</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="batVoltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Battery Voltage</label>
|
||||
<description>Battery voltage in V</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="uptime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Uptime</label>
|
||||
<description>Number of seconds since the device was powered up</description>
|
||||
<state readOnly="true" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="heartBeat" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Heartbeat</label>
|
||||
<description>Last time we received a signal from the device (API result or sensor report), This indicates that device
|
||||
communication works on the network layer (WiFi + IP).</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="updateAvailable" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Update available</label>
|
||||
<description>ON: A firmware update is available (use Shelly App to perform update)</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="deviceTemp" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Internal Temperature</label>
|
||||
<description>Internal device temperature, helps to detect overheating due to installation issues</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="selfTest">
|
||||
<item-type>String</item-type>
|
||||
<label>Self Test</label>
|
||||
<description>Self Test Status/Result</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="not_completed">Not completed</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="running">Running</option>
|
||||
<option value="pending">Pending</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,264 @@
|
||||
<?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="shellybulb">
|
||||
<label>Shelly Bulb (SHBULB)</label>
|
||||
<description>Shelly Bulb</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="bulbControl"/>
|
||||
<channel-group id="color" typeId="colorSettingsBulb"/>
|
||||
<channel-group id="white" typeId="whiteSettings"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybulbduo">
|
||||
<label>Shelly Duo (SHBDUO-1)</label>
|
||||
<description>Shelly Duo</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="duoControl"/>
|
||||
<channel-group id="white" typeId="whiteSettings"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyvintage">
|
||||
<label>Shelly Vintage (SHVIN-1)</label>
|
||||
<description>Shelly Vintage Light Bulb</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="duoControl"/>
|
||||
<channel-group id="white" typeId="whiteSettingsSimple"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-color">
|
||||
<label>Shelly RGBW2 Color Mode (SHRGBW2)</label>
|
||||
<description>Shelly RGBW2 Controller - Color Mode</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="rgbw2ColorControl"/>
|
||||
<channel-group id="color" typeId="colorSettingsRGBW2"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:rgbw2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-white">
|
||||
<label>Shelly RGBW2 White Mode (SHRGBW2)</label>
|
||||
<description>Shelly RGBW2 Controller - White Mode</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="rgbw2WhiteControl"/>
|
||||
<channel-group id="channel1" typeId="rgbw2Channel">
|
||||
<label>Channel 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel2" typeId="rgbw2Channel">
|
||||
<label>Channel 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel3" typeId="rgbw2Channel">
|
||||
<label>Channel 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel4" typeId="rgbw2Channel">
|
||||
<label>Channel 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:rgbw2"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="bulbControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="mode" typeId="colorMode"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="duoControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light</description>
|
||||
<channels>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2ColorControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light channels</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2WhiteControl">
|
||||
<label>White Control</label>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="colorSettingsBulb">
|
||||
<label>Colors</label>
|
||||
<description>Light colors</description>
|
||||
<channels>
|
||||
<channel id="hsb" typeId="system.color"/>
|
||||
<channel id="full" typeId="colorFull"/>
|
||||
<channel id="red" typeId="colorRed"/>
|
||||
<channel id="green" typeId="colorGreen"/>
|
||||
<channel id="blue" typeId="colorBlue"/>
|
||||
<channel id="white" typeId="colorWhite"/>
|
||||
<channel id="gain" typeId="colorGain"/>
|
||||
<channel id="effect" typeId="colorEffectBulb"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="colorSettingsRGBW2">
|
||||
<label>Colors</label>
|
||||
<description>Light colors</description>
|
||||
<channels>
|
||||
<channel id="hsb" typeId="system.color"/>
|
||||
<channel id="full" typeId="colorFull"/>
|
||||
<channel id="red" typeId="colorRed"/>
|
||||
<channel id="green" typeId="colorGreen"/>
|
||||
<channel id="blue" typeId="colorBlue"/>
|
||||
<channel id="white" typeId="colorWhite"/>
|
||||
<channel id="gain" typeId="colorGain"/>
|
||||
<channel id="effect" typeId="colorEffectRGBW2"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="whiteSettings">
|
||||
<label>White settings</label>
|
||||
<description>Adjust colors when device is in white mode</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
<channel id="temperature" typeId="whiteTemp"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="whiteSettingsSimple">
|
||||
<label>White settings</label>
|
||||
<description>Adjust colors when device is in white mode</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2Channel">
|
||||
<label>Control</label>
|
||||
<description>Switch channel and adjust settings</description>
|
||||
<channels>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
||||
<channel-type id="colorMode">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mode (ON=color, OFF=white)</label>
|
||||
<description>ON: Device is in color mode, OFF: Device is in White Mode</description>
|
||||
</channel-type>
|
||||
<channel-type id="colorFull">
|
||||
<item-type>String</item-type>
|
||||
<label>Full Color</label>
|
||||
<description>Setting this channel sets the selected color to 255, all others to 0</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="yellow">Yellow</option>
|
||||
<option value="white">White</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="colorRed" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Red</label>
|
||||
<description>red, 0..255, only in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorGreen" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Green</label>
|
||||
<description>green, 0..255, applies Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorBlue" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Blue</label>
|
||||
<description>blue, 0..255, only in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorWhite" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>White</label>
|
||||
<description>white, 0..255, applies in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorGain">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Gain</label>
|
||||
<description>gain for all channels, 0..100, applies in Color Mode</description>
|
||||
<state min="0" max="100" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="whiteTemp">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Light Temperature</label>
|
||||
<description>Light Temperature 3000..6500K</description>
|
||||
<state min="3000" max="6500" step="10" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="whiteBrightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>Brightness: 0..100%</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
<channel-type id="colorEffectBulb">
|
||||
<item-type>Number</item-type>
|
||||
<label>Effect</label>
|
||||
<description>Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual Change, 3: Breath, 4: Flash,
|
||||
5: On/Off Gradual, 6: Red/Green Change</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Meteor Shower</option>
|
||||
<option value="2">Gradual Change</option>
|
||||
<option value="3">Breath</option>
|
||||
<option value="4">Flash</option>
|
||||
<option value="5">On/Off Gradual</option>
|
||||
<option value="6">Red/Green Change</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="colorEffectRGBW2">
|
||||
<item-type>Number</item-type>
|
||||
<label>Effect</label>
|
||||
<description>Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual Change, 3: Flash</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Meteor Shower</option>
|
||||
<option value="2">Gradual Change</option>
|
||||
<option value="3">Flash</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,528 @@
|
||||
<?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="shelly1">
|
||||
<label>Shelly1 (SHSW-1)</label>
|
||||
<description>Shelly1 device with single relay</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly1pm">
|
||||
<label>Shelly1PM (SHSW-PM)</label>
|
||||
<description>Shelly1PM device with single relay and power meter</description>
|
||||
<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>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyem">
|
||||
<label>ShellyEM (SHEM)</label>
|
||||
<description>Shelly EM device with single relay and power meter</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Shelly 3EM - device reports wrong service name: shellyem3, see README -->
|
||||
<thing-type id="shellyem3">
|
||||
<label>Shelly 3EM (SHEM-3)</label>
|
||||
<description>Shelly 3EM device with 3 power meters and a relay (thing name is shellyem3, see README)</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter3" typeId="meter">
|
||||
<label>Power Meter 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly2-relay">
|
||||
<label>Shelly2 Relay (SHSW-21)</label>
|
||||
<description>Shelly2 device with two relays</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
<thing-type id="shelly2-roller">
|
||||
<label>Shelly2 Roller (SHSW-21)</label>
|
||||
<description>Shelly2 in Roller Mode</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly25-relay">
|
||||
<label>Shelly2.5 Relay (SHSW-25)</label>
|
||||
<description>Shelly2.5 device with two relays, 2 meters</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
<thing-type id="shelly25-roller">
|
||||
<label>Shelly2.5 Roller (SHSW-25)</label>
|
||||
<description>Shelly2 in Roller Mode</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly4pro">
|
||||
<label>Shelly4 Pro Relay (SHSW-44)</label>
|
||||
<description>Shelly4Pro device with 4 relays</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay3" typeId="relayChannel">
|
||||
<label>Relay 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter3" typeId="meter">
|
||||
<label>Power Meter 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay4" typeId="relayChannel">
|
||||
<label>Relay 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter4" typeId="meter">
|
||||
<label>Power Meter 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplug">
|
||||
<label>Shelly Plug</label>
|
||||
<description>Shelly Plug device with relay and power meter</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannelPlug"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplugs">
|
||||
<label>Shelly Plug-S (SHPLG-S)</label>
|
||||
<description>Shelly Plug-S with relay, meter and LED control</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannelPlug"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer">
|
||||
<label>Shelly Dimmer (SHDM-1)</label>
|
||||
<description>Shelly Dimmer</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="dimmerChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:dimmer"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer2">
|
||||
<label>Shelly Dimmer 2 (SHDM-2)</label>
|
||||
<description>Shelly Dimmer 2</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="dimmerChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:dimmer"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyix3">
|
||||
<label>Shelly ix3 (SHIX3-1)</label>
|
||||
<description>Shelly i3 Device with 3 inputs</description>
|
||||
<channel-groups>
|
||||
<channel-group id="status1" typeId="ix3Channel"/>
|
||||
<channel-group id="status2" typeId="ix3Channel"/>
|
||||
<channel-group id="status3" typeId="ix3Channel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="relayChannel">
|
||||
<label>Relay</label>
|
||||
<description>A Shelly relay channel</description>
|
||||
<channels>
|
||||
<channel id="output" typeId="system.power"/>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="relayChannelPlug">
|
||||
<label>Relay</label>
|
||||
<description>A Shelly relay channel</description>
|
||||
<channels>
|
||||
<channel id="output" typeId="system.power"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="dimmerChannel">
|
||||
<label>Dimmer</label>
|
||||
<description>A Shelly Dimmer channel</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="dimmerBrightness"/>
|
||||
<channel id="input1" typeId="inputState1"/>
|
||||
<channel id="input2" typeId="inputState2"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="ix3Channel">
|
||||
<label>Input</label>
|
||||
<description>Input Status</description>
|
||||
<channels>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="lastEvent" typeId="lastEvent"/>
|
||||
<channel id="eventCount" typeId="eventCount"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rollerControl">
|
||||
<label>Roller Control</label>
|
||||
<description>Controlling the roller mode</description>
|
||||
<channels>
|
||||
<channel id="control" typeId="rollerShutter"/>
|
||||
<channel id="rollerpos" typeId="rollerPosition"/>
|
||||
<channel id="state" typeId="rollerState"/>
|
||||
<channel id="stopReason" typeId="rollerStop"/>
|
||||
<channel id="input1" typeId="inputState1"/>
|
||||
<channel id="input2" typeId="inputState2"/>
|
||||
<channel id="event" typeId="eventTrigger"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="meter">
|
||||
<label>Power Meter</label>
|
||||
<description>Power consumption for the relay</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="externalSensors">
|
||||
<label>External Sensors</label>
|
||||
<description>Temperatures from external sensors connected to the optional Addon</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="outputName">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.shelly.outputName.label</label>
|
||||
<description>@text/channel-type.shelly.outputName.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="timerAutoOn" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Auto-ON Timer</label>
|
||||
<description>ON: After the output was turned off it turns on automatically after xx seconds; 0 disables the timer</description>
|
||||
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="timerAutoOff" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Auto-OFF Timer</label>
|
||||
<description>ON: After the output was turned on it turns off automatically after xx seconds; 0 disables the timer</description>
|
||||
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="timerActive" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Auto ON/OFF timer active</label>
|
||||
<description>ON: A timer is active, OFF: no timer active</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerShutter">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Roller Control (0=open, 100=closed))</label>
|
||||
<description>Controls the roller</description>
|
||||
<category>Blinds</category>
|
||||
</channel-type>
|
||||
<channel-type id="rollerPosition" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Roller Position (100=open, 0=closed)</label>
|
||||
<description>Position the roller (100..0 in %, where 100%=open, 0%=closed)</description>
|
||||
<state readOnly="false" min="0" max="100"/>
|
||||
</channel-type>
|
||||
<channel-type id="rollerState">
|
||||
<item-type>String</item-type>
|
||||
<label>State</label>
|
||||
<description>State of the roller (open/close/stop)</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="open">opening</option>
|
||||
<option value="close">closing</option>
|
||||
<option value="stop">stopped</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerStop">
|
||||
<item-type>String</item-type>
|
||||
<label>Roller stop reason</label>
|
||||
<description>Last cause for stopping: normal, safety switch, obstacle</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="normal">normal</option>
|
||||
<option value="safety_switch">safety switch</option>
|
||||
<option value="obstacle">obstacle detected</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerDirection">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Roller Direction</label>
|
||||
<description>Last direction: open or close</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="open">open</option>
|
||||
<option value="close">close</option>
|
||||
<option value="stop">stopped</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input</label>
|
||||
<description>Input/Button state</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState1" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input #1</label>
|
||||
<description>Input/Button state #1</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState2" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input #2</label>
|
||||
<description>Input/Button state #2</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="dimmerBrightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>Light brightness in %</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
<channel-type id="meterWatts">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Watt</label>
|
||||
<description>Current power consumption in Watt</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuWatts" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Watt</label>
|
||||
<description>Accumulated current power consumption in Watt from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuTotal" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Total</label>
|
||||
<description>Accumulated total power consumption in kw/h from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuReturned" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Returned</label>
|
||||
<description>Accumulated returned power consumption in kw/h from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterReactive">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Reactive Watt</label>
|
||||
<description>Instantaneous reactive power in Watts (W)</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower1" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #1</label>
|
||||
<description>Last power consumption #1 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower2" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #2</label>
|
||||
<description>Last power consumption #2 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower3" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #3</label>
|
||||
<description>Last power consumption #3 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterTotal">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Energy</label>
|
||||
<description>Total power consumption in kw/h</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterReturned">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Returned Energy (kw/h)</label>
|
||||
<description>Total returned energy in kw/h</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterVoltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>RMS voltage, Volts </description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterCurrent">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Current</label>
|
||||
<description>Current in A </description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterPowerFactor">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power Factor</label>
|
||||
<description></description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Timestamp of last measurement</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="ledStatusDisable">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Disable Status LED</label>
|
||||
<description>Disable LED indication for network status</description>
|
||||
</channel-type>
|
||||
<channel-type id="ledPowerDisable">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Disable Power LED</label>
|
||||
<description>Disable LED indication for output status</description>
|
||||
</channel-type>
|
||||
<channel-type id="lastUpdate" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Timestamp of last status update</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="eventTrigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Event</label>
|
||||
<description>Event Trigger, e.g. button on/off, Output on/off</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="ROLLER_OPEN">Roller is open</option>
|
||||
<option value="ROLLER_CLOSE">Roller is closed</option>
|
||||
<option value="ROLLER_STOP">Roller has stopped</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,347 @@
|
||||
<?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="shellyht">
|
||||
<label>Shelly H&T (SHHT-1)</label>
|
||||
<description>Shelly H&T Sensor</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="htSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellysmoke">
|
||||
<label>Shelly Smoke</label>
|
||||
<description>Shelly Smoke Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="smokeSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellygas">
|
||||
<label>Shelly GAS (SHGS-1)</label>
|
||||
<description>Shelly Gas Sensor</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="gasSensor"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:basic"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyflood">
|
||||
<label>Shelly Flood (SHWT-1)</label>
|
||||
<description>Shelly Flood Sensor (battery powered)</description>
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="floodSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw">
|
||||
<label>Shelly Door/Window (SHDW-1)</label>
|
||||
<description>Shelly Door/Window Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="doorWinSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw2">
|
||||
<label>Shelly Door/Window (SHDW-2)</label>
|
||||
<description>Shelly Door/Window 2 Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="doorWinSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellysense">
|
||||
<label>Shelly Sense (SHSEN-1)</label>
|
||||
<description>Shelly Sense Remote IR Controller</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="senseControl"/>
|
||||
<channel-group id="sensors" typeId="senseSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybutton1">
|
||||
<label>Shelly Button 1 (SHBTN-1)</label>
|
||||
<description>Shelly Button 1 (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="status" typeId="buttonState"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="htSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the HT Sensor</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="floodSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Flood Sensor</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="smokeSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Flood Sensor</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="gasSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Gas Sensor</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="doorWinSensors">
|
||||
<label>Sensors</label>
|
||||
<description>Data from the sensors</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="senseSensors">
|
||||
<label>Sensors</label>
|
||||
<description>Data from the Sense sensors</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="batteryStatus">
|
||||
<label>Battery Status</label>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="senseControl">
|
||||
<label>Sense Control</label>
|
||||
<description>Sense Settings</description>
|
||||
<channels>
|
||||
<channel id="key" typeId="senseKey"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="buttonState">
|
||||
<label>Button State</label>
|
||||
<description>Status of the Button</description>
|
||||
<channels>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="lastEvent" typeId="lastEvent"/>
|
||||
<channel id="eventCount" typeId="eventCount"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
||||
<channel-type id="sensorContact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact</label>
|
||||
<description>State of the contact: open/close</description>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="OPEN">Open</option>
|
||||
<option value="CLOSED">Closed</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorState">
|
||||
<item-type>String</item-type>
|
||||
<label>Sensor State</label>
|
||||
<description>Sensor State: normal</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="warmup">Warm-Up</option>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="fault">Fault</option>
|
||||
<option value="unknown">unknown</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="alarmState">
|
||||
<item-type>String</item-type>
|
||||
<label>Alarm State</label>
|
||||
<description>Alarm State: normal</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="unknown">Unknown</option>
|
||||
<option value="none">None</option>
|
||||
<option value="mild">Mild</option>
|
||||
<option value="heavy">Heavy</option>
|
||||
<option value="test">Test</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastEvent">
|
||||
<item-type>String</item-type>
|
||||
<label>Event</label>
|
||||
<description>Event Type</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="S">Short push</option>
|
||||
<option value="SS">Double-Short push</option>
|
||||
<option value="SSS">Triple-Short push</option>
|
||||
<option value="L">Long push</option>
|
||||
<option value="SL">Short-Long push</option>
|
||||
<option value="LS">Long-Short push</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="eventCount">
|
||||
<item-type>Number</item-type>
|
||||
<label>Event Count</label>
|
||||
<description>Event Count</description>
|
||||
<state pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sensorTemp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature from the sensor</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorExtTemp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature from the external sensor</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorExtHum">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Relative humidity in % (0..100%) from external sensor</description>
|
||||
<category>Humidity</category>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorHumidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Relative humidity in % (0..100%)</description>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state readOnly="true" min="0" max="100" pattern="%f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="sensorFlood">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Flood Alarm</label>
|
||||
<description>ON: Indicates flood condition / water detected</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorSmoke">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Smoke Alarm</label>
|
||||
<description>ON: Indicates smoke detection</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorLux">
|
||||
<item-type>Number:Illuminance</item-type>
|
||||
<label>Lux</label>
|
||||
<description>Brightness from the sensor (Lux)</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorIllumination">
|
||||
<item-type>String</item-type>
|
||||
<label>Illumination</label>
|
||||
<description>Current illumination: dark/twilight/bright</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="twilight">Twilight</option>
|
||||
<option value="bright">Bright</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorVibration">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Vibration</label>
|
||||
<description>ON: Vibration detected</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorTilt">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Tilt</label>
|
||||
<description>Tilt in degrees (requires calibration)</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorPPM">
|
||||
<item-type>Number:Density</item-type>
|
||||
<label>Concentration</label>
|
||||
<description>Gas concentration in ppm</description>
|
||||
<state readOnly="true" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorValve">
|
||||
<item-type>String</item-type>
|
||||
<label>Valve</label>
|
||||
<description>Current valve status: closed/opened/not_connected/failure/closing/opening/checking</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="closed">closed</option>
|
||||
<option value="opened">opened</option>
|
||||
<option value="not_connected">not connected</option>
|
||||
<option value="failure">failure</option>
|
||||
<option value="closing">closing</option>
|
||||
<option value="opening">opening</option>
|
||||
<option value="checking">checking</option>
|
||||
<option value="unknown">UNKNOWN</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorError">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Error</label>
|
||||
<description>Only valid in case of error</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="senseKey">
|
||||
<item-type>String</item-type>
|
||||
<label>IR Key to Send</label>
|
||||
<description>Send a defined key code</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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="shellydevice">
|
||||
<label>Shelly Device</label>
|
||||
<description>A password protected or unknown device.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="userId" type="text" required="true">
|
||||
<label>UserID</label>
|
||||
<description>User ID for API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<description>Password for API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>IP Address</label>
|
||||
<description>IP Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyunknown">
|
||||
<label>Unknown Shelly Device</label>
|
||||
<description>This device is currently not supported</description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user