TS0601_TZE204_ltwbm23f

I have a bunch of these thermostats that work great in ZHA except for the temperature calibration. I can change (supposedly) the calibration from -6 to 6ºC but only in 1ºC increments. And when I apply 1ºC it only applies 0.1ºC.

Anyone has a fixed quirks file?

“manufacturer”: “_TZE204_ltwbm23f”,
“model”: “TS0601”,
“class”: “zigpy.quirks.v2.CustomDeviceV2”

I don’t have the quirks file, but I was curious so I took a look at the quirks file your device is using. Basically, the issue is that what is shown to you in ZHA and what the code actually does is a difference of 10x the value.

Looks like you only need to make a change in a single line in this file to get it to actually calibrate by 1 degree.

        if attribute == "occupied_heating_setpoint":
            # centidegree to decidegree
            return {SITERWELL_TARGET_TEMP_ATTR: round(value / 10)}

That (value/10) is your issue. Remove that (or change that to value/1) in your custom quirk & it should work like you expect it.

I think my device is using this quirks file.

Can I just copy it, edit the lines I want and use it?

    .tuya_number(
        dp_id=47,
        attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.local_temperature_calibration.name,
        type=t.int32s,
        min_value=-6,
        max_value=6,
        unit=UnitOfTemperature.CELSIUS,
        step=1,
        translation_key="local_temperature_calibration",
        fallback_name="Local temperature calibration",

This looks like the problematic part. If I change that min and max value to 6.0 and -6.0, the step to 0.1 and remove the type t.int32s would the problem be fixed? Or leave it as int32 but increase the max and min and use a converter. Or is this a device limitation?

How would i go about creating a simple quirk to fix just this?

I copied this file to my custom quirks folder and changed the following lines:

.tuya_number(
    dp_id=47,
    attribute_name=TuyaThermostatV2NoSchedule.AttributeDefs.local_temperature_calibration.name,
    type=t.int32s,
    min_value=-100,
    max_value=100,
    unit=UnitOfTemperature.CELSIUS,
    step=1,
    translation_key="local_temperature_calibration",
    fallback_name="Local temperature calibration",
)

In my calibration scripts I just take into account that the value is x10 and that is fine.

I copied the whole file to my quirks folder, is there a way to include only this change and leave the rest of the device description in the default location?

First things first - ignore my previous advice from yesterday. I pointed you to the wrong file and the wrong attribute :man_facepalming:

The file you pointed to should be the correct one.

You should be able to use the original values in that part of the file and simply add multiplier=1 (or 10 if that still doesn’t work). The multiplier is being used to override other number values in that file, so should work.
If it doesn’t and you decide to go for changes to the min & max value, I would recommend you use -60 & 60. That way you’ll stay within the known working range for the device & avoid errors.

Just tested with -100 and 100 and it works fine for now. My question is, can I use a quirks file with just that change? If so, how?

I’m not sure if you can. There’s other important code in that file which is required.
At the very least, you’d need everything between rows 655-845 (and probably the first 163 rows).

Hopefully someone with more up to date knowledge will chime in. I haven’t needed to mess with ZHA quirks for over 5 years, so I don’t remember, sorry.

Great, I just leave it as is for now.

BTW, i made this small script to compensate all my thermostats of this kind with external sensors. Hope this helps someone.

import appdaemon.plugins.hass.hassapi as hass
import time
from datetime import datetime
from minhas_funcoes import getFloatOrDefault 

class CalibrarTermostatos(hass.Hass): 
    jaAtualizou = {}

    CALIBRATION_ENTITIES = [
        {
            "temp_ref_id": "sensor.temperatura_sotao_1",
            "calib_offset_id": "number.termostato_sotao_local_temperature_offset",
            "thermostat_id": "climate.termostato_sotao"
        },
        {
            "temp_ref_id": "sensor.temperatura_wc",
            "calib_offset_id": "number.termostato_wc_local_temperature_offset",
            "thermostat_id": "climate.termostato_wc"
        }
    ]

    def initialize(self):
        for config in self.CALIBRATION_ENTITIES:
            self.jaAtualizou[config["calib_offset_id"]] = True 
            
            self.listen_state(
                self.temperature_change_callback, 
                config["temp_ref_id"],
                calib_config=config
            )
            
            self.listen_state(
                self.calibration_change_callback, 
                config["thermostat_id"], 
                attribute="current_temperature",
                calib_offset_id=config["calib_offset_id"]
            )
        
        self.log("CalibrarTermostatos initialized for multiple entities.")

    def calibration_change_callback(self, entity, attribute, old, new, kwargs):
        calib_offset_id = kwargs.get("calib_offset_id")
        if calib_offset_id:
            self.jaAtualizou[calib_offset_id] = True

    def temperature_change_callback(self, entity, attribute, old, new, kwargs):       
        config = kwargs.get("calib_config")
        if config:
            self.calibrar(
                config["temp_ref_id"], 
                config["calib_offset_id"], 
                config["thermostat_id"]
            )

    def calibrar(self, id_ref, id_calib, id_termostato):
        if not self.jaAtualizou.get(id_calib, False):
            return

        calib_atual = getFloatOrDefault(self, self.get_state(id_calib), 0) / 10.0
        ref_atual = getFloatOrDefault(self, self.get_state(id_ref), 0.0)
        
        temp_atual_raw = getFloatOrDefault(self, self.get_state(id_termostato, attribute="current_temperature"), 0.0)
        temp_atual = temp_atual_raw - calib_atual

        calib_nova = 0
        try:
            calib_nova = int(round((ref_atual - temp_atual_raw + calib_atual) * 10))
            
            self.jaAtualizou[id_calib] = False 

            self.call_service(
                "number/set_value",
                entity_id = id_calib,
                value = calib_nova
            )
            
        except Exception as e:
            self.error(f"Failed to set state for {id_calib}. Error: {e}")
1 Like