[shelly] New channel group ncurrent for 3EM (#16336)

* Add channels for emeter_n (neutral current-based measurements) - polled
status and CoAP update (ncurrent only, no other values)

Signed-off-by: Markus Michels <markus7017@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Markus Michels 2024-02-17 15:36:20 +01:00 committed by Ciprian Pascu
parent b7aaf621f5
commit a7eb60c15d
9 changed files with 150 additions and 11 deletions

View File

@ -492,7 +492,7 @@ In this case the is no real measurement based on power consumption, but the Shel
| | input | Switch | yes | ON: Input/Button is powered, see General Notes on Channels |
| | button | Trigger | yes | Event trigger, see section Button Events |
| meter | currentWatts | Number | yes | Current power consumption in Watts |
| | lastPower1 | Number | yes | Energy consumption for a round minute, 1 minute ago |
| | lastPower1 | Number | yes | The average power for the previous minute |
| | totalKWH | Number | yes | Total energy consumption in kwh since the device powered up (resets on restart) |
| | | | | |
| | lastUpdate | DateTime | yes | Timestamp of the last measurement |
@ -573,6 +573,15 @@ The Thing id is derived from the service name, so that's the reason why the Thin
| | powerFactor | Number | yes | Power Factor in percent |
| | resetTotals | Switch | yes | ON: Resets total values for the power meter |
| | lastUpdate | DateTime | yes | Timestamp of the last measurement |
| nmeter | ncurrent | Number | yes | Current current based on N clamp (requires calibration) |
| | ixsum | Number | yes | Measured current over all phases |
| | nmismatch | Switch | yes | ON: abs(ncurrent-ixsum) is greater than nmTreshhold |
| | nmTreshhold | Number | yes | Treshhod (delta) before nMismatch goes ON |
_Note:
You should calibrate the device if you want to use "neutral current" measurements.
Check the Shelly documentation for details._
### Shelly 2 - relay mode (thing-type: shelly2-relay)

View File

@ -178,6 +178,11 @@ public class ShellyBindingConstants {
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_NMETER = "nmeter";
public static final String CHANNEL_NMETER_CURRENT = "ncurrent";
public static final String CHANNEL_NMETER_IXSUM = "ixsum";
public static final String CHANNEL_NMETER_MISMATCH = "nmismatch";
public static final String CHANNEL_NMETER_MTRESHHOLD = "nmTreshhold";
public static final String CHANNEL_GROUP_SENSOR = "sensors";
public static final String CHANNEL_SENSOR_TEMP = "temperature";

View File

@ -553,6 +553,23 @@ public class Shelly1ApiJsonDTO {
public Double current; // 3EM
}
public static class ShellyEMNCurrentSettings {
// "emeter_n":{ "range_extender":1, "mismatch_threshold":0.00}
@SerializedName("range_extender")
public Integer rangeExtender;
@SerializedName("mismatch_threshold")
public Double mismatchThreshold;
}
public static class ShellyEMNCurrentStatus {
// "emeter_n":{"current":2.28,"ixsum":2.29,"mismatch":false,"is_valid":true}
public Double current;
public Double ixsum;
public Boolean mismatch;
@SerializedName("is_valid")
public Boolean isValid;
}
public static class ShellySettingsUpdate {
public String status;
@SerializedName("has_update")
@ -621,6 +638,8 @@ public class Shelly1ApiJsonDTO {
public @Nullable ArrayList<ShellySettingsRoller> rollers;
public @Nullable ArrayList<ShellySettingsRgbwLight> lights;
public @Nullable ArrayList<ShellySettingsEMeter> emeters;
@SerializedName("emeter_n")
public ShellyEMNCurrentSettings neutralCurrent;
public @Nullable ArrayList<ShellyThermnostat> thermostats; // TRV
@SerializedName("ext_switch_enable")
@ -745,6 +764,9 @@ public class Shelly1ApiJsonDTO {
public ArrayList<ShellySettingsMeter> meters;
public ArrayList<ShellySettingsEMeter> emeters;
@SerializedName("emeter_n")
public ShellyEMNCurrentStatus neutralCurrent;
public Double totalCurrent;
public Double totalPower;
public Double totalReturned;

View File

@ -142,9 +142,6 @@ public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly
processed = true;
switch (sen.id) {
case "6": // 3EM: neutralCurrent
break;
case "3106": // L, luminosity, lux, U32, -1
case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown
case "3111": // B, battery, 0-100%, unknown -1
@ -307,7 +304,7 @@ public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly
case "4209": // emeter_1: A, current, 0/120A, -1
case "4309": // emeter_2: A, current, 0/120A, -1
updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT,
toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE));
toQuantityType(getDouble(s.value), DIGITS_AMPERE, Units.AMPERE));
break;
case "4110": // emeter_0: S, powerFactor, 0/1, -1
@ -316,6 +313,11 @@ public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
break;
case "6": // 3EM: emeter_n: nCurrent
updateChannel(updates, CHANNEL_GROUP_NMETER, CHANNEL_NMETER_CURRENT,
toQuantityType(value, DIGITS_AMPERE, Units.AMPERE));
break;
case "5101": // {"I":5101,"T":"S","D":"brightness","R":"0/100","L":1},
case "5102": // {"I":5102,"T":"S","D":"gain","R":"0/100","L":1},
case "5103": // {"I":5103,"T":"S","D":"colorTemp","U":"K","R":"3000/6500","L":1},

View File

@ -362,6 +362,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
if (!valid) {
logger.debug("{}: WARNING: Incompatible device description detected for CoIoT version {}!", thingName,
coiot.getVersion());
return;
}
coiot.completeMissingSensorDefinition(sensorMap); // fix incomplete format
@ -386,8 +387,8 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
// This happens on firmware up/downgrades (version 1.8 brings CoIoT v2 with 4 digit IDs)
int vers = coiot.getVersion();
if (((vers == COIOT_VERSION_1) && (sen.id.length() > 3))
|| ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4))) {
logger.debug("{}: Invalid format for sensor defition detected, id={}", thingName, sen.id);
|| ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4) && !sen.id.equals("6"))) {
logger.debug("{}: Invalid format for sensor definition detected, id={}", thingName, sen.id);
return false;
}

