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][WIP] V2.5-2 (fixes & enhancements for the 2.5 release version) #6592

Closed
wants to merge 19 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0bdaaa7
Fix: input channel for relay; deregister from onEvent on dispose(); post
markus7017 Dec 11, 2019
67c3099
New channel button for relay; event channel removed except for roller;
markus7017 Dec 12, 2019
f4c3f1d
call listenerManager.unregister() from Thing dispose()
markus7017 Dec 12, 2019
8814b55
event decoding fixed; avoid NPE on event and uninitialized thing
markus7017 Dec 12, 2019
84a64f3
button channel added for Dimmer
markus7017 Dec 13, 2019
c635467
Renamed channel signal to wifiSignal (prevent old JSONDB data) and
markus7017 Dec 13, 2019
8c74fde
deregister disposed thing handler from deviceListeners from Handler
markus7017 Dec 13, 2019
31a5ba7
Reflect review comments, README updated
markus7017 Dec 13, 2019
12b0c3d
Missing channels input and button added for Dimmer and RGBW2 in color
markus7017 Dec 15, 2019
ea92ad7
Fix: update input for dimmer; meter added to Plug; Fix LOW_BATTERY alarm
markus7017 Dec 21, 2019
c2fa475
German translation added (WIP)
markus7017 Dec 22, 2019
1e3e796
Merge conflict resolved
markus7017 Dec 22, 2019
6b35193
Remove NonNullByDefault warnings for Gson mapper: ShellyApiJson renamed
markus7017 Dec 22, 2019
e64b602
Merge remote-tracking branch 'origin/shelly-25final-2' into shelly-25…
markus7017 Dec 23, 2019
01cbcfe
New thing config option brightnessAutoOn, weakSignal; implements FTRs
markus7017 Dec 27, 2019
0785983
weakSignal and brightnessAutoOn added to thing config
markus7017 Dec 27, 2019
1ce9179
Add serviceName to properties when thing was added manually
markus7017 Dec 28, 2019
9d9001a
#6706: Post update on input and output channels directly from onEvent()
markus7017 Dec 30, 2019
31e7d61
Shelly EM #18: Compensate missing timestamp/lastUpdate with system
markus7017 Dec 30, 2019
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
Prev Previous commit
Next Next commit
New thing config option brightnessAutoOn, weakSignal; implements FTRs
#6560, #6608; update lastUpdated channel only when another value
changed; thing status messages translated to German
(COMM_ERROR/CONF_ERRORs)

Signed-off-by: Markus Michels <markus7017@gmail.com>
  • Loading branch information
markus7017 committed Dec 27, 2019

Unverified

The signing certificate or its chain could not be verified.
commit 01cbcfea3eab2f7eb1f2794e59d1ee16a98501f4
Original file line number Diff line number Diff line change
@@ -300,7 +300,6 @@ public class ShellyBindingConstants {

// Minimum signal strength for basic connectivity. Packet delivery may be unreliable.
public static final int HEALTH_CHECK_INTERVAL_SEC = 300;
public static final int SIGNAL_ALARM_MIN_RSSI = -80;

public static final int DIM_STEPSIZE = 5;

Original file line number Diff line number Diff line change
@@ -137,11 +137,14 @@ public void setRelayTurn(Integer relayIndex, String turnMode) throws IOException
+ "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase());
}

