Skip to content

Add Tuya v2 quirk builder #3417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
851d54f
Clean up soil sensors.
prairiesnpr Oct 11, 2024
8555f3e
Refactor
prairiesnpr Oct 11, 2024
ded6e7a
Need to set in init.
prairiesnpr Oct 11, 2024
88068fc
Add power, temp, and soil functions
prairiesnpr Oct 11, 2024
4e7fb61
Cleanup, set defaults
prairiesnpr Oct 11, 2024
91a4012
Cleanup, set defaults
prairiesnpr Oct 11, 2024
6b32bff
all kwargs
prairiesnpr Oct 11, 2024
f1906b0
Remove default DPs, Simplify scaling
prairiesnpr Oct 11, 2024
c9cc9b2
Don't format tuya_dp function def
prairiesnpr Oct 11, 2024
b83c29f
Add EntityMetadata methods
prairiesnpr Oct 12, 2024
c6d5f30
Initial tests
prairiesnpr Oct 12, 2024
241a50f
Clean up soil sensors.
prairiesnpr Oct 11, 2024
ac35ab9
Refactor
prairiesnpr Oct 11, 2024
a698b9d
Need to set in init.
prairiesnpr Oct 11, 2024
63725d9
Add power, temp, and soil functions
prairiesnpr Oct 11, 2024
316d3f6
Cleanup, set defaults
prairiesnpr Oct 11, 2024
6458e02
Cleanup, set defaults
prairiesnpr Oct 11, 2024
9e8fade
all kwargs
prairiesnpr Oct 11, 2024
3c7e5fc
Remove default DPs, Simplify scaling
prairiesnpr Oct 11, 2024
bc790aa
Don't format tuya_dp function def
prairiesnpr Oct 11, 2024
2123900
Add EntityMetadata methods
prairiesnpr Oct 12, 2024
56266db
Initial tests
prairiesnpr Oct 12, 2024
ac9ccc7
Merge remote-tracking branch 'origin/clean-up-soil-sensors' into clea…
prairiesnpr Oct 13, 2024
24d3e4c
Move TuyaQuirkBuilder out of MCU, use AttrDefs
prairiesnpr Oct 13, 2024
a90674a
Remove unused logging
prairiesnpr Oct 13, 2024
6803bc0
Revert unrelated changes
prairiesnpr Oct 13, 2024
fac92fe
Add converter and dp_converter to tuya_sensor
prairiesnpr Oct 14, 2024
c3d1c93
Ensure mcu_version attr is present.
prairiesnpr Oct 14, 2024
7f27503
Add humidity method
prairiesnpr Oct 14, 2024
eff64ba
Require zigpy > 0.68
prairiesnpr Oct 14, 2024
868d0a8
Update tests for zigpy changes
prairiesnpr Oct 14, 2024
3ff810f
Additonal test fixes
prairiesnpr Oct 14, 2024
d47d2f4
Revert Test Updates
prairiesnpr Oct 16, 2024
c043b1f
Merge branch 'dev' of https://github.com/prairiesnpr/zha-device-handl…
prairiesnpr Oct 16, 2024
576b8e6
Update tests.
prairiesnpr Oct 16, 2024
8c28fef
Use AttributeDefs
prairiesnpr Oct 17, 2024
89cd451
Use zigpy 0.69
prairiesnpr Oct 17, 2024
87ba6f1
Code cleanup
prairiesnpr Oct 18, 2024
ea9d912
Formatting, add entity_type to binary_sensor
prairiesnpr Oct 18, 2024
f1323ac
commas and more commas
prairiesnpr Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions zhaquirks/tuya/mcu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import datetime
from typing import Any, Optional, Union

from zigpy.quirks import _DEVICE_REGISTRY
from zigpy.quirks.registry import DeviceRegistry
from zigpy.quirks.v2 import QuirkBuilder, QuirksV2RegistryEntry
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import LevelControl, OnOff
from zigpy.zcl.clusters.measurement import SoilMoisture, TemperatureMeasurement

from zhaquirks import Bus, DoublingPowerConfigurationCluster

