mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Add select for heating circuit to Tado zones (#147902)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
parent
d774de79db
commit
bc0162cf85
@ -41,6 +41,7 @@ PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.CLIMATE,
|
||||
Platform.DEVICE_TRACKER,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.WATER_HEATER,
|
||||
|
@ -73,6 +73,8 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
"weather": {},
|
||||
"geofence": {},
|
||||
"zone": {},
|
||||
"zone_control": {},
|
||||
"heating_circuits": {},
|
||||
}
|
||||
|
||||
@property
|
||||
@ -99,11 +101,14 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
self.home_name = tado_home["name"]
|
||||
|
||||
devices = await self._async_update_devices()
|
||||
zones = await self._async_update_zones()
|
||||
zones, zone_controls = await self._async_update_zones()
|
||||
home = await self._async_update_home()
|
||||
heating_circuits = await self._async_update_heating_circuits()
|
||||
|
||||
self.data["device"] = devices
|
||||
self.data["zone"] = zones
|
||||
self.data["zone_control"] = zone_controls
|
||||
self.data["heating_circuits"] = heating_circuits
|
||||
self.data["weather"] = home["weather"]
|
||||
self.data["geofence"] = home["geofence"]
|
||||
|
||||
@ -166,7 +171,7 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
|
||||
return mapped_devices
|
||||
|
||||
async def _async_update_zones(self) -> dict[int, dict]:
|
||||
async def _async_update_zones(self) -> tuple[dict[int, dict], dict[int, dict]]:
|
||||
"""Update the zone data from Tado."""
|
||||
|
||||
try:
|
||||
@ -179,10 +184,12 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
raise UpdateFailed(f"Error updating Tado zones: {err}") from err
|
||||
|
||||
mapped_zones: dict[int, dict] = {}
|
||||
mapped_zone_controls: dict[int, dict] = {}
|
||||
for zone in zone_states:
|
||||
mapped_zones[int(zone)] = await self._update_zone(int(zone))
|
||||
mapped_zone_controls[int(zone)] = await self._update_zone_control(int(zone))
|
||||
|
||||
return mapped_zones
|
||||
return mapped_zones, mapped_zone_controls
|
||||
|
||||
async def _update_zone(self, zone_id: int) -> dict[str, str]:
|
||||
"""Update the internal data of a zone."""
|
||||
@ -199,6 +206,24 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
_LOGGER.debug("Zone %s updated, with data: %s", zone_id, data)
|
||||
return data
|
||||
|
||||
async def _update_zone_control(self, zone_id: int) -> dict[str, Any]:
|
||||
"""Update the internal zone control data of a zone."""
|
||||
|
||||
_LOGGER.debug("Updating zone control for zone %s", zone_id)
|
||||
try:
|
||||
zone_control_data = await self.hass.async_add_executor_job(
|
||||
self._tado.get_zone_control, zone_id
|
||||
)
|
||||
except RequestException as err:
|
||||
_LOGGER.error(
|
||||
"Error updating Tado zone control for zone %s: %s", zone_id, err
|
||||
)
|
||||
raise UpdateFailed(
|
||||
f"Error updating Tado zone control for zone {zone_id}: {err}"
|
||||
) from err
|
||||
|
||||
return zone_control_data
|
||||
|
||||
async def _async_update_home(self) -> dict[str, dict]:
|
||||
"""Update the home data from Tado."""
|
||||
|
||||
@ -217,6 +242,23 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
|
||||
return {"weather": weather, "geofence": geofence}
|
||||
|
||||
async def _async_update_heating_circuits(self) -> dict[str, dict]:
|
||||
"""Update the heating circuits data from Tado."""
|
||||
|
||||
try:
|
||||
heating_circuits = await self.hass.async_add_executor_job(
|
||||
self._tado.get_heating_circuits
|
||||
)
|
||||
except RequestException as err:
|
||||
_LOGGER.error("Error updating Tado heating circuits: %s", err)
|
||||
raise UpdateFailed(f"Error updating Tado heating circuits: {err}") from err
|
||||
|
||||
mapped_heating_circuits: dict[str, dict] = {}
|
||||
for circuit in heating_circuits:
|
||||
mapped_heating_circuits[circuit["driverShortSerialNo"]] = circuit
|
||||
|
||||
return mapped_heating_circuits
|
||||
|
||||
async def get_capabilities(self, zone_id: int | str) -> dict:
|
||||
"""Fetch the capabilities from Tado."""
|
||||
|
||||
@ -364,6 +406,20 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
except RequestException as exc:
|
||||
raise HomeAssistantError(f"Error setting Tado child lock: {exc}") from exc
|
||||
|
||||
async def set_heating_circuit(self, zone_id: int, circuit_id: int | None) -> None:
|
||||
"""Set heating circuit for zone."""
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
self._tado.set_zone_heating_circuit,
|
||||
zone_id,
|
||||
circuit_id,
|
||||
)
|
||||
except RequestException as exc:
|
||||
raise HomeAssistantError(
|
||||
f"Error setting Tado heating circuit: {exc}"
|
||||
) from exc
|
||||
await self._update_zone_control(zone_id)
|
||||
|
||||
|
||||
class TadoMobileDeviceUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
"""Class to manage the mobile devices from Tado via PyTado."""
|
||||
|
108
homeassistant/components/tado/select.py
Normal file
108
homeassistant/components/tado/select.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""Module for Tado select entities."""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import TadoConfigEntry
|
||||
from .entity import TadoDataUpdateCoordinator, TadoZoneEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NO_HEATING_CIRCUIT_OPTION = "no_heating_circuit"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: TadoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Tado select platform."""
|
||||
|
||||
tado = entry.runtime_data.coordinator
|
||||
entities: list[SelectEntity] = [
|
||||
TadoHeatingCircuitSelectEntity(tado, zone["name"], zone["id"])
|
||||
for zone in tado.zones
|
||||
if zone["type"] == "HEATING"
|
||||
]
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class TadoHeatingCircuitSelectEntity(TadoZoneEntity, SelectEntity):
|
||||
"""Representation of a Tado heating circuit select entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_has_entity_name = True
|
||||
_attr_icon = "mdi:water-boiler"
|
||||
_attr_translation_key = "heating_circuit"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TadoDataUpdateCoordinator,
|
||||
zone_name: str,
|
||||
zone_id: int,
|
||||
) -> None:
|
||||
"""Initialize the Tado heating circuit select entity."""
|
||||
super().__init__(zone_name, coordinator.home_id, zone_id, coordinator)
|
||||
|
||||
self._attr_unique_id = f"{zone_id} {coordinator.home_id} heating_circuit"
|
||||
|
||||
self._attr_options = []
|
||||
self._attr_current_option = None
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Update the selected heating circuit."""
|
||||
heating_circuit_id = (
|
||||
None
|
||||
if option == NO_HEATING_CIRCUIT_OPTION
|
||||
else self.coordinator.data["heating_circuits"].get(option, {}).get("number")
|
||||
)
|
||||
await self.coordinator.set_heating_circuit(self.zone_id, heating_circuit_id)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._async_update_callback()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@callback
|
||||
def _async_update_callback(self) -> None:
|
||||
"""Handle update callbacks."""
|
||||
# Heating circuits list
|
||||
heating_circuits = self.coordinator.data["heating_circuits"].values()
|
||||
self._attr_options = [NO_HEATING_CIRCUIT_OPTION]
|
||||
self._attr_options.extend(hc["driverShortSerialNo"] for hc in heating_circuits)
|
||||
|
||||
# Current heating circuit
|
||||
zone_control = self.coordinator.data["zone_control"].get(self.zone_id)
|
||||
if zone_control and "heatingCircuit" in zone_control:
|
||||
heating_circuit_number = zone_control["heatingCircuit"]
|
||||
if heating_circuit_number is None:
|
||||
self._attr_current_option = NO_HEATING_CIRCUIT_OPTION
|
||||
else:
|
||||
# Find heating circuit by number
|
||||
heating_circuit = next(
|
||||
(
|
||||
hc
|
||||
for hc in heating_circuits
|
||||
if hc.get("number") == heating_circuit_number
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if heating_circuit is None:
|
||||
_LOGGER.error(
|
||||
"Heating circuit with number %s not found for zone %s",
|
||||
heating_circuit_number,
|
||||
self.zone_name,
|
||||
)
|
||||
self._attr_current_option = NO_HEATING_CIRCUIT_OPTION
|
||||
else:
|
||||
self._attr_current_option = heating_circuit.get(
|
||||
"driverShortSerialNo"
|
||||
)
|
@ -59,6 +59,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"heating_circuit": {
|
||||
"name": "Heating circuit",
|
||||
"state": {
|
||||
"no_heating_circuit": "No circuit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"child_lock": {
|
||||
"name": "Child lock"
|
||||
|
7
tests/components/tado/fixtures/heating_circuits.json
Normal file
7
tests/components/tado/fixtures/heating_circuits.json
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"number": 1,
|
||||
"driverSerialNo": "RU1234567890",
|
||||
"driverShortSerialNo": "RU1234567890"
|
||||
}
|
||||
]
|
80
tests/components/tado/fixtures/zone_control.json
Normal file
80
tests/components/tado/fixtures/zone_control.json
Normal file
@ -0,0 +1,80 @@
|
||||
{
|
||||
"type": "HEATING",
|
||||
"earlyStartEnabled": false,
|
||||
"heatingCircuit": 1,
|
||||
"duties": {
|
||||
"type": "HEATING",
|
||||
"leader": {
|
||||
"deviceType": "RU01",
|
||||
"serialNo": "RU1234567890",
|
||||
"shortSerialNo": "RU1234567890",
|
||||
"currentFwVersion": "54.20",
|
||||
"connectionState": {
|
||||
"value": true,
|
||||
"timestamp": "2025-06-30T19:53:40.710Z"
|
||||
},
|
||||
"characteristics": {
|
||||
"capabilities": ["INSIDE_TEMPERATURE_MEASUREMENT", "IDENTIFY"]
|
||||
},
|
||||
"batteryState": "NORMAL"
|
||||
},
|
||||
"drivers": [
|
||||
{
|
||||
"deviceType": "VA01",
|
||||
"serialNo": "VA1234567890",
|
||||
"shortSerialNo": "VA1234567890",
|
||||
"currentFwVersion": "54.20",
|
||||
"connectionState": {
|
||||
"value": true,
|
||||
"timestamp": "2025-06-30T19:54:15.166Z"
|
||||
},
|
||||
"characteristics": {
|
||||
"capabilities": ["INSIDE_TEMPERATURE_MEASUREMENT", "IDENTIFY"]
|
||||
},
|
||||
"mountingState": {
|
||||
"value": "CALIBRATED",
|
||||
"timestamp": "2025-06-09T23:25:12.678Z"
|
||||
},
|
||||
"mountingStateWithError": "CALIBRATED",
|
||||
"batteryState": "LOW",
|
||||
"childLockEnabled": false
|
||||
}
|
||||
],
|
||||
"uis": [
|
||||
{
|
||||
"deviceType": "RU01",
|
||||
"serialNo": "RU1234567890",
|
||||
"shortSerialNo": "RU1234567890",
|
||||
"currentFwVersion": "54.20",
|
||||
"connectionState": {
|
||||
"value": true,
|
||||
"timestamp": "2025-06-30T19:53:40.710Z"
|
||||
},
|
||||
"characteristics": {
|
||||
"capabilities": ["INSIDE_TEMPERATURE_MEASUREMENT", "IDENTIFY"]
|
||||
},
|
||||
"batteryState": "NORMAL"
|
||||
},
|
||||
{
|
||||
"deviceType": "VA01",
|
||||
"serialNo": "VA1234567890",
|
||||
"shortSerialNo": "VA1234567890",
|
||||
"currentFwVersion": "54.20",
|
||||
"connectionState": {
|
||||
"value": true,
|
||||
"timestamp": "2025-06-30T19:54:15.166Z"
|
||||
},
|
||||
"characteristics": {
|
||||
"capabilities": ["INSIDE_TEMPERATURE_MEASUREMENT", "IDENTIFY"]
|
||||
},
|
||||
"mountingState": {
|
||||
"value": "CALIBRATED",
|
||||
"timestamp": "2025-06-09T23:25:12.678Z"
|
||||
},
|
||||
"mountingStateWithError": "CALIBRATED",
|
||||
"batteryState": "LOW",
|
||||
"childLockEnabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -62,6 +62,13 @@
|
||||
'presence': 'HOME',
|
||||
'presenceLocked': False,
|
||||
}),
|
||||
'heating_circuits': dict({
|
||||
'RU1234567890': dict({
|
||||
'driverSerialNo': 'RU1234567890',
|
||||
'driverShortSerialNo': 'RU1234567890',
|
||||
'number': 1,
|
||||
}),
|
||||
}),
|
||||
'weather': dict({
|
||||
'outsideTemperature': dict({
|
||||
'celsius': 7.46,
|
||||
@ -110,6 +117,560 @@
|
||||
'repr': "TadoZone(zone_id=6, current_temp=24.3, connection=None, current_temp_timestamp='2024-06-28T22: 23: 15.679Z', current_humidity=70.9, current_humidity_timestamp='2024-06-28T22: 23: 15.679Z', is_away=False, current_hvac_action='HEATING', current_fan_speed='AUTO', current_fan_level='LEVEL3', current_hvac_mode='HEAT', current_swing_mode='OFF', current_vertical_swing_mode='ON', current_horizontal_swing_mode='ON', target_temp=25.0, available=True, power='ON', link='ONLINE', ac_power_timestamp='2022-07-13T18: 06: 58.183Z', heating_power_timestamp=None, ac_power='ON', heating_power=None, heating_power_percentage=None, tado_mode='HOME', overlay_termination_type='MANUAL', overlay_termination_timestamp=None, default_overlay_termination_type='MANUAL', default_overlay_termination_duration=None, preparation=False, open_window=False, open_window_detected=False, open_window_attr={}, precision=0.1)",
|
||||
}),
|
||||
}),
|
||||
'zone_control': dict({
|
||||
'1': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
'2': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
'3': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
'4': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
'5': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
'6': dict({
|
||||
'duties': dict({
|
||||
'drivers': list([
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
'leader': dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
'type': 'HEATING',
|
||||
'uis': list([
|
||||
dict({
|
||||
'batteryState': 'NORMAL',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:53:40.710Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'RU01',
|
||||
'serialNo': 'RU1234567890',
|
||||
'shortSerialNo': 'RU1234567890',
|
||||
}),
|
||||
dict({
|
||||
'batteryState': 'LOW',
|
||||
'characteristics': dict({
|
||||
'capabilities': list([
|
||||
'INSIDE_TEMPERATURE_MEASUREMENT',
|
||||
'IDENTIFY',
|
||||
]),
|
||||
}),
|
||||
'childLockEnabled': False,
|
||||
'connectionState': dict({
|
||||
'timestamp': '2025-06-30T19:54:15.166Z',
|
||||
'value': True,
|
||||
}),
|
||||
'currentFwVersion': '54.20',
|
||||
'deviceType': 'VA01',
|
||||
'mountingState': dict({
|
||||
'timestamp': '2025-06-09T23:25:12.678Z',
|
||||
'value': 'CALIBRATED',
|
||||
}),
|
||||
'mountingStateWithError': 'CALIBRATED',
|
||||
'serialNo': 'VA1234567890',
|
||||
'shortSerialNo': 'VA1234567890',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
'earlyStartEnabled': False,
|
||||
'heatingCircuit': 1,
|
||||
'type': 'HEATING',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'mobile_devices': dict({
|
||||
'mobile_device': dict({
|
||||
|
91
tests/components/tado/test_select.py
Normal file
91
tests/components/tado/test_select.py
Normal file
@ -0,0 +1,91 @@
|
||||
"""The select tests for the tado platform."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.select import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
HEATING_CIRCUIT_SELECT_ENTITY = "select.baseboard_heater_heating_circuit"
|
||||
NO_HEATING_CIRCUIT = "no_heating_circuit"
|
||||
HEATING_CIRCUIT_OPTION = "RU1234567890"
|
||||
ZONE_ID = 1
|
||||
HEATING_CIRCUIT_ID = 1
|
||||
|
||||
|
||||
async def test_heating_circuit_select(hass: HomeAssistant) -> None:
|
||||
"""Test creation of heating circuit select entity."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
state = hass.states.get(HEATING_CIRCUIT_SELECT_ENTITY)
|
||||
assert state is not None
|
||||
assert state.state == HEATING_CIRCUIT_OPTION
|
||||
assert NO_HEATING_CIRCUIT in state.attributes["options"]
|
||||
assert HEATING_CIRCUIT_OPTION in state.attributes["options"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("option", "expected_circuit_id"),
|
||||
[(HEATING_CIRCUIT_OPTION, HEATING_CIRCUIT_ID), (NO_HEATING_CIRCUIT, None)],
|
||||
)
|
||||
async def test_heating_circuit_select_action(
|
||||
hass: HomeAssistant, option, expected_circuit_id
|
||||
) -> None:
|
||||
"""Test selecting heating circuit option."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
# Test selecting a specific heating circuit
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.tado.PyTado.interface.api.Tado.set_zone_heating_circuit"
|
||||
) as mock_set_zone_heating_circuit,
|
||||
patch(
|
||||
"homeassistant.components.tado.PyTado.interface.api.Tado.get_zone_control"
|
||||
) as mock_get_zone_control,
|
||||
):
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: HEATING_CIRCUIT_SELECT_ENTITY,
|
||||
ATTR_OPTION: option,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_set_zone_heating_circuit.assert_called_with(ZONE_ID, expected_circuit_id)
|
||||
assert mock_get_zone_control.called
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("caplog")
|
||||
async def test_heating_circuit_not_found(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test when a heating circuit with a specific number is not found."""
|
||||
circuit_not_matching_zone_control = 999
|
||||
heating_circuits = [
|
||||
{
|
||||
"number": circuit_not_matching_zone_control,
|
||||
"driverSerialNo": "RU1234567890",
|
||||
"driverShortSerialNo": "RU1234567890",
|
||||
}
|
||||
]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.tado.PyTado.interface.api.Tado.get_heating_circuits",
|
||||
return_value=heating_circuits,
|
||||
):
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get(HEATING_CIRCUIT_SELECT_ENTITY)
|
||||
assert state.state == NO_HEATING_CIRCUIT
|
||||
|
||||
assert "Heating circuit with number 1 not found for zone" in caplog.text
|
@ -20,8 +20,10 @@ async def async_init_integration(
|
||||
me_fixture = "me.json"
|
||||
weather_fixture = "weather.json"
|
||||
home_fixture = "home.json"
|
||||
home_heating_circuits_fixture = "heating_circuits.json"
|
||||
home_state_fixture = "home_state.json"
|
||||
zones_fixture = "zones.json"
|
||||
zone_control_fixture = "zone_control.json"
|
||||
zone_states_fixture = "zone_states.json"
|
||||
|
||||
# WR1 Device
|
||||
@ -70,6 +72,10 @@ async def async_init_integration(
|
||||
"https://my.tado.com/api/v2/homes/1/",
|
||||
text=await async_load_fixture(hass, home_fixture, DOMAIN),
|
||||
)
|
||||
m.get(
|
||||
"https://my.tado.com/api/v2/homes/1/heatingCircuits",
|
||||
text=await async_load_fixture(hass, home_heating_circuits_fixture, DOMAIN),
|
||||
)
|
||||
m.get(
|
||||
"https://my.tado.com/api/v2/homes/1/weather",
|
||||
text=await async_load_fixture(hass, weather_fixture, DOMAIN),
|
||||
@ -178,6 +184,12 @@ async def async_init_integration(
|
||||
"https://my.tado.com/api/v2/homes/1/zones/1/state",
|
||||
text=await async_load_fixture(hass, zone_1_state_fixture, DOMAIN),
|
||||
)
|
||||
zone_ids = [1, 2, 3, 4, 5, 6]
|
||||
for zone_id in zone_ids:
|
||||
m.get(
|
||||
f"https://my.tado.com/api/v2/homes/1/zones/{zone_id}/control",
|
||||
text=await async_load_fixture(hass, zone_control_fixture, DOMAIN),
|
||||
)
|
||||
m.post(
|
||||
"https://login.tado.com/oauth2/token",
|
||||
text=await async_load_fixture(hass, token_fixture, DOMAIN),
|
||||
|
Loading…
x
Reference in New Issue
Block a user