Merge branch 'dev' into dev-rc

This commit is contained in:
Bram Kragten 2025-07-30 17:07:48 +02:00
commit 02f87cba9b
18 changed files with 470 additions and 936 deletions

View File

@ -41,7 +41,6 @@ PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CLIMATE,
Platform.DEVICE_TRACKER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.WATER_HEATER,

View File

@ -73,8 +73,6 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
"weather": {},
"geofence": {},
"zone": {},
"zone_control": {},
"heating_circuits": {},
}
@property
@ -101,14 +99,11 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
self.home_name = tado_home["name"]
devices = await self._async_update_devices()
zones, zone_controls = await self._async_update_zones()
zones = 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"]
@ -171,7 +166,7 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]):
return mapped_devices
async def _async_update_zones(self) -> tuple[dict[int, dict], dict[int, dict]]:
async def _async_update_zones(self) -> dict[int, dict]:
"""Update the zone data from Tado."""
try:
@ -184,12 +179,10 @@ 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, mapped_zone_controls
return mapped_zones
async def _update_zone(self, zone_id: int) -> dict[str, str]:
"""Update the internal data of a zone."""
@ -206,24 +199,6 @@ 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."""
@ -242,23 +217,6 @@ 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."""
@ -406,20 +364,6 @@ 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."""

View File

@ -1,108 +0,0 @@
"""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"
)

View File

