Skip to content

Commit

Permalink
Init quirk
Browse files Browse the repository at this point in the history
  • Loading branch information
prairiesnpr committed Jan 31, 2025
1 parent 69b227a commit 08dfd2d
Showing 1 changed file with 190 additions and 31 deletions.
221 changes: 190 additions & 31 deletions zhaquirks/tuya/tuya_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import copy

from zigpy.quirks.v2 import BinarySensorDeviceClass, EntityType
from zigpy.quirks.v2 import BinarySensorDeviceClass, EntityPlatform, EntityType
from zigpy.quirks.v2.homeassistant import (
UnitOfElectricCurrent,
UnitOfElectricPotential,
Expand All @@ -20,6 +20,14 @@
from zhaquirks.tuya.mcu import TuyaAttributesCluster, TuyaMCUCluster


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

Cool = 0x00
Heat = 0x01
FanOnly = 0x02


class RegulatorPeriod(t.enum8):
"""Tuya regulator period enum."""

Expand All @@ -30,6 +38,22 @@ class RegulatorPeriod(t.enum8):
_90_min = 0x04


class ValveState(t.enum8):
"""Tuya valve current state enum."""

Open = 0x00
Closed = 0x01


class FanMode(t.enum8):
"""Tuya HVAC fan mode enum."""

Low = 0x00
Medium = 0x01
High = 0x02
Auto = 0x03


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

Expand Down Expand Up @@ -101,10 +125,6 @@ class TuyaThermostat(Thermostat, TuyaAttributesCluster):

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)
Expand All @@ -117,6 +137,22 @@ def __init__(self, *args, **kwargs):
self.add_unsupported_attribute(Thermostat.AttributeDefs.pi_heating_demand.id)


class TuyaThermostatHeatOnly(TuyaThermostat):
"""Tuya local thermostat with heat only."""

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


class TuyaThermostatHeatCool(TuyaThermostat):
"""Tuya local thermostat with heat and cool."""

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


