[tasmotaplug] Add energy monitor channels (#16494)

* Add energy monitor channels

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
mlobstein 2024-03-10 15:44:26 -05:00 committed by Ciprian Pascu
parent d647ce3e3c
commit c45f7b4f57
8 changed files with 460 additions and 30 deletions

View File

@ -2,6 +2,7 @@
This binding connects Tasmota flashed smart plugs with 1, 2, 3 or 4 relay channels to openHAB.
The plug must report the status of the relay via the url `http://$PLUG_IP/cm?cmnd=Power` in order for the binding to work.
The energy monitoring channels can be used if the plug reports energy status via the url `http://$PLUG_IP/cm?cmnd=Status%2010`.
See the [Tasmota Supported Devices Repository](https://templates.blakadder.com/plug.html) for a list of supported plugs.
## Supported Things
@ -34,12 +35,22 @@ Channels above the number specified are automatically removed.
Therefore `numChannels` cannot be changed upward after Thing creation.
If the number of channels must be increased, delete the Thing and re-create it with the correct number.
| Channel ID | Item Type | Description |
|------------|-----------|-----------------------------------------|
| power | Switch | Turns the smart plug relay #1 ON or OFF |
| power2 | Switch | Turns the smart plug relay #2 ON or OFF |
| power3 | Switch | Turns the smart plug relay #3 ON or OFF |
| power4 | Switch | Turns the smart plug relay #4 ON or OFF |
| Channel ID | Item Type | Description |
|----------------------|--------------------------|-------------------------------------------------|
| power | Switch | Turns the smart plug relay #1 ON or OFF |
| power2 | Switch | Turns the smart plug relay #2 ON or OFF |
| power3 | Switch | Turns the smart plug relay #3 ON or OFF |
| power4 | Switch | Turns the smart plug relay #4 ON or OFF |
| voltage | Number:ElectricPotential | Channel for output voltage measurement |
| current | Number:ElectricCurrent | Channel for output current measurement |
| watts | Number:Power | Channel for output power measurement |
| volt-ampere | Number:Power | Channel for output VA measurement |
| volt-ampere-reactive | Number:Power | Channel for output VAr measurement |
| power-factor | Number:Dimensionless | Channel for output power factor measurement |
| energy-today | Number:Energy | Channel for output energy today measurement |
| energy-yesterday | Number:Energy | Channel for output energy yesterday measurement |
| energy-total | Number:Energy | Channel for output energy total measurement |
| energy-total-start | DateTime | Channel for output energy total start date/time |
## Full Example
@ -53,7 +64,17 @@ tasmotaplug:plug:plug2 "Plug 2" [ hostName="myplug2", refresh=30 ]
tasmotaplug.items:
```java
Switch Plug1 "Plug 1 Power" { channel="tasmotaplug:plug:plug1:power" }
Switch Plug1 "Plug 1 Power" { channel="tasmotaplug:plug:plug1:power" }
Number:ElectricPotential Voltage { channel="tasmotaplug:plug:plug1:voltage" }
Number:ElectricCurrent Current { channel="tasmotaplug:plug:plug1:current" }
Number:Power Watts { channel="tasmotaplug:plug:plug1:watts" }
Number:Power VoltAmpere { channel="tasmotaplug:plug:plug1:volt-ampere" }
Number:Power VoltAmpereReactive { channel="tasmotaplug:plug:plug1:volt-ampere-reactive" }
Number PowerFactor { channel="tasmotaplug:plug:plug1:power-factor" }
Number:Energy EnergyToday { channel="tasmotaplug:plug:plug1:energy-today" }
Number:Energy EnergyYesterday { channel="tasmotaplug:plug:plug1:energy-yesterday" }
Number:Energy EnergyTotal { channel="tasmotaplug:plug:plug1:energy-total" }
DateTime EnergyTotalStart "Total Start [%s]" { channel="tasmotaplug:plug:plug1:energy-total-start" }
Switch Plug2a "4ch Power 1" { channel="tasmotaplug:plug:plug2:power" }
Switch Plug2b "4ch Power 2" { channel="tasmotaplug:plug:plug2:power2" }
@ -68,6 +89,18 @@ sitemap tasmotaplug label="My Tasmota Plugs" {
Frame label="Plugs" {
Switch item=Plug1
// Energy monitoring
Text item=Voltage
Text item=Current
Text item=Watts
Text item=VoltAmpere
Text item=VoltAmpereReactive
Text item=PowerFactor
Text item=EnergyToday
Text item=EnergyYesterday
Text item=EnergyTotal
Text item=EnergyTotalStart
Switch item=Plug2a
Switch item=Plug2b
Switch item=Plug2c

View File

@ -0,0 +1,182 @@
/**
* Copyright (c) 2010-2024 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.tasmotaplug.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link TasmotaDTO} is responsible for storing
* all of the JSON data objects retrieved from the Tasmota plug
*
* @author Michael Lobstein - Initial contribution
*/
public class TasmotaDTO {
@SerializedName("StatusSNS")
private TasmotaStatus status = new TasmotaStatus();
public TasmotaDTO() {
}
public TasmotaStatus getStatus() {
return status;
}
public void setStatus(TasmotaStatus status) {
this.status = status;
}
public class TasmotaStatus {
@SerializedName("Time")
private String time = "";
@SerializedName("ENERGY")
private Energy energy = new Energy();
public TasmotaStatus() {
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public Energy getEnergy() {
return energy;
}
public void setEnergy(Energy energy) {
this.energy = energy;
}
}
public class Energy {
@SerializedName("Voltage")
private Integer voltage = 0;
@SerializedName("Current")
private Double current = 0d;
@SerializedName("Power")
private Integer activePower = 0;
@SerializedName("ApparentPower")
private Integer apparentPower = 0;
@SerializedName("ReactivePower")
private Integer reactivePower = 0;
@SerializedName("Factor")
private Double powerFactor = 0d;
@SerializedName("Today")
private Double energyToday = 0d;
@SerializedName("Yesterday")
private Double energyYesterday = 0d;
@SerializedName("Total")
private Double energyTotal = 0d;
@SerializedName("TotalStartTime")
private String energyTotalStart = "";
public Energy() {
}
public Integer getVoltage() {
return voltage;
}
public void setVoltage(Integer voltage) {
this.voltage = voltage;
}
public Double getCurrent() {
return current;
}
public void setCurrent(Double current) {
this.current = current;
}
public Integer getActivePower() {
return activePower;
}
public void setActivePower(Integer activePower) {
this.activePower = activePower;
}
public Integer getApparentPower() {
return apparentPower;
}
public void setApparentPower(Integer apparentPower) {
this.apparentPower = apparentPower;
}
public Integer getReactivePower() {
return reactivePower;
}
public void setReactivePower(Integer reactivePower) {
this.reactivePower = reactivePower;
}
public Double getPowerFactor() {
return powerFactor;
}
public void setPowerFactor(Double powerFactor) {
this.powerFactor = powerFactor;
}
public Double getEnergyToday() {
return energyToday;
}
public void setEnergyToday(Double energyToday) {
this.energyToday = energyToday;
}
public Double getEnergyYesterday() {
return energyYesterday;
}
public void setEnergyYesterday(Double energyYesterday) {
this.energyYesterday = energyYesterday;
}
public Double getEnergyTotal() {
return energyTotal;
}
public void setEnergyTotal(Double energyTotal) {
this.energyTotal = energyTotal;
}
public String getEnergyTotalStart() {
return energyTotalStart;
}
public void setEnergyTotalStart(String energyTotalStart) {
this.energyTotalStart = energyTotalStart;
}
}
}

View File

@ -34,6 +34,8 @@ public class TasmotaPlugBindingConstants {
public static final String CMD_URI = "/cm?cmnd=%s";
public static final String CMD_URI_AUTH = "/cm?user=%s&password=%s&cmnd=%s";
public static final String STATUS = "Status";
public static final String STATUS_CMD = "10";
public static final String ON = "ON";
public static final String OFF = "OFF";
public static final String BLANK = "";
@ -46,7 +48,19 @@ public class TasmotaPlugBindingConstants {
public static final String POWER2 = "power2";
public static final String POWER3 = "power3";
public static final String POWER4 = "power4";
public static final String VOLTAGE = "voltage";
public static final String CURRENT = "current";
public static final String WATTS = "watts";
public static final String VOLT_AMPERE = "volt-ampere";
public static final String VOLT_AMPERE_REACTIVE = "volt-ampere-reactive";
public static final String POWER_FACTOR = "power-factor";
public static final String ENERGY_TODAY = "energy-today";
public static final String ENERGY_YESTERDAY = "energy-yesterday";
public static final String ENERGY_TOTAL = "energy-total";
public static final String ENERGY_TOTAL_START = "energy-total-start";
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLUG);
public static final List<String> SUPPORTED_CHANNEL_IDS = List.of(POWER, POWER2, POWER3, POWER4);
public static final List<String> CONTROL_CHANNEL_IDS = List.of(POWER, POWER2, POWER3, POWER4);
public static final List<String> ENERGY_CHANNEL_IDS = List.of(VOLTAGE, CURRENT, WATTS, VOLT_AMPERE,
VOLT_AMPERE_REACTIVE, POWER_FACTOR, ENERGY_TODAY, ENERGY_YESTERDAY, ENERGY_TOTAL, ENERGY_TOTAL_START);
}

View File

@ -12,9 +12,7 @@
*/
package org.openhab.binding.tasmotaplug.internal;
import static org.openhab.binding.tasmotaplug.internal.TasmotaPlugBindingConstants.THING_TYPE_PLUG;
import java.util.Set;
import static org.openhab.binding.tasmotaplug.internal.TasmotaPlugBindingConstants.SUPPORTED_THING_TYPES_UIDS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -39,8 +37,6 @@ import org.osgi.service.component.annotations.Reference;
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tasmotaplug")
public class TasmotaPlugHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLUG);
private final HttpClient httpClient;
@Activate
@ -55,7 +51,7 @@ public class TasmotaPlugHandlerFactory extends BaseThingHandlerFactory {
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (THING_TYPE_PLUG.equals(thing.getThingTypeUID())) {
if (SUPPORTED_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new TasmotaPlugHandler(thing, httpClient);
}

View File

@ -27,8 +27,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.openhab.binding.tasmotaplug.dto.TasmotaDTO;
import org.openhab.binding.tasmotaplug.dto.TasmotaDTO.Energy;
import org.openhab.binding.tasmotaplug.internal.TasmotaPlugConfiguration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@ -39,6 +45,10 @@ import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* The {@link TasmotaPlugHandler} is responsible for handling commands, which are
* sent to one of the channels.
@ -52,6 +62,7 @@ public class TasmotaPlugHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(TasmotaPlugHandler.class);
private final HttpClient httpClient;
private final Gson gson;
private @Nullable ScheduledFuture<?> refreshJob;
@ -65,6 +76,7 @@ public class TasmotaPlugHandler extends BaseThingHandler {
public TasmotaPlugHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.httpClient = httpClient;
gson = new GsonBuilder().serializeNulls().create();
}
@Override
@ -93,10 +105,10 @@ public class TasmotaPlugHandler extends BaseThingHandler {
plugHost = "http://" + hostName;
// remove the channels we are not using
if (this.numChannels < SUPPORTED_CHANNEL_IDS.size()) {
if (this.numChannels < CONTROL_CHANNEL_IDS.size()) {
List<Channel> channels = new ArrayList<>(this.getThing().getChannels());
List<Integer> channelsToRemove = IntStream.range(this.numChannels + 1, SUPPORTED_CHANNEL_IDS.size() + 1)
List<Integer> channelsToRemove = IntStream.range(this.numChannels + 1, CONTROL_CHANNEL_IDS.size() + 1)
.boxed().toList();
channelsToRemove.forEach(channel -> {
@ -124,12 +136,12 @@ public class TasmotaPlugHandler extends BaseThingHandler {
public void handleCommand(ChannelUID channelUID, Command command) {
if (channelUID.getId().contains(POWER)) {
if (command instanceof OnOffType) {
getCommand(channelUID.getId(), command);
getCommand(channelUID.getId(), command.toString());
} else {
updateChannelState(channelUID.getId());
}
} else {
logger.warn("Unsupported command: {}", command.toString());
logger.debug("Unsupported command: {}", command.toString());
}
}
@ -141,9 +153,13 @@ public class TasmotaPlugHandler extends BaseThingHandler {
if (refreshJob == null || refreshJob.isCancelled()) {
refreshJob = null;
this.refreshJob = scheduler.scheduleWithFixedDelay(() -> {
SUPPORTED_CHANNEL_IDS.stream().limit(numChannels).forEach(channelId -> {
CONTROL_CHANNEL_IDS.stream().limit(numChannels).forEach(channelId -> {
updateChannelState(channelId);
});
if (ENERGY_CHANNEL_IDS.stream().anyMatch(energyCh -> isLinked(energyCh))) {
updateEnergyChannels();
}
}, 0, refreshPeriod, TimeUnit.SECONDS);
}
}
@ -157,18 +173,76 @@ public class TasmotaPlugHandler extends BaseThingHandler {
}
}
private String getCommand(String channelId, @Nullable Command command) {
final String plugChannel = channelId.substring(0, 1).toUpperCase() + channelId.substring(1);
String url;
if (isAuth) {
url = String.format(CMD_URI_AUTH, user, pass, plugChannel);
private void updateEnergyChannels() {
final Energy energyDto;
final String json = getCommand(STATUS, STATUS_CMD);
if (!json.isBlank()) {
try {
final TasmotaDTO dto = gson.fromJson(json, TasmotaDTO.class);
if (dto != null) {
energyDto = dto.getStatus().getEnergy();
} else {
logger.debug("TasmotaDTO was null for JSON: '{}'", json);
return;
}
} catch (JsonSyntaxException e) {
logger.debug("Error parsing Tasmota status JSON: '{}' Exception: {}", json, e.getMessage());
return;
}
} else {
url = String.format(CMD_URI, plugChannel);
return;
}
if (command != null) {
url += "%20" + command;
if (isLinked(VOLTAGE)) {
updateState(VOLTAGE, new QuantityType<>(energyDto.getVoltage(), Units.VOLT));
}
if (isLinked(CURRENT)) {
updateState(CURRENT, new QuantityType<>(energyDto.getCurrent(), Units.AMPERE));
}
if (isLinked(WATTS)) {
updateState(WATTS, new QuantityType<>(energyDto.getActivePower(), Units.WATT));
}
if (isLinked(VOLT_AMPERE)) {
updateState(VOLT_AMPERE, new QuantityType<>(energyDto.getApparentPower(), Units.VOLT_AMPERE));
}
if (isLinked(VOLT_AMPERE_REACTIVE)) {
updateState(VOLT_AMPERE_REACTIVE, new QuantityType<>(energyDto.getReactivePower(), Units.VAR));
}
if (isLinked(POWER_FACTOR)) {
updateState(POWER_FACTOR, new DecimalType(energyDto.getPowerFactor()));
}
if (isLinked(ENERGY_TODAY)) {
updateState(ENERGY_TODAY, new QuantityType<>(energyDto.getEnergyToday(), Units.KILOWATT_HOUR));
}
if (isLinked(ENERGY_YESTERDAY)) {
updateState(ENERGY_YESTERDAY, new QuantityType<>(energyDto.getEnergyYesterday(), Units.KILOWATT_HOUR));
}
if (isLinked(ENERGY_TOTAL)) {
updateState(ENERGY_TOTAL, new QuantityType<>(energyDto.getEnergyTotal(), Units.KILOWATT_HOUR));
}
if (isLinked(ENERGY_TOTAL_START) && !energyDto.getEnergyTotalStart().isBlank()) {
updateState(ENERGY_TOTAL_START, new DateTimeType(energyDto.getEnergyTotalStart()));
}
}
private String getCommand(String commmand, @Nullable String commandArg) {
final String tasmotaCommand;
if (STATUS.equals(commmand)) {
tasmotaCommand = commmand;
} else {
// uppercase the first character of the channel id
tasmotaCommand = commmand.substring(0, 1).toUpperCase() + commmand.substring(1);
}
String url;
if (isAuth) {
url = String.format(CMD_URI_AUTH, user, pass, tasmotaCommand);
} else {
url = String.format(CMD_URI, tasmotaCommand);
}
if (commandArg != null) {
url += "%20" + commandArg;
}
try {

View File

@ -7,6 +7,12 @@ addon.tasmotaplug.description = Controls Wi-Fi Smart Plugs flashed with Tasmota
thing-type.tasmotaplug.plug.label = Plug
thing-type.tasmotaplug.plug.description = Tasmota Smart Plug
thing-type.tasmotaplug.plug.channel.energy-today.label = Energy Today
thing-type.tasmotaplug.plug.channel.energy-today.description = Energy consumed today (kWh)
thing-type.tasmotaplug.plug.channel.energy-total.label = Energy Total
thing-type.tasmotaplug.plug.channel.energy-total.description = Total energy consumed (kWh)
thing-type.tasmotaplug.plug.channel.energy-yesterday.label = Energy Yesterday
thing-type.tasmotaplug.plug.channel.energy-yesterday.description = Energy consumed yesterday (kWh)
thing-type.tasmotaplug.plug.channel.power.label = Power
thing-type.tasmotaplug.plug.channel.power.description = Controls the smart plug relay for the 1st channel
thing-type.tasmotaplug.plug.channel.power2.label = Power 2
@ -15,6 +21,8 @@ thing-type.tasmotaplug.plug.channel.power3.label = Power 3
thing-type.tasmotaplug.plug.channel.power3.description = Controls the smart plug relay for the 3rd channel
thing-type.tasmotaplug.plug.channel.power4.label = Power 4
thing-type.tasmotaplug.plug.channel.power4.description = Controls the smart plug relay for the 4th channel
thing-type.tasmotaplug.plug.channel.watts.label = Active Power
thing-type.tasmotaplug.plug.channel.watts.description = Active Power (W)
# thing types config
@ -29,6 +37,17 @@ thing-type.config.tasmotaplug.plug.refresh.description = Specifies the refresh i
thing-type.config.tasmotaplug.plug.username.label = Username
thing-type.config.tasmotaplug.plug.username.description = Tasmota username
# channel types
channel-type.tasmotaplug.energy-total-start.label = Energy Total Start
channel-type.tasmotaplug.energy-total-start.description = The date/time when the energy total measurement started
channel-type.tasmotaplug.power-factor.label = Power Factor
channel-type.tasmotaplug.power-factor.description = Power Factor (PF)
channel-type.tasmotaplug.volt-ampere-reactive.label = Reactive Power
channel-type.tasmotaplug.volt-ampere-reactive.description = Reactive Power (VAr)
channel-type.tasmotaplug.volt-ampere.label = Apparent Power
channel-type.tasmotaplug.volt-ampere.description = Apparent Power (VA)
# thing status descriptions
offline.communication-error.http-failure = Tasmota http response code was: {0}

View File

@ -28,8 +28,39 @@
<label>Power 4</label>
<description>Controls the smart plug relay for the 4th channel</description>
</channel>
<!-- Energy Monitor Channels -->
<channel id="voltage" typeId="system.electric-voltage"/>
<channel id="current" typeId="system.electric-current"/>
<channel id="watts" typeId="system.electric-power">
<label>Active Power</label>
<description>Active Power (W)</description>
</channel>
<channel id="volt-ampere" typeId="volt-ampere"/>
<channel id="volt-ampere-reactive" typeId="volt-ampere-reactive"/>
<channel id="power-factor" typeId="power-factor"/>
<channel id="energy-today" typeId="system.electric-energy">
<label>Energy Today</label>
<description>Energy consumed today (kWh)</description>
</channel>
<channel id="energy-yesterday" typeId="system.electric-energy">
<label>Energy Yesterday</label>
<description>Energy consumed yesterday (kWh)</description>
</channel>
<channel id="energy-total" typeId="system.electric-energy">
<label>Energy Total</label>
<description>Total energy consumed (kWh)</description>
</channel>
<channel id="energy-total-start" typeId="energy-total-start"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<config-description>
<parameter name="hostName" type="text" required="true">
<context>network-address</context>
@ -62,4 +93,36 @@
</thing-type>
<channel-type id="volt-ampere">
<item-type>Number:Power</item-type>
<label>Apparent Power</label>
<description>Apparent Power (VA)</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="volt-ampere-reactive">
<item-type>Number:Power</item-type>
<label>Reactive Power</label>
<description>Reactive Power (VAr)</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="power-factor">
<item-type>Number:Dimensionless</item-type>
<label>Power Factor</label>
<description>Power Factor (PF)</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="energy-total-start">
<item-type>DateTime</item-type>
<label>Energy Total Start</label>
<description>The date/time when the energy total measurement started</description>
<category>Time</category>
<tags>
<tag>Timestamp</tag>
</tags>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="tasmotaplug:plug">
<instruction-set targetVersion="1">
<add-channel id="voltage">
<type>system:electric-voltage</type>
</add-channel>
<add-channel id="current">
<type>system:electric-current</type>
</add-channel>
<add-channel id="watts">
<type>system:electric-power</type>
<label>Active Power</label>
<description>Active Power (W)</description>
</add-channel>
<add-channel id="volt-ampere">
<type>tasmotaplug:volt-ampere</type>
</add-channel>
<add-channel id="volt-ampere-reactive">
<type>tasmotaplug:volt-ampere-reactive</type>
</add-channel>
<add-channel id="power-factor">
<type>tasmotaplug:power-factor</type>
</add-channel>
<add-channel id="energy-today">
<type>system:electric-energy</type>
<label>Energy Today</label>
<description>Energy consumed today (kWh)</description>
</add-channel>
<add-channel id="energy-yesterday">
<type>system:electric-energy</type>
<label>Energy Yesterday</label>
<description>Energy consumed yesterday (kWh)</description>
</add-channel>
<add-channel id="energy-total">
<type>system:electric-energy</type>
<label>Energy Total</label>
<description>Total energy consumed (kWh)</description>
</add-channel>
<add-channel id="energy-total-start">
<type>tasmotaplug:energy-total-start</type>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>