I have a few of these: https://www.aliexpress.com/item/1005006310330140.html
Using the quirk below, in HA they come up as:
All good so far (I don’t mind a few unknown sensors, the main ones are there). However on a reboot of HA, or sometimes just randomly, the energy sum will jump back to an integer value, then some time later, jump back to the original value, as below:
This happens on all of these clamp meters I have except one (the overall switchboard meter), but all have identical device info (including quirk, firmware version, model etc).
Does anyone have any ideas why this is happening? An error in my quirk perhaps?
"""Tuya Din Power Meter."""
import logging
from zigpy.profiles import zha
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
from zigpy.zcl.clusters.smartenergy import Metering
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import TuyaManufClusterAttributes, TuyaOnOff, TuyaSwitch
TUYA_TOTAL_ENERGY_ATTR = 0x0211 #energy
TUYA_CURRENT_ATTR = 0x0212 #current
TUYA_POWER_ATTR = 0x0213 #power
TUYA_VOLTAGE_ATTR = 0x0214 #voltaje
TUYA_DIN_SWITCH_ATTR = 0x0101 #switch
SWITCH_EVENT = "switch_event"
"""Hiking Power Meter Attributes"""
HIKING_DIN_SWITCH_ATTR = 0x0110
HIKING_TOTAL_ENERGY_DELIVERED_ATTR = 0x0201
HIKING_TOTAL_ENERGY_RECEIVED_ATTR = 0x0266
HIKING_VOLTAGE_CURRENT_ATTR = 0x0006
HIKING_POWER_ATTR = 0x0267
HIKING_FREQUENCY_ATTR = 0x0269
HIKING_POWER_FACTOR_ATTR = 0x026F
HIKING_TOTAL_REACTIVE_ATTR = 0x026D
HIKING_REACTIVE_POWER_ATTR = 0x026E
"""MatSee Power Meter Attributes"""
MATSEE_DPID_POWER_TOTAL_ID_ATTR = 0x0273
MATSEE_DPID_POWER_ID_A_ATTR = 0x0265
MATSEE_DPID_POWER_ID_B_ATTR = 0x0269
MATSEE_DPID_POWER_DIRECTION_ID_A_ATTR = 0x0266
MATSEE_DPID_POWER_DIRECTION_ID_B_ATTR = 0x0268
MATSEE_DPID_FORWARD_ENERGY_TOTAL_A_ATTR = 0x026A
MATSEE_DPID_REVERSE_ENERGY_TOTAL_A_ATTR = 0x026B
MATSEE_DPID_FORWARD_ENERGY_TOTAL_B_ATTR = 0x026C
MATSEE_DPID_REVERSE_ENERGY_TOTAL_B_ATTR = 0x026D
MATSEE_DPID_POWER_FACTOR_A_ATTR = 0x026E
MATSEE_DPID_POWER_FACTOR_B_ATTR = 0x0279
MATSEE_DPID_POWER_FREQ_ATTR = 0x026F
MATSEE_DPID_VOLTAGE_A_ATTR = 0x0270
MATSEE_DPID_CURRENT_A_ATTR = 0x0271
MATSEE_DPID_CURRENT_B_ATTR = 0x0272
MATSEE_DPID_UPDATE_RATE_ATTR = 0x0281
MATSEE_DPID_VOLTAGE_A_COEF_ATTR = 0x0274
MATSEE_DPID_CURRENT_A_COEF_ATTR = 0x0275
MATSEE_DPID_POWER_A_COEF_ATTR = 0x0276
MATSEE_DPID_ENERGY_A_COEF_ATTR = 0x0277
MATSEE_DPID_ENERGY_A_COEF_REV_ATTR = 0x027F
MATSEE_DPID_FREQ_COEF_ATTR = 0x027A
MATSEE_DPID_CURRENT_B_COEF_ATTR = 0x027B
MATSEE_DPID_POWER_B_COEF_ATTR = 0x027C
MATSEE_DPID_ENERGY_B_COEF_ATTR = 0x027D
MATSEE_DPID_ENERGY_B_COEF_REV_ATTR = 0x0280
_LOGGER = logging.getLogger(__name__)
class TuyaManufClusterDinPower(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the Tuya Power Meter device."""
attributes = {
TUYA_TOTAL_ENERGY_ATTR: ("energy", t.uint32_t, True),
TUYA_CURRENT_ATTR: ("current", t.int16s, True),
TUYA_POWER_ATTR: ("power", t.uint16_t, True),
TUYA_VOLTAGE_ATTR: ("voltage", t.uint16_t, True),
TUYA_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == TUYA_TOTAL_ENERGY_ATTR:
self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100)
elif attrid == TUYA_CURRENT_ATTR:
self.endpoint.electrical_measurement.current_reported(value)
elif attrid == TUYA_POWER_ATTR:
self.endpoint.electrical_measurement.power_reported(value / 10)
elif attrid == TUYA_VOLTAGE_ATTR:
self.endpoint.electrical_measurement.voltage_reported(value / 10)
elif attrid == TUYA_DIN_SWITCH_ATTR:
self.endpoint.device.switch_bus.listener_event(
SWITCH_EVENT, self.endpoint.endpoint_id, value
)
class TuyaPowerMeasurement(LocalDataCluster, ElectricalMeasurement):
"""Custom class for power, voltage and current measurement."""
cluster_id = ElectricalMeasurement.cluster_id
POWER_ID = 0x050B
VOLTAGE_ID = 0x0505
CURRENT_ID = 0x0508
REACTIVE_POWER_ID = 0x050E
AC_FREQUENCY_ID = 0x0300
TOTAL_REACTIVE_POWER_ID = 0x0305
POWER_FACTOR_ID = 0x0510
AC_VOLTAGE_MULTIPLIER = 0x0600
AC_VOLTAGE_DIVISOR = 0x0601
AC_CURRENT_MULTIPLIER = 0x0602
AC_CURRENT_DIVISOR = 0x0603
AC_FREQUENCY_MULTIPLIER = 0x0400
AC_FREQUENCY_DIVISOR = 0x0401
_CONSTANT_ATTRIBUTES = {
AC_VOLTAGE_MULTIPLIER: 1,
AC_VOLTAGE_DIVISOR: 10,
AC_CURRENT_MULTIPLIER: 1,
AC_CURRENT_DIVISOR: 1000,
AC_FREQUENCY_MULTIPLIER: 1,
AC_FREQUENCY_DIVISOR: 100,
}
def voltage_reported(self, value):
"""Voltage reported."""
self._update_attribute(self.VOLTAGE_ID, value)
def power_reported(self, value):
"""Power reported."""
self._update_attribute(self.POWER_ID, value)
def power_factor_reported(self, value):
"""Power Factor reported."""
self._update_attribute(self.POWER_FACTOR_ID, value)
def reactive_power_reported(self, value):
"""Reactive Power reported."""
self._update_attribute(self.REACTIVE_POWER_ID, value)
def current_reported(self, value):
"""Ampers reported."""
self._update_attribute(self.CURRENT_ID, value)
def frequency_reported(self, value):
"""AC Frequency reported."""
self._update_attribute(self.AC_FREQUENCY_ID, value)
def reactive_energy_reported(self, value):
"""Summation Reactive Energy reported."""
self._update_attribute(self.TOTAL_REACTIVE_POWER_ID, value)
class TuyaElectricalMeasurement(LocalDataCluster, Metering):
"""Custom class for total energy measurement."""
cluster_id = Metering.cluster_id
CURRENT_DELIVERED_ID = 0x0000
CURRENT_RECEIVED_ID = 0x0001
#CURRENT_TIER1_DELIVERED_ID = 0x0100
POWER_WATT = 0x0000
"""Setting unit of measurement."""
_CONSTANT_ATTRIBUTES = {0x0300: POWER_WATT}
def energy_deliver_reported(self, value):
"""Summation Energy Deliver reported."""
self._update_attribute(self.CURRENT_DELIVERED_ID, value)
def energy_receive_reported(self, value):
"""Summation Energy Receive reported."""
self._update_attribute(self.CURRENT_RECEIVED_ID, value)
#def energy_tier1_deliver_reported(self, value):
# """Summation Energy Tier1 Receive reported."""
# self._update_attribute(self.CURRENT_TIER1_DELIVERED_ID, value)
class HikingManufClusterDinPower(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the Hiking Power Meter device."""
attributes = {
HIKING_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),
HIKING_TOTAL_ENERGY_DELIVERED_ATTR: ("energy_delivered", t.uint32_t, True),
HIKING_TOTAL_ENERGY_RECEIVED_ATTR: ("energy_received", t.uint16_t, True),
HIKING_VOLTAGE_CURRENT_ATTR: ("voltage_current", t.uint32_t, True),
HIKING_POWER_ATTR: ("power", t.uint16_t, True),
HIKING_FREQUENCY_ATTR: ("frequency", t.uint16_t, True),
HIKING_TOTAL_REACTIVE_ATTR: ("total_reactive_energy", t.int32s, True),
HIKING_REACTIVE_POWER_ATTR: ("reactive_power", t.int16s, True),
HIKING_POWER_FACTOR_ATTR: ("power_factor", t.uint16_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == HIKING_DIN_SWITCH_ATTR:
self.endpoint.device.switch_bus.listener_event(SWITCH_EVENT, 16, value)
elif attrid == HIKING_TOTAL_ENERGY_DELIVERED_ATTR:
self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100)
elif attrid == HIKING_TOTAL_ENERGY_RECEIVED_ATTR:
self.endpoint.smartenergy_metering.energy_receive_reported(value / 100)
elif attrid == HIKING_VOLTAGE_CURRENT_ATTR:
self.endpoint.electrical_measurement.current_reported(value >> 16)
self.endpoint.electrical_measurement.voltage_reported(
(value & 0x0000FFFF) / 10
)
elif attrid == HIKING_POWER_ATTR:
self.endpoint.electrical_measurement.power_reported(value)
elif attrid == HIKING_FREQUENCY_ATTR:
self.endpoint.electrical_measurement.frequency_reported(value)
elif attrid == HIKING_TOTAL_REACTIVE_ATTR:
self.endpoint.electrical_measurement.reactive_energy_reported(value)
elif attrid == HIKING_REACTIVE_POWER_ATTR:
self.endpoint.electrical_measurement.reactive_power_reported(value)
elif attrid == HIKING_POWER_FACTOR_ATTR:
self.endpoint.electrical_measurement.power_factor_reported(value / 10)
class MatSeeManufClusterDinPower(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the Hiking Power Meter device."""
_LOGGER.debug("PRUEBA MatSeeManufClusterDinPower")
attributes = {
HIKING_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),
MATSEE_DPID_REVERSE_ENERGY_TOTAL_A_ATTR: ("energy_delivered", t.uint32_t, True),
MATSEE_DPID_FORWARD_ENERGY_TOTAL_A_ATTR: ("energy_received", t.uint32_t, True),
MATSEE_DPID_REVERSE_ENERGY_TOTAL_B_ATTR: ("energy_delivered_b", t.uint32_t, True),
MATSEE_DPID_FORWARD_ENERGY_TOTAL_B_ATTR: ("energy_received_b", t.uint32_t, True),
MATSEE_DPID_VOLTAGE_A_ATTR: ("voltage_current", t.uint32_t, True),
MATSEE_DPID_POWER_ID_A_ATTR: ("power", t.uint32_t, True),
MATSEE_DPID_POWER_ID_B_ATTR: ("power_b", t.uint32_t, True),
MATSEE_DPID_POWER_FREQ_ATTR: ("frequency", t.uint32_t, True),
MATSEE_DPID_POWER_TOTAL_ID_ATTR: ("total_reactive_energy", t.int32s, True),
MATSEE_DPID_POWER_A_COEF_ATTR: ("reactive_power", t.uint32_t, True),
MATSEE_DPID_POWER_B_COEF_ATTR: ("reactive_power_b", t.uint32_t, True),
MATSEE_DPID_POWER_FACTOR_A_ATTR: ("power_factor", t.uint32_t, True),
MATSEE_DPID_POWER_FACTOR_B_ATTR: ("power_factor_b", t.uint32_t, True),
MATSEE_DPID_CURRENT_A_ATTR: ("current", t.uint32_t, True),
MATSEE_DPID_CURRENT_B_ATTR: ("current_b", t.uint32_t, True),
MATSEE_DPID_POWER_DIRECTION_ID_A_ATTR: ("power_direction", t.uint8_t, True),
MATSEE_DPID_POWER_DIRECTION_ID_B_ATTR: ("power_direction_b", t.uint8_t, True),
MATSEE_DPID_UPDATE_RATE_ATTR: ("update_rate", t.uint32_t, True),
MATSEE_DPID_VOLTAGE_A_COEF_ATTR: ("voltage_coef", t.uint32_t, True),
MATSEE_DPID_CURRENT_A_COEF_ATTR: ("current_coef", t.uint32_t, True),
MATSEE_DPID_CURRENT_B_COEF_ATTR: ("current_coef_b", t.uint32_t, True),
MATSEE_DPID_ENERGY_A_COEF_ATTR: ("energy_coef", t.uint32_t, True),
MATSEE_DPID_ENERGY_B_COEF_ATTR: ("energy_coef_b", t.uint32_t, True),
MATSEE_DPID_ENERGY_A_COEF_REV_ATTR: ("energy_coef_rev", t.uint32_t, True),
MATSEE_DPID_ENERGY_B_COEF_REV_ATTR: ("energy_coef_rev_b", t.uint32_t, True),
MATSEE_DPID_FREQ_COEF_ATTR: ("frequency_coef", t.uint32_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == HIKING_DIN_SWITCH_ATTR:
self.endpoint.device.switch_bus.listener_event(SWITCH_EVENT, 16, value)
elif attrid == MATSEE_DPID_REVERSE_ENERGY_TOTAL_A_ATTR:
self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100.0)
elif attrid == MATSEE_DPID_FORWARD_ENERGY_TOTAL_A_ATTR:
self.endpoint.smartenergy_metering.energy_receive_reported(value / 100.0)
#elif attrid == MATSEE_DPID_REVERSE_ENERGY_TOTAL_B_ATTR:
# self.endpoint.smartenergy_metering.energy_tier1_deliver_reported(value / 100)
elif attrid == MATSEE_DPID_CURRENT_A_ATTR:
self.endpoint.electrical_measurement.current_reported(value)
#elif attrid == MATSEE_DPID_CURRENT_B_ATTR:
# self.endpoint.electrical_measurement.current_reported((value & 0x0000FFFF)/1000)
elif attrid == MATSEE_DPID_VOLTAGE_A_ATTR:
#self.endpoint.electrical_measurement.current_reported(value >> 16)
#self.endpoint.electrical_measurement.current_reported((value & 0x0000FFFF) / 10)
self.endpoint.electrical_measurement.voltage_reported(
value
)
elif attrid == MATSEE_DPID_POWER_ID_A_ATTR:
self.endpoint.electrical_measurement.power_reported(value / 10.0)
elif attrid == MATSEE_DPID_POWER_FREQ_ATTR:
self.endpoint.electrical_measurement.frequency_reported(value)
elif attrid == MATSEE_DPID_POWER_TOTAL_ID_ATTR:
self.endpoint.electrical_measurement.reactive_energy_reported(value)
elif attrid == MATSEE_DPID_POWER_A_COEF_ATTR:
self.endpoint.electrical_measurement.reactive_power_reported(value)
elif attrid == MATSEE_DPID_POWER_FACTOR_A_ATTR:
self.endpoint.electrical_measurement.power_factor_reported(value / 100.0)
class TuyaPowerMeter(TuyaSwitch):
"""Tuya power meter device."""
def __init__(self, *args, **kwargs):
"""Init device."""
self.switch_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# device_version=1
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]
MODELS_INFO: [
("_TZE200_byzdayie", "TS0601"),
("_TZE200_ewxhg6o9", "TS0601"),
],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=51
# device_version=1
# input_clusters=[0, 4, 5, 61184]
# output_clusters=[10, 25]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterAttributes.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterDinPower,
TuyaPowerMeasurement,
TuyaElectricalMeasurement,
TuyaOnOff,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
}
class HikingPowerMeter(TuyaSwitch):
"""Hiking Power Meter Device - DDS238-2."""
signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# device_version=1
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]
MODELS_INFO: [("_TZE200_bkkmqmyo", "TS0601")],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=51
# device_version=1
# input_clusters=[0, 4, 5, 61184]
# output_clusters=[10, 25]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterAttributes.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
HikingManufClusterDinPower,
TuyaElectricalMeasurement,
TuyaPowerMeasurement,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
16: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
TuyaOnOff,
],
OUTPUT_CLUSTERS: [],
},
}
}
class TuyaPowerMeter_GPP(TuyaSwitch):
"""Tuya power meter device."""
def __init__(self, *args, **kwargs):
"""Init device."""
self.switch_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# device_version=1
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]
MODELS_INFO: [
("_TZE200_lsanae15", "TS0601"),
("_TZE204_81yrt3lo", "TS0601"),
],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=51
# device_version=1
# input_clusters=[0, 4, 5, 61184]
# output_clusters=[10, 25]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterAttributes.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
MatSeeManufClusterDinPower,
TuyaPowerMeasurement,
TuyaElectricalMeasurement,
TuyaOnOff,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
242: {
# <SimpleDescriptor endpoint=242 profile=41440 device_type=97
# input_clusters=[]
# output_clusters=[33]
PROFILE_ID: 41440,
DEVICE_TYPE: 97,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
}
}