public void setDimmerBrightness(Integer relayIndex, Integer brightness) throws IOException {
// request(SHELLY_URL_CONTROL_LIGHT + "/" + relayIndex.toString() + "?" + SHELLY_LIGHT_TURN + "=" +
// SHELLY_API_ON
// + "&brightness=" + brightness.toString());
request(SHELLY_URL_CONTROL_LIGHT + "/" + relayIndex.toString() + "?" + "&brightness=" + brightness.toString());
public void setDimmerBrightness(Integer relayIndex, Integer brightness, boolean autoOn) throws IOException {
if (autoOn) {
request(SHELLY_URL_CONTROL_LIGHT + "/" + relayIndex.toString() + "?" + SHELLY_LIGHT_TURN + "="
+ SHELLY_API_ON + "&brightness=" + brightness.toString());
} else {
request(SHELLY_URL_CONTROL_LIGHT + "/" + relayIndex.toString() + "?" + "&brightness="
+ brightness.toString());
}
}

@Nullable
Original file line number Diff line number Diff line change
@@ -22,12 +22,14 @@
@NonNullByDefault
public class ShellyThingConfiguration {
public String deviceIp = ""; // ip address of thedevice
public int updateInterval = 60; // schedule interval for the update job
public float lowBattery = 20; // threshold for battery value

public String userId = ""; // userid for http basic auth
public String password = ""; // password for http basic auth

public int updateInterval = 60; // schedule interval for the update job
public int lowBattery = 20; // threshold for battery value
public int weakSignal = -80; // threshold for weak wifi signal
public boolean brightnessAutoOn = true; // true: turn on device if brightness > 0 is set

public boolean eventsButton = false; // true: register for Relay btn_xxx events
public boolean eventsSwitch = true; // true: register for device out_xxx events
public boolean eventsPush = true; // true: register for short/long push events
Original file line number Diff line number Diff line change
@@ -118,7 +118,6 @@ public ShellyBaseHandler(Thing thing, ShellyBindingConfiguration bindingConfig,
*/
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
// start background initialization:
scheduler.schedule(() -> {
boolean start = true;
@@ -177,12 +176,17 @@ private boolean initializeThing() throws IOException {
Map<String, String> properties = getThing().getProperties();
logger.debug("{}: Start initializing, ip address {}, CoIoT: {}", getThing().getLabel(), config.deviceIp,
config.eventsCoIoT);
if (config.deviceIp.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/config-status.error.missing-device-ip");
return false;
}

// Initialize API access, exceptions will be catched by initialize()
api = new ShellyHttpApi(config);
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(this.getThing().getThingTypeUID().getId());
thingName = properties.get(PROPERTY_SERVICE_NAME) != null ? properties.get(PROPERTY_SERVICE_NAME).toLowerCase()
: "";
api = new ShellyHttpApi(config);
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(this.getThing().getThingTypeUID().getId());
thingName = (!thingName.isEmpty() ? thingName : tmpPrf.hostname).toLowerCase();
Validate.isTrue(!thingName.isEmpty(), "initializeThing(): thingName must not be empty!");

@@ -233,9 +237,11 @@ private boolean initializeThing() throws IOException {
String thingType = getThing().getThingTypeUID().getId();
String reqMode = thingType.contains("-") ? StringUtils.substringAfter(thingType, "-") : "";
if (!reqMode.isEmpty() && !tmpPrf.mode.equals(reqMode)) {
logger.info(
"{}: Thing is in mode {}, expecting mode {} - going offline. Re-run discovery to changed device mode.",
thingName, profile.mode, reqMode);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Thing is in mode " + profile.mode + ", required is " + reqMode
+ " - going offline. Re-run discovery to find the thing for the requested mode.");
"@text/offline.conf-error-wrong-mode");
return false;
}

@@ -358,19 +364,25 @@ protected void updateStatus() {
}
} catch (IOException e) {
// http call failed: go offline except for battery devices, which might be in
// sleep mode
// once the next update is successful the device goes back online
// sleep mode. Once the next update is successful the device goes back online
String status = "";
if (e.getMessage().contains("Timeout")) {
logger.debug("Device {} is not reachable, update canceled ({} skips, {} scheduledUpdates)!", thingName,
logger.debug("{}: Device is not reachable, update canceled ({} skips, {} scheduledUpdates)!", thingName,
skipCount, scheduledUpdates);
status = "@text/offline.status-error-timeout";
} else if (e.getMessage().contains(APIERR_HTTP_401_UNAUTHORIZED)) {
logger.debug("{}: Unable to access device, check credentials!", thingName);
status = "@text/offline.conf-error-access-denied";
} else if (e.getMessage().contains("Not calibrated!")) {
logger.debug("{}: Roller is not calibrated! Use the Shelly App or Web UI to run calibration.",
thingName);
status = "@text/offline.conf-error-not-calibrated";
} else {
logger.debug("{}: Unable to update status: {} ({})", thingName, e.getMessage(), e.getClass());
status = "@text/offline.status-error-unexpected-api-result";
}
if (e.getMessage().contains(APIERR_HTTP_401_UNAUTHORIZED) || (profile != null && !profile.isSensor)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, status);
}
} catch (NullPointerException e) {
logger.warn("{}: Unable to update status: {} ({})", thingName, e.getMessage(), e.getClass());
@@ -416,7 +428,7 @@ private void fillDeviceStatus(ShellySettingsStatus status, boolean updated) {
}
lastUptime = uptime;

if ((rssi < SIGNAL_ALARM_MIN_RSSI) && ((lastAlarmTs == 0))) {
if ((rssi < config.weakSignal) && ((lastAlarmTs == 0))) {
alarm = ALARM_TYPE_WEAKSIGNAL;
}
if (getBool(status.overtemperature)) {
@@ -468,17 +480,14 @@ public void postAlarm(String alarm, boolean force) {
@SuppressWarnings({ "null" })
@Override
public boolean onEvent(String deviceName, String deviceIndex, String type, Map<String, String> parameters) {
if (profile == null) {
logger.debug("OnEvent: Thing not yet initialized, skip event");
}
if (thingName.equalsIgnoreCase(deviceName) || config.deviceIp.equals(deviceName)) {
logger.debug("{}: Event received: class={}, index={}, parameters={}", deviceName, type, deviceIndex,
parameters.toString());
boolean hasBattery = profile != null && profile.hasBattery ? true : false;
if (profile == null) {
logger.debug("{}: Device is not yet initialized, event triggers initialization", deviceName);
requestUpdates(1, true);
} else {

String group = "";
Integer rindex = !deviceIndex.isEmpty() ? Integer.parseInt(deviceIndex) + 1 : -1;
if (type.equals(EVENT_TYPE_RELAY)) {
@@ -577,7 +586,7 @@ private boolean authorizationFailed(String response) {
logger.warn("{}: Device {} reported 'Access Denied' (user id/password mismatch)", getThing().getLabel(),
config.deviceIp);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Access denied, configure user id and password");
"@text/offline.conf-error-access-denied");
changeThingType(THING_TYPE_SHELLYPROTECTED_STR, "");
return true;
}
Original file line number Diff line number Diff line change
@@ -86,8 +86,10 @@ public static boolean updateMeters(ShellyBaseHandler th, ShellySettingsStatus st
updated |= th.updateChannel(groupName, CHANNEL_METER_LASTMIN3,
toQuantityType(getDouble(meter.counters[2]), DIGITS_WATT, SmartHomeUnits.WATT));
}
th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp)));
if (updated) {
th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp)));
}
m++;
}
}
@@ -108,8 +110,10 @@ public static boolean updateMeters(ShellyBaseHandler th, ShellySettingsStatus st
toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, SmartHomeUnits.WATT));
updated |= th.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
toQuantityType(getDouble(emeter.voltage), DIGITS_VOLT, SmartHomeUnits.VOLT));
th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), getLong(emeter.timestamp)));
if (updated) {
th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), getLong(emeter.timestamp)));
}
m++;
}
}
@@ -152,8 +156,10 @@ public static boolean updateMeters(ShellyBaseHandler th, ShellySettingsStatus st
updated |= th.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
toQuantityType(getDouble(totalWatts), DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));

