various tidyups

This commit is contained in:
zorruno
2026-07-24 18:55:12 +12:00
parent fce8c7b0bc
commit fe6feee250
58 changed files with 749 additions and 5339 deletions
-137
View File
@@ -1,137 +0,0 @@
"""Quirk for TS0207 rain sensors."""
import zigpy.types as t
from typing import Any, Type
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice, CustomCluster
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
OnOff,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zigpy.zcl.clusters.lightlink import LightLink
from zigpy.zcl.clusters.security import IasZone
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster
from zhaquirks.tuya import (
TuyaManufCluster,
DPToAttributeMapping,
EnchantedDevice,
TuyaNoBindPowerConfigurationCluster,
)
ZONE_TYPE = 0x0001
class TuyaSolarRainSensorCluster(TuyaMCUCluster):
"""Tuya manufacturer cluster."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0xEF65: ("light_intensity", t.uint32_t, True),
0xEF66: ("average_light_intensity_20mins", t.uint32_t, True),
0xEF67: ("todays_max_light_intensity", t.uint32_t, True),
0xEF68: ("cleaning_reminder", t.Bool, True),
0xEF69: ("rain_sensor_voltage", t.uint32_t, True),
}
)
dp_to_attribute: dict[int, DPToAttributeMapping] = {
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"light_intensity",
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"average_light_intensity_20mins",
),
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"todays_max_light_intensity",
),
104: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cleaning_reminder",
),
105: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"rain_sensor_voltage",
),
}
data_point_handlers = {
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
103: "_dp_2_attr_update",
104: "_dp_2_attr_update",
105: "_dp_2_attr_update",
}
class TuyaIasZone(CustomCluster, IasZone):
"""IAS Zone for rain sensors."""
_CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Water_Sensor}
class TuyaSolarRainSensor(EnchantedDevice):
"""TS0207 Rain sensor quirk."""
signature = {
MODELS_INFO: [("_TZ3210_tgvtvdoc", "TS0207")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
IasZone.cluster_id,
TuyaMCUCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
OnOff.cluster_id,
Time.cluster_id,
Ota.cluster_id,
LightLink.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaNoBindPowerConfigurationCluster,
TuyaIasZone,
TuyaSolarRainSensorCluster,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
-358
View File
@@ -1,358 +0,0 @@
# rivsc https://blog.rivsc.ovh
import logging
from typing import Final
from zigpy.quirks.v2 import QuirkBuilder, BinarySensorDeviceClass
import zigpy.types as t
from zigpy.zcl.foundation import ZCLAttributeDef
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
)
from zigpy.zcl.clusters.security import IasZone
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType
from zhaquirks.tuya import (
TuyaLocalCluster,
TuyaPowerConfigurationCluster2AAA,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster, DPToAttributeMapping
class PresenceState(t.enum8):
"""Presence State values"""
none = 0x00
presence = 0x01
peaceful = 0x02
small_movement = 0x03
large_movement = 0x04
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class HumanPresenceSensorManufCluster(TuyaMCUCluster):
"""Human Presence Sensor ZG-205Z (5.8GHz)"""
class AttributeDefs(TuyaMCUCluster.AttributeDefs):
"""Tuya DataPoints attributes"""
# Presence state
presence_state: Final = ZCLAttributeDef(
id=0x0001, # DP 1
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# Target distance
target_distance: Final = ZCLAttributeDef(
id=0x0101, # DP 101
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# Illuminance value
illuminance_lux: Final = ZCLAttributeDef(
id=0x0102, # DP 102
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# None delay time (presence keep time)
none_delay_time: Final = ZCLAttributeDef(
id=0x0103, # DP 103
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Indicator
indicator: Final = ZCLAttributeDef(
id=0x0104, # DP 104
type=t.Bool,
is_manufacturer_specific=True,
)
# Move detection max distance
move_detection_max: Final = ZCLAttributeDef(
id=0x0107, # DP 107
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Move detection min distance
move_detection_min: Final = ZCLAttributeDef(
id=0x0108, # DP 108
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Breath detection max distance
breath_detection_max: Final = ZCLAttributeDef(
id=0x0109, # DP 109
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Breath detection min distance
breath_detection_min: Final = ZCLAttributeDef(
id=0x0110, # DP 110
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Small move detection max distance
small_move_detection_max: Final = ZCLAttributeDef(
id=0x0114, # DP 114
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Small move detection min distance
small_move_detection_min: Final = ZCLAttributeDef(
id=0x0115, # DP 115
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Move sensitivity
move_sensitivity: Final = ZCLAttributeDef(
id=0x0116, # DP 116
type=t.uint8_t,
is_manufacturer_specific=True,
)
# Small move sensitivity
small_move_sensitivity: Final = ZCLAttributeDef(
id=0x0117, # DP 117
type=t.uint8_t,
is_manufacturer_specific=True,
)
# Breath sensitivity
breath_sensitivity: Final = ZCLAttributeDef(
id=0x0118, # DP 118
type=t.uint8_t,
is_manufacturer_specific=True,
)
dp_to_attribute: dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"presence_state",
converter=PresenceState
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"target_distance",
converter=lambda x: x / 100 if x is not None else 0
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"illuminance_lux",
),
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"none_delay_time",
converter=lambda x: x if x is not None else 30
),
104: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"indicator",
),
107: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_detection_max",
converter=lambda x: x / 100 if x is not None else 10
),
108: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
109: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_detection_max",
converter=lambda x: x / 100 if x is not None else 6
),
110: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
114: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_detection_max",
converter=lambda x: x / 100 if x is not None else 6
),
115: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
116: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_sensitivity",
converter=lambda x: x if x is not None else 5
),
117: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_sensitivity",
converter=lambda x: x if x is not None else 5
),
118: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_sensitivity",
converter=lambda x: x if x is not None else 5
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
103: "_dp_2_attr_update",
104: "_dp_2_attr_update",
107: "_dp_2_attr_update",
108: "_dp_2_attr_update",
109: "_dp_2_attr_update",
110: "_dp_2_attr_update",
114: "_dp_2_attr_update",
115: "_dp_2_attr_update",
116: "_dp_2_attr_update",
117: "_dp_2_attr_update",
118: "_dp_2_attr_update",
}
(
QuirkBuilder("_TZE204_dapwryy7", "TS0601")
.skip_configuration()
.removes(IasZone.cluster_id)
.adds(HumanPresenceSensorManufCluster)
#.adds(TuyaOccupancySensing)
.replaces(TuyaPowerConfigurationCluster2AAA)
.replaces(TuyaIlluminanceMeasurement)
.binary_sensor(
"presence_state",
HumanPresenceSensorManufCluster.cluster_id,
endpoint_id=1,
#entity_type=EntityType.STANDARD, # very soon (zigpy channel #dev on github
device_class=BinarySensorDeviceClass.OCCUPANCY,
fallback_name="Presence"
)
.enum(
HumanPresenceSensorManufCluster.AttributeDefs.presence_state.name,
PresenceState,
HumanPresenceSensorManufCluster.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
fallback_name="Presence State",
translation_key="presence_state"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.target_distance.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m", # fail :/
fallback_name="Target Distance (m)",
translation_key="target_distance",
#entity_type=EntityType.STANDARD # not yet :/
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.none_delay_time.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=28800,
unit="s",
fallback_name="Hold Delay Time",
translation_key="none_delay_time"
)
.switch(
HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
HumanPresenceSensorManufCluster.cluster_id,
fallback_name="LED Indicator",
translation_key="indicator"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m",
fallback_name="Move Detection Max Distance (m)",
translation_key="move_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m",
fallback_name="Move Detection Min Distance (m)",
translation_key="move_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Small Move Detection Max Distance (m)",
translation_key="small_move_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Small Move Detection Min Distance (m)",
translation_key="small_move_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Breath Detection Max Distance (m)",
translation_key="breath_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Breath Detection Min Distance (m)",
translation_key="breath_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Move Sensitivity",
translation_key="move_sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Small Move Sensitivity",
translation_key="small_move_sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Breath Sensitivity",
translation_key="breath_sensitivity"
)
.add_to_registry()
)
@@ -1,293 +0,0 @@
"""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: [],
},
}
}
-175
View File
@@ -1,175 +0,0 @@
"""
DY-YG400A Smoke Sensor, TS0601 from _TZE204_ntcy3xu1
https://www.aliexpress.com/item/1005005863519099.html
https://www.aliexpress.com/item/1005005854203557.html
"""
import logging
import zigpy.profiles.zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Ota,
Scenes,
Time,
BinaryInput,
BatterySize,
)
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_TYPE,
)
from zhaquirks.tuya import (
TuyaLocalCluster,
TuyaManufCluster,
TuyaManufClusterAttributes,
TuyaPowerConfigurationCluster,
)
_LOGGER = logging.getLogger(__name__)
TUYA_SMOKE_DETECTED_ATTR = 0x0401 # [0]/[1] [Detected]/[Clear]!
TUYA_SMOKE_TAMPERED_ATTR = 0x0104 # [0]/[1] [Clear]/[Tampered]!
TUYA_SMOKE_BATTERY_ATTR = 0x040e # [0]/[1]/[2] [Low]/[Med]]/[Full]!
class TuyaSmokeDetectorCluster(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the TS0601 smoke detector."""
attributes = {
TUYA_SMOKE_DETECTED_ATTR: ("smoke_detected", t.uint8_t, True),
TUYA_SMOKE_TAMPERED_ATTR: ("tampered_device", t.uint8_t, True),
TUYA_SMOKE_BATTERY_ATTR: ("battery_status", t.uint8_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == TUYA_SMOKE_DETECTED_ATTR:
status = IasZone.ZoneStatus.Alarm_1 if value == 0 else 0
self.endpoint.device.ias_bus.listener_event("update_attribute", "zone_status", status)
elif attrid == TUYA_SMOKE_TAMPERED_ATTR:
self.endpoint.device.tamper_detection_bus.listener_event(
"update_attribute", "present_value", bool(value)
)
elif attrid == TUYA_SMOKE_BATTERY_ATTR:
batt = 5 if value == 0 else 40 if value == 1 else 100
self.endpoint.device.battery_bus.listener_event("battery_change", batt)
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(TuyaLocalCluster, IasZone):
"""
IAS Zone: this generates the "Smoke" entity for HA.
Receives updates from TuyaSmokeDetectorCluster.
"""
_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)
class TuyaPowerConfigCluster(TuyaPowerConfigurationCluster):
"""
Power Config Cluster: this generates the "Battery" entity for HA.
Receives updates from TuyaSmokeDetectorCluster.
"""
_CONSTANT_ATTRIBUTES = {
TuyaPowerConfigurationCluster.attributes_by_name["battery_size"].id: BatterySize.AAA,
TuyaPowerConfigurationCluster.attributes_by_name["battery_quantity"].id: 2,
TuyaPowerConfigurationCluster.attributes_by_name["battery_rated_voltage"].id: 15,
}
class TuyaTamperDetection(TuyaLocalCluster, BinaryInput):
"""
Tamper Detection Cluster: this generates the "Binary input" entity for HA, which is updated
with the Tampered state. Receives updates from TuyaSmokeDetectorCluster.
"""
_CONSTANT_ATTRIBUTES = {
BinaryInput.attributes_by_name["description"].id: "Tamper Detected",
BinaryInput.attributes_by_name["active_text"].id: "Tampered",
BinaryInput.attributes_by_name["inactive_text"].id: "Clear",
}
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.tamper_detection_bus.add_listener(self)
class TuyaSmokeDetector0601(CustomDevice):
"""TS0601 Smoke detector quirk."""
def __init__(self, *args, **kwargs):
"""Init."""
self.ias_bus = Bus()
self.battery_bus = Bus()
self.tamper_detection_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
MODELS_INFO: [
"""("_TZE200_uebojraa", "TS0601"),"""
("_TZE200_uebojraa"),
],
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,
TuyaPowerConfigCluster,
TuyaTamperDetection,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
@@ -1,151 +0,0 @@
"""Tuya valve devices."""
import logging
from typing import Dict
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaAttributesCluster,
TuyaDPType,
TuyaLevelControl,
TuyaMCUCluster,
TuyaOnOff,
TuyaOnOffNM,
TuyaPowerConfigurationCluster,
)
_LOGGER = logging.getLogger(__name__)
class TuyaLevelControlNM(NoManufacturerCluster, TuyaLevelControl):
"""Tuya LevelControl cluster with NoManufacturerID."""
class TuyaValveManufCluster(TuyaMCUCluster):
"""Tuya valve manufacturer cluster."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0xEF03: ("dp_3", t.uint32_t, True),
0xEF65: ("dp_101", t.uint32_t, True),
0xEF66: ("dp_102", t.uint32_t, True), # <-- new cluster attribute with fake IDs (102=0x66).Format= ID: ("name", type, True)
}
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
2: DPToAttributeMapping(
TuyaOnOffNM.ep_attribute,
"on_off",
dp_type=TuyaDPType.BOOL,
),
1: DPToAttributeMapping(
TuyaLevelControlNM.ep_attribute,
"current_level",
dp_type=TuyaDPType.VALUE,
converter=lambda x: (x * 255) // 100,
dp_converter=lambda x: (x * 100) // 255,
),
3: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"dp_3",
dp_type=TuyaDPType.VALUE,
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"dp_101",
dp_type=TuyaDPType.VALUE,
),
102: DPToAttributeMapping( # <-- device DP102
TuyaMCUCluster.ep_attribute, # <-- reference to the cluster which has the attribute
"dp_102", # <-- attribute "name"
dp_type=TuyaDPType.VALUE, # <-- DP Type it is related to the attribute type
),
108: DPToAttributeMapping(
TuyaPowerConfigurationCluster.ep_attribute,
"battery_percentage_remaining",
dp_type=TuyaDPType.VALUE,
),
}
data_point_handlers = {
2: "_dp_2_attr_update",
1: "_dp_2_attr_update",
3: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
108: "_dp_2_attr_update",
}
async def write_attributes(self, attributes, manufacturer=None):
"""Overwrite to force manufacturer code."""
return await super().write_attributes(
attributes, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID
)
class TuyaValve(CustomDevice):
"""Tuya valve device."""
signature = {
"""MODELS_INFO: [("_TZE200_sh1btabb", "TS0601")],"""
MODELS_INFO: [("_TZE200_sh1btabb")],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaValveManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaValveManufCluster,
TuyaOnOffNM,
TuyaLevelControlNM,
TuyaPowerConfigurationCluster,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
}