I’m trying to build a custom quirk for a Moes Thermostat ( TZE204_lpedvtvr).
I found something similar to this thermostat as a TRV. I suppose it makes sense as the Thermostat itself looks like a TRV in a standard wall mount!
I just can’t get the HVAC Action sensor.
Anyhow, here’s my quirk - with some functions working:
"""Map from manufacturer to standard clusters for thermostatic valves."""
from typing import Any
from zigpy.profiles import zha
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfTemperature, UnitOfTime
from zigpy.quirks.v2.homeassistant.binary_sensor import BinarySensorDeviceClass
from zigpy.quirks.v2.homeassistant.sensor import SensorStateClass
import zigpy.types as t
from zigpy.zcl.clusters.hvac import RunningState, Thermostat
from zhaquirks.tuya import TUYA_CLUSTER_ID
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaAttributesCluster,
TuyaMCUCluster,
)
class TuyaThermostatSystemMode(t.enum8):
"""Tuya thermostat system mode enum."""
Auto = 0x00
Heat = 0x01
Off = 0x02
class TuyaThermostatSystemModeV02(t.enum8):
"""Tuya thermostat system mode enum, auto and manual."""
Auto = 0x00
Manual = 0x02
class TuyaThermostatEcoMode(t.enum8):
"""Tuya thermostat eco mode enum."""
Comfort = 0x00
Eco = 0x01
class State(t.enum8):
"""State option."""
Off = 0x00
On = 0x01
class TuyaHysteresis(t.enum8):
"""Tuya hysteresis mode."""
Comfort = 0x00
Eco = 0x01
class TuyaPresetMode(t.enum8):
"""Tuya preset mode."""
Eco = 0x00
Auto = 0x01
Off = 0x02
Heat = 0x03
class TuyaThermostatV2(Thermostat, TuyaAttributesCluster):
"""Tuya local thermostat cluster."""
_CONSTANT_ATTRIBUTES = {
Thermostat.AttributeDefs.abs_min_heat_setpoint_limit.id: 500,
Thermostat.AttributeDefs.abs_max_heat_setpoint_limit.id: 3000,
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)
# Previously mapped, marking as explicitly unsupported.
self.add_unsupported_attribute(
Thermostat.AttributeDefs.local_temperature_calibration.id
)
self.add_unsupported_attribute(
Thermostat.AttributeDefs.min_heat_setpoint_limit.id
)
self.add_unsupported_attribute(
Thermostat.AttributeDefs.max_heat_setpoint_limit.id
)
class TuyaThermostatV2NoSchedule(TuyaThermostatV2):
"""Ensures schedule is disabled on system_mode change."""
async def write_attributes(
self,
attributes: dict[str | int, Any],
manufacturer: int | None = None,
**kwargs,
) -> list:
"""Catch attribute writes for system_mode and set schedule to off."""
results = await super().write_attributes(attributes, manufacturer)
if (
Thermostat.AttributeDefs.system_mode.id in attributes
or Thermostat.AttributeDefs.system_mode.name in attributes
):
tuya_cluster = self.endpoint.tuya_manufacturer
await tuya_cluster.write_attributes({"schedule_enable": False})
return results
(
TuyaQuirkBuilder("_TZE204_lpedvtvr", "TS0601")
# default device type is `SMART_PLUG` for this,
# so change it back to keep UID/entity the same
.replaces_endpoint(1, device_type=zha.DeviceType.THERMOSTAT)
.tuya_dp(
dp_id=2,
ep_attribute=TuyaThermostatV2NoSchedule.ep_attribute,
attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.running_state.name,
converter=lambda x: RunningState.Heat_State_On if x else RunningState.Idle,
)
.tuya_switch(
dp_id=39,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_dp(
dp_id=1,
ep_attribute=TuyaThermostatV2NoSchedule.ep_attribute,
attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.system_mode.name,
converter=lambda x: {
True: Thermostat.SystemMode.Heat,
False: Thermostat.SystemMode.Off,
}[x],
dp_converter=lambda x: {
Thermostat.SystemMode.Heat: True,
Thermostat.SystemMode.Off: False,
}[x],
)
.tuya_dp(
dp_id=16,
ep_attribute=TuyaThermostatV2NoSchedule.ep_attribute,
attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_dp(
dp_id=50,
ep_attribute=TuyaThermostatV2NoSchedule.ep_attribute,
attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.adds(TuyaThermostatV2NoSchedule)
.tuya_number(
dp_id=2,
attribute_name="hvac_action",
type=t.uint16_t,
translation_key="hvac_action",
fallback_name="HVAC Mode",
)
.tuya_number(
dp_id=34,
attribute_name="max_temperature",
type=t.uint16_t,
unit=UnitOfTemperature.CELSIUS,
min_value=15,
max_value=35,
step=1,
multiplier=0.1,
translation_key="max_temperature",
fallback_name="Max temperature",
)
.tuya_number(
dp_id=18,
attribute_name="min_temperature",
type=t.uint16_t,
unit=UnitOfTemperature.CELSIUS,
min_value=1,
max_value=15,
step=1,
multiplier=0.1,
translation_key="min_temperature",
fallback_name="Min temperature",
)
.tuya_number(
dp_id=48,
attribute_name="display_brightness",
type=t.uint16_t,
min_value=1,
max_value=100,
step=1,
translation_key="display_brightness",
fallback_name="Display brightness",
)
.tuya_number(
dp_id=113,
attribute_name="eco_temperature",
type=t.uint16_t,
unit=UnitOfTemperature.CELSIUS,
min_value=5,
max_value=30,
step=1,
multiplier=0.1,
translation_key="eco_temperature",
fallback_name="Eco temperature",
)
.skip_configuration()
.add_to_registry()
)
What I can’t work out is how to get the HVAC action setting - its under sensors in the screenshot here:
Any ideas?
Here are the ups:
{
"1": "Switch",
"2": "Work Mode",
"16": "Current temperature",
"18": "The lower limit of temperature",
"28": "Factory data reset",
"32": "Sensor selection",
"34": "Set temperature ceiling",
"39": "Child lock",
"47": "State of the valve",
"48": "Backlight brightness",
"50": "Set temperature",
"101": "Temp Calibration",
"102": "Week Program 13 1",
"103": "Week Program 13 2",
"104": "Week Program 13 3",
"105": "Week Program 13 4",
"106": "Week Program 13 5",
"107": "Week Program 13 6",
"108": "Week Program 13 7",
"109": "Floor temp.",
"110": "Dead zone temp.",
"111": "High protect temp.",
"112": "Low protection temp.",
"113": "Eco cool temp.",
"114": "Screen Time Set",
"115": "Rgblight"
}
and I’m getting these attributes:
whenever I turn the thermostat on or off from HA, I get the message shown at the bottom of the screen: