various incl esphome devices

This commit is contained in:
root
2025-02-15 17:16:41 +13:00
parent 70ed59fef8
commit 92a54f7ae5
12 changed files with 1758 additions and 489 deletions

View File

@@ -0,0 +1,471 @@
"""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)
elif attrid == MATSEE_DPID_FORWARD_ENERGY_TOTAL_A_ATTR:
self.endpoint.smartenergy_metering.energy_receive_reported(value / 100)
#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)
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)
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"),
("_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],
}
},
}
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],
},
}
}

View File

@@ -0,0 +1,293 @@
"""Tuya Din Power Meter."""
from zigpy.profiles import zha
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, 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
TUYA_CURRENT_ATTR = 0x0212
TUYA_POWER_ATTR = 0x0213
TUYA_VOLTAGE_ATTR = 0x0214
TUYA_DIN_SWITCH_ATTR = 0x0101
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
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."""
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_CURRENT_MULTIPLIER = 0x0602
AC_CURRENT_DIVISOR = 0x0603
AC_FREQUENCY_MULTIPLIER = 0x0400
AC_FREQUENCY_DIVISOR = 0x0401
_CONSTANT_ATTRIBUTES = {
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."""
CURRENT_DELIVERED_ID = 0x0000
CURRENT_RECEIVED_ID = 0x0001
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)
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 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"),
("_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],
}
},
}
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: [],
},
}
}

View File

@@ -0,0 +1,120 @@
"""Smoke Sensor."""
import logging
import zigpy.profiles.zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone
from zhaquirks import Bus
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_STATUS,
ZONE_TYPE,
)
from zhaquirks.tuya import TuyaManufCluster, TuyaManufClusterAttributes
_LOGGER = logging.getLogger(__name__)
TUYA_SMOKE_DETECTED_ATTR = 0x0401 # [0]/[1] [Detected]/[Clear]!
class TuyaSmokeDetectorCluster(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the TS0601 smoke detector."""
attributes = {
TUYA_SMOKE_DETECTED_ATTR: ("smoke_detected", t.uint8_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == TUYA_SMOKE_DETECTED_ATTR:
if value == 0:
self.endpoint.device.ias_bus.listener_event(
"update_zone_status", IasZone.ZoneStatus.Alarm_1
)
else:
self.endpoint.device.ias_bus.listener_event("update_zone_status", 0)
else:
_LOGGER.warning(
"[0x%04x:%s:0x%04x] unhandled attribute: 0x%04x",
self.endpoint.device.nwk,
self.endpoint.endpoint_id,
self.cluster_id,
attrid,
)
class TuyaIasZone(CustomCluster, IasZone):
"""IAS Zone."""
_CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Fire_Sensor}
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.ias_bus.add_listener(self)
def update_zone_status(self, value):
"""Update IAS status."""
super()._update_attribute(ZONE_STATUS, value)
class TuyaSmokeDetector0601(CustomDevice):
"""TS0601 Smoke detector quirk."""
def __init__(self, *args, **kwargs):
"""Init."""
self.ias_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
MODELS_INFO: [
("_TZE200_aycxwiau", "TS0601"),
("_TZE200_ntcy3xu1", "TS0601"),
("_TZE200_vzekyi4c", "TS0601"),
("_TZE200_uebojraa", "TS0601"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaIasZone,
TuyaSmokeDetectorCluster,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}