Skip to content
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

Add Tuya pool sensor quirk #2927

Closed
wants to merge 9 commits into from
Closed
1 change: 1 addition & 0 deletions zhaquirks/quirk_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Tuya
TUYA_PLUG_ONOFF = "tuya.plug_on_off_attributes" # plugs with configurable attributes on the OnOff cluster
TUYA_PLUG_MANUFACTURER = "tuya.plug_manufacturer_attributes" # plugs with configurable attributes on a custom cluster
TUYA_POOL_SENSOR = "tuya.pool_sensor_attributes"

# Xiaomi
XIAOMI_AQARA_VIBRATION_AQ1 = (
Expand Down
269 changes: 269 additions & 0 deletions zhaquirks/tuya/ts0601_pool_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
"""Tuya pool sensor."""

from typing import Any, Dict

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.measurement import (
PH,
ChlorineConcentration,
ElectricalConductivity,
SodiumConcentration,
TemperatureMeasurement,
)

from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.quirk_ids import TUYA_POOL_SENSOR
from zhaquirks.tuya import TUYA_QUERY_DATA, TuyaEnchantableCluster, TuyaLocalCluster
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
EnchantedDevice,
TuyaMCUCluster,
TuyaPowerConfigurationCluster,
)


# XXX: Move this to a shared location later, as the TuyaPowerConfigurationCluster name is used twice:
# once in zhaquirks.tuya and once in zhaquirks.tuya.mcu. Both have a different function.
class TuyaPowerConfigurationSpellCluster(
TuyaEnchantableCluster, TuyaPowerConfigurationCluster
):
"""Doubling, local, and enchantable TuyaPowerConfigurationCluster."""


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


class TuyaPH(PH, TuyaLocalCluster):
"""Tuya local pH cluster."""


class TuyaORP(TuyaLocalCluster):
"""Tuya local Oxido-Reduction Potential cluster."""

cluster_id = 0x042F
name = "ORP Level"
ep_attribute = "redox_potential"

attributes = {
0x0000: ("measured_value", t.Single), # fraction of 1 (one)
0x0001: ("min_measured_value", t.Single),
0x0002: ("max_measured_value", t.Single),
}

server_commands = {}
client_commands = {}


class TuyaTDS(TuyaLocalCluster):
"""Tuya local Total Dissolved Solids cluster."""

cluster_id = 0x0430
name = "TDS Level"
ep_attribute = "total_dissolved_solids"

attributes = {
0x0000: ("measured_value", t.Single), # fraction of 1 (one)
0x0001: ("min_measured_value", t.Single),
0x0002: ("max_measured_value", t.Single),
}

server_commands = {}
client_commands = {}


class TuyaSodiumConcentration(SodiumConcentration, TuyaLocalCluster):
"""Tuya local NaCl cluster."""


class TuyaElectricalConductivity(ElectricalConductivity, TuyaLocalCluster):
"""Tuya local Electrical Conductivity cluster."""


class TuyaChlorineConcentration(ChlorineConcentration, TuyaLocalCluster):
"""Tuya local Chlorine Concentration cluster with a device RH_MULTIPLIER factor."""


class PoolManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Pool data points."""

attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
# random attribute IDs
0xEF01: ("ph_min_value", t.uint32_t, True),
0xEF02: ("ph_max_value", t.uint32_t, True),
0xEF03: ("cl_min_value", t.uint32_t, True),
0xEF04: ("cl_max_value", t.uint32_t, True),
0xEF05: ("ec_min_value", t.uint32_t, True),
0xEF06: ("ec_max_value", t.uint32_t, True),
0xEF07: ("orp_min_value", t.uint32_t, True),
0xEF08: ("orp_max_value", t.uint32_t, True),
0xEF09: ("update", t.Single, True),
}
)

def _update_attribute(self, attrid: int, value: Any) -> None:
"""Catch button attribute to emit data_query."""
super()._update_attribute(attrid, value)
if attrid == 0xEF09:
tuya_manuf_cluster = self.endpoint.device.endpoints[1].in_clusters[

Check warning on line 119 in zhaquirks/tuya/ts0601_pool_sensor.py

View check run for this annotation

Codecov / codecov/patch

zhaquirks/tuya/ts0601_pool_sensor.py#L117-L119

Added lines #L117 - L119 were not covered by tests
TuyaMCUCluster.cluster_id
]
tuya_manuf_cluster.command(TUYA_QUERY_DATA)

Check warning on line 122 in zhaquirks/tuya/ts0601_pool_sensor.py

View check run for this annotation

Codecov / codecov/patch

zhaquirks/tuya/ts0601_pool_sensor.py#L122

Added line #L122 was not covered by tests

dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaTDS.ep_attribute,
"measured_value",
),
2: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
converter=lambda x: x * 10,
),
101: DPToAttributeMapping(
TuyaORP.ep_attribute,
"measured_value",
),
102: DPToAttributeMapping(
TuyaChlorineConcentration.ep_attribute,
"measured_value",
),
7: DPToAttributeMapping(
TuyaPowerConfigurationSpellCluster.ep_attribute,
"battery_percentage_remaining",
),
# TODO 103 pH Calibration
# TODO 104 Backlight
# TODO 105 Backlight Value
#
10: DPToAttributeMapping(
TuyaPH.ep_attribute,
"measured_value",
),
11: DPToAttributeMapping(
TuyaElectricalConductivity.ep_attribute,
"measured_value",
),
106: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"ph_max_value",
),
107: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"ph_min_value",
),
108: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"ec_max_value",
),
109: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"ec_min_value",
),
110: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"orp_max_value",
),
111: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"orp_min_value",
),
112: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cl_max_value",
),
113: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cl_min_value",
),
# TODO 114: PH Calibration
# TODO 115: EC Calibration
# TODO 116: ORP Calibration
117: DPToAttributeMapping(
TuyaSodiumConcentration.ep_attribute,
"measured_value",
),
}

data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
7: "_dp_2_attr_update",
10: "_dp_2_attr_update",
11: "_dp_2_attr_update",
106: "_dp_2_attr_update",
107: "_dp_2_attr_update",
108: "_dp_2_attr_update",
109: "_dp_2_attr_update",
110: "_dp_2_attr_update",
111: "_dp_2_attr_update",
112: "_dp_2_attr_update",
113: "_dp_2_attr_update",
117: "_dp_2_attr_update",
}


class TuyaPoolSensor(EnchantedDevice):
"""Tuya Pool sensor."""

quirk_id = TUYA_POOL_SENSOR
tuya_spell_data_query = True

signature = {
# "profile_id": 260,
# "device_type": "0x0051",
# "in_clusters": ["0x0000","0x0004","0x0005","0xef00"],
# "out_clusters": ["0x000a","0x0019"]
MODELS_INFO: [
("_TZE200_v1jqz5cy", "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,
PoolManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}

replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
PoolManufCluster,
TuyaTemperatureMeasurement,
TuyaPH,
TuyaORP,
TuyaChlorineConcentration,
TuyaTDS,
TuyaElectricalConductivity,
TuyaSodiumConcentration,
TuyaPowerConfigurationSpellCluster,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
Loading