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 Namron thermostat _TZE204_p3lqqy2r #3625

Merged
merged 15 commits into from
Dec 22, 2024
71 changes: 71 additions & 0 deletions tests/test_tuya_thermostat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Tests for Tuya Smoke Detector."""
TheJulianJES marked this conversation as resolved.
Show resolved Hide resolved

import pytest
from zigpy.zcl import foundation
from zigpy.zcl.clusters.hvac import Thermostat

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

zhaquirks.setup()


@pytest.mark.parametrize(
"msg,attr,value",
[
(
b"\t\x13\x02\x00\x06\x01\x01\x00\x01\x01",
Thermostat.AttributeDefs.system_mode,
Thermostat.SystemMode.Heat,
), # Set to heat, dp 1
(
b"\t\x16\x02\x00\t\x18\x02\x00\x04\x00\x00\x00\x18",
Thermostat.AttributeDefs.local_temperature,
2400,
), # Current temp 24, dp 24
(
b"\t\x15\x02\x00\x08\x10\x02\x00\x04\x00\x00\x00\x19",
Thermostat.AttributeDefs.occupied_heating_setpoint,
2500,
), # Setpoint to 25, dp 16
(
b"\t\x17\x02\x00\n\x1c\x02\x00\x04\x00\x00\x00\x00",
Thermostat.AttributeDefs.local_temperature_calibration,
0,
), # Local calibration to 0, dp 28
(
b"\t\x1c\x02\x00\x0fh\x01\x00\x01\x01",
Thermostat.AttributeDefs.running_state,
Thermostat.RunningState.Heat_State_On,
), # Running state, dp 104
(
b"\t\x1d\x02\x00\x10k\x02\x00\x04\x00\x00\x00\x1b",
Thermostat.AttributeDefs.max_heat_setpoint_limit,
2700,
), # Max heat set point, dp 107
],
)
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_p3lqqy2r", "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
221 changes: 221 additions & 0 deletions zhaquirks/tuya/ts0601_thermostat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
"""Tuya TS0601 Thermostat."""

from zigpy.quirks.v2.homeassistant import (
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfTemperature,
)
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
from zigpy.types import t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.hvac import Thermostat

from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.mcu import TuyaAttributesCluster


class RegulatorPeriod(t.enum8):
"""Tuya Regulator Period enum."""

FifteenMin = 0x00
ThirtyMin = 0x01
FortyFiveMin = 0x02
SixtyMin = 0x03
NinetyMin = 0x04


class ThermostatMode(t.enum8):
"""Tuya Thermostat mode."""

Regulator = 0x00
Thermostat = 0x01


class PresetMode(t.enum8):
"""Tuya PresetMode enum."""

Manual = 0x00
Home = 0x01
Away = 0x02


class SensorMode(t.enum8):
"""Tuya SensorMode enum."""

Air = 0x00
Floor = 0x01
Both = 0x02


class TuyaThermostat(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)
prairiesnpr marked this conversation as resolved.
Show resolved Hide resolved


(
TuyaQuirkBuilder("_TZE204_p3lqqy2r", "TS0601")
.tuya_dp(
dp_id=1,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.system_mode.name,
converter=lambda x: 0x00 if not x else 0x04,
dp_converter=lambda x: x != 0x00,
)
.tuya_enum(
dp_id=2,
attribute_name="preset_mode",
enum_class=PresetMode,
translation_key="preset_mode",
fallback_name="Preset mode",
)
.tuya_dp(
dp_id=16,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_dp(
dp_id=24,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.local_temperature.name,
converter=lambda x: x * 100,
)
.tuya_dp(
dp_id=28,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=Thermostat.AttributeDefs.local_temperature_calibration.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_switch(
dp_id=30,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_sensor(
dp_id=101,
attribute_name="local_temperature_floor",
type=t.int16s,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
unit=UnitOfTemperature.CELSIUS,
translation_key="local_temperature_floor",
fallback_name="Floor temperature",
)
.tuya_enum(
dp_id=102,
attribute_name="temperature_sensor_select",
enum_class=SensorMode,
translation_key="sensor_mode",
fallback_name="Sensor mode",
)
.tuya_dp(
dp_id=104,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.running_state.name,
converter=lambda x: 0x00 if not x else 0x01,
)
.tuya_binary_sensor(
dp_id=106,
attribute_name="window_detection",
translation_key="window_detection",
fallback_name="Open window detection",
)
.tuya_dp(
dp_id=107,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.max_heat_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_enum(
dp_id=108,
attribute_name="thermostat_mode",
enum_class=ThermostatMode,
translation_key="thermostat_mode",
fallback_name="Mode",
)
.tuya_enum(
dp_id=109,
attribute_name="regulator_period",
enum_class=RegulatorPeriod,
translation_key="regulator_period",
fallback_name="Regulator period",
)
.tuya_number(
dp_id=110,
attribute_name="regulator_set_point",
type=t.uint16_t,
unit=UnitOfTemperature.CELSIUS,
min_value=0,
max_value=100,
step=1,
translation_key="regulator_set_point",
fallback_name="Regulator set point",
)
.adds(TuyaThermostat)
.tuya_sensor(
dp_id=120,
attribute_name="current",
type=t.int16s,
divisor=10,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
unit=UnitOfElectricCurrent.AMPERE,
translation_key="current",
fallback_name="Current",
)
.tuya_sensor(
dp_id=121,
attribute_name="voltage",
type=t.int16s,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
unit=UnitOfElectricPotential.VOLT,
translation_key="voltage",
fallback_name="Voltage",
)
.tuya_sensor(
dp_id=122,
attribute_name="power",
type=t.int16s,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
unit=UnitOfPower.WATT,
translation_key="power",
fallback_name="Power",
)
.tuya_sensor(
dp_id=123,
attribute_name="energy",
type=t.int16s,
divisor=100,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL,
unit=UnitOfEnergy.KILO_WATT_HOUR,
translation_key="energy",
fallback_name="Energy",
)
Comment on lines +177 to +218
Copy link
Collaborator

@TheJulianJES TheJulianJES Dec 23, 2024

Choose a reason for hiding this comment

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

uh, I missed this too: I think we do not want a translation_key for these, as HA will use the device_class name by default.
We should remove the translation_key for these four sensors.

EDIT: See #3630

.skip_configuration()
.add_to_registry()
)
Loading