@ -59,14 +59,6 @@
}
}
},
"select": {
"heating_circuit": {
"name": "Heating circuit",
"state": {
"no_heating_circuit": "No circuit"
}
}
},
"switch": {
"child_lock": {
"name": "Child lock"

View File

@ -89,6 +89,7 @@ from .light import (
CONF_TEMPERATURE_ACTION,
async_create_preview_light,
)
from .lock import CONF_LOCK, CONF_OPEN, CONF_UNLOCK, async_create_preview_lock
from .number import (
CONF_MAX,
CONF_MIN,
@ -103,6 +104,18 @@ from .select import CONF_OPTIONS, CONF_SELECT_OPTION, async_create_preview_selec
from .sensor import async_create_preview_sensor
from .switch import async_create_preview_switch
from .template_entity import TemplateEntity
from .vacuum import (
CONF_FAN_SPEED,
CONF_FAN_SPEED_LIST,
SERVICE_CLEAN_SPOT,
SERVICE_LOCATE,
SERVICE_PAUSE,
SERVICE_RETURN_TO_BASE,
SERVICE_SET_FAN_SPEED,
SERVICE_START,
SERVICE_STOP,
async_create_preview_vacuum,
)
_SCHEMA_STATE: dict[vol.Marker, Any] = {
vol.Required(CONF_STATE): selector.TemplateSelector(),
@ -221,6 +234,14 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
vol.Optional(CONF_TEMPERATURE_ACTION): selector.ActionSelector(),
}
if domain == Platform.LOCK:
schema |= _SCHEMA_STATE | {
vol.Required(CONF_LOCK): selector.ActionSelector(),
vol.Required(CONF_UNLOCK): selector.ActionSelector(),
vol.Optional(CONF_CODE_FORMAT): selector.TemplateSelector(),
vol.Optional(CONF_OPEN): selector.ActionSelector(),
}
if domain == Platform.NUMBER:
schema |= {
vol.Required(CONF_STATE): selector.TemplateSelector(),
@ -294,6 +315,26 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
vol.Optional(CONF_TURN_OFF): selector.ActionSelector(),
}
if domain == Platform.VACUUM:
schema |= _SCHEMA_STATE | {
vol.Required(SERVICE_START): selector.ActionSelector(),
vol.Optional(CONF_FAN_SPEED): selector.TemplateSelector(),
vol.Optional(CONF_FAN_SPEED_LIST): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[],
multiple=True,
custom_value=True,
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
vol.Optional(SERVICE_SET_FAN_SPEED): selector.ActionSelector(),
vol.Optional(SERVICE_STOP): selector.ActionSelector(),
vol.Optional(SERVICE_PAUSE): selector.ActionSelector(),
vol.Optional(SERVICE_RETURN_TO_BASE): selector.ActionSelector(),
vol.Optional(SERVICE_CLEAN_SPOT): selector.ActionSelector(),
vol.Optional(SERVICE_LOCATE): selector.ActionSelector(),
}
schema |= {
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
vol.Optional(CONF_ADVANCED_OPTIONS): section(
@ -403,10 +444,12 @@ TEMPLATE_TYPES = [
Platform.FAN,
Platform.IMAGE,
Platform.LIGHT,
Platform.LOCK,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
Platform.VACUUM,
]
CONFIG_FLOW = {
@ -445,6 +488,11 @@ CONFIG_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.LIGHT),
),
Platform.LOCK: SchemaFlowFormStep(
config_schema(Platform.LOCK),
preview="template",
validate_user_input=validate_user_input(Platform.LOCK),
),
Platform.NUMBER: SchemaFlowFormStep(
config_schema(Platform.NUMBER),
preview="template",
@ -465,6 +513,11 @@ CONFIG_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.SWITCH),
),
Platform.VACUUM: SchemaFlowFormStep(
config_schema(Platform.VACUUM),
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
}
@ -504,6 +557,11 @@ OPTIONS_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.LIGHT),
),
Platform.LOCK: SchemaFlowFormStep(
options_schema(Platform.LOCK),
preview="template",
validate_user_input=validate_user_input(Platform.LOCK),
),
Platform.NUMBER: SchemaFlowFormStep(
options_schema(Platform.NUMBER),
preview="template",
@ -524,6 +582,11 @@ OPTIONS_FLOW = {
preview="template",
validate_user_input=validate_user_input(Platform.SWITCH),
),
Platform.VACUUM: SchemaFlowFormStep(
options_schema(Platform.VACUUM),
preview="template",
validate_user_input=validate_user_input(Platform.VACUUM),
),
}
CREATE_PREVIEW_ENTITY: dict[
@ -535,10 +598,12 @@ CREATE_PREVIEW_ENTITY: dict[
Platform.COVER: async_create_preview_cover,
Platform.FAN: async_create_preview_fan,
Platform.LIGHT: async_create_preview_light,
Platform.LOCK: async_create_preview_lock,
Platform.NUMBER: async_create_preview_number,
Platform.SELECT: async_create_preview_select,
Platform.SENSOR: async_create_preview_sensor,
Platform.SWITCH: async_create_preview_switch,
Platform.VACUUM: async_create_preview_vacuum,
}

View File

@ -15,6 +15,7 @@ from homeassistant.components.lock import (
LockEntityFeature,
LockState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_CODE,
CONF_NAME,
@ -26,15 +27,23 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError, TemplateError
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN
from .coordinator import TriggerUpdateCoordinator
from .entity import AbstractTemplateEntity
from .helpers import async_setup_template_platform
from .helpers import (
async_setup_template_entry,
async_setup_template_platform,
async_setup_template_preview,
)
from .template_entity import (
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
TemplateEntity,
make_template_entity_common_modern_schema,
@ -82,6 +91,10 @@ PLATFORM_SCHEMA = LOCK_PLATFORM_SCHEMA.extend(
}
).extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY.schema)
LOCK_CONFIG_ENTRY_SCHEMA = LOCK_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
)
async def async_setup_platform(
hass: HomeAssistant,
@ -102,6 +115,35 @@ async def async_setup_platform(
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Initialize config entry."""
await async_setup_template_entry(
hass,
config_entry,
async_add_entities,
StateLockEntity,
LOCK_CONFIG_ENTRY_SCHEMA,
)
@callback
def async_create_preview_lock(
hass: HomeAssistant, name: str, config: dict[str, Any]
) -> StateLockEntity:
"""Create a preview."""
return async_setup_template_preview(
hass,
name,
config,
StateLockEntity,
LOCK_CONFIG_ENTRY_SCHEMA,
)
class AbstractTemplateLock(AbstractTemplateEntity, LockEntity):
"""Representation of a template lock features."""

View File

@ -188,6 +188,29 @@
},
"title": "Template light"
},
"lock": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"state": "[%key:component::template::common::state%]",
"lock": "Actions on lock",
"unlock": "Actions on unlock",
"code_format": "[%key:component::template::common::code_format%]",
"open": "Actions on open"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "Template lock"
},
"number": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
@ -265,10 +288,12 @@
"fan": "Template a fan",
"image": "Template an image",
"light": "Template a light",
"lock": "Template a lock",
"number": "Template a number",
"select": "Template a select",
"sensor": "Template a sensor",
"switch": "Template a switch"
"switch": "Template a switch",
"vacuum": "Template a vacuum"
},
"title": "Template helper"
},
@ -293,6 +318,34 @@
}
},
"title": "Template switch"
},
"vacuum": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"state": "[%key:component::template::common::state%]",
"start": "Actions on turn off",
"fan_speed": "Fan speed",
"fan_speeds": "Fan speeds",
"set_fan_speed": "Actions on set fan speed",
"stop": "Actions on stop",
"pause": "Actions on pause",
"return_to_base": "Actions on return to base",
"clean_spot": "Actions on clean spot",
"locate": "Actions on locate"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "Template vacuum"
}
}
},
@ -466,6 +519,28 @@
},
"title": "[%key:component::template::config::step::light::title%]"
},
"lock": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"state": "[%key:component::template::common::state%]",
"lock": "[%key:component::template::config::step::lock::data::lock%]",
"unlock": "[%key:component::template::config::step::lock::data::unlock%]",
"code_format": "[%key:component::template::common::code_format%]",
"open": "[%key:component::template::config::step::lock::data::open%]"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "[%key:component::template::config::step::lock::title%]"
},
"number": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
@ -552,6 +627,34 @@
}
},
"title": "[%key:component::template::config::step::switch::title%]"
},
"vacuum": {
"data": {
"device_id": "[%key:common::config_flow::data::device%]",
"name": "[%key:common::config_flow::data::name%]",
"state": "[%key:component::template::common::state%]",
"start": "[%key:component::template::config::step::vacuum::data::start%]",
"fan_speed": "[%key:component::template::config::step::vacuum::data::fan_speed%]",
"fan_speeds": "[%key:component::template::config::step::vacuum::data::fan_speeds%]",
"set_fan_speed": "[%key:component::template::config::step::vacuum::data::set_fan_speed%]",
"stop": "[%key:component::template::config::step::vacuum::data::stop%]",
"pause": "[%key:component::template::config::step::vacuum::data::pause%]",
"return_to_base": "[%key:component::template::config::step::vacuum::data::return_to_base%]",
"clean_spot": "[%key:component::template::config::step::vacuum::data::clean_spot%]",
"locate": "[%key:component::template::config::step::vacuum::data::locate%]"
},
"data_description": {
"device_id": "[%key:component::template::common::device_id_description%]"
},
"sections": {
"advanced_options": {
"name": "[%key:component::template::common::advanced_options%]",
"data": {
"availability": "[%key:component::template::common::availability%]"
}
}
},
"title": "Template vacuum"
}
}
},

View File

@ -22,6 +22,7 @@ from homeassistant.components.vacuum import (
VacuumActivity,
VacuumEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ENTITY_ID,
CONF_FRIENDLY_NAME,
@ -34,16 +35,24 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN
from .coordinator import TriggerUpdateCoordinator
from .entity import AbstractTemplateEntity
from .helpers import async_setup_template_platform
from .helpers import (
async_setup_template_entry,
async_setup_template_platform,
async_setup_template_preview,
)
from .template_entity import (
TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA_LEGACY,
TEMPLATE_ENTITY_AVAILABILITY_SCHEMA_LEGACY,
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
TemplateEntity,
make_template_entity_common_modern_attributes_schema,
@ -125,6 +134,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
{vol.Required(CONF_VACUUMS): cv.schema_with_slug_keys(VACUUM_LEGACY_YAML_SCHEMA)}
)
VACUUM_CONFIG_ENTRY_SCHEMA = VACUUM_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
)
async def async_setup_platform(
hass: HomeAssistant,
@ -146,6 +159,35 @@ async def async_setup_platform(
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Initialize config entry."""
await async_setup_template_entry(
hass,
config_entry,
async_add_entities,
TemplateStateVacuumEntity,
VACUUM_CONFIG_ENTRY_SCHEMA,
)
@callback
def async_create_preview_vacuum(
hass: HomeAssistant, name: str, config: dict[str, Any]
) -> TemplateStateVacuumEntity:
"""Create a preview."""
return async_setup_template_preview(
hass,
name,
config,
TemplateStateVacuumEntity,
VACUUM_CONFIG_ENTRY_SCHEMA,
)
class AbstractTemplateVacuum(AbstractTemplateEntity, StateVacuumEntity):
"""Representation of a template vacuum features."""

View File

@ -1,7 +0,0 @@
[
{
"number": 1,
"driverSerialNo": "RU1234567890",
"driverShortSerialNo": "RU1234567890"
}
]

View File

@ -1,80 +0,0 @@
{
"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
}
]
}
}

View File

@ -62,13 +62,6 @@
'presence': 'HOME',
'presenceLocked': False,
}),
'heating_circuits': dict({
'RU1234567890': dict({
'driverSerialNo': 'RU1234567890',
'driverShortSerialNo': 'RU1234567890',
'number': 1,
}),
}),
'weather': dict({
'outsideTemperature': dict({
'celsius': 7.46,
@ -117,560 +110,6 @@
'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({

View File

@ -1,91 +0,0 @@
"""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

View File

@ -20,10 +20,8 @@ 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
@ -72,10 +70,6 @@ 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),
@ -184,12 +178,6 @@ 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),

View File

@ -0,0 +1,15 @@
# serializer version: 1
# name: test_setup_config_entry
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'My template',
'supported_features': <LockEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'lock.my_template',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'locked',
})
# ---

View File

@ -0,0 +1,15 @@
# serializer version: 1
# name: test_setup_config_entry
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'My template',
'supported_features': <VacuumEntityFeature: 12288>,
}),
'context': <ANY>,
'entity_id': 'vacuum.my_template',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'docked',
})
# ---

View File

@ -179,6 +179,16 @@ BINARY_SENSOR_OPTIONS = {
{"turn_on": [], "turn_off": []},
{},
),
(
"lock",
{"state": "{{ states('lock.one') }}"},
"locked",
{"one": "locked", "two": "unlocked"},
{},
{"lock": [], "unlock": []},
{"lock": [], "unlock": []},
{},
),
(
"number",
{"state": "{{ states('number.one') }}"},
@ -229,6 +239,16 @@ BINARY_SENSOR_OPTIONS = {
{},
{},
),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
"docked",
{"one": "docked", "two": "cleaning"},
{},
{"start": []},
{"start": []},
{},
),
],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@ -362,6 +382,12 @@ async def test_config_flow(
{"turn_on": [], "turn_off": []},
{"turn_on": [], "turn_off": []},
),
(
"lock",
{"state": "{{ states('lock.one') }}"},
{"lock": [], "unlock": []},
{"lock": [], "unlock": []},
),
(
"number",
{"state": "{{ states('number.one') }}"},
@ -398,6 +424,12 @@ async def test_config_flow(
{"options": "{{ ['off', 'on', 'auto'] }}"},
{"options": "{{ ['off', 'on', 'auto'] }}"},
),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"start": []},
{"start": []},
),
],
)
async def test_config_flow_device(
@ -587,6 +619,16 @@ async def test_config_flow_device(
{"turn_on": [], "turn_off": []},
"state",
),
(
"lock",
{"state": "{{ states('lock.one') }}"},
{"state": "{{ states('lock.two') }}"},
["locked", "unlocked"],
{"one": "locked", "two": "unlocked"},
{"lock": [], "unlock": []},
{"lock": [], "unlock": []},
"state",
),
(
"number",
{"state": "{{ states('number.one') }}"},
@ -647,6 +689,16 @@ async def test_config_flow_device(
{},
"value_template",
),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"state": "{{ states('vacuum.two') }}"},
["docked", "cleaning"],
{"one": "docked", "two": "cleaning"},
{"start": []},
{"start": []},
"state",
),
],
)
@pytest.mark.freeze_time("2024-07-09 00:00:00+00:00")
@ -1438,6 +1490,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
{"turn_on": [], "turn_off": []},
{"turn_on": [], "turn_off": []},
),
(
"lock",
{"state": "{{ states('lock.one') }}"},
{"lock": [], "unlock": []},
{"lock": [], "unlock": []},
),
(
"number",
{"state": "{{ states('number.one') }}"},
@ -1480,6 +1538,12 @@ async def test_option_flow_sensor_preview_config_entry_removed(
{},
{},
),
(
"vacuum",
{"state": "{{ states('vacuum.one') }}"},
{"start": []},
{"start": []},
),
],
)
async def test_options_flow_change_device(

View File

@ -3,6 +3,7 @@
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant import setup
from homeassistant.components import lock, template
@ -19,9 +20,10 @@ from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from .conftest import ConfigurationStyle, async_get_flow_preview_state
from tests.common import assert_setup_component
from tests.common import MockConfigEntry, assert_setup_component
from tests.typing import WebSocketGenerator
TEST_OBJECT_ID = "test_template_lock"
TEST_ENTITY_ID = f"lock.{TEST_OBJECT_ID}"
@ -1186,3 +1188,58 @@ async def test_optimistic(hass: HomeAssistant) -> None:
state = hass.states.get(TEST_ENTITY_ID)
assert state.state == LockState.UNLOCKED
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Tests creating a lock from a config entry."""
hass.states.async_set(
"sensor.test_state",
LockState.LOCKED,
{},
)
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"state": "{{ states('sensor.test_state') }}",
"lock": [],
"unlock": [],
"template_type": lock.DOMAIN,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("lock.my_template")
assert state is not None
assert state == snapshot
async def test_flow_preview(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the config flow preview."""
state = await async_get_flow_preview_state(
hass,
hass_ws_client,
lock.DOMAIN,
{
"name": "My template",
"state": "{{ 'locked' }}",
"lock": [],
"unlock": [],
},
)
assert state["state"] == LockState.LOCKED

View File

@ -3,6 +3,7 @@
from typing import Any
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components import template, vacuum
from homeassistant.components.vacuum import (
@ -18,10 +19,11 @@ from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import async_update_entity
from homeassistant.setup import async_setup_component
from .conftest import ConfigurationStyle
from .conftest import ConfigurationStyle, async_get_flow_preview_state
from tests.common import assert_setup_component
from tests.common import MockConfigEntry, assert_setup_component
from tests.components.vacuum import common
from tests.typing import WebSocketGenerator
TEST_OBJECT_ID = "test_vacuum"
TEST_ENTITY_ID = f"vacuum.{TEST_OBJECT_ID}"
@ -1261,3 +1263,56 @@ async def test_optimistic_option(
state = hass.states.get(TEST_ENTITY_ID)
assert state.state == VacuumActivity.DOCKED
async def test_setup_config_entry(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
) -> None:
"""Tests creating a vacuum from a config entry."""
hass.states.async_set(
"sensor.test_sensor",
"docked",
{},
)
template_config_entry = MockConfigEntry(
data={},
domain=template.DOMAIN,
options={
"name": "My template",
"state": "{{ states('sensor.test_sensor') }}",
"start": [],
"template_type": vacuum.DOMAIN,
},
title="My template",
)
template_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(template_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("vacuum.my_template")
assert state is not None
assert state == snapshot
async def test_flow_preview(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the config flow preview."""
state = await async_get_flow_preview_state(
hass,
hass_ws_client,
vacuum.DOMAIN,
{
"name": "My template",
"state": "{{ 'cleaning' }}",
"start": [],
},
)
assert state["state"] == VacuumActivity.CLEANING