Skip to content

Commit

Permalink
[shelly] Improved Motion Support, Support CoIoT Unicast, fixes (#10220)
Browse files Browse the repository at this point in the history
* New feature: Shelly Manager

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

* Removed Shelly Manager to reduce PR size (will be another PR)

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

* CoIoT initialization handles new COIOT options for the device,
sensorSleepTime is now adadvanced; Roller set position 0/100 is mapped
to UP/DOWN; Reference to Shelly Manager removed from README

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

* Nullpointer check added on settings.coiot (4Pro has this null)

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

* README updated

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

* Use regex to extract fw version from string, check fw version to detect
restarted, README updated, moved channel sensorSleepTime from group
device to sensors

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

* Review changes

Signed-off-by: Markus Michels <markus7017@gmail.com>
  • Loading branch information
markus7017 authored Mar 3, 2021
1 parent 51ddbdb commit 3af0392
Show file tree
Hide file tree
Showing 24 changed files with 372 additions and 97 deletions.
12 changes: 12 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 Down Expand Up @@ -793,6 +794,10 @@ 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

|Group |Channel |Type |read-only|Description |
|----------|---------------|---------|---------|---------------------------------------------------------------------|
|sensors |motion |Switch |yes |ON: Motion was detected |
Expand All @@ -801,10 +806,17 @@ 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. |
| |motionActive |Switch |yes |ON: Motion detection is currently active |
| |sensorSleepTime|Number |no |Specifies the number of sec the sensor should not report events ]
| |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%) |

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 suppress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on.

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

|Group |Channel |Type |read-only|Description |
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,8 +249,10 @@ 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_SLEEPTIME = "sensorSleepTime";
public static final String CHANNEL_SENSOR_ERROR = "lastError";

// External sensors for Shelly1/1PM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return null;
}

public Map<String, ShellyBaseHandler> getThingHandlers() {
return deviceListeners;
public Map<String, ShellyManagerInterface> getThingHandlers() {
return new HashMap<>(deviceListeners);
}

/**
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 @@ -18,8 +18,11 @@

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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 All @@ -41,6 +44,7 @@
@NonNullByDefault
public class ShellyDeviceProfile {
private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class);
private final static Pattern VERSION_PATTERN = Pattern.compile("v\\d+\\.\\d+\\.\\d+");

public boolean initialized = false; // true when initialized

Expand All @@ -54,6 +58,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 +121,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 +135,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 +181,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 +205,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 +324,24 @@ public int getRollerFav(int id) {
}
return -1;
}

public static String extractFwVersion(@Nullable String version) {
if (version != null) {
Matcher matcher = VERSION_PATTERN.matcher(version);
if (matcher.find()) {
// e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master
return matcher.group(0);
}
}
return "";
}

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

0 comments on commit 3af0392

Please sign in to comment.