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
97 changes: 97 additions & 0 deletions tests/test_tuya_water_leak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Test for Tuya water leak sensor."""

import pytest

from tests.common import ClusterListener
import zhaquirks

zhaquirks.setup()

ZCL_TUYA_WATER_PRESENCE = bytes.fromhex("09 e0 02 01 e9 01 04 00 01 01")
ZCL_TUYA_WATER_ABSENCE = bytes.fromhex("09 e0 02 01 e9 01 04 00 01 00")
ZCL_TUYA_WATER_LEAK_TRUE = bytes.fromhex("09 e1 02 01 ea 66 04 00 01 01")
ZCL_TUYA_WATER_LEAK_FALSE = bytes.fromhex("09 e1 02 01 ea 66 04 00 01 00")
ZCL_TUYA_BATTERY_37 = bytes.fromhex("09 da 02 01 e5 04 02 00 04 00 00 00 25")
ZCL_TUYA_BATTERY_100 = bytes.fromhex("09 da 02 01 e5 04 02 00 04 00 00 00 64")
ZCL_TUYA_ALARM_MODE_0 = bytes.fromhex("09 f0 02 01 f9 65 04 00 01 00")
ZCL_TUYA_ALARM_MODE_1 = bytes.fromhex("09 f0 02 01 f9 65 04 00 01 01")
ZCL_TUYA_RINGTONE_0 = bytes.fromhex("09 db 02 01 e6 67 04 00 01 00")
ZCL_TUYA_RINGTONE_2 = bytes.fromhex("09 db 02 01 e6 67 04 00 01 02")


@pytest.mark.parametrize(
"frame, expected_attr_id, expected_value",
[
(ZCL_TUYA_WATER_PRESENCE, 0xEF01, True),
(ZCL_TUYA_WATER_ABSENCE, 0xEF01, False),
(ZCL_TUYA_WATER_LEAK_TRUE, 0xEF66, True),
(ZCL_TUYA_WATER_LEAK_FALSE, 0xEF66, False),
],
)
async def test_water_sensors_state_report(
zigpy_device_from_v2_quirk, frame, expected_attr_id, expected_value
):
"""Test water presence and leak sensors."""

dev = zigpy_device_from_v2_quirk("_TZE284_1di7ujzp", "TS0601")
tuya_cluster = dev.endpoints[1].tuya_manufacturer

tuya_listener = ClusterListener(tuya_cluster)
hdr, args = tuya_cluster.deserialize(frame)
tuya_cluster.handle_message(hdr, args)

assert len(tuya_listener.attribute_updates) == 1
assert tuya_listener.attribute_updates[0][0] == expected_attr_id
assert tuya_listener.attribute_updates[0][1] == expected_value


@pytest.mark.parametrize(
"frame, expected_attr_id, expected_value",
[
(ZCL_TUYA_ALARM_MODE_0, 0xEF65, 0),
(ZCL_TUYA_ALARM_MODE_1, 0xEF65, 1),
(ZCL_TUYA_RINGTONE_0, 0xEF67, 0),
(ZCL_TUYA_RINGTONE_2, 0xEF67, 2),
],
)
async def test_sensor_and_enum_state_report(
zigpy_device_from_v2_quirk, frame, expected_attr_id, expected_value
):
"""Test alarm mode and ringtone enum attributes."""

dev = zigpy_device_from_v2_quirk("_TZE284_1di7ujzp", "TS0601")
tuya_cluster = dev.endpoints[1].tuya_manufacturer

tuya_listener = ClusterListener(tuya_cluster)
hdr, args = tuya_cluster.deserialize(frame)
tuya_cluster.handle_message(hdr, args)

assert len(tuya_listener.attribute_updates) == 1
assert tuya_listener.attribute_updates[0][0] == expected_attr_id
assert tuya_listener.attribute_updates[0][1] == expected_value


@pytest.mark.parametrize(
"frame, expected_value",
[
(ZCL_TUYA_BATTERY_37, 37),
(ZCL_TUYA_BATTERY_100, 100),
],
)
async def test_battery_via_tuya_cluster(
zigpy_device_from_v2_quirk, frame, expected_value
):
"""Test battery level via Tuya cluster."""

dev = zigpy_device_from_v2_quirk("_TZE284_1di7ujzp", "TS0601")

tuya_cluster = dev.endpoints[1].tuya_manufacturer
power_cluster = dev.endpoints[1].power

listener = ClusterListener(power_cluster)
hdr, args = tuya_cluster.deserialize(frame)
tuya_cluster.handle_message(hdr, args)

assert len(listener.attribute_updates) == 1
# Due to half-percent precision
assert listener.attribute_updates[0][1] == expected_value * 2
62 changes: 62 additions & 0 deletions zhaquirks/tuya/ts0601_water_leak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Tuya water leak sensor."""

from zigpy.profiles import zha
from zigpy.quirks.v2 import EntityType
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
from zigpy.types import enum8

from zhaquirks.const import BatterySize
from zhaquirks.tuya.builder import TuyaQuirkBuilder


class AlarmMode(enum8):
"""Alarm mode of the sensor, which can be configured by user."""

WATER_PRESENCE = 0x00
WATER_ABSENCE = 0x01


class Ringtone(enum8):
"""Alarm tone of the sensor, which can be configured by user."""

MUTED = 0x00
TONE_1 = 0x01
TONE_2 = 0x02
TONE_3 = 0x03


# <SimpleDescriptor endpoint=1 profile=260 device_type=81
# device_version=1
# input_clusters=[4, 5, 61184, 0, 60672]
# output_clusters=[25, 10]>
TuyaQuirkBuilder(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you wrap this with parentheses? See other quirks using TuyaQuirkBuilder or QuirkBuilder.
E.g. something like this:

(
    QuirkBuilder("", "")
    .something()
    .add_to_registry()
)

"_TZE284_1di7ujzp", "TS0601"
).tuya_binary_sensor( # Water presence (DP 1)
dp_id=1,
attribute_name="water_presence",
device_class=BinarySensorDeviceClass.MOISTURE,
fallback_name="Water presence",
).tuya_binary_sensor( # Water leak (DP 102)
dp_id=102,
attribute_name="water_leak",
device_class=BinarySensorDeviceClass.PROBLEM,
fallback_name="Water leak",
).tuya_battery( # Battery (DP 4)
dp_id=4, battery_type=BatterySize.AAA, battery_qty=2
).tuya_enum( # Alarm mode (DP 101)
dp_id=101,
attribute_name="alarm_mode",
enum_class=AlarmMode,
translation_key="alarm_mode",
fallback_name="Alarm mode",
entity_type=EntityType.CONFIG,
).tuya_enum( # Ringtone (DP 103)
dp_id=103,
attribute_name="ringtone",
enum_class=Ringtone,
translation_key="ringtone",
fallback_name="Ringtone",
entity_type=EntityType.CONFIG,
).replaces_endpoint(
1, device_type=zha.DeviceType.IAS_ZONE
).skip_configuration().add_to_registry()
Loading