A Zigbee Thermostat That Lies: Moving a TYBAC-006 Fan Coil Stat off Tuya Cloud
121 AC button presses in 8 days proved my wall thermostat's reading was useless. Moving to a Zigbee TYBAC-006 fixed the cloud problem and brought four new quirks.
HomeLab, Smart home. Updated . 6 min read.
My wall thermostat read the same temperature when I was too hot and when I was too cold. Over 8 days I pressed the AC buttons 121 times, and at almost every press, boost or pause, the stat said 24.5 to 25.0 C.
That is how a small "make the AC less annoying" job turned into leaving the Tuya cloud, flashing a Zigbee dongle, and learning four ways a cheap fan coil thermostat can quietly do the wrong thing.
TL;DR: the wall stat is a control input, not a comfort sensor. Put a real sensor where the person is. If you move a TYBAC-006 to Zigbee2MQTT, expect a deadzone that only takes whole numbers, a factory schedule that takes over after a power cycle, a wall-box offset of about 3 C, and a lockup that looks healthy from the outside. Each one has a cheap guard.
121 presses and one useless number
The apartment has one ducted fan coil unit: one valve, one fan, two outlets. Home Assistant drives it with two buttons: boost (cool hard for a few minutes) and pause (AC off for a while). I pulled 8 days out of the logbook:
| Action | Presses |
|---|---|
| Pause | 67 |
| Boost | 54 |
| Boost/pause flip-flops | 13 (3 of them same-minute misclicks) |
The pattern was clear once I looked. Cold at the PC during the day, so pause chains: 7 pauses of 10 minutes back to back. Hot in bed at night, so a boost every 1 to 2 hours.
The thermostat couldn't tell those apart. It read 24.5 to 25.0 C at both kinds of press. An ESP32 air quality board on the desk could: median 25.6 C at boost, 24.7 C at pause. That's roughly a 1 C comfort band, and the stat's reading sat right in the middle of it.
The reason is where it hangs. The stat is mounted next to the bedroom door, right beside the living room outlet, in a wall box. Its reading follows the cold air coming out of the grille, not the room.
Getting off the Tuya cloud first
The old stat was a WiFi Tuya device on a community fork of the Tuya integration ("Tuya Climate Multiplier Edit", a March 2025 copy of core Tuya with a temperature scale fix). 21 devices rode on it.
Then its MQTT thread died:
API_QPS_LIMIT_OR_DEGRADE
climate.ac_thermostat froze in Home Assistant. Commands still went out, but the state never came back, so every automation was reading a stale value. A homeassistant.reload_config_entry fixed it for the moment. The same failure could come back any day, so I stopped relying on the cloud.
I moved every WiFi device to Tuya Local (the make-all project, from HACS). The local keys came out of the cloud account once, through a script run inside the container. The trick that kept dashboards, automations and energy stats intact was an entity swap:
- Rename the cloud entity to
<id>_cloudand disable it. - Rename the new local entity (
<id>_2) to the old id.
Nothing that referenced climate.ac_thermostat had to change. The one edit was hvac_mode: heat_cool to cool in the AC scripts and automations. Then the fork was removed.
That fixed the cloud. It didn't fix the stat.
Moving to Zigbee
The replacement is a TYBAC-006, 2-pipe, Zigbee, mains powered (_TZE204_mpbki2zm, a Tuya TS0601 device). ZHA created no entities for it and Zigbee2MQTT supports it, so the Zigbee side moved too: the Sonoff Dongle Plus V2 got EmberZNet 8.0.3 flashed in place, and Zigbee2MQTT replaced ZHA.
After pairing I renamed it AC Thermostat in Zigbee2MQTT, set control_sequence_of_operation: cooling_only, and did the same swap trick. The WiFi twin became climate.ac_thermostat_wifi, disabled. The Zigbee entity took climate.ac_thermostat. No automation edits, because the scripts only ever used cool, off and set_temperature.
Boost, pause and resume worked on the first test. The valve reported OPEN under a cooling call and CLOSE when off. Then the quirks showed up.
Quirk 1: the deadzone takes whole numbers only
I wanted a 1.5 C deadzone. The device stores whole numbers only, and 1.5 gets stored as 256. With that value the valve never opens. You don't get an error, just a room that never cools.
Only 1 and 2 work. I measured 2 for one cycle: 7 minutes open, 43 minutes closed (14% duty), and a 1.2 C room swing. Worse. It stays at 1.
Quirk 2: a power cycle hands control to a hidden schedule
At 23:00 on the first night, after a power cycle, the stat ignored every setpoint. manual_mode had gone off, so it was following its factory weekly schedule. That schedule is 20.0 C in every slot.
So every target Home Assistant sent was ignored in favor of 20 C, in the middle of the night. The fix was switch.ac_thermostat_manual_mode back on. Two guards stop it from happening again:
automation.ac_thermostat_manual_mode_watchdog: if manual mode is off for 30 seconds, turn it back on and notify my phone.- The schedule slots now hold 25.5 C instead of 20.0, so even a fallback lands on a safe target.
Quirk 3: the wall box runs 3 C hot
The new stat has the old one's problem, because it hangs in the same spot. Raw, it read about 27.5 C while a bedroom sensor said 25.5.
Before trusting a calibration I checked the sensor itself. A hair dryer gave +1.5 C in 45 seconds, reports came every 15 seconds, and it sent a fresh value 40 seconds after a power cycle. The sensor is fine. The box is warm.
With a Zigbee temperature sensor held in front of the stat, -3 calibration gave a 0.0 to 0.4 C delta. It stays at -3. The offset still drifts with the day, though: 0.3 C below the bed at 23:00, 1.5 C above it at 07:00. No fixed calibration fixes a number that moves like that.
Quirk 4: a lockup that looks healthy
This one is the nastiest. The stat locked up with no Home Assistant action before it. It stayed available in Zigbee2MQTT, so every availability check was green. But it sent one frozen report (cool, 25.5 C, valve OPEN) about 5 times per second and ignored every write. Normal traffic is about 8 reports per minute.
The valve report was frozen too. The room rose to 26 C, so the valve was probably shut while the stat claimed it was open. Every boost and pause failed its retries and sent a phone notice.
Recovery took two steps:
- Breaker off and on. The flood stopped and live values came back.
- The stat then accepted a 20 C target but kept the valve closed for more than 4 minutes (normal is 1 to 15 seconds). An off and on of
climate.ac_thermostatfrom Home Assistant made it open.
Availability can't catch this, so the watchdog watches the report rate:
| Helper | What it does |
|---|---|
sensor.ac_thermostat_report_rate | Statistics helper: count of sensor.ac_thermostat_linkquality changes over 10 minutes |
binary_sensor.ac_thermostat_lockup | Template helper, device class problem, on above 400 |
Uptime Kuma AC Thermostat Lockup | Alerts on Telegram when the binary sensor is not off |
Normal is 60 to 100 per 10 minutes. The lockup produced 1,500 to 2,100. A threshold of 400 sits far from both.
Letting the bedroom sensor decide at night
The desk board settled the comfort question for the day. For the night, a Sonoff SNZB-02D sits in the bedroom, and the Sleep Comfort Guard uses it instead of the stat:
- Bedroom at or below 24.8 C: set the 26 C rest target.
- Bedroom at or above 25.8 C: set the 24 C cooling target.
- Both limits include equality and need to hold for one minute.
The stat still does the actual on and off switching, so if Home Assistant dies, the apartment still gets cooled. Home Assistant only moves the target around.
The part that took two tries was freshness. My first check used the temperature's last_reported, and it raised false alarms, because MQTT skips the write when the value hasn't changed. One morning it fell back to 25 C even though a Zigbee message had arrived 67 seconds earlier. Now it uses Zigbee2MQTT's last_seen: no contact for 20 minutes while cooling, or 60 while resting, and it falls back to input_number.ac_sleep_setpoint and notifies my phone.
The stat's reading does one job now: it switches the valve. The comfort decisions come from sensors that sit where I do.
Questions
- Why does the TYBAC-006 ignore a 1.5 deadzone?
- The device stores whole numbers only. Writing 1.5 ends up stored as 256, and with that deadzone the valve never opens. Use 1 or 2.
- Why does my TYBAC-006 go back to 20 C after a power cut?
- After a power cycle manual_mode can turn off, and the stat then follows its factory weekly schedule, which is 20.0 C in every slot. Turn manual_mode back on, guard it with an automation, and rewrite the schedule slots to a safe value.
- How do I detect a Zigbee thermostat that is locked up but still available?
- Count its reports. A Statistics helper that counts linkquality changes over 10 minutes read 60 to 100 normally and 1,500 to 2,100 during a lockup. A template binary sensor that turns on above 400 catches it.
- Is a wall thermostat reading a good comfort signal?
- Not when it sits next to an AC outlet in a wall box. Mine read 24.5 to 25.0 C both when I asked for more cooling and when I asked for less. A separate air quality board showed a clear 1 C gap between those two moments.