class NoManufTimeNoVersionRespTuyaMCUCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with set_time mod."""

Expand Down Expand Up @@ -144,8 +180,8 @@ def handle_mcu_version_response(
TuyaQuirkBuilder("_TZE204_p3lqqy2r", "TS0601")
.tuya_dp(
dp_id=1,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.system_mode.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.system_mode.name,
converter=lambda x: 0x00 if not x else 0x04,
dp_converter=lambda x: x != 0x00,
)
Expand All @@ -158,21 +194,21 @@ def handle_mcu_version_response(
)
.tuya_dp(
dp_id=16,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.occupied_heating_setpoint.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.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,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.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,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.local_temperature_calibration.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
Expand Down Expand Up @@ -201,8 +237,8 @@ def handle_mcu_version_response(
)
.tuya_dp(
dp_id=104,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.running_state.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.running_state.name,
converter=lambda x: 0x00 if not x else 0x01,
)
.tuya_binary_sensor(
Expand All @@ -213,8 +249,8 @@ def handle_mcu_version_response(
)
.tuya_dp(
dp_id=107,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.max_heat_setpoint_limit.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.max_heat_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
Expand Down Expand Up @@ -243,7 +279,7 @@ def handle_mcu_version_response(
translation_key="regulator_set_point",
fallback_name="Regulator set point",
)
.adds(TuyaThermostat)
.adds(TuyaThermostatHeatOnly)
.tuya_sensor(
dp_id=120,
attribute_name="current",
Expand Down Expand Up @@ -292,22 +328,22 @@ def handle_mcu_version_response(
TuyaQuirkBuilder()
.tuya_dp(
dp_id=1,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.system_mode.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.system_mode.name,
converter=lambda x: 0x00 if not x else 0x04,
dp_converter=lambda x: x != 0x00,
)
.tuya_dp(
dp_id=2,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.occupied_heating_setpoint.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.tuya_dp(
dp_id=3,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.local_temperature.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_switch(
Expand All @@ -326,22 +362,22 @@ def handle_mcu_version_response(
)
.tuya_dp(
dp_id=15,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.max_heat_setpoint_limit.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.max_heat_setpoint_limit.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.tuya_dp(
dp_id=19,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.local_temperature_calibration.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.local_temperature_calibration.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.tuya_dp(
dp_id=101,
ep_attribute=TuyaThermostat.ep_attribute,
attribute_name=TuyaThermostat.AttributeDefs.running_state.name,
ep_attribute=TuyaThermostatHeatOnly.ep_attribute,
attribute_name=TuyaThermostatHeatOnly.AttributeDefs.running_state.name,
converter=lambda x: 0x00 if not x else 0x01,
)
.tuya_switch(
Expand Down Expand Up @@ -382,7 +418,7 @@ def handle_mcu_version_response(
translation_key="backlight_mode",
fallback_name="Backlight mode",
)
.adds(TuyaThermostat)
.adds(TuyaThermostatHeatOnly)
.skip_configuration()
)

Expand Down Expand Up @@ -449,3 +485,126 @@ def handle_mcu_version_response(
)
.add_to_registry(replacement_cluster=NoManufTimeNoVersionRespTuyaMCUCluster)
)


(
TuyaQuirkBuilder("_TZE204_mpbki2zm", "TS0601")
.tuya_onoff(dp_id=1)
.tuya_dp(
dp_id=2,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.system_mode.name,
converter=lambda x: {
TuyaThermostatSystemMode.Cool: Thermostat.SystemMode.Cool,
TuyaThermostatSystemMode.Heat: Thermostat.SystemMode.Heat,
TuyaThermostatSystemMode.FanOnly: Thermostat.SystemMode.Fan_only,
}[x],
dp_converter=lambda x: {
Thermostat.SystemMode.Cool: TuyaThermostatSystemMode.Cool,
Thermostat.SystemMode.Heat: TuyaThermostatSystemMode.Heat,
Thermostat.SystemMode.Fan_only: TuyaThermostatSystemMode.FanOnly,
}[x],
)
.tuya_switch(
dp_id=4,
attribute_name="eco_mode",
translation_key="eco_mode",
fallback_name="Eco mode",
)
.tuya_dp(
dp_id=16,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
# Uncertain if this is the correct method, since we can set both heat and cool max
.tuya_dp(
dp_id=19,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.max_heat_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_dp(
dp_id=24,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.local_temperature.name,
converter=lambda x: x * 100,
)
# Uncertain if this is the correct method, since we can set both heat and cool min
.tuya_dp(
dp_id=26,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.min_cool_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_dp(
dp_id=27,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.local_temperature_calibration.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.adds(TuyaThermostatHeatCool)
# This should be part of the climate entity, but don't think we can do that yet
.tuya_enum(
dp_id=28,
attribute_name="fan_mode",
enum_class=FanMode,
translation_key="fan_mode",
fallback_name="Fan mode",
)
.tuya_enum(
dp_id=36,
attribute_name="valve_state",
enum_class=ValveState,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.DIAGNOSTIC,
translation_key="valve_state",
fallback_name="Valve state",
)
.tuya_switch(
dp_id=40,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_switch(
dp_id=101,
attribute_name="manual_mode",
translation_key="manual_mode",
fallback_name="Manual mode",
)
.tuya_number(
dp_id=103,
attribute_name="dead_zone",
type=t.uint16_t,
unit=UnitOfTemperature.CELSIUS,
min_value=0,
max_value=5,
step=1,
translation_key="dead_zone",
fallback_name="Dead zone",
)
# Uncertain if this is the correct method, since we can set both heat and cool abs min
.tuya_dp(
dp_id=104,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.abs_min_cool_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
# Uncertain if this is the correct method, since we can set both heat and cool abs max
.tuya_dp(
dp_id=105,
ep_attribute=TuyaThermostatHeatCool.ep_attribute,
attribute_name=TuyaThermostatHeatCool.AttributeDefs.abs_max_heat_setpoint_limit.name,
converter=lambda x: x * 100,
dp_converter=lambda x: x // 100,
)
.tuya_enchantment()
.skip_configuration()
.add_to_registry()
)

0 comments on commit 08dfd2d

Please sign in to comment.