Skip to content

Commit

Permalink
[shelly] Support for Plus Smoke, Plus Plug-S/IT/UK/US, Plus Dimmer US…
Browse files Browse the repository at this point in the history
…, Pro 3EM; fix Gen1 sensor initialization (openhab#14532)

* support for Pro 3EM (WIP)
* Support for Plug-S and Smoke added
* new channel resetTotals for emeters, new channel sensor#mute for Smoke
* Validate Temp reported by CoAP before updating channel, ignore 999
* Support device temp for Pro3, fix emeter.current rouding on 1 instead of
3 deciaml digits, check for reinit before executing channel command,
avoid NPE in Shelly Manaher
* Fix NPE in Shelly Manager with Plus/Pro devices (not having all Gen1
settings initialized)
* Avoid NPE if device time is not set; check thing stopping state to
prevent "handler already disposed"

Signed-off-by: Markus Michels <markus7017@gmail.com>
  • Loading branch information
markus7017 committed May 24, 2023
1 parent df9c270 commit 1f774db
Show file tree
Hide file tree
Showing 25 changed files with 568 additions and 116 deletions.
109 changes: 90 additions & 19 deletions bundles/org.openhab.binding.shelly/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@ public class ShellyBindingConstants {
THING_TYPE_SHELLYPRO2PM_RELAY, //
THING_TYPE_SHELLYPRO2PM_ROLLER, //
THING_TYPE_SHELLYPRO3, //
THING_TYPE_SHELLYPRO3EM, //
THING_TYPE_SHELLYPRO4PM, //
THING_TYPE_SHELLYPLUSI4, //
THING_TYPE_SHELLYPLUSI4DC, //
THING_TYPE_SHELLYPLUSHT, //
THING_TYPE_SHELLYPLUSSMOKE, //
THING_TYPE_SHELLYPLUSPLUGS, //
THING_TYPE_SHELLYPLUSPLUGUS, //
THING_TYPE_SHELLYPROTECTED, //
THING_TYPE_SHELLYUNKNOWN);
Expand Down Expand Up @@ -149,6 +152,7 @@ public class ShellyBindingConstants {
public static final String CHANNEL_EMETER_VOLTAGE = "voltage";
public static final String CHANNEL_EMETER_CURRENT = "current";
public static final String CHANNEL_EMETER_PFACTOR = "powerFactor";
public static final String CHANNEL_EMETER_RESETTOTAL = "resetTotals";

public static final String CHANNEL_GROUP_SENSOR = "sensors";
public static final String CHANNEL_SENSOR_TEMP = "temperature";
Expand All @@ -161,7 +165,9 @@ public class ShellyBindingConstants {
public static final String CHANNEL_SENSOR_TILT = "tilt";
public static final String CHANNEL_SENSOR_FLOOD = "flood";
public static final String CHANNEL_SENSOR_SMOKE = "smoke";
public static final String CHANNEL_SENSOR_MUTE = "mute";
public static final String CHANNEL_SENSOR_STATE = "state";
public static final String CHANNEL_SENSOR_OPEN = "open";
public static final String CHANNEL_SENSOR_VALVE = "valve";
public static final String CHANNEL_SENSOR_SSTATE = "status"; // Shelly Gas
public static final String CHANNEL_SENSOR_MOTION_ACT = "motionActive";
Expand Down Expand Up @@ -293,6 +299,7 @@ public class ShellyBindingConstants {
public static final int DIGITS_WATT = 2;
public static final int DIGITS_KWH = 3;
public static final int DIGITS_VOLT = 1;
public static final int DIGITS_AMPERE = 3;
public static final int DIGITS_TEMP = 1;
public static final int DIGITS_LUX = 0;
public static final int DIGITS_PERCENT = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public interface ShellyApiInterface {

void setRelayTurn(int id, String turnMode) throws ShellyApiException;

ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException;
public void resetMeterTotal(int id) throws ShellyApiException;

public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException;

void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException;

Expand Down Expand Up @@ -91,6 +93,8 @@ public interface ShellyApiInterface {

void startValveBoost(int valveId, int value) throws ShellyApiException;

void muteSmokeAlarm(int smokeId) throws ShellyApiException;

ShellyOtaCheckResult checkForUpdate() throws ShellyApiException;

ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public class ShellyDeviceProfile {
public boolean isButton = false; // true for a Shelly Button 1
public boolean isIX = false; // true for a Shelly IX
public boolean isTRV = false; // true for a Shelly TRV
public boolean isSmoke = false; // true for Shelly Smoke

public int minTemp = 0; // Bulb/Duo: Min Light Temp
public int maxTemp = 0; // Bulb/Duo: Max Light Temp
Expand Down Expand Up @@ -196,7 +197,7 @@ public void initFromThingType(String name) {
}

boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR) || thingType.equals(THING_TYPE_SHELLYPLUSSMOKE_STR);
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR) || thingType.equals(THING_TYPE_SHELLYPLUSHT_STR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public class Shelly1ApiJsonDTO {
//
// API values
//
public static final double SHELLY_API_INVTEMP = -999.0;
public static final double SHELLY_API_INVTEMP = 999.0;

public static final String SHELLY_BTNT_MOMENTARY = "momentary";
public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release";
Expand Down Expand Up @@ -920,6 +920,8 @@ public class ShellyThermTemp {
public ShellyThermTemp tmp;
@SerializedName("boost_minutes")
public Integer boostMinutes;
@SerializedName("window_open")
public Boolean windowOpen;
}

public static class ShellySensorTmp {
Expand Down Expand Up @@ -1103,6 +1105,7 @@ public static class ShellyExtSwitchStatusInput {
public ShellySensorState sensor;
public Boolean smoke; // SHelly Smoke
public Boolean flood; // Shelly Flood: true = flood condition detected
public Boolean mute; // mute enabled/disabled
@SerializedName("rain_sensor")
public Boolean rainSensor; // Shelly Flood: true=in rain mode

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,11 @@ protected void handleInputEvent(CoIotDescrSen sen, String type, int count, int s
// event count
updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(idx), getDecimal(count));
logger.trace(
"{}: Check button[{}] for event trigger (isButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}",
"{}: Check button[{}] for event trigger (inButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}",
thingName, idx, profile.inButtonMode(idx), profile.isButton, profile.hasBattery, serial, count, idx,
lastEventCount[idx]);
if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1))
|| ((lastEventCount[idx] != -1) && (count != lastEventCount[idx])))) {
if (profile.inButtonMode(idx) && ((profile.hasBattery && count == 1)
|| (lastEventCount[idx] != -1 && count != lastEventCount[idx]))) {
if (!profile.isButton || (profile.isButton && (serial != 0x200))) { // skip duplicate on wake-up
logger.debug("{}: Trigger event {}", thingName, inputEvent[idx]);
thingHandler.triggerButton(group, idx, inputEvent[idx]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@ public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen
// Special handling for TRV, because it uses duplicate ID values with different meanings
switch (sen.id) {
case "3101": // current temp
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
if (value != SHELLY_API_INVTEMP) {
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
}
break;
case "3103": // target temp in C. 4/31, 999=unknown
updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SETTEMP,
Expand Down Expand Up @@ -196,7 +198,7 @@ public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen
case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999
case "3301": // sensor_2: T, extTemp, C, -55/125; unknown 999
int idx = getExtTempId(sen.id);
if (idx >= 0) {
if (idx >= 0 && value != SHELLY_API_INVTEMP) {
// H&T, Fllod, DW only have 1 channel, 1/1PM with Addon have up to to 3 sensors
String channel = profile.isSensor ? CHANNEL_SENSOR_TEMP : CHANNEL_SENSOR_TEMP + idx;
// Some devices report values = -999 or 99 during fw update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ public void processResponse(@Nullable Response response) {
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
}
if (thingHandler.isStopping()) {
logger.debug("{}: Thing is shutting down, ignore CoIOT message", thingName);
return;
}

if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
thingHandler.incProtErrors();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,12 @@ public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiExceptio

@Override
public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
ShellyShortLightStatus.class);
callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(), String.class);
}

@Override
public void resetMeterTotal(int id) throws ShellyApiException {
callApi(SHELLY_URL_STATUS_EMETER + "/" + id + "/reset_totals=1", ShellyStatusRelay.class);
}

@Override
Expand Down Expand Up @@ -531,6 +535,11 @@ private void setDimmerEvents() throws ShellyApiException {
}
}

@Override
public void muteSmokeAlarm(int id) throws ShellyApiException {
throw new ShellyApiException("Request not supported");
}

/**
* Set sensor Action URLs
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusEm;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusSmoke;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
Expand Down Expand Up @@ -175,18 +177,24 @@ protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceSta
boolean channelUpdate) throws ShellyApiException {
boolean updated = false;

if (result.temperature0 != null && !getProfile().isSensor) {
status.temperature = status.tmp.tC = result.temperature0.tC;
}

updated |= updateInputStatus(status, result, channelUpdate);
updated |= updateRelayStatus(status, result.switch0, channelUpdate);
updated |= updateRelayStatus(status, result.switch1, channelUpdate);
updated |= updateRelayStatus(status, result.switch2, channelUpdate);
updated |= updateRelayStatus(status, result.switch3, channelUpdate);
updated |= updateEmStatus(status, result.em0, channelUpdate);
updated |= updateRollerStatus(status, result.cover0, channelUpdate);
if (channelUpdate) {
updated |= ShellyComponents.updateMeters(getThing(), status);
}

updateHumidityStatus(sensorData, result.humidity0);
updateTemperatureStatus(sensorData, result.temperature0);
updateSmokeStatus(sensorData, result.smoke0);
updateBatteryStatus(sensorData, result.devicepower0);
updateAddonStatus(status, result);
updated |= ShellyComponents.updateSensors(getThing(), status);
Expand Down Expand Up @@ -263,14 +271,91 @@ private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2

// Update internal structures
status.relays.set(rs.id, rstatus);
status.meters.set(rs.id, sm);
status.emeters.set(rs.id, emeter);
relayStatus.relays.set(rs.id, sr);
relayStatus.meters.set(rs.id, sm);

updateMeter(status, rs.id, sm, emeter, channelUpdate);
return channelUpdate ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rs.id) : false;
}

private void updateMeter(ShellySettingsStatus status, int id, ShellySettingsMeter sm, ShellySettingsEMeter emeter,
boolean channelUpdate) throws ShellyApiException {
status.meters.set(id, sm);
status.emeters.set(id, emeter);
relayStatus.meters.set(id, sm);
}

private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusEm em,
boolean channelUpdate) throws ShellyApiException {
if (em == null) {
return false;
}

boolean updated = false;
ShellySettingsMeter sm = new ShellySettingsMeter();
ShellySettingsEMeter emeter = status.emeters.get(0);
sm.isValid = emeter.isValid = true;
if (em.aActPower != null) {
sm.power = emeter.power = em.aActPower;
}
if (em.aAprtPower != null) {
emeter.totalReturned = em.aAprtPower;
}
if (em.aVoltage != null) {
emeter.voltage = em.aVoltage;
}
if (em.aCurrent != null) {
emeter.current = em.aCurrent;
}
if (em.aPF != null) {
emeter.pf = em.aPF;
}
// Update internal structures
updateMeter(status, 0, sm, emeter, channelUpdate);

sm = new ShellySettingsMeter();
emeter = status.emeters.get(1);
sm.isValid = emeter.isValid = true;
if (em.bActPower != null) {
sm.power = emeter.power = em.bActPower;
}
if (em.bAprtPower != null) {
emeter.totalReturned = em.bAprtPower;
}
if (em.bVoltage != null) {
emeter.voltage = em.bVoltage;
}
if (em.bCurrent != null) {
emeter.current = em.bCurrent;
}
if (em.bPF != null) {
emeter.pf = em.bPF;
}
// Update internal structures
updateMeter(status, 1, sm, emeter, channelUpdate);

sm = new ShellySettingsMeter();
emeter = status.emeters.get(2);
sm.isValid = emeter.isValid = true;
if (em.cActPower != null) {
sm.power = emeter.power = em.cActPower;
}
if (em.cAprtPower != null) {
emeter.totalReturned = em.cAprtPower;
}
if (em.cVoltage != null) {
emeter.voltage = em.cVoltage;
}
if (em.cCurrent != null) {
emeter.current = em.cCurrent;
}
if (em.cPF != null) {
emeter.pf = em.cPF;
}
// Update internal structures
updateMeter(status, 2, sm, emeter, channelUpdate);

return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
}

protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
Shelly2GetConfigResult dc) {
if (dc.cover0 == null) {
Expand Down Expand Up @@ -360,7 +445,9 @@ private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly
if (cs.aenergy != null) {
sm.total = emeter.total = cs.aenergy.total;
sm.counters = cs.aenergy.byMinute;
sm.timestamp = (long) cs.aenergy.minuteTs;
if (cs.aenergy.minuteTs != null) {
sm.timestamp = (long) cs.aenergy.minuteTs;
}
}
if (cs.voltage != null) {
emeter.voltage = cs.voltage;
Expand Down Expand Up @@ -448,6 +535,14 @@ protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shell
sdata.tmp.tF = value.tF;
}

protected void updateSmokeStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusSmoke value) {
if (value == null) {
return;
}
sdata.smoke = getBool(value.alarm);
sdata.mute = getBool(value.mute);
}

protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
if (value == null) {
return;
Expand Down

0 comments on commit 1f774db

Please sign in to comment.