Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
100 changes: 100 additions & 0 deletions tests/test_tuya.py
Original file line number Diff line number Diff line change
Expand Up @@ -2023,3 +2023,103 @@ async def test_ts601_door_sensor(
attrs = await cluster.read_attributes(attributes=[attribute])

assert attrs[0].get(attribute) == expected_value


async def test_hobeian_zg_303z_sensor(zigpy_device_from_v2_quirk):
"""Test HOBEIAN ZG-303Z sensor."""

from zigpy.zcl.clusters.general import PowerConfiguration
from zigpy.zcl.clusters.measurement import (
RelativeHumidity,
SoilMoisture,
TemperatureMeasurement,
)

from tests.common import ClusterListener
from zhaquirks.tuya import TuyaCommand, TuyaData, TuyaDatapointData
from zhaquirks.tuya.tuya_sensor import TuyaTempUnitConvert

device = zigpy_device_from_v2_quirk("HOBEIAN", "ZG-303Z")

ep = device.endpoints[1]
tuya_cluster = ep.tuya_manufacturer

# 1. Test that the original RelativeHumidity cluster is removed
assert RelativeHumidity.cluster_id not in ep.in_clusters

# 2. Setup listeners for standard clusters
soil_listener = ClusterListener(ep.in_clusters[SoilMoisture.cluster_id])
temp_listener = ClusterListener(ep.in_clusters[TemperatureMeasurement.cluster_id])
battery_listener = ClusterListener(ep.in_clusters[PowerConfiguration.cluster_id])

# 3. Test DP 3: Soil Moisture
dp_data = TuyaDatapointData(dp=3, data=TuyaData(value=55))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert len(soil_listener.attribute_updates) == 1
assert soil_listener.attribute_updates[0][0] == 0
assert soil_listener.attribute_updates[0][1] == 5500

# 4. Test DP 5: Temperature
dp_data = TuyaDatapointData(dp=5, data=TuyaData(value=251))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert len(temp_listener.attribute_updates) == 1
assert temp_listener.attribute_updates[0][0] == 0
assert temp_listener.attribute_updates[0][1] == 2510

# 5. Test DP 9: Display Unit
dp_data = TuyaDatapointData(
dp=9, data=TuyaData(value=TuyaTempUnitConvert.Fahrenheit)
)
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("display_unit") == TuyaTempUnitConvert.Fahrenheit

# 6. Test DP 15: Battery
dp_data = TuyaDatapointData(dp=15, data=TuyaData(value=88))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert len(battery_listener.attribute_updates) == 1
assert battery_listener.attribute_updates[0][0] == 33
assert battery_listener.attribute_updates[0][1] == 176 # Default scale is 2

# 7. Test DP 102: Soil Moisture Calibration
dp_data = TuyaDatapointData(dp=102, data=TuyaData(value=15))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("soil_moisture_calibration") == 15

# 8. Test DP 104: Temperature Calibration
dp_data = TuyaDatapointData(dp=104, data=TuyaData(value=-12)) # -1.2
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("temperature_calibration") == -12

# 9. Test DP 105: Humidity Calibration
dp_data = TuyaDatapointData(dp=105, data=TuyaData(value=22))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("humidity_calibration") == 22

# 10. Test DP 106: Dry binary sensor
dp_data = TuyaDatapointData(dp=106, data=TuyaData(value=True)) # Not dry
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert not tuya_cluster.get("dry")

dp_data = TuyaDatapointData(dp=106, data=TuyaData(value=False)) # Dry
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("dry")

# 11. Test DP 109: Humidity Sensor
dp_data = TuyaDatapointData(dp=109, data=TuyaData(value=68))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("humidity_value") == 68

# 12. Test DP 110: Alarm Soil Moisture Min
dp_data = TuyaDatapointData(dp=110, data=TuyaData(value=10))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("alarm_soil_moisture_min") == 10

# 13. Test DP 111: Temperature Sampling
dp_data = TuyaDatapointData(dp=111, data=TuyaData(value=120))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("temperature_sampling") == 120

# 14. Test DP 112: Soil Moisture Sampling
dp_data = TuyaDatapointData(dp=112, data=TuyaData(value=180))
tuya_cluster.handle_get_data(TuyaCommand(status=0, tsn=0, datapoints=[dp_data]))
assert tuya_cluster.get("soil_moisture_sampling") == 180
130 changes: 130 additions & 0 deletions zhaquirks/tuya/tuya_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

from zigpy.quirks.v2 import EntityPlatform, EntityType
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfTemperature, UnitOfTime
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
from zigpy.quirks.v2.homeassistant.number import NumberDeviceClass
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.measurement import RelativeHumidity

from zhaquirks.const import BatterySize
from zhaquirks.tuya import (
TUYA_SET_TIME,
TuyaPowerConfigurationCluster2AAA,
Expand Down Expand Up @@ -333,3 +337,129 @@ class ServerCommandDefs(TuyaMCUCluster.ServerCommandDefs):
.skip_configuration()
.add_to_registry()
)


(
TuyaQuirkBuilder("HOBEIAN", "ZG-303Z")
.applies_to("_TZE200_npj9bug3", "TS0601") # COOLO CS-201Z
.tuya_soil_moisture(dp_id=3)
.tuya_temperature(dp_id=5, scale=10)
.tuya_enum(
dp_id=9,
attribute_name="display_unit",
enum_class=TuyaTempUnitConvert,
entity_type=EntityType.CONFIG,
translation_key="display_unit",
fallback_name="Display unit",
)
.tuya_battery(dp_id=15, battery_type=BatterySize.AAA)
.tuya_number(
dp_id=102,
attribute_name="soil_moisture_calibration",
type=t.uint16_t,
unit=PERCENTAGE,
min_value=-30,
max_value=30,
step=1,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.HUMIDITY,
translation_key="soil_moisture_calibration",
fallback_name="Soil moisture Calibration",
)
.tuya_number(
dp_id=104,
attribute_name="temperature_calibration",
type=t.uint16_t,
unit=UnitOfTemperature.KELVIN,
min_value=-2,
max_value=2,
step=0.1,
multiplier=10,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.TEMPERATURE,
translation_key="temperature_calibration",
fallback_name="Temperature Calibration",
)
.tuya_number(
dp_id=105,
attribute_name="humidity_calibration",
type=t.uint16_t,
unit=PERCENTAGE,
min_value=-30,
max_value=30,
step=1,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.HUMIDITY,
translation_key="humidity_calibration",
fallback_name="Humidity Calibration",
)
.tuya_dp_attribute(
dp_id=106,
attribute_name="dry",
converter=lambda x: not bool(x),
)
.binary_sensor(
attribute_name="dry",
cluster_id=TuyaMCUCluster.cluster_id,
endpoint_id=1,
entity_type=EntityType.STANDARD,
device_class=BinarySensorDeviceClass.MOISTURE,
initially_disabled=False,
attribute_initialized_from_cache=False,
translation_key="dry",
fallback_name="dry",
)
.removes(RelativeHumidity.cluster_id)
.tuya_sensor(
dp_id=109,
type=t.uint16_t,
attribute_name="humidity_value",
unit=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
entity_type=EntityType.STANDARD,
translation_key="humidity",
fallback_name="Humidity",
)
.tuya_number(
dp_id=110,
attribute_name="alarm_soil_moisture_min",
type=t.uint16_t,
unit=PERCENTAGE,
min_value=0,
max_value=100,
step=1,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.MOISTURE,
translation_key="alarm_soil_moisture_min",
fallback_name="Alarm soil moisture min",
)
.tuya_number(
dp_id=111,
attribute_name="temperature_sampling",
type=t.uint16_t,
unit=UnitOfTime.SECONDS,
min_value=5,
max_value=3600,
step=1,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.DURATION,
translation_key="temperature_sampling",
fallback_name="Temperature Sampling",
)
.tuya_number(
dp_id=112,
attribute_name="soil_moisture_sampling",
type=t.uint16_t,
unit=UnitOfTime.SECONDS,
min_value=5,
max_value=3600,
step=1,
entity_type=EntityType.CONFIG,
device_class=NumberDeviceClass.DURATION,
translation_key="soil_moisture_sampling",
fallback_name="Soil Moisture Sampling",
)
.tuya_enchantment(data_query_spell=True)
.skip_configuration()
.add_to_registry()
)
Loading