Skip to content

Commit

Permalink
Add Tuya TRV _TZE204_ogx8u5z6 (#3682)
Browse files Browse the repository at this point in the history
  • Loading branch information
prairiesnpr authored Jan 16, 2025
1 parent f1b8f5c commit 567660d
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
86 changes: 86 additions & 0 deletions tests/test_tuya_trv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Test for Tuya TRV."""

from unittest import mock

import pytest
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.hvac import Thermostat

from tests.common import ClusterListener, wait_for_zigpy_tasks
import zhaquirks
from zhaquirks.tuya.mcu import TuyaMCUCluster

zhaquirks.setup()


@pytest.mark.parametrize(
"msg,attr,value",
[
(
b"\t\xc2\x02\x00q\x02\x04\x00\x01\x00",
Thermostat.AttributeDefs.system_mode,
Thermostat.SystemMode.Auto,
), # Set to Auto (0x00), dp 2
(
b"\t\xc3\x02\x00r\x02\x04\x00\x01\x01",
Thermostat.AttributeDefs.system_mode,
Thermostat.SystemMode.Heat,
), # Set to Heat (0x01), dp 2
(
b"\t\xc2\x02\x00q\x02\x04\x00\x01\x02",
Thermostat.AttributeDefs.system_mode,
Thermostat.SystemMode.Off,
), # Set to Off (0x02), dp 2
],
)
async def test_handle_get_data(zigpy_device_from_v2_quirk, msg, attr, value):
"""Test handle_get_data for multiple attributes."""

quirked = zigpy_device_from_v2_quirk("_TZE204_ogx8u5z6", "TS0601")
ep = quirked.endpoints[1]

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

assert ep.thermostat is not None
assert isinstance(ep.thermostat, Thermostat)

thermostat_listener = ClusterListener(ep.thermostat)

hdr, data = ep.tuya_manufacturer.deserialize(msg)
status = ep.tuya_manufacturer.handle_get_data(data.data)
assert status == foundation.Status.SUCCESS

assert len(thermostat_listener.attribute_updates) == 1
assert thermostat_listener.attribute_updates[0][0] == attr.id
assert thermostat_listener.attribute_updates[0][1] == value

assert ep.thermostat.get(attr.id) == value

async def async_success(*args, **kwargs):
return foundation.Status.SUCCESS

with mock.patch.object(
ep.tuya_manufacturer.endpoint, "request", side_effect=async_success
) as m1:
(status,) = await ep.thermostat.write_attributes(
{
"occupied_heating_setpoint": 2500,
}
)
await wait_for_zigpy_tasks()
m1.assert_called_with(
cluster=0xEF00,
sequence=1,
data=b"\x01\x01\x00\x00\x01\x04\x02\x00\x04\x00\x00\x00\xfa",
command_id=0,
timeout=5,
expect_reply=False,
use_ieee=False,
ask_for_ack=None,
priority=t.PacketPriority.NORMAL,
)
assert status == [
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
]
111 changes: 111 additions & 0 deletions zhaquirks/tuya/ts0601_trv.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
TuyaThermostatCluster,
TuyaUserInterfaceCluster,
)
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.mcu import TuyaAttributesCluster

# info from https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/converters/common.js#L113
# and https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/converters/fromZigbee.js#L362
Expand All @@ -50,6 +52,42 @@
_LOGGER = logging.getLogger(__name__)


class TuyaThermostatSystemMode(t.enum8):
"""Tuya thermostat system mode enum."""

Auto = 0x00
Heat = 0x01
Off = 0x02


class TuyaThermostatV2(Thermostat, TuyaAttributesCluster):
"""Tuya local thermostat cluster."""

manufacturer_id_override: t.uint16_t = foundation.ZCLHeader.NO_MANUFACTURER_ID

_CONSTANT_ATTRIBUTES = {
Thermostat.AttributeDefs.ctrl_sequence_of_oper.id: Thermostat.ControlSequenceOfOperation.Heating_Only
}

def __init__(self, *args, **kwargs):
"""Init a TuyaThermostat cluster."""
super().__init__(*args, **kwargs)
self.add_unsupported_attribute(
Thermostat.AttributeDefs.setpoint_change_source.id
)
self.add_unsupported_attribute(
Thermostat.AttributeDefs.setpoint_change_source_timestamp.id
)
self.add_unsupported_attribute(Thermostat.AttributeDefs.pi_heating_demand.id)

async def write_attributes(self, attributes, manufacturer=None):
"""Overwrite to force manufacturer code."""

return await super().write_attributes(
attributes, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID
)


class SiterwellManufCluster(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of some thermostatic valves."""

Expand Down Expand Up @@ -1751,3 +1789,76 @@ def __init__(self, *args, **kwargs):
},
}
}


(
TuyaQuirkBuilder("_TZE204_ogx8u5z6", "TS0601")
.tuya_dp(
dp_id=2,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.system_mode.name,
converter=lambda x: {
TuyaThermostatSystemMode.Auto: Thermostat.SystemMode.Auto,
TuyaThermostatSystemMode.Heat: Thermostat.SystemMode.Heat,
TuyaThermostatSystemMode.Off: Thermostat.SystemMode.Off,
}[x],
dp_converter=lambda x: {
Thermostat.SystemMode.Auto: TuyaThermostatSystemMode.Auto,
Thermostat.SystemMode.Heat: TuyaThermostatSystemMode.Heat,
Thermostat.SystemMode.Off: TuyaThermostatSystemMode.Off,
}[x],
)
.tuya_dp(
dp_id=3,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.running_state.name,
converter=lambda x: 0x01 if not x else 0x00, # Heat, Idle
)
.tuya_dp(
dp_id=4,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.tuya_dp(
dp_id=5,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_dp(
dp_id=47,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature_calibration.name,
converter=lambda x: x,
dp_converter=lambda x: x + 0x100000000 if x < 0 else x,
)
.tuya_switch(
dp_id=7,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_switch(
dp_id=35,
attribute_name="frost_protection",
translation_key="frost_protection",
fallback_name="Frost protection",
)
.tuya_switch(
dp_id=39,
attribute_name="scale_protection",
translation_key="scale_protection",
fallback_name="Scale protection",
)
.tuya_binary_sensor(
dp_id=35,
attribute_name="error_or_battery_low",
translation_key="error_or_battery_low",
fallback_name="Error or battery low",
)
.adds(TuyaThermostatV2)
.skip_configuration()
.add_to_registry()
)

0 comments on commit 567660d

Please sign in to comment.