Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[shelly] Improved Motion Support, Support CoIoT Unicast, fixes #10220

Merged
merged 7 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added bundles/org.openhab.binding.shelly 2.zip
Binary file not shown.
Binary file added bundles/org.openhab.binding.shelly.zip
Binary file not shown.
21 changes: 21 additions & 0 deletions bundles/org.openhab.binding.shelly/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Shelly Binding

This Binding integrates [Shelly devices](https://shelly.cloud) devloped by Allterco.

![](https://shop.shelly.cloud/image/cache/catalog/shelly_1/s1_x1-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_dimmer2/shelly_dimmer2_x1-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_vintage/shelly_vintage_A60-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_plug_s/s_plug_s_x1-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_button1/shelly_button1_x1-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_gas/shelly_gas_eu-80x80.jpg) ![](https://shop.shelly.cloud/image/cache/catalog/shelly_ht/s_ht_x1-80x80.jpg)

Allterco provides a rich set of smart home devices. All of them are WiFi enabled (2,4GHz, IPv4 only) and provide a documented API.
Expand All @@ -15,6 +16,14 @@ The binding gets in sync with the next status refresh.

Refer to [Advanced Users](doc/AdvancedUsers.md) for more information on openHAB Shelly integration, e.g. firmware update, network communication or log filtering.

Also check out the [Shelly Manager](doc/ShellyManager.md), which
- provides detailed information on your Shellys
- helps to diagnose WiFi issues or device instabilities
- includes some common actions and
- simplifies firmware updates.

[Shelly Manager](doc/ShellyManager.md) could also act as a firmware upgrade proxy - the device doesn't need to connect directly to the Internet, instead openHAB services as a download proxy, which improves device security.

## Supported Devices

| thing-type | Model | Vendor ID |
Expand Down Expand Up @@ -793,6 +802,11 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa

### Shelly Motion (thing-type: shellymotion)

Important: The Shelly Motion does only support CoIoT Unicast, which means you need to set the CoIoT peer address.

- Use device WebUI, open COIOT settings, make sure CoIoT is enabled and enter the openHAB IP address or
- Use [Shelly Manager](doc/ShellyManager.md, select Action 'Set CoIoT peer' and the Manager will sets the openHAB IP address as peer address
markus7017 marked this conversation as resolved.
Show resolved Hide resolved

|Group |Channel |Type |read-only|Description |
|----------|---------------|---------|---------|---------------------------------------------------------------------|
|sensors |motion |Switch |yes |ON: Motion was detected |
Expand All @@ -801,9 +815,16 @@ You can define 2 items (1 Switch, 1 Number) mapping to the same channel, see exa
| |illumination |String |yes |Current illumination: dark/twilight/bright |
| |vibration |Switch |yes |ON: Vibration detected |
| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. |
| |notionActive |Switch |yes |ON: Motion detection is currently active |
markus7017 marked this conversation as resolved.
Show resolved Hide resolved
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
|device |sensorSleepTime|Number |no |Specifies the number of sec the sensor should not report events ]

Use case for the 'sensorSleepTime':
You have a Motion controlling your light.
You switch off the light and want to leave the room, but the motion sensor immediately switches light back on.
Using 'sensorSleepTime' you could suspress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on.
markus7017 marked this conversation as resolved.
Show resolved Hide resolved

### Shelly Button 1 (thing-type: shellybutton1)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public class ShellyBindingConstants {
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_ACT = "motionActive";
public static final String CHANNEL_SENSOR_MOTION = "motion";
public static final String CHANNEL_SENSOR_MOTION_TS = "motionTimestamp";
public static final String CHANNEL_SENSOR_ERROR = "lastError";
Expand Down Expand Up @@ -302,6 +303,7 @@ public class ShellyBindingConstants {
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_DEVST_SENSOR_SLEEPTIME = "sensorSleepTime";

public static final String CHANNEL_LED_STATUS_DISABLE = "statusLed";
public static final String CHANNEL_LED_POWER_DISABLE = "powerLed";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -25,6 +26,7 @@
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.ShellyManagerInterface;
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
Expand Down Expand Up @@ -141,8 +143,12 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return null;
}

public Map<String, ShellyBaseHandler> getThingHandlers() {
return deviceListeners;
public Map<String, ShellyManagerInterface> getThingHandlers() {
Map<String, ShellyManagerInterface> handlers = new TreeMap<>();
for (Map.Entry<String, ShellyBaseHandler> bh : deviceListeners.entrySet()) {
handlers.put(bh.getKey(), bh.getValue());
}
return handlers;
markus7017 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.ArrayList;
import java.util.List;

import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor.ShellyMotionSettings;
import org.openhab.core.thing.CommonTriggerEvents;

import com.google.gson.annotations.SerializedName;
Expand Down Expand Up @@ -226,6 +227,12 @@ public class ShellyApiJsonDTO {
public static final String SHELLY_TEMP_CELSIUS = "C";
public static final String SHELLY_TEMP_FAHRENHEIT = "F";

// Motion
public static final int SHELLY_MOTION_SLEEPTIME_OFFSET = 3; // we need to substract and offset

// CoIoT Multicast setting
public static final String SHELLY_COIOT_MCAST = "mcast";

public static class ShellySettingsDevice {
public String type;
public String mac;
Expand Down Expand Up @@ -267,7 +274,7 @@ public static class ShellySettingsWiFiNetwork {
}

public static class ShellySettingsMqtt {
public Boolean enabled;
public Boolean enable;
public String server;
public String user;
@SerializedName("reconnect_timeout_max")
Expand All @@ -292,10 +299,17 @@ public static class ShellySettingsMqtt {
public static class ShellySettingsCoiot { // FW 1.6+
@SerializedName("update_period")
public Integer updatePeriod;
public Boolean enabled; // Motion 1.0.7: Coap can be disabled
public String peer; // if set the device uses singlecast CoAP, mcast=set back to Multicast
}

public static class ShellyStatusMqtt {
public Boolean connected;
}

public static class ShellySettingsSntp {
public String server;
public Boolean enabled;
}

public static class ShellySettingsLogin {
Expand All @@ -319,10 +333,6 @@ public static class ShellyStatusCloud {
public Boolean connected;
}

public static class ShellyStatusMqtt {
public Boolean connected;
}

public static class ShellySettingsHwInfo {
@SerializedName("hw_revision")
public String hwRevision;
Expand Down Expand Up @@ -532,8 +542,11 @@ public static class ShellySettingsGlobal {
public ShellySettingsWiFiNetwork wifiSta;
@SerializedName("wifi_sta1")
public ShellySettingsWiFiNetwork wifiSta1;
// public ShellySettingsMqtt mqtt; // not used for now
// public ShellySettingsSntp sntp; // not used for now
@SerializedName("wifirecovery_reboot_enabled")
public Boolean wifiRecoveryReboot;

public ShellySettingsMqtt mqtt; // not used for now
public ShellySettingsSntp sntp; // not used for now
public ShellySettingsCoiot coiot; // Firmware 1.6+
public ShellySettingsLogin login;
@SerializedName("pin_code")
Expand All @@ -544,8 +557,8 @@ public static class ShellySettingsGlobal {
public Boolean discoverable; // FW 1.6+
public String fw;
@SerializedName("build_info")
ShellySettingsBuildInfo buildInfo;
ShellyStatusCloud cloud;
public ShellySettingsBuildInfo buildInfo;
public ShellyStatusCloud cloud;
@SerializedName("sleep_mode")
public ShellySensorSleepMode sleepMode; // FW 1.6
@SerializedName("external_power")
Expand Down Expand Up @@ -626,6 +639,18 @@ public static class ShellySettingsGlobal {
@SerializedName("favorites_enabled")
public Boolean favoritesEnabled;
public ArrayList<ShellyFavPos> favorites;

// Motion
public ShellyMotionSettings motion;
@SerializedName("tamper_sensitivity")
public Integer tamperSensitivity;
@SerializedName("dark_threshold")
public Integer darkThreshold;
@SerializedName("twilight_threshold")
public Integer twilightThreshold;

@SerializedName("sleep_time") // Shelly Motion
public Integer sleepTime;
}

public static class ShellySettingsAttributes {
Expand All @@ -644,11 +669,17 @@ public static class ShellySettingsAttributes {
public String fw; // current FW version
}

public static class ShellyActionsStats {
public Integer skipped;
}

public static class ShellySettingsStatus {
public String name; // FW 1.8: Symbolic Device name is configurable

@SerializedName("wifi_sta")
public ShellySettingsWiFiNetwork wifiSta; // WiFi client configuration. See /settings/sta for details
public ShellyStatusCloud cloud;
public ShellyStatusMqtt mqtt;

public String time;
public Integer serial;
Expand All @@ -658,6 +689,8 @@ public static class ShellySettingsStatus {
public Boolean discoverable; // FW 1.6+
@SerializedName("cfg_changed_cnt")
public Integer cfgChangedCount; // FW 1.8
@SerializedName("actions_stats")
public ShellyActionsStats astats;

public ArrayList<ShellySettingsRelay> relays;
public ArrayList<ShellySettingsRoller> rollers;
Expand Down Expand Up @@ -690,6 +723,9 @@ public static class ShellySettingsStatus {
public Long fsFree;
public Long uptime;

@SerializedName("sleep_time") // Shelly Motion
public Integer sleepTime;

public String json;
}

Expand Down Expand Up @@ -908,6 +944,17 @@ public static class ShellySensorAccel {
public Integer vibration; // Whether vibration is detected
}

public static class ShellyMotionSettings {
public Integer sensitivity;
@SerializedName("blind_time_minutes")
public Integer blindTimeMinutes;
@SerializedName("pulse_count")
public Integer pulseCount;
@SerializedName("operating_mode")
public Integer operatingMode;
public Boolean enabled;
}

public static class ShellyExtTemperature {
public static class ShellyShortTemp {
public Double tC; // temperature in deg C
Expand Down Expand Up @@ -953,8 +1000,6 @@ public static class ShellyADC {

public Boolean motion; // Shelly Sense: true=motion detected
public Boolean charger; // Shelly Sense: true=charger connected
@SerializedName("external_power")
public Integer externalPower; // H&T FW 1.6, seems to be the same like charger for the Sense

@SerializedName("act_reasons")
public List<Object> actReasons; // HT/Smoke/Flood: list of reasons which woke up the device
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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;
Expand Down Expand Up @@ -54,6 +55,8 @@ public class ShellyDeviceProfile {
public String hostname = "";
public String mode = "";
public boolean discoverable = true;
public boolean auth = false;
public boolean alwaysOn = true;

public String hwRev = "";
public String hwBatchId = "";
Expand Down Expand Up @@ -115,11 +118,11 @@ public ShellyDeviceProfile initialize(String thingType, String json) throws Shel
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() : "";
mode = 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, "/", "@");
fwVersion = extractFwVersion(settings.fw);
fwId = substringAfter(settings.fw, "@");
discoverable = (settings.discoverable == null) || settings.discoverable;

Expand All @@ -129,8 +132,6 @@ public ShellyDeviceProfile initialize(String thingType, String json) throws Shel
if ((numRelays > 0) && (settings.relays == null)) {
numRelays = 0;
}
isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
hasRelays = (numRelays > 0) || isDimmer;
numRollers = getInteger(settings.device.numRollers);
numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0;
Expand Down Expand Up @@ -177,6 +178,9 @@ public void initFromThingType(String name) {
return;
}

isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);

isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)
|| thingType.equals(THING_TYPE_SHELLYDUORGBW_STR);
Expand All @@ -198,14 +202,14 @@ public void initFromThingType(String name) {
isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isMotion || isSense;
hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion; // we assume that Sense is connected to
// the charger
hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion;

alwaysOn = !hasBattery || isMotion || isSense; // true means: device is reachable all the time (no sleep mode)
}

public void updateFromStatus(ShellySettingsStatus status) {
if (hasRelays) {
// Dimmer-2 doesn't report inputs under /settings, only on /status, we need to update that info after
// initialization
// Dimmer-2 doesn't report inputs under /settings, only on /status, we need to update that info after init
if (status.inputs != null) {
numInputs = status.inputs.size();
}
Expand Down Expand Up @@ -317,4 +321,31 @@ public int getRollerFav(int id) {
}
return -1;
}

public static String extractFwVersion(@Nullable String version) {
if (version != null) {
if (version.chars().filter(ch -> ch == '-').count() >= 3) {
// new format starting with version 1.0
// e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master
}
if (version.contains("@")) { // standard format
return substringBetween(getString(version), "/", "@");
}

if (version.indexOf('/') != version.lastIndexOf('/')) {
// includes 2 /, e.g. beta
return substringAfterLast(version, "/");
}
}
return "";
}
markus7017 marked this conversation as resolved.
Show resolved Hide resolved

public boolean coiotEnabled() {
if ((settings.coiot != null) && (settings.coiot.enabled != null)) {
return settings.coiot.enabled;
}

// If device is not yet intialized or the enabled property is missing we assume that CoIoT is enabled
return true;
}
}
Loading