Expand All @@ -19,12 +23,14 @@
TUYA_SET_TIME,
EnchantedDevice, # noqa: F401
NoManufacturerCluster,
PowerConfiguration,
PowerOnState,
TuyaCommand,
TuyaDatapointData,
TuyaEnchantableCluster,
TuyaLocalCluster,
TuyaNewManufCluster,
TuyaPowerConfigurationCluster2AAA,
TuyaTimePayload,
)

Expand Down Expand Up @@ -724,3 +730,108 @@ class TuyaLevelControlManufCluster(TuyaMCUCluster):
17: "_dp_2_attr_update",
18: "_dp_2_attr_update",
}


class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
"""Tuya local TemperatureMeasurement cluster."""


class TuyaSoilMoisture(SoilMoisture, TuyaLocalCluster):
"""Tuya local SoilMoisture cluster with a device RH_MULTIPLIER factor if required."""


class TuyaQuirkBuilder(QuirkBuilder):
"""Tuya QuirkBuilder."""

def __init__(
self, manufacturer: str, model: str, registry: DeviceRegistry = _DEVICE_REGISTRY
) -> None:
"""Init the TuyaQuirkBuilder."""
self.tuya_data_point_handlers: dict[int, str] = {}
self.tuya_dp_to_attribute: dict[int, DPToAttributeMapping] = {}
super().__init__(manufacturer, model, registry)

def battery(
self,
dp_id: int,
power_cfg: PowerConfiguration = TuyaPowerConfigurationCluster2AAA,
scale=2,
) -> QuirkBuilder:
"""Add a Tuya Battery Power Configuration."""
self.tuya_dp(
dp_id,
power_cfg.ep_attribute,
"battery_percentage_remaining",
converter=lambda x: x * scale,
)
self.adds(power_cfg)
return self

def soil_moisture(
self,
dp_id: int,
soil_cfg: TuyaLocalCluster = TuyaSoilMoisture,
scale=100,
) -> QuirkBuilder:
"""Add a Tuya Soil Moisture Configuration."""
self.tuya_dp(
dp_id,
soil_cfg.ep_attribute,
"measured_value",
converter=lambda x: x * scale,
)
self.adds(soil_cfg)
return self

def temperature(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not set on this yet, but would it make sense to append a tuya_ prefix to all Tuya related methods?
Just so there aren't any conflicts down the road and it's clear this is for Tuya datapoints?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards adding the tuya prefix. I'm working on adding support for adding attributes, switches, and enums now, and if we add the prefix, then you can still call the original methods if needed. I think it adds clarity and keeps functionality that we may need in the future if we run into something that doesn't fit the normal approach.

self,
dp_id: int,
temp_cfg: TuyaLocalCluster = TuyaTemperatureMeasurement,
scale=100,
) -> QuirkBuilder:
"""Add a Tuya Soil Moisture Configuration."""
self.tuya_dp(
dp_id,
temp_cfg.ep_attribute,
"measured_value",
converter=lambda x: x * scale,
)
self.adds(temp_cfg)
return self

def tuya_dp(
self,
dp_id: int,
ep_attribute: str,
attribute_name: Union[str, tuple],
converter: Optional[Callable[[Any,], Any,]] = None,
dp_converter: Optional[Callable[[Any,], Any,]] = None,
endpoint_id: Optional[int] = None,
dp_handler: str = "_dp_2_attr_update",
) -> QuirkBuilder: # fmt: skip
"""Add Tuya DP Converter."""
self.tuya_dp_to_attribute.update(
{
dp_id: DPToAttributeMapping(
ep_attribute,
attribute_name,
converter=converter,
dp_converter=dp_converter,
endpoint_id=endpoint_id,
)
}
)
self.tuya_data_point_handlers.update({dp_id: dp_handler})
return self

def add_to_registry(self) -> QuirksV2RegistryEntry:
"""Build the quirks v2 registry entry."""

class TuyaReplacementCluster(TuyaMCUCluster):
"""Replacement Tuya Cluster."""

data_point_handlers: dict[int, str] = self.tuya_data_point_handlers
dp_to_attribute: dict[int, DPToAttributeMapping] = self.tuya_dp_to_attribute

self.replaces(TuyaReplacementCluster)
return super().add_to_registry()
188 changes: 25 additions & 163 deletions zhaquirks/tuya/ts0601_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.measurement import (
RelativeHumidity,
SoilMoisture,
TemperatureMeasurement,
)
from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement

from zhaquirks.const import (
DEVICE_TYPE,
Expand All @@ -21,15 +17,12 @@
SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster


class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
"""Tuya local TemperatureMeasurement cluster."""


class TuyaSoilMoisture(SoilMoisture, TuyaLocalCluster):
"""Tuya local SoilMoisture cluster with a device RH_MULTIPLIER factor if required."""
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaMCUCluster,
TuyaQuirkBuilder,
TuyaTemperatureMeasurement,
)


class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
Expand Down Expand Up @@ -334,154 +327,23 @@ class TuyaTempHumiditySensorVar04(CustomDevice):
}


class SoilManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points."""

dp_to_attribute: dict[int, DPToAttributeMapping] = {
5: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
converter=lambda x: x * 100,
),
3: DPToAttributeMapping(
TuyaSoilMoisture.ep_attribute,
"measured_value",
converter=lambda x: x * 100,
),
15: DPToAttributeMapping(
TuyaPowerConfigurationCluster2AAA.ep_attribute,
"battery_percentage_remaining",
converter=lambda x: x * 2, # double reported percentage
),
}

data_point_handlers = {
3: "_dp_2_attr_update",
5: "_dp_2_attr_update",
15: "_dp_2_attr_update",
}


class TuyaSoilSensor(CustomDevice):
"""Tuya temp and humidity sensor (variation 03)."""

signature = {
# "profile_id": 260,
# "device_type": "0x0051",
# "in_clusters": ["0x0000","0x0004","0x0005","0xef00"],
# "out_clusters": ["0x000a","0x0019"]
MODELS_INFO: [
("_TZE200_myd45weu", "TS0601"),
("_TZE200_ga1maeof", "TS0601"),
("_TZE200_9cqcpkgb", "TS0601"),
("_TZE204_myd45weu", "TS0601"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TemperatureHumidityManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}

replacement = {
SKIP_CONFIGURATION: True,
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
SoilManufCluster,
TuyaTemperatureMeasurement,
TuyaSoilMoisture,
TuyaPowerConfigurationCluster2AAA,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}


class SoilManufClusterVar02(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points (variation 02)."""

dp_to_attribute: dict[int, DPToAttributeMapping] = {
5: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
converter=lambda x: x * 10,
),
3: DPToAttributeMapping(
TuyaSoilMoisture.ep_attribute,
"measured_value",
converter=lambda x: x * 100,
),
15: DPToAttributeMapping(
TuyaPowerConfigurationCluster2AAA.ep_attribute,
"battery_percentage_remaining",
converter=lambda x: x * 2, # double reported percentage
),
}

data_point_handlers = {
3: "_dp_2_attr_update",
5: "_dp_2_attr_update",
15: "_dp_2_attr_update",
}


class TuyaSoilSensorVar02(CustomDevice):
"""Tuya temp and humidity sensor (variation 05)."""
(
TuyaQuirkBuilder("_TZE284_aao3yzhs", "TS0601")
.also_applies_to("_TZE284_sgabhwa6", "TS0601")
.temperature(dp_id=5, scale=10)
.battery(dp_id=15)
.soil_moisture(dp_id=3)
.add_to_registry()
)

signature = {
# "profile_id": 260,
# "device_type": "0x0051",
# "in_clusters": ["0x0000","0x0004","0x0005","0xed00","0xef00"],
# "out_clusters": ["0x000a","0x0019"]
MODELS_INFO: [
("_TZE284_aao3yzhs", "TS0601"),
("_TZE284_sgabhwa6", "TS0601"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
0xED00,
TemperatureHumidityManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}

replacement = {
SKIP_CONFIGURATION: True,
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
SoilManufClusterVar02,
TuyaTemperatureMeasurement,
TuyaSoilMoisture,
TuyaPowerConfigurationCluster2AAA,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
(
TuyaQuirkBuilder("_TZE200_myd45weu", "TS0601")
.also_applies_to("_TZE200_ga1maeof", "TS0601")
.also_applies_to("_TZE200_9cqcpkgb", "TS0601")
.also_applies_to("_TZE204_myd45weu", "TS0601")
.temperature(dp_id=5)
.battery(dp_id=15)
.soil_moisture(dp_id=3)
.add_to_registry()
)
Loading