Hi there,
is there a way to get that thermostat to Supported state?
I found this on github:
Is there a way to get this into z2m - for noobs?
Hi there,
is there a way to get that thermostat to Supported state?
I found this on github:
Is there a way to get this into z2m - for noobs?
See Support new devices | Zigbee2MQTT and use the definition from github
Thanks, but I donât get it to work.
When I add that part to the z2m configuration.yaml, z2m doesnât start up anymore (I edited everything to fit my .js file)
advanced:
log_level: debug
external_converters:
- WSDCGQ01LM.js
Can you elaborate a bit further?
Forget it - I had the stupid
I donât get any debug messages in the log.
Do you have an idea why that could be?
In advanced settings on z2m I checked log level debug
log output is console first
under external converters my .js file shows up
Restarted z2mqtt
Device is still described as âunsupportedâ and debug log is not showing any message
only messages I get are those:
info 2024-11-18 14:29:24z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/0xa4c1385c0166c8f1', payload '{"last_seen":"2024-11-18T13:29:25.265Z","linkquality":81}'
Thanks for the reply, I dug a bit further and found this in the logs:
[2024-11-18 14:26:36] error: z2m: Failed to load external converter file âZBHTR20WT.jsâ (Cannot find module âzigbee-herdsman-converters/lib/extendâ
I am currently running Z2MQTT 1.41.0-1
Iâve read something about z2m Edge version, but these posts are from 2022.
The generated file says this:
Just remove the line
Hah, that did the trick.
Thank you so much!
Has anyone turned that into a ZHA configuration/quirk? Otherwise Iâll look into itâŚ
Sweet bananas! ⌠and I immediately found a bug.
Canât set the thermostat to a different temperature via UI. If you try, the set temperature jumps to 160C and the UI complains that itâs not a valid one.
That would be little hot. I donât have issues, but dont use latest quirk yet, see below code in my quirk
"""Map from manufacturer to standard clusters for thermostatic valves."""
from zigpy.profiles import zha
from zigpy.quirks.v2.homeassistant import PERCENTAGE, UnitOfTemperature
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 import foundation
from zigpy.zcl.clusters.hvac import RunningState, Thermostat
from zhaquirks.tuya.builder import TuyaQuirkBuilder
from zhaquirks.tuya.mcu import TuyaAttributesCluster
class TuyaThermostatSystemMode(t.enum8):
"""Tuya thermostat system mode enum."""
Auto = 0x00
Heat = 0x01
Off = 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 BatteryState(t.enum8):
"""Battery state option."""
Normal = 0x00
Low = 0x01
class ScheduleState(t.enum8):
"""Schedule state option."""
Disabled = 0x00
Enabled = 0x01
class TuyaThermostatV2(Thermostat, TuyaAttributesCluster):
"""Tuya local thermostat cluster."""
manufacturer_id_override: t.uint16_t = foundation.ZCLHeader.NO_MANUFACTURER_ID
_CONSTANT_ATTRIBUTES = {
Thermostat.AttributeDefs.min_heat_setpoint_limit.id: 500,
Thermostat.AttributeDefs.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)
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
)
(
TuyaQuirkBuilder("_TYST11_KGbxAXL2", "GbxAXL2")
.applies_to("_TYST11_c88teujp", "88teujp")
.applies_to("_TYST11_azqp6ssj", "zqp6ssj")
.applies_to("_TYST11_yw7cahqs", "w7cahqs")
.applies_to("_TYST11_9gvruqf5", "gvruqf5")
.applies_to("_TYST11_zuhszj9s", "uhszj9s")
.applies_to("_TYST11_caj4jz0i", "aj4jz0i")
.applies_to("_TZE200_c88teujp", "TS0601")
.applies_to("_TZE200_azqp6ssj", "TS0601")
.applies_to("_TZE200_yw7cahqs", "TS0601")
.applies_to("_TZE200_9gvruqf5", "TS0601")
.applies_to("_TZE200_zuhszj9s", "TS0601")
.applies_to("_TZE200_zr9c0day", "TS0601")
.applies_to("_TZE200_0dvm9mva", "TS0601")
.applies_to("_TZE200_h4cgnbzg", "TS0601")
.applies_to("_TZE200_exfrnlow", "TS0601")
.applies_to("_TZE200_9m4kmbfu", "TS0601")
.applies_to("_TZE200_3yp57tby", "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=3,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.running_state.name,
converter=lambda x: RunningState.Heat_State_On if x else RunningState.Idle,
)
.tuya_switch(
dp_id=8,
attribute_name="window_detection",
translation_key="window_detection",
fallback_name="Open window detection",
)
.tuya_switch(
dp_id=10,
attribute_name="frost_protection",
translation_key="frost_protection",
fallback_name="Frost protection",
)
.tuya_number(
dp_id=27,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature_calibration.name,
type=t.uint32_t,
min_value=-6,
max_value=6,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="local_temperature_calibration",
fallback_name="Local temperature calibration",
)
.tuya_switch(
dp_id=40,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_dp(
dp_id=101,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.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=102,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_dp(
dp_id=103,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.occupied_heating_setpoint.name,
converter=lambda x: x * 10,
dp_converter=lambda x: x // 10,
)
.adds(TuyaThermostatV2)
.tuya_sensor(
dp_id=104,
attribute_name="valve_position",
type=t.int16s,
divisor=10,
state_class=SensorStateClass.MEASUREMENT,
unit=PERCENTAGE,
translation_key="valve_position",
fallback_name="Valve position",
)
.tuya_binary_sensor(
dp_id=105,
attribute_name="battery_low",
device_class=BinarySensorDeviceClass.BATTERY,
fallback_name="Battery low",
)
.tuya_switch(
dp_id=106,
attribute_name="away_mode",
translation_key="away_mode",
fallback_name="Away mode",
)
.tuya_switch(
dp_id=108,
attribute_name="schedule_enable",
translation_key="schedule_enable",
fallback_name="Schedule enable",
)
.tuya_switch(
dp_id=130,
attribute_name="scale_protection",
translation_key="scale_protection",
fallback_name="Scale protection",
)
.skip_configuration()
.add_to_registry()
)
(
TuyaQuirkBuilder("_TZE204_rtrmfadk", "TS0601")
.tuya_dp(
dp_id=1,
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=2,
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=3,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_dp(
dp_id=6,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.running_state.name,
converter=lambda x: RunningState.Heat_State_On if x else RunningState.Idle,
)
.tuya_binary_sensor(
dp_id=7,
attribute_name="window_open",
device_class=BinarySensorDeviceClass.WINDOW,
fallback_name="Window open",
)
.tuya_switch(
dp_id=8,
attribute_name="window_detection",
translation_key="window_detection",
fallback_name="Open window detection",
)
.tuya_switch(
dp_id=12,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_battery(dp_id=13)
.tuya_binary_sensor(
dp_id=14,
attribute_name="error_or_battery_low",
device_class=BinarySensorDeviceClass.PROBLEM,
translation_key="error_or_battery_low",
fallback_name="Error or battery low",
)
.tuya_number(
dp_id=15,
attribute_name="min_temperature",
type=t.uint16_t,
min_value=1,
max_value=15,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="min_temperature",
fallback_name="Min temperature",
)
.tuya_number(
dp_id=16,
attribute_name="max_temperature",
type=t.uint16_t,
min_value=15,
max_value=35,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="max_temperature",
fallback_name="Max temperature",
)
.tuya_number(
dp_id=101,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature_calibration.name,
type=t.uint32_t,
min_value=-6,
max_value=6,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="local_temperature_calibration",
fallback_name="Local temperature calibration",
)
.tuya_enum(
dp_id=114,
attribute_name="eco_mode",
enum_class=TuyaThermostatEcoMode,
translation_key="eco_mode",
fallback_name="Eco mode",
)
.adds(TuyaThermostatV2)
.skip_configuration()
.add_to_registry()
)
(
TuyaQuirkBuilder("_TZE200_bvu2wnxz", "TS0601")
.applies_to("_TZE200_6rdj8dzm", "TS0601")
.applies_to("_TZE200_9xfjixap", "TS0601")
.applies_to("_TZE200_p3dbf6qs", "TS0601")
.applies_to("_TZE200_rxntag7i", "TS0601")
.applies_to("_TZE200_yqgbrdyo", "TS0601")
.applies_to("_TZE284_p3dbf6qs", "TS0601")
.applies_to("_TZE200_rxq4iti9", "TS0601")
.applies_to("_TZE200_hvaxb2tc", "TS0601")
.applies_to("_TZE284_o3x45p96", "TS0601")
.applies_to("_TZE284_c6wv4xyo", "TS0601")
.applies_to("_TZE204_o3x45p96", "TS0601")
.applies_to("_TZE204_ogx8u5z6", "TS0601")
.applies_to("_TZE284_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: RunningState.Heat_State_On if not x else RunningState.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_number(
dp_id=47,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature_calibration.name,
type=t.uint32_t,
min_value=-6,
max_value=6,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="local_temperature_calibration",
fallback_name="Local temperature calibration",
)
.tuya_switch(
dp_id=7,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_binary_sensor(
dp_id=35,
attribute_name="error_or_battery_low",
device_class=BinarySensorDeviceClass.PROBLEM,
translation_key="error_or_battery_low",
fallback_name="Error or battery low",
)
.tuya_switch(
dp_id=36,
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",
)
.adds(TuyaThermostatV2)
.skip_configuration()
.add_to_registry()
)
(
TuyaQuirkBuilder("_TZE200_ne4pikwm", "TS0601") # Nedis ZBHTR20WT
.applies_to("_TZE284_ne4pikwm", "TS0601")
.tuya_dp(
dp_id=3,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.running_state.name,
converter=lambda x: RunningState.Heat_State_On if x else RunningState.Idle,
)
.tuya_switch(
dp_id=8,
attribute_name="window_detection",
translation_key="window_detection",
fallback_name="Open window detection",
)
.tuya_switch(
dp_id=10,
attribute_name="frost_protection",
translation_key="frost_protection",
fallback_name="Frost protection",
)
.tuya_number(
dp_id=27,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature_calibration.name,
type=t.uint32_t,
min_value=-6,
max_value=6,
unit=UnitOfTemperature.CELSIUS,
step=1,
translation_key="local_temperature_calibration",
fallback_name="Local temperature calibration",
)
.tuya_switch(
dp_id=40,
attribute_name="child_lock",
translation_key="child_lock",
fallback_name="Child lock",
)
.tuya_dp(
dp_id=101,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.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=102,
ep_attribute=TuyaThermostatV2.ep_attribute,
attribute_name=TuyaThermostatV2.AttributeDefs.local_temperature.name,
converter=lambda x: x * 10,
)
.tuya_dp(
dp_id=103,
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_binary_sensor(
dp_id=105,
attribute_name="battery_low",
device_class=BinarySensorDeviceClass.BATTERY,
fallback_name="Battery low",
)
.tuya_switch(
dp_id=106,
attribute_name="leave_home",
translation_key="leave_home",
fallback_name="Leave home",
)
.tuya_switch(
dp_id=108,
attribute_name="schedule_mode",
translation_key="schedule_mode",
fallback_name="Schedule mode",
)
.tuya_switch(
dp_id=130,
attribute_name="scale_protection",
translation_key="scale_protection",
fallback_name="Scale protection",
)
.adds(TuyaThermostatV2)
.skip_configuration()
.add_to_registry()
)
I can compare versions later. Iâve deployed the official ZHA device handlers release 0.0.134.
You quirk helped to find the issue: Itâs this:
- Thermostat.AttributeDefs.abs_min_heat_setpoint_limit.id: 500,
- Thermostat.AttributeDefs.abs_max_heat_setpoint_limit.id: 3000,
+ Thermostat.AttributeDefs.min_heat_setpoint_limit.id: 500,
+ Thermostat.AttributeDefs.max_heat_setpoint_limit.id: 3000,
Iâve replied to the issue. The abs
attributes should be used for the constant attributes. So, the upstream quirk is correct in that regard.
If you were using custom quirks before, you may need to completely remove and re-pair the device to clear attribute cache, as the non-abs values may be cached. They should not be set by the quirk.
At least currently, I donât see an issue with the quirk, but weâll investigate further in the issue. Please attach device diagnostics there.
In general, custom quirks are only meant to be temporarily used for testing.
If you want to have the new upstream quirks, you should try the Home Assistant Core 2025.3.0 betas, as they include the latest ZHA library (with the latest zigpy and zha-quirks 0.0.134). The full HA 2025.3.0 version will release in about two days, but the betas are already pretty stable at this point.
I have no problem waiting for the full release. (I usually donât upgrade to .0 releases but in this case Iâll make an exception )
It was a rather weird timely coincidence that I bought a full set of those TRVs a week ago, hoping that they would already be supported but apparently were just being added so I had a time constraint to either getting them (close) to working or sending them back to not miss the return window and ending up with expensive paper weightsâŚ
Nice, i had exactly the same timing for getting them work or sending it back
glad i came quick to something working with all the efforts others already made