mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[shelly] Improved Motion Support, Support CoIoT Unicast, fixes (#10220)
* 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>
This commit is contained in:
parent
51ddbdb84d
commit
3af0392724
@ -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.
|
||||
@ -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 |
|
||||
@ -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 |
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
After Width: | Height: | Size: 238 KiB |
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
@ -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
|
||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.shelly.internal;
|
||||
|
||||
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;
|
||||
@ -25,6 +26,7 @@ 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.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;
|
||||
@ -141,8 +143,8 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, ShellyBaseHandler> getThingHandlers() {
|
||||
return deviceListeners;
|
||||
public Map<String, ShellyManagerInterface> getThingHandlers() {
|
||||
return new HashMap<>(deviceListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,6 +15,7 @@ package org.openhab.binding.shelly.internal.api;
|
||||
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;
|
||||
@ -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;
|
||||
@ -267,7 +274,7 @@ public class ShellyApiJsonDTO {
|
||||
}
|
||||
|
||||
public static class ShellySettingsMqtt {
|
||||
public Boolean enabled;
|
||||
public Boolean enable;
|
||||
public String server;
|
||||
public String user;
|
||||
@SerializedName("reconnect_timeout_max")
|
||||
@ -292,10 +299,17 @@ public class ShellyApiJsonDTO {
|
||||
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 {
|
||||
@ -319,10 +333,6 @@ public class ShellyApiJsonDTO {
|
||||
public Boolean connected;
|
||||
}
|
||||
|
||||
public static class ShellyStatusMqtt {
|
||||
public Boolean connected;
|
||||
}
|
||||
|
||||
public static class ShellySettingsHwInfo {
|
||||
@SerializedName("hw_revision")
|
||||
public String hwRevision;
|
||||
@ -532,8 +542,11 @@ public class ShellyApiJsonDTO {
|
||||
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")
|
||||
@ -544,8 +557,8 @@ public class ShellyApiJsonDTO {
|
||||
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")
|
||||
@ -626,6 +639,18 @@ public class ShellyApiJsonDTO {
|
||||
@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 {
|
||||
@ -644,11 +669,17 @@ public class ShellyApiJsonDTO {
|
||||
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;
|
||||
@ -658,6 +689,8 @@ public class ShellyApiJsonDTO {
|
||||
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;
|
||||
@ -690,6 +723,9 @@ public class ShellyApiJsonDTO {
|
||||
public Long fsFree;
|
||||
public Long uptime;
|
||||
|
||||
@SerializedName("sleep_time") // Shelly Motion
|
||||
public Integer sleepTime;
|
||||
|
||||
public String json;
|
||||
}
|
||||
|
||||
@ -908,6 +944,17 @@ public class ShellyApiJsonDTO {
|
||||
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
|
||||
@ -953,8 +1000,6 @@ public class ShellyApiJsonDTO {
|
||||
|
||||
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
|
||||
|
@ -18,8 +18,11 @@ import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
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;
|
||||
@ -41,6 +44,7 @@ import com.google.gson.Gson;
|
||||
@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
|
||||
|
||||
@ -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 = "";
|
||||
@ -115,11 +121,11 @@ public class ShellyDeviceProfile {
|
||||
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;
|
||||
|
||||
@ -129,8 +135,6 @@ public class ShellyDeviceProfile {
|
||||
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;
|
||||
@ -177,6 +181,9 @@ public class ShellyDeviceProfile {
|
||||
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);
|
||||
@ -198,14 +205,14 @@ public class ShellyDeviceProfile {
|
||||
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();
|
||||
}
|
||||
@ -317,4 +324,24 @@ public class ShellyDeviceProfile {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -194,26 +194,28 @@ public class ShellyHttpApi {
|
||||
.convert(getDouble(status.tmp.value));
|
||||
status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value : f;
|
||||
}
|
||||
if ((status.charger == null) && (status.externalPower != null)) {
|
||||
if ((status.charger == null) && (profile.settings.externalPower != null)) {
|
||||
// SHelly H&T uses external_power, Sense uses charger
|
||||
status.charger = status.externalPower != 0;
|
||||
status.charger = profile.settings.externalPower != 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setTimer(Integer index, String timerName, Double value) throws ShellyApiException {
|
||||
public void setTimer(int index, String timerName, int 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();
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value;
|
||||
request(uri);
|
||||
}
|
||||
|
||||
public void setSleepTime(int value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?sleep_time=" + value);
|
||||
}
|
||||
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
|
||||
}
|
||||
@ -239,6 +241,10 @@ public class ShellyHttpApi {
|
||||
ShellySettingsLogin.class);
|
||||
}
|
||||
|
||||
public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_SETTINGS + "?coiot_enable=true&coiot_peer=" + peer, ShellySettingsLogin.class);
|
||||
}
|
||||
|
||||
public String deviceReboot() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_RESTART, String.class);
|
||||
}
|
||||
@ -251,6 +257,10 @@ public class ShellyHttpApi {
|
||||
return callApi("/ota?" + uri, ShellySettingsUpdate.class);
|
||||
}
|
||||
|
||||
public String setCloud(boolean enabled) throws ShellyApiException {
|
||||
return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change between White and Color Mode
|
||||
*
|
||||
|
@ -111,8 +111,12 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
|
||||
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));
|
||||
logger.debug("{}: CoAP update roller position: control={}, position={}", thingName,
|
||||
SHELLY_MAX_ROLLER_POS - pos, pos);
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType((double) pos, Units.PERCENT));
|
||||
break;
|
||||
case "1105": // S, valvle, closed/opened/not_connected/failure/closing/opening/checking or unbknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr));
|
||||
@ -304,6 +308,8 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
|
||||
break;
|
||||
case "3120": // motionActive
|
||||
// {"I":3120,"T":"S","D":"motionActive","R":["0/1","-1"],"L":1},
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_ACT,
|
||||
getTimestamp(getString(profile.settings.timezone), (long) s.value));
|
||||
break;
|
||||
|
||||
case "6108": // A, gas, none/mild/heavy/test or unknown
|
||||
|
@ -38,6 +38,7 @@ 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.coap.ShellyCoapHandler;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
@ -72,7 +73,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener {
|
||||
public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener, ShellyManagerInterface {
|
||||
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
|
||||
protected final ShellyChannelDefinitions channelDefinitions;
|
||||
|
||||
@ -216,7 +217,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
// Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing could not be
|
||||
// fully initialized here. In this case the CoAP messages triggers auto-initialization (like the Action URL does
|
||||
// when enabled)
|
||||
if (config.eventsCoIoT && profile.hasBattery && !profile.isMotion && !profile.isSense) {
|
||||
if (config.eventsCoIoT && !profile.alwaysOn) {
|
||||
coap.start(thingName, config);
|
||||
}
|
||||
|
||||
@ -242,6 +243,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
// New Shelly devices might use a different endpoint for the CoAP listener
|
||||
tmpPrf.coiotEndpoint = devInfo.coiot;
|
||||
}
|
||||
tmpPrf.auth = devInfo.auth; // missing in /settings
|
||||
|
||||
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {} ({})",
|
||||
thingName, tmpPrf.hostname, tmpPrf.deviceType, tmpPrf.hwRev, tmpPrf.hwBatchId, tmpPrf.fwVersion,
|
||||
@ -249,18 +251,34 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
logger.debug("{}: Shelly settings info for {}: {}", thingName, tmpPrf.hostname, tmpPrf.settingsJson);
|
||||
logger.debug("{}: Device "
|
||||
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||
+ ",updatePeriod:{}sec", thingName, tmpPrf.hasRelays, tmpPrf.numRelays, tmpPrf.isRoller,
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||
+ ",alwaysOn:{}, ,updatePeriod:{}sec", thingName, tmpPrf.hasRelays, tmpPrf.numRelays, tmpPrf.isRoller,
|
||||
tmpPrf.numRollers, tmpPrf.isDimmer, tmpPrf.numMeters, tmpPrf.isEMeter, tmpPrf.isSensor, tmpPrf.isDW,
|
||||
tmpPrf.hasBattery, tmpPrf.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "",
|
||||
tmpPrf.isSense, tmpPrf.isLight, profile.isBulb, tmpPrf.isDuo, tmpPrf.isRGBW2, tmpPrf.inColor,
|
||||
tmpPrf.updatePeriod);
|
||||
tmpPrf.isSense, tmpPrf.isMotion, tmpPrf.isLight, profile.isBulb, tmpPrf.isDuo, tmpPrf.isRGBW2,
|
||||
tmpPrf.inColor, tmpPrf.alwaysOn, tmpPrf.updatePeriod);
|
||||
|
||||
// update thing properties
|
||||
tmpPrf.status = api.getStatus();
|
||||
tmpPrf.updateFromStatus(tmpPrf.status);
|
||||
updateProperties(tmpPrf, tmpPrf.status);
|
||||
checkVersion(tmpPrf, tmpPrf.status);
|
||||
if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) {
|
||||
String devpeer = getString(tmpPrf.settings.coiot.peer);
|
||||
String ourpeer = config.localIp + ":" + ShellyCoapJSonDTO.COIOT_PORT;
|
||||
if (!tmpPrf.settings.coiot.enabled || (profile.isMotion && devpeer.isEmpty())) {
|
||||
try {
|
||||
api.setCoIoTPeer(ourpeer);
|
||||
logger.info("{}: CoIoT peer updated to {}", thingName, ourpeer);
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to set CoIoT peer: {}", thingName, e.toString());
|
||||
}
|
||||
} else if (!devpeer.equals(ourpeer)) {
|
||||
logger.warn("{}: CoIoT peer in device settings does not point this to this host, disabling CoIoT",
|
||||
thingName);
|
||||
config.eventsCoIoT = autoCoIoT = false;
|
||||
}
|
||||
}
|
||||
if (autoCoIoT) {
|
||||
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
|
||||
config.eventsCoIoT = true;
|
||||
@ -327,6 +345,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
api.setLedStatus(SHELLY_LED_POWER_DISABLE, command == OnOffType.ON);
|
||||
break;
|
||||
|
||||
case CHANNEL_SENSOR_SLEEPTIME:
|
||||
logger.debug("{}: Set sensor sleep time to {}", thingName, command);
|
||||
int value = ((DecimalType) command).intValue();
|
||||
value = value > 0 ? Math.max(SHELLY_MOTION_SLEEPTIME_OFFSET, value - SHELLY_MOTION_SLEEPTIME_OFFSET)
|
||||
: 0;
|
||||
api.setSleepTime(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
update = handleDeviceCommand(channelUID, command);
|
||||
break;
|
||||
@ -368,10 +394,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
initializeThing(); // may fire an exception if initialization failed
|
||||
}
|
||||
// Get profile, if refreshSettings == true reload settings from device
|
||||
profile = getProfile(refreshSettings);
|
||||
|
||||
logger.trace("{}: Updating status", thingName);
|
||||
logger.trace("{}: Updating status (refreshSettings={})", thingName, refreshSettings);
|
||||
ShellySettingsStatus status = api.getStatus();
|
||||
profile = getProfile(refreshSettings || checkRestarted(status));
|
||||
profile.status = status;
|
||||
profile.updateFromStatus(status);
|
||||
|
||||
@ -446,16 +471,18 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
return getThing().getStatus() == ThingStatus.OFFLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingOnline() {
|
||||
if (!isThingOnline()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// request 3 updates in a row (during the first 2+3*3 sec)
|
||||
requestUpdates(!profile.hasBattery ? 3 : 1, channelsCreated == false);
|
||||
requestUpdates(profile.alwaysOn ? 3 : 1, channelsCreated == false);
|
||||
}
|
||||
restartWatchdog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey) {
|
||||
if (!isThingOffline()) {
|
||||
logger.info("{}: Thing goes OFFLINE: {}", thingName, messages.get(messageKey));
|
||||
@ -485,8 +512,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
}
|
||||
|
||||
public void reinitializeThing() {
|
||||
logger.debug("{}: Re-Initialize Thing", thingName);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
requestUpdates(1, true);
|
||||
requestUpdates(0, true);
|
||||
}
|
||||
|
||||
private void fillDeviceStatus(ShellySettingsStatus status, boolean updated) {
|
||||
@ -495,6 +523,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
|
||||
// Update uptime and WiFi, internal temp
|
||||
ShellyComponents.updateDeviceStatus(this, status);
|
||||
stats.wifiRssi = status.wifiSta.rssi;
|
||||
|
||||
if (api.isInitialized()) {
|
||||
stats.timeoutErrors = api.getTimeoutErrors();
|
||||
@ -503,15 +532,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
stats.remainingWatchdog = watchdog > 0 ? now() - watchdog : 0;
|
||||
|
||||
// Check various device indicators like overheating
|
||||
logger.debug("{}: status.update={}, lastUpdate={}", thingName, status.uptime, stats.lastUptime);
|
||||
if ((status.uptime < stats.lastUptime) && profile.isInitialized()) {
|
||||
alarm = ALARM_TYPE_RESTARTED;
|
||||
force = true;
|
||||
stats.unexpectedRestarts++;
|
||||
logger.debug("{}: Device restart #{} detected", thingName, stats.unexpectedRestarts);
|
||||
|
||||
if (checkRestarted(status)) {
|
||||
// Force re-initialization on next status update
|
||||
if (!profile.hasBattery || profile.isMotion) {
|
||||
if (profile.alwaysOn) {
|
||||
reinitializeThing();
|
||||
}
|
||||
} else if (getBool(status.overtemperature)) {
|
||||
@ -521,6 +544,15 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
} else if (getBool(status.loaderror)) {
|
||||
alarm = ALARM_TYPE_LOADERR;
|
||||
}
|
||||
State internalTemp = getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP);
|
||||
if (internalTemp != UnDefType.NULL) {
|
||||
int temp = ((Number) internalTemp).intValue();
|
||||
if (temp > stats.maxInternalTemp) {
|
||||
logger.debug("{}: Max Internal Temp for device changed to {}", thingName, temp);
|
||||
stats.maxInternalTemp = temp;
|
||||
}
|
||||
}
|
||||
|
||||
stats.lastUptime = getLong(status.uptime);
|
||||
stats.coiotMessages = coap.getMessageCount();
|
||||
stats.coiotErrors = coap.getErrorCount();
|
||||
@ -530,6 +562,24 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device has restarted and needs a new Thing initialization
|
||||
*
|
||||
* @return true: restart detected
|
||||
*/
|
||||
|
||||
private boolean checkRestarted(ShellySettingsStatus status) {
|
||||
if (profile.isInitialized() && (status.uptime < stats.lastUptime || !profile.status.update.oldVersion.isEmpty()
|
||||
&& !status.update.oldVersion.equals(profile.status.update.oldVersion))) {
|
||||
logger.debug("{}: Device restart #{} detected", thingName, stats.restarts);
|
||||
stats.restarts++;
|
||||
postEvent(ALARM_TYPE_RESTARTED, true);
|
||||
updateProperties(profile, status);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save alarm to the lastAlarm channel
|
||||
*
|
||||
@ -752,7 +802,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate,
|
||||
prf.fwId, SHELLY_API_MIN_FWVERSION));
|
||||
} else {
|
||||
if (version.compare(prf.fwVersion, SHELLY_API_MIN_FWVERSION) < 0) {
|
||||
if ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWVERSION) < 0) && !profile.isMotion) {
|
||||
logger.warn("{}: {}", prf.hostname, messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate,
|
||||
prf.fwId, SHELLY_API_MIN_FWVERSION));
|
||||
}
|
||||
@ -838,13 +888,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
* @return true=Update schedule, false=skipped (too many updates already
|
||||
* scheduled)
|
||||
*/
|
||||
@Override
|
||||
public boolean requestUpdates(int requestCount, boolean refreshSettings) {
|
||||
this.refreshSettings |= refreshSettings;
|
||||
if (refreshSettings) {
|
||||
if (requestCount == 0) {
|
||||
logger.debug("{}: Request settings refresh", thingName);
|
||||
}
|
||||
scheduledUpdates = requestCount;
|
||||
scheduledUpdates = 1;
|
||||
return true;
|
||||
}
|
||||
if (scheduledUpdates < 10) { // < 30s
|
||||
@ -920,7 +971,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
profile.isRoller ? CHANNEL_EVENT_TRIGGER : CHANNEL_BUTTON_TRIGGER + profile.getInputSuffix(idx),
|
||||
trigger);
|
||||
updateChannel(group, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
if (!profile.hasBattery) {
|
||||
if (profile.alwaysOn) {
|
||||
// refresh status of the input channel
|
||||
requestUpdates(1, false);
|
||||
}
|
||||
@ -941,6 +992,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
return !stopping && cache.updateChannel(channelId, value, force);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getChannelValue(String group, String channel) {
|
||||
return cache.getValue(group, channel);
|
||||
}
|
||||
@ -1005,6 +1057,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
* @param status the /status result
|
||||
*/
|
||||
protected void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
|
||||
logger.debug("{}: Update properties", thingName);
|
||||
Map<String, Object> properties = fillDeviceProperties(profile);
|
||||
String serviceName = getString(getThing().getProperties().get(PROPERTY_SERVICE_NAME));
|
||||
String hostname = getString(profile.settings.device.hostname).toLowerCase();
|
||||
@ -1112,6 +1165,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
* @return ShellyDeviceProfile instance
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
@Override
|
||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException {
|
||||
try {
|
||||
refreshSettings |= forceRefresh;
|
||||
@ -1127,6 +1181,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyDeviceProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
@ -1185,16 +1240,28 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
|
||||
return false;
|
||||
}
|
||||
|
||||
public ShellyDeviceStats getStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, String> getStatsProp() {
|
||||
return stats.asProperties(getString(profile.settings.timezone));
|
||||
@Override
|
||||
public String getThingName() {
|
||||
return thingName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetStats() {
|
||||
// reset statistics
|
||||
stats = new ShellyDeviceStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyDeviceStats getStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyHttpApi getApi() {
|
||||
return api;
|
||||
}
|
||||
|
||||
public Map<String, String> getStatsProp() {
|
||||
return stats.asProperties();
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,9 @@ public class ShellyComponents {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
}
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME,
|
||||
toQuantityType(getInteger(status.sleepTime), Units.SECOND));
|
||||
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
|
||||
|
||||
return false; // device status never triggers update
|
||||
@ -212,7 +215,7 @@ public class ShellyComponents {
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(getDouble(totalWatts), DIGITS_KWH, Units.KILOWATT_HOUR));
|
||||
|
||||
if (updated) {
|
||||
if (updated && timestamp > 0) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
getTimestamp(getString(profile.settings.timezone), timestamp));
|
||||
}
|
||||
@ -247,7 +250,6 @@ public class ShellyComponents {
|
||||
boolean updated = false;
|
||||
if (profile.isSensor || profile.hasBattery) {
|
||||
ShellyStatusSensor sdata = thingHandler.api.getSensorStatus();
|
||||
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
|
||||
thingHandler.updateChannelDefinitions(
|
||||
@ -355,6 +357,8 @@ public class ShellyComponents {
|
||||
getOnOff(sdata.motion));
|
||||
}
|
||||
if (sdata.sensor != null) { // Shelly Motion
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_ACT,
|
||||
getOnOff(sdata.sensor.motionActive));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
|
||||
getOnOff(sdata.sensor.motion));
|
||||
long timestamp = getLong(sdata.sensor.motionTimestamp);
|
||||
|
@ -26,7 +26,7 @@ import org.openhab.binding.shelly.internal.util.ShellyUtils;
|
||||
@NonNullByDefault
|
||||
public class ShellyDeviceStats {
|
||||
public long lastUptime = 0;
|
||||
public long unexpectedRestarts = 0;
|
||||
public long restarts = 0;
|
||||
public long timeoutErrors = 0;
|
||||
public long timeoutsRecorvered = 0;
|
||||
public long remainingWatchdog = 0;
|
||||
@ -35,20 +35,22 @@ public class ShellyDeviceStats {
|
||||
public long lastAlarmTs = 0;
|
||||
public long coiotMessages = 0;
|
||||
public long coiotErrors = 0;
|
||||
public int wifiRssi = 0;
|
||||
public int maxInternalTemp = 0;
|
||||
|
||||
public Map<String, String> asProperties(String timeZone) {
|
||||
public Map<String, String> asProperties() {
|
||||
Map<String, String> prop = new HashMap<>();
|
||||
prop.put("lastUptime", String.valueOf(lastUptime));
|
||||
prop.put("unexpectedRestarts", String.valueOf(unexpectedRestarts));
|
||||
prop.put("deviceRestarts", String.valueOf(restarts));
|
||||
prop.put("timeoutErrors", String.valueOf(timeoutErrors));
|
||||
prop.put("timeoutsRecovered", String.valueOf(timeoutsRecorvered));
|
||||
prop.put("remainingWatchdog", String.valueOf(remainingWatchdog));
|
||||
prop.put("alarmCount", String.valueOf(alarms));
|
||||
prop.put("lastAlarm", lastAlarm);
|
||||
prop.put("lastAlarmTs",
|
||||
lastAlarmTs != 0 ? ShellyUtils.getTimestamp(timeZone, lastAlarmTs).format(null).replace('T', ' ') : "");
|
||||
prop.put("lastAlarmTs", ShellyUtils.convertTimestamp(lastAlarmTs));
|
||||
prop.put("coiotMessages", String.valueOf(coiotMessages));
|
||||
prop.put("coiotErrors", String.valueOf(coiotErrors));
|
||||
prop.put("wifiRssi", String.valueOf(wifiRssi));
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ShellyManagerInterface} implements the interface for Shelly Manager to access the thing handler
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyManagerInterface {
|
||||
|
||||
public Thing getThing();
|
||||
|
||||
public String getThingName();
|
||||
|
||||
public ShellyDeviceProfile getProfile();
|
||||
|
||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
|
||||
public ShellyHttpApi getApi();
|
||||
|
||||
public ShellyDeviceStats getStats();
|
||||
|
||||
public void resetStats();
|
||||
|
||||
public State getChannelValue(String group, String channel);
|
||||
|
||||
public void setThingOnline();
|
||||
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey);
|
||||
|
||||
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
}
|
@ -134,11 +134,11 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
|
||||
case CHANNEL_TIMER_AUTOON:
|
||||
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command));
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).intValue());
|
||||
break;
|
||||
case CHANNEL_TIMER_AUTOOFF:
|
||||
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command));
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).intValue());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
@ -219,7 +219,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
*/
|
||||
private void handleRoller(Command command, String groupName, Integer index, boolean isControl)
|
||||
throws ShellyApiException {
|
||||
Integer position = -1;
|
||||
int position = -1;
|
||||
|
||||
if ((command instanceof UpDownType) || (command instanceof OnOffType)) {
|
||||
ShellyControlRoller rstatus = api.getRollerStatus(index);
|
||||
@ -235,29 +235,33 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (command == UpDownType.UP || command == OnOffType.ON) {
|
||||
if (command == UpDownType.UP || command == OnOffType.ON
|
||||
|| ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 100))) {
|
||||
logger.debug("{}: Open roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||
int pos = profile.getRollerFav(config.favoriteUP - 1);
|
||||
position = pos > 0 ? pos : SHELLY_MAX_ROLLER_POS;
|
||||
if (pos > 0) {
|
||||
int shpos = profile.getRollerFav(config.favoriteUP - 1);
|
||||
if (shpos > 0) {
|
||||
logger.debug("{}: Use favoriteUP id {} for positioning roller({}%)", thingName, config.favoriteUP,
|
||||
pos);
|
||||
shpos);
|
||||
api.setRollerPos(index, shpos);
|
||||
position = shpos;
|
||||
} else {
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||
position = SHELLY_MIN_ROLLER_POS;
|
||||
}
|
||||
} else if (command == UpDownType.DOWN || command == OnOffType.OFF) {
|
||||
} else if (command == UpDownType.DOWN || command == OnOffType.OFF
|
||||
|| ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 0))) {
|
||||
logger.debug("{}: Closing roller", thingName);
|
||||
int pos = profile.getRollerFav(config.favoriteDOWN - 1);
|
||||
if (pos > 0) {
|
||||
int shpos = profile.getRollerFav(config.favoriteDOWN - 1);
|
||||
if (shpos > 0) {
|
||||
// use favorite position
|
||||
if (pos > 0) {
|
||||
logger.debug("{}: Use favoriteDOWN id {} for positioning roller ({}%)", thingName,
|
||||
config.favoriteDOWN, pos);
|
||||
}
|
||||
api.setRollerPos(index, pos);
|
||||
logger.debug("{}: Use favoriteDOWN id {} for positioning roller ({}%)", thingName,
|
||||
config.favoriteDOWN, shpos);
|
||||
api.setRollerPos(index, shpos);
|
||||
position = shpos;
|
||||
} else {
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
||||
position = SHELLY_MAX_ROLLER_POS;
|
||||
}
|
||||
position = SHELLY_MAX_ROLLER_POS - pos;
|
||||
}
|
||||
} else if (command == StopMoveType.STOP) {
|
||||
logger.debug("{}: Stop roller", thingName);
|
||||
@ -275,8 +279,8 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
"Invalid value type for roller control/position" + 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 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);
|
||||
@ -284,12 +288,15 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
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));
|
||||
logger.debug("{}: Set roller position for control channel to {}", thingName, pos);
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
|
||||
} else {
|
||||
logger.debug("{}: Set roller position channel to {}", thingName, position);
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
|
||||
}
|
||||
}
|
||||
@ -399,6 +406,8 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
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));
|
||||
logger.debug("{}: REST Update roller position: control={}, position={}", thingName,
|
||||
SHELLY_MAX_ROLLER_POS - pos, pos);
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
|
||||
|
@ -100,7 +100,7 @@ public class ShellyChannelDefinitions {
|
||||
public ShellyChannelDefinitions(@Reference ShellyTranslationProvider translationProvider) {
|
||||
ShellyTranslationProvider m = translationProvider;
|
||||
|
||||
// Device: Internal Temp
|
||||
// Device
|
||||
CHANNEL_DEFINITIONS
|
||||
// Device
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEMT_STRING))
|
||||
@ -179,12 +179,14 @@ public class ShellyChannelDefinitions {
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEMT_ANGLE))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_TS, "motionTimestamp", ITEMT_DATETIME))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION_ACT, "motionActive", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEMT_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME, "sensorSleepTime", ITEMT_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEMT_DATETIME))
|
||||
|
||||
// Button/ix3
|
||||
@ -249,6 +251,7 @@ public class ShellyChannelDefinitions {
|
||||
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
|
||||
CHANNEL_DEVST_ITEMP);
|
||||
}
|
||||
addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME);
|
||||
|
||||
// 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
|
||||
@ -259,13 +262,9 @@ public class ShellyChannelDefinitions {
|
||||
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
|
||||
}
|
||||
addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
|
||||
addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi
|
||||
//
|
||||
return add;
|
||||
}
|
||||
|
||||
@ -417,6 +416,8 @@ public class ShellyChannelDefinitions {
|
||||
CHANNEL_SENSOR_MOTION);
|
||||
if (sdata.sensor != null) { // DW, Sense or Motion
|
||||
addChannel(thing, newChannels, sdata.sensor.state != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT); // DW/DW2
|
||||
addChannel(thing, newChannels, sdata.sensor.motionActive != null, CHANNEL_GROUP_SENSOR, // Motion
|
||||
CHANNEL_SENSOR_MOTION_ACT);
|
||||
addChannel(thing, newChannels, sdata.sensor.motionTimestamp != null, CHANNEL_GROUP_SENSOR, // Motion
|
||||
CHANNEL_SENSOR_MOTION_TS);
|
||||
addChannel(thing, newChannels, sdata.sensor.vibration != null, CHANNEL_GROUP_SENSOR,
|
||||
|
@ -16,6 +16,7 @@ import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyUtils;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.osgi.framework.Bundle;
|
||||
@ -44,14 +45,15 @@ public class ShellyTranslationProvider {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public @Nullable String get(String key, @Nullable Object... arguments) {
|
||||
public 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) {
|
||||
public String getText(String key, @Nullable Object... arguments) {
|
||||
try {
|
||||
Locale locale = localeProvider.getLocale();
|
||||
return i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
|
||||
String message = i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
|
||||
return ShellyUtils.getString(message);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "Unable to load message for key " + key;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
@ -53,6 +54,7 @@ import com.google.gson.internal.Primitives;
|
||||
@NonNullByDefault
|
||||
public class ShellyUtils {
|
||||
private final static String PRE = "Unable to create object of type ";
|
||||
public final static DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern(DateTimeType.DATE_PATTERN);
|
||||
|
||||
public static <T> T fromJson(Gson gson, @Nullable String json, Class<T> classOfT) throws ShellyApiException {
|
||||
@Nullable
|
||||
@ -256,7 +258,7 @@ public class ShellyUtils {
|
||||
public static DateTimeType getTimestamp(String zone, long timestamp) {
|
||||
try {
|
||||
if (timestamp == 0) {
|
||||
return getTimestamp();
|
||||
throw new IllegalArgumentException("Timestamp value 0 is invalid");
|
||||
}
|
||||
ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
|
||||
ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
|
||||
@ -268,6 +270,18 @@ public class ShellyUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTimestamp(DateTimeType dt) {
|
||||
return dt.getZonedDateTime().toString().replace('T', ' ').replace('-', '/');
|
||||
}
|
||||
|
||||
public static String convertTimestamp(long ts) {
|
||||
if (ts == 0) {
|
||||
return "";
|
||||
}
|
||||
String time = DATE_TIME.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(ts), ZoneId.systemDefault()));
|
||||
return time.replace('T', ' ').replace('-', '/');
|
||||
}
|
||||
|
||||
public static Integer getLightIdFromGroup(String groupName) {
|
||||
if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
|
||||
return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1;
|
||||
|
@ -34,7 +34,7 @@ message.status.unknown.initializing = Initializing or device in sleep mode.
|
||||
message.statusupdate.failed = Unable to update status
|
||||
message.event.triggered = Event triggered: {0}
|
||||
message.coap.init.failed = Unable to start CoIoT: {0}
|
||||
message.discovery.disabled = Device is marked as non-discoverable -> skip
|
||||
message.discovery.disabled = Device is marked as non-discoverable, will be skipped
|
||||
message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
|
||||
message.discovery.failed = Device discovery of device with IP address {0} failed: {1}
|
||||
message.roller.favmissing = Roller position favorites are not supported by installed firmware or not configured in the Shelly App
|
||||
@ -42,6 +42,8 @@ message.roller.favmissing = Roller position favorites are not supported by insta
|
||||
# Device
|
||||
channel-type.shelly.deviceName.label = Device Name
|
||||
channel-type.shelly.deviceName.description = Symbolic Device Name as configured in the Shelly App.
|
||||
channel-type.shelly.sensorSleepTime.label = Sensor Sleep Time
|
||||
channel-type.shelly.sensorSleepTime.description = The sensor will not send notifications and will not perform actions until the specified time expires. (0=disable)
|
||||
|
||||
# Relay, external sensors
|
||||
channel-type.shelly.outputName.label = Output Name
|
||||
@ -54,6 +56,8 @@ channel-type.shelly.temperature3.label = Temperature 3
|
||||
channel-type.shelly.temperature3.description = Temperature of external Sensor #3
|
||||
channel-type.shelly.humidity.label = Humidity
|
||||
channel-type.shelly.humidity.description = Relative humidity (0..100%)
|
||||
channel-type.shelly.motionActive.label = Motion Active
|
||||
channel-type.shelly.motionActive.description = Indicates if motion sensor is active or within sleep time
|
||||
channel-type.shelly.motionTimestamp.label = Last Motion
|
||||
channel-type.shelly.motionTimestamp.description = Timestamp when last motion was detected.
|
||||
|
||||
@ -64,7 +68,6 @@ channel-type.shelly.rollerState.description = State of the roller (open/closed/s
|
||||
|
||||
# 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.ledPowerDisable.description = ON: The power status LED will be deactivated
|
||||
channel-type.shelly.ledStatusDisable.label = Disable Status LED
|
||||
channel-type.shelly.ledStatusDisable.description = ON: The WiFi status LED will be decativated
|
||||
|
||||
channel-type.shelly.ledStatusDisable.description = ON: The WiFi status LED will be deactivated
|
||||
|
@ -600,6 +600,8 @@ channel-type.shelly.sensorVibration.label = Vibration
|
||||
channel-type.shelly.sensorVibration.description = ON: Sensor hat eine Vibration erkannt
|
||||
channel-type.shelly.sensorMotion.label = Bewegung
|
||||
channel-type.shelly.sensorMotion.description = ON: Es wurde eine Bewegung erkannt
|
||||
channel-type.shelly.motionActive.label = Bewegungssensor aktiv
|
||||
channel-type.shelly.motionActive.description = Zeigt an, ob die Bewegungserkennung aktiv oder pausiert ist
|
||||
channel-type.shelly.motionTimestamp.label = Letzte Bewegung
|
||||
channel-type.shelly.motionTimestamp.description = Datum/Uhrzeit, wann die letzte Bewegung erkannt wurde.
|
||||
channel-type.shelly.sensorValve.label = Ventil
|
||||
@ -689,5 +691,7 @@ 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
|
||||
channel-type.shelly.sensorSleepTime.label = Sensor Standby Timer
|
||||
channel-type.shelly.sensorSleepTime.description = Das Gerät sendet kein Ereignis solange die Zeitspanne nicht abgelaufen ist.
|
||||
|
||||
|
||||
|
@ -293,6 +293,13 @@
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="motionActive" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>@text/channel-type.shelly.motionActive.label</label>
|
||||
<description>channel-type.shelly.motionActive.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorMotion">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Motion</label>
|
||||
@ -352,6 +359,12 @@
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorSleepTime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>@text/channel-type.shelly.sensorSleepTime.label</label>
|
||||
<description>@text/channel-type.shelly.sensorSleepTime.description</description>
|
||||
<state readOnly="false" min="0" max="86400" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="senseKey">
|
||||
<item-type>String</item-type>
|
||||
|
Loading…
Reference in New Issue
Block a user