View File

@ -264,6 +264,25 @@ public class ShellyComponents {
m++;
}
} else {
if (status.neutralCurrent != null) {
if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createEMNCurrentChannels(
thingHandler.getThing(), profile.settings.neutralCurrent, status.neutralCurrent));
}
if (getBool(status.neutralCurrent.isValid)) {
String ngroup = CHANNEL_GROUP_NMETER;
updated |= thingHandler.updateChannel(ngroup, CHANNEL_NMETER_CURRENT, toQuantityType(
getDouble(status.neutralCurrent.current), DIGITS_AMPERE, Units.AMPERE));
updated |= thingHandler.updateChannel(ngroup, CHANNEL_NMETER_IXSUM, toQuantityType(
getDouble(status.neutralCurrent.ixsum), DIGITS_AMPERE, Units.AMPERE));
updated |= thingHandler.updateChannel(ngroup, CHANNEL_NMETER_MTRESHHOLD,
toQuantityType(getDouble(profile.settings.neutralCurrent.mismatchThreshold),
DIGITS_AMPERE, Units.AMPERE));
updated |= thingHandler.updateChannel(ngroup, CHANNEL_NMETER_MISMATCH,
getOnOff(status.neutralCurrent.mismatch));
}
}
for (ShellySettingsEMeter emeter : status.emeters) {
if (getBool(emeter.isValid)) {
String groupName = profile.getMeterGroup(m);

View File

@ -31,6 +31,8 @@ import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyEMNCurrentSettings;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyEMNCurrentStatus;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer;
@ -94,6 +96,7 @@ public class ShellyChannelDefinitions {
private static final String CHGR_LIGHTCH = CHANNEL_GROUP_LIGHT_CHANNEL;
private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
private static final String CHGR_METER = CHANNEL_GROUP_METER;
private static final String CHGR_EMN = CHANNEL_GROUP_NMETER;
private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
private static final String CHGR_CONTROL = CHANNEL_GROUP_CONTROL;
private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
@ -203,6 +206,12 @@ public class ShellyChannelDefinitions {
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_RESETTOTAL, "meterResetTotals", ITEMT_SWITCH))
// 3EM: neutral current (emeter_n)
.add(new ShellyChannel(m, CHGR_EMN, CHANNEL_NMETER_CURRENT, "ncurrent", ITEMT_AMP))
.add(new ShellyChannel(m, CHGR_EMN, CHANNEL_NMETER_IXSUM, "ixsum", ITEMT_AMP))
.add(new ShellyChannel(m, CHGR_EMN, CHANNEL_NMETER_MTRESHHOLD, "nmTreshhold", ITEMT_AMP))
.add(new ShellyChannel(m, CHGR_EMN, CHANNEL_NMETER_MISMATCH, "nmismatch", ITEMT_SWITCH))
// Sensors
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEMT_PERCENT))
@ -264,13 +273,13 @@ public class ShellyChannelDefinitions {
String group = substringBefore(channelName, "#");
String channel = substringAfter(channelName, "#");
if (group.contains(CHANNEL_GROUP_METER)) {
if (group.startsWith(CHANNEL_GROUP_METER)) {
group = CHANNEL_GROUP_METER; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
} else if (group.startsWith(CHANNEL_GROUP_RELAY_CONTROL)) {
group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
} else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
} else if (group.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
group = CHANNEL_GROUP_LIGHT_CHANNEL;
} else if (group.contains(CHANNEL_GROUP_STATUS)) {
} else if (group.startsWith(CHANNEL_GROUP_STATUS)) {
group = CHANNEL_GROUP_STATUS; // map status1..n to meter
}
@ -491,6 +500,17 @@ public class ShellyChannelDefinitions {
return newChannels;
}
public static Map<String, Channel> createEMNCurrentChannels(final Thing thing, ShellyEMNCurrentSettings settings,
ShellyEMNCurrentStatus status) {
String group = CHANNEL_GROUP_NMETER;
Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, status.current != null, group, CHANNEL_NMETER_CURRENT);
addChannel(thing, newChannels, status.ixsum != null, group, CHANNEL_NMETER_IXSUM);
addChannel(thing, newChannels, status.mismatch != null, group, CHANNEL_NMETER_MISMATCH);
addChannel(thing, newChannels, settings.mismatchThreshold != null, group, CHANNEL_NMETER_MTRESHHOLD);
return newChannels;
}
public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
final ShellyStatusSensor sdata) {
Map<String, Channel> newChannels = new LinkedHashMap<>();

View File

@ -222,6 +222,8 @@ channel-group-type.shelly.meter2.label = Power Meter 2
channel-group-type.shelly.meter3.label = Power Meter 3
channel-group-type.shelly.meter4.label = Power Meter 4
channel-group-type.shelly.meter.description = Power consumption for the relay
channel-group-type.shelly.nmeter.label = Neutral Current
channel-group-type.shelly.nmeter.description = Based measurements based on the N clamp (has to be calibrated)
channel-group-type.shelly.externalSensors.label = External Sensors
channel-group-type.shelly.externalSensors.description = Temperatures from external sensors connected to the optional Addon
@ -310,6 +312,14 @@ channel-type.shelly.meterCurrent.label = Current
channel-type.shelly.meterCurrent.description = Current in A
channel-type.shelly.meterPowerFactor.label = Power Factor
channel-type.shelly.meterPowerFactor.description = Power Factor in percent for photovoltaic
channel-type.shelly.ncurrent.label = Neutral Current
channel-type.shelly.ncurrent.description = Measured neutral current
channel-type.shelly.ixsum.label = Accumulated Current
channel-type.shelly.ixsum.description = Sum of measured current on all phases
channel-type.shelly.nmismatch.label = N-Current Mismatch
channel-type.shelly.nmismatch.description = ON: Sum of all signed currents of the 4 wires is greater than the configured N Current Treshhold
channel-type.shelly.nmTreshhold.label = Mismatch Treshhold
channel-type.shelly.nmTreshhold.description = Mismatch becomes ON when treshhold is exceeded
channel-type.shelly.timestamp.label = Last Update
channel-type.shelly.timestamp.description = Timestamp of last measurement
channel-type.shelly.ledStatusDisable.label = Disable Status LED

View File

@ -77,6 +77,7 @@
<channel-group id="meter3" typeId="meter">
<label>@text/channel-group-type.shelly.meter3.label</label>
</channel-group>
<channel-group id="nmeter" typeId="nmeter"/>
<channel-group id="relay" typeId="relayChannel"/>
<channel-group id="device" typeId="deviceStatus"/>
</channel-groups>
@ -328,6 +329,11 @@
<description>@text/channel-group-type.shelly.meter.description</description>
</channel-group-type>
<channel-group-type id="nmeter">
<label>@text/channel-group-type.shelly.nmeter.label</label>
<description>@text/channel-group-type.shelly.nmeter.description</description>
</channel-group-type>
<channel-group-type id="externalSensors">
<label>@text/channel-group-type.shelly.externalSensors.label</label>
<description>@text/channel-group-type.shelly.externalSensors.description</description>
@ -604,6 +610,51 @@
<description>@text/channel-type.shelly.meterResetTotals.description</description>
</channel-type>
<channel-type id="ncurrent">
<item-type>Number:ElectricCurrent</item-type>
<label>@text/channel-type.shelly.ncurrent.label</label>
<description>@text/channel-type.shelly.ncurrent.description</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="ixsum">
<item-type>Number:ElectricCurrent</item-type>
<label>@text/channel-type.shelly.ixsum.label</label>
<description>@text/channel-type.shelly.ixsum.description</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="nmTreshhold">
<item-type>Number:ElectricCurrent</item-type>
<label>@text/channel-type.shelly.nmTreshhold.label</label>
<description>@text/channel-type.shelly.nmTreshhold.description</description>
<category>Energy</category>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state readOnly="true" pattern="%.3f %unit%">
</state>
</channel-type>
<channel-type id="nmismatch">
<item-type>Switch</item-type>
<label>@text/channel-type.shelly.nmismatch.label</label>
<description>@text/channel-type.shelly.nmismatch.description</description>
</channel-type>
<channel-type id="timestamp">
<item-type>DateTime</item-type>
<label>@text/channel-type.shelly.timestamp.label</label>