updated |= th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), timestamp));
if (updated) {
th.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), timestamp));
}
}
}
return updated;
@@ -221,7 +227,9 @@ public static boolean updateSensors(ShellyBaseHandler th, ShellySettingsStatus s
updated |= th.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CHARGER, getOnOff(sdata.charger));
}

th.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_LAST_UPDATE, getTimestamp());
if (updated) {
th.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_LAST_UPDATE, getTimestamp());
}
}
}
return updated;
Original file line number Diff line number Diff line change
@@ -208,7 +208,7 @@ public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throw
}

// send changed colors to the device
sendColors(profile, lightId, oldCol, col);
sendColors(profile, lightId, oldCol, col, config.brightnessAutoOn);
}

return true;
@@ -439,7 +439,7 @@ private void setFullColor(String colorGroup, ShellyColorUtils col) {

@SuppressWarnings("null")
private void sendColors(@Nullable ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils oldCol,
ShellyColorUtils newCol) throws IOException {
ShellyColorUtils newCol, boolean autoOn) throws IOException {
Validate.notNull(profile);

// boolean updated = false;
@@ -450,7 +450,9 @@ private void sendColors(@Nullable ShellyDeviceProfile profile, Integer lightId,
"{}: New color settings for channel {}: RGB {}/{}/{}, white={}, gain={}, brightness={}, color-temp={}",
thingName, channelId, newCol.red, newCol.green, newCol.blue, newCol.white, newCol.gain,
newCol.brightness, newCol.temp);
parms.put(SHELLY_LIGHT_TURN, profile.inColor || newCol.brightness > 0 ? SHELLY_API_ON : SHELLY_API_OFF);
if (autoOn) {
parms.put(SHELLY_LIGHT_TURN, profile.inColor || newCol.brightness > 0 ? SHELLY_API_ON : SHELLY_API_OFF);
}
if (profile.inColor) {
if (!oldCol.red.equals(newCol.red) || !oldCol.green.equals(newCol.green) || !oldCol.blue.equals(newCol.blue)
|| !oldCol.white.equals(newCol.white)) {
Original file line number Diff line number Diff line change
@@ -199,7 +199,7 @@ private void handleBrightness(Command command, Integer index) throws IOException

validateRange("brightness", value, 0, 100);
logger.debug("{}: Setting dimmer brightness to {}", thingName, value);
api.setDimmerBrightness(index, value);
api.setDimmerBrightness(index, value, config.brightnessAutoOn);

}

Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
</parameter-group>

<parameter name="userId" type="text" required="false">
<label>UserID</label>
<label>User</label>
<description>User ID for HTTP API access.</description>
</parameter>
<parameter name="password" type="text" required="false">
@@ -23,6 +23,17 @@
<description>IP-Address of the Shelly device.</description>
<context>network-address</context>
</parameter>
<parameter name="weakSignal" type="integer" required="false">
<label>Weak Signal Threshold</label>
<description>An alarm is triggered if a device reports WiFi signal strength below this threshold. Default: -80 dBm</description>
<default>-80</default>
<unitLabel>%</unitLabel>
</parameter>
<parameter name="brightnessAutoOn" type="boolean" required="false">
<label>Brightness Auto-ON</label>
<description>true: Turn device ON if brightness > 0 is set; false: don't touch power status when brightness is set.</description>
<default>true</default>
</parameter>
<parameter name="eventsButton" type="boolean" required="false">
<label>Enable Button Events</label>
<description>True if the binding should register to get the Buttons Events.</description>
@@ -72,8 +83,8 @@
<context>network-address</context>
</parameter>
<parameter name="lowBattery" type="integer" required="false">
<label>Low Battery</label>
<description>Threshold for the battery level, alert will be signed when battery level is below..</description>
<label>Low Battery Threshold</label>
<description>Threshold for the battery level, alert will be signed when battery level is below. Default: 20%</description>
<default>20</default>
<unitLabel>%</unitLabel>
</parameter>
@@ -89,11 +100,19 @@
<advanced>true</advanced>
<default>true</default>
</parameter>
<parameter name="weakSignal" type="integer" required="false">
<label>Weak Signal Threshold</label>
<description>An alarm is triggered if a device reports WiFi signal strength below this threshold. Default: -80 dBm</description>
<advanced>true</advanced>
<default>80</default>
<unitLabel>%</unitLabel>
</parameter>
<parameter name="updateInterval" type="integer" required="true">
<label>Update Interval</label>
<description>Interval in seconds to query an update from the device.</description>
<default>3600</default>
<unitLabel>sec</unitLabel>
<advanced>true</advanced>
</parameter>
</config-description>

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Config status messages
config-status.error.missing-device-ip=IP address of the Shelly device is missing.

# Thing status descriptions
offline.conf-error-wrong-mode=Device is no longer in the configured device mode. Delete the thing and re-discover the device.
offline.conf-error-access-denied=Access denied, check user id and password.
offline.conf-error-not-calibrated=Roller is not calibrated, run calibration in Shelly App.
offline.status-error-timeout=Device is not reachable (API timeout).
offline.status-error-unexpected-api-result=An unexpected API response. Please verify the logfile to get more detailed information.
Loading