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
- Build and run TadoLocal via Docker.
- Pair successfully with the tado Internet Bridge.
- Authenticate successfully with the tado Cloud API.
- 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.
Summary
Observed behavior:
/refreshreturns valid raw HomeKit characteristic values./zones,/devices, and/thermostatskeep normalizedstatevalues asnull.This causes downstream consumers, for example the Home Assistant
tado_localcustom 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 intoDeviceStateManager.Environment
1.1.2mainpython:3.12-slimIB01VA02, exposed via HomeKit asSRT01WR02devices also presentarray81/tado-localSteps to reproduce
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.