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 11 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
43 changes: 43 additions & 0 deletions tests/async_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Mock utilities that are async aware."""

from unittest.mock import * # noqa: F401, F403


class _IntSentinelObject(int):
"""Sentinel-like object that is also an integer subclass.

Allows sentinels to be used
in loggers that perform int-specific string formatting.
"""

def __new__(cls, name):
instance = super().__new__(cls, 0)
instance.name = name
return instance

def __repr__(self):
return f"int_sentinel.{self.name}"

def __hash__(self):
return hash((int(self), self.name))

def __eq__(self, other):
return self is other

__str__ = __reduce__ = __repr__


class _IntSentinel:
def __init__(self):
self._sentinels = {}

def __getattr__(self, name):
if name == "__bases__":
raise AttributeError
return self._sentinels.setdefault(name, _IntSentinelObject(name))

def __reduce__(self):
return "int_sentinel"


int_sentinel = _IntSentinel()
141 changes: 140 additions & 1 deletion tests/test_tuya_mcu.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
from unittest import mock

import pytest
from zigpy.device import Device
from zigpy.quirks.registry import DeviceRegistry
from zigpy.quirks.v2 import CustomDeviceV2
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic

from tests.common import ClusterListener, MockDatetime
from tests.common import ClusterListener, MockDatetime, wait_for_zigpy_tasks
import zhaquirks
from zhaquirks.tuya import TUYA_MCU_VERSION_RSP, TUYA_SET_TIME, TuyaDPType
from zhaquirks.tuya.mcu import (
Expand All @@ -15,8 +20,15 @@
TuyaAttributesCluster,
TuyaClusterData,
TuyaMCUCluster,
TuyaOnOffNM,
TuyaQuirkBuilder,
TuyaSoilMoisture,
TuyaTemperatureMeasurement,
TuyaValveWaterConsumed,
)

from .async_mock import sentinel

zhaquirks.setup()

ZCL_TUYA_VERSION_RSP = b"\x09\x06\x11\x01\x6d\x82"
Expand Down Expand Up @@ -375,3 +387,130 @@
TuyaClusterData(manufacturer="xiaomi")
with pytest.raises(ValueError):
TuyaClusterData(manufacturer=b"")


@pytest.fixture(name="device_mock")
def real_device(MockAppController):
"""Device fixture with a single endpoint."""
ieee = sentinel.ieee
nwk = 0x2233
device = Device(MockAppController, ieee, nwk)

device.add_endpoint(1)
device[1].profile_id = 0x0104
device[1].device_type = 0x0051
device.model = "model"
device.manufacturer = "manufacturer"
device[1].add_input_cluster(0x0000)
device[1].add_input_cluster(0xEF00)
device[1].add_output_cluster(0x000A)
device[1].add_output_cluster(0x0019)
return device


async def test_tuya_quirkbuilder(device_mock):
"""Test adding a v2 Tuya Quirk to the registry and getting back a quirked device."""

registry = DeviceRegistry()

class TestEnum(t.enum8):
"""Test Enum."""

A = 0x00
B = 0x01

entry = (
TuyaQuirkBuilder(device_mock.manufacturer, device_mock.model, registry=registry)
.tuya_battery(dp_id=1)
.tuya_metering(dp_id=2)
.tuya_onoff(dp_id=3)
.tuya_soil_moisture(dp_id=4)
.tuya_temperature(dp_id=5)
.tuya_switch(

Check failure on line 429 in tests/test_tuya_mcu.py

View workflow job for this annotation

GitHub Actions / shared-ci / Run tests Python 3.12

test_tuya_quirkbuilder TypeError: QuirkBuilder.switch() got an unexpected keyword argument 'entity_type'
dp_id=6,
attribute_name="test_onoff",
)
.tuya_number(
dp_id=7,
attribute_name="test_number",
type=t.uint16_t,
)
.tuya_binary_sensor(
dp_id=8,
attribute_name="test_binary",
)
.tuya_sensor(
dp_id=9,
attribute_name="test_sensor",
type=t.uint8_t,
)
.tuya_enum(
dp_id=10,
attribute_name="test_enum",
enum_class=TestEnum,
)
.add_to_registry()
)

# coverage for overridden __eq__ method
assert entry.adds_metadata[0] != entry.adds_metadata[1]
assert entry.adds_metadata[0] != entry

quirked = registry.get_device(device_mock)
assert isinstance(quirked, CustomDeviceV2)
assert quirked in registry

ep = quirked.endpoints[1]

assert ep.basic is not None
assert isinstance(ep.basic, Basic)

assert ep.tuya_manufacturer is not None
assert isinstance(ep.tuya_manufacturer, TuyaMCUCluster)

tuya_cluster = ep.tuya_manufacturer
tuya_listener = ClusterListener(tuya_cluster)
assert tuya_cluster.attributes_by_name["test_onoff"].id == 0xEF06
assert tuya_cluster.attributes_by_name["test_number"].id == 0xEF07
assert tuya_cluster.attributes_by_name["test_binary"].id == 0xEF08
assert tuya_cluster.attributes_by_name["test_sensor"].id == 0xEF09
assert tuya_cluster.attributes_by_name["test_enum"].id == 0xEF0A

assert ep.smartenergy_metering is not None
assert isinstance(ep.smartenergy_metering, TuyaValveWaterConsumed)

assert ep.on_off is not None
assert isinstance(ep.on_off, TuyaOnOffNM)

assert ep.soil_moisture is not None
assert isinstance(ep.soil_moisture, TuyaSoilMoisture)

assert ep.temperature is not None
assert isinstance(ep.temperature, TuyaTemperatureMeasurement)

with mock.patch.object(
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
) as m1:
(status,) = await tuya_cluster.write_attributes(
{
"test_enum": 0x01,
}
)

await wait_for_zigpy_tasks()
m1.assert_called_with(
cluster=61184,
sequence=1,
data=b"\x01\x01\x00\x00\x01\n\x04\x00\x01\x01",
command_id=0,
timeout=5,
expect_reply=False,
use_ieee=False,
ask_for_ack=None,
)
assert status == [
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
]

assert tuya_listener.attribute_updates[0][0] == 0xEF0A
assert tuya_listener.attribute_updates[0][1] == TestEnum.B
Loading
Loading