Skip to content

Normalized device/zone state remains empty after successful HomeKit refresh #54

@steinseifers

Description

@steinseifers

Summary

Observed behavior:

  • TadoLocal successfully pairs with the tado Internet Bridge.
  • Cloud authentication succeeds.
  • Zones and devices are discovered correctly.
  • /refresh returns valid raw HomeKit characteristic values.
  • /zones, /devices, and /thermostats keep normalized state values as null.

This causes downstream consumers, for example the Home Assistant tado_local custom integration, to create entities without usable temperature, target temperature, or humidity values.

A local patch in refresh_accessories() fixes the issue by writing refreshed HomeKit characteristic values into DeviceStateManager.


Environment

  • TadoLocal version: 1.1.2
  • Install method: Docker
  • Image: locally built from current main
  • Host: QNAP NAS / Container Station
  • Python base image: python:3.12-slim
  • tado setup:
    • Internet Bridge: IB01
    • Smart Radiator Thermostats: VA02, exposed via HomeKit as SRT01
    • Wireless Receiver / AC-related WR02 devices also present
  • Client integration:
    • Home Assistant custom integration: array81/tado-local

Steps to reproduce

  1. Build and run TadoLocal via Docker.
  2. Pair successfully with the tado Internet Bridge.
  3. Authenticate successfully with the tado Cloud API.
  4. Call:
    curl -s http://<host>:4407/status curl -s -X POST http://<host>:4407/refresh curl -s http://<host>:4407/thermostats curl -s http://<host>:4407/zones

Actual behavior

/status reports a healthy connection:

{
"status": "connected",
"version": "1.1.2",
"bridge_connected": true,
"cloud_api": {
"enabled": true,
"authenticated": true
}
}

/refresh/cloud reports successful cloud sync:

{
"home_name": "Hause Hohenhain",
"zones_synced": 7,
"devices_synced": 1,
"refreshed": [
"home_info",
"zones",
"battery_status",
"device_status"
],
"success": true
}

/refresh returns valid HomeKit characteristic values, for example:

{
"type": "00000011-0000-1000-8000-0026BB765291",
"value": 23.7,
"format": "float",
"unit": "celsius"
}

and:

{
"type": "00000035-0000-1000-8000-0026BB765291",
"value": 20,
"format": "float",
"unit": "celsius"
}

and:

{
"type": "00000010-0000-1000-8000-0026BB765291",
"value": 46,
"format": "float",
"unit": "percentage"
}

But /thermostats initially returns normalized states like this:

{
"device_id": 7,
"aid": 2,
"serial_number": "VA3201582848",
"zone_name": "Schlafzimmer",
"zone_id": 5,
"device_type": "thermostat",
"is_zone_leader": true,
"state": {
"cur_temp_c": null,
"cur_temp_f": null,
"hum_perc": null,
"target_temp_c": null,
"target_temp_f": null,
"mode": 0,
"cur_heating": null,
"valve_position": null,
"battery_low": false
}
}

The same applies to /zones: zone metadata is present, but the normalized state fields remain empty.

Relevant log lines:

Initializing device states from current values...
No characteristics found to poll for initialization
No event-capable characteristics found
Events not available, falling back to polling
No characteristics found for polling

Despite that, /refresh clearly contains readable HomeKit characteristics with valid values.

Expected behavior

After /refresh, the normalized state returned by /thermostats, /devices, and /zones should contain values derived from the refreshed HomeKit characteristics.

Example expected /thermostats state:

{
"state": {
"cur_temp_c": 23.7,
"cur_temp_f": 74.7,
"hum_perc": 46.0,
"target_temp_c": 20.0,
"target_temp_f": 68.0,
"mode": 1,
"cur_heating": 0,
"valve_position": null,
"battery_low": false
}
}

Analysis

The HomeKit characteristic UUID mapping in state.py appears to be correct:

CHAR_CURRENT_TEMPERATURE = '00000011-0000-1000-8000-0026bb765291'
CHAR_TARGET_TEMPERATURE = '00000035-0000-1000-8000-0026bb765291'
CHAR_CURRENT_HUMIDITY = '00000010-0000-1000-8000-0026bb765291'

DeviceStateManager.update_device_characteristic() also appears to correctly map these UUIDs to normalized state fields:

char_mapping = {
self.CHAR_CURRENT_TEMPERATURE: 'current_temperature',
self.CHAR_TARGET_TEMPERATURE: 'target_temperature',
self.CHAR_CURRENT_HUMIDITY: 'humidity',
}

The issue seems to be that refresh_accessories() updates self.accessories_cache, but does not populate DeviceStateManager.current_state from the refreshed HomeKit characteristic values.

Before patching, refresh_accessories() did roughly this:

self.accessories_cache = list(self.accessories_dict.values())
self.last_update = time.time()
logger.info(f"Refreshed {len(self.accessories_cache)} accessories total")
return self.accessories_cache

So /refresh returned valid raw values, but the normalized state layer used by /thermostats, /devices, and /zones was not updated.

Local workaround / proposed fix

Adding a state population step inside refresh_accessories() fixed the issue locally:

self.accessories_cache = list(self.accessories_dict.values())

Populate normalized device state from refreshed HomeKit characteristics.

timestamp = time.time()
for accessory in self.accessories_cache:
device_id = accessory.get('id')
if not device_id:
continue
for service in accessory.get('services', []):
for char in service.get('characteristics', []):
char_type = char.get('type', '').lower()
value = char.get('value')
if value is None:
continue
field_name, old_val, new_val = self.state_manager.update_device_characteristic(
device_id,
char_type,
value,
timestamp
)
if field_name:
logger.debug(f"Refreshed device {device_id} {field_name}: {old_val} -> {new_val}")
self.last_update = timestamp
logger.info(f"Refreshed {len(self.accessories_cache)} accessories total")
return self.accessories_cache

After rebuilding the Docker image and restarting the container, /thermostats returned populated state values:

{
"device_id": 7,
"aid": 2,
"serial_number": "VA3201582848",
"zone_name": "Schlafzimmer",
"zone_id": 5,
"device_type": "thermostat",
"is_zone_leader": true,
"state": {
"cur_temp_c": 23.7,
"cur_temp_f": 74.7,
"hum_perc": 46.0,
"target_temp_c": 20.0,
"target_temp_f": 68.0,
"mode": 1,
"cur_heating": 0,
"valve_position": null,
"battery_low": false
}
}

/zones also returned populated state values for heating zones:

{
"zone_id": 5,
"name": "Schlafzimmer",
"zone_type": "HEATING",
"state": {
"cur_temp_c": 23.7,
"cur_temp_f": 74.7,
"hum_perc": 46.0,
"target_temp_c": 20.0,
"target_temp_f": 68.0,
"mode": 1,
"cur_heating": 0,
"window_open": false
}
}

Notes

The WR02 / air-conditioning zones still returned null values after this patch, but that may be a separate device-type mapping issue. The patch fixed the VA02/SRT01 heating thermostat state issue and allowed Home Assistant to receive actual values.

It may be worth checking whether refresh_accessories() should always update DeviceStateManager from readable HomeKit characteristics, or whether device_to_characteristics should be populated earlier so the existing initialization/polling path can work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions