diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 30351a9381e641..610fed902adcff 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.12.0
+ rev: v0.12.1
hooks:
- id: ruff-check
args:
diff --git a/CODEOWNERS b/CODEOWNERS
index 4e224f8802bcaf..28deb93492cef8 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1553,6 +1553,8 @@ build.json @home-assistant/supervisor
/tests/components/technove/ @Moustachauve
/homeassistant/components/tedee/ @patrickhilker @zweckj
/tests/components/tedee/ @patrickhilker @zweckj
+/homeassistant/components/telegram_bot/ @hanwg
+/tests/components/telegram_bot/ @hanwg
/homeassistant/components/tellduslive/ @fredrike
/tests/components/tellduslive/ @fredrike
/homeassistant/components/template/ @Petro31 @home-assistant/core
diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py
index afe8ea6f356af6..f70237645e0d19 100644
--- a/homeassistant/bootstrap.py
+++ b/homeassistant/bootstrap.py
@@ -607,7 +607,7 @@ async def async_enable_logging(
)
threading.excepthook = lambda args: logging.getLogger().exception(
"Uncaught thread exception",
- exc_info=( # type: ignore[arg-type] # noqa: LOG014
+ exc_info=( # type: ignore[arg-type]
args.exc_type,
args.exc_value,
args.exc_traceback,
@@ -1061,5 +1061,5 @@ async def _async_setup_multi_components(
_LOGGER.error(
"Error setting up integration %s - received exception",
domain,
- exc_info=(type(result), result, result.__traceback__), # noqa: LOG014
+ exc_info=(type(result), result, result.__traceback__),
)
diff --git a/homeassistant/components/alexa_devices/manifest.json b/homeassistant/components/alexa_devices/manifest.json
index e82cd471ac79d0..cdf942e836d9b4 100644
--- a/homeassistant/components/alexa_devices/manifest.json
+++ b/homeassistant/components/alexa_devices/manifest.json
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "bronze",
- "requirements": ["aioamazondevices==3.1.19"]
+ "requirements": ["aioamazondevices==3.1.22"]
}
diff --git a/homeassistant/components/backup/__init__.py b/homeassistant/components/backup/__init__.py
index 51503230530281..973f354060ac81 100644
--- a/homeassistant/components/backup/__init__.py
+++ b/homeassistant/components/backup/__init__.py
@@ -2,7 +2,7 @@
from homeassistant.config_entries import SOURCE_SYSTEM
from homeassistant.const import Platform
-from homeassistant.core import HomeAssistant, ServiceCall
+from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery_flow
from homeassistant.helpers.backup import DATA_BACKUP
from homeassistant.helpers.hassio import is_hassio
@@ -45,6 +45,7 @@
WrittenBackup,
)
from .models import AddonInfo, AgentBackup, BackupNotFound, Folder
+from .services import async_setup_services
from .util import suggested_filename, suggested_filename_from_name_date
from .websocket import async_register_websocket_handlers
@@ -113,29 +114,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async_register_websocket_handlers(hass, with_hassio)
- async def async_handle_create_service(call: ServiceCall) -> None:
- """Service handler for creating backups."""
- agent_id = list(backup_manager.local_backup_agents)[0]
- await backup_manager.async_create_backup(
- agent_ids=[agent_id],
- include_addons=None,
- include_all_addons=False,
- include_database=True,
- include_folders=None,
- include_homeassistant=True,
- name=None,
- password=None,
- )
-
- async def async_handle_create_automatic_service(call: ServiceCall) -> None:
- """Service handler for creating automatic backups."""
- await backup_manager.async_create_automatic_backup()
-
- if not with_hassio:
- hass.services.async_register(DOMAIN, "create", async_handle_create_service)
- hass.services.async_register(
- DOMAIN, "create_automatic", async_handle_create_automatic_service
- )
+ async_setup_services(hass)
async_register_http_views(hass)
diff --git a/homeassistant/components/backup/services.py b/homeassistant/components/backup/services.py
new file mode 100644
index 00000000000000..17448f7bb065c1
--- /dev/null
+++ b/homeassistant/components/backup/services.py
@@ -0,0 +1,36 @@
+"""The Backup integration."""
+
+from homeassistant.core import HomeAssistant, ServiceCall
+from homeassistant.helpers.hassio import is_hassio
+
+from .const import DATA_MANAGER, DOMAIN
+
+
+async def _async_handle_create_service(call: ServiceCall) -> None:
+ """Service handler for creating backups."""
+ backup_manager = call.hass.data[DATA_MANAGER]
+ agent_id = list(backup_manager.local_backup_agents)[0]
+ await backup_manager.async_create_backup(
+ agent_ids=[agent_id],
+ include_addons=None,
+ include_all_addons=False,
+ include_database=True,
+ include_folders=None,
+ include_homeassistant=True,
+ name=None,
+ password=None,
+ )
+
+
+async def _async_handle_create_automatic_service(call: ServiceCall) -> None:
+ """Service handler for creating automatic backups."""
+ await call.hass.data[DATA_MANAGER].async_create_automatic_backup()
+
+
+def async_setup_services(hass: HomeAssistant) -> None:
+ """Register services."""
+ if not is_hassio(hass):
+ hass.services.async_register(DOMAIN, "create", _async_handle_create_service)
+ hass.services.async_register(
+ DOMAIN, "create_automatic", _async_handle_create_automatic_service
+ )
diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py
index 918d4e339718d2..03e89b971fc37b 100644
--- a/homeassistant/components/dsmr/sensor.py
+++ b/homeassistant/components/dsmr/sensor.py
@@ -241,6 +241,7 @@ class MbusDeviceType(IntEnum):
obis_reference="SHORT_POWER_FAILURE_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -249,6 +250,7 @@ class MbusDeviceType(IntEnum):
obis_reference="LONG_POWER_FAILURE_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -257,6 +259,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SAG_L1_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -265,6 +268,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SAG_L2_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -273,6 +277,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SAG_L3_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -281,6 +286,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SWELL_L1_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -289,6 +295,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SWELL_L2_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
@@ -297,6 +304,7 @@ class MbusDeviceType(IntEnum):
obis_reference="VOLTAGE_SWELL_L3_COUNT",
dsmr_versions={"2.2", "4", "5", "5L"},
entity_registry_enabled_default=False,
+ state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=EntityCategory.DIAGNOSTIC,
),
DSMRSensorEntityDescription(
diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json
index 8e4ea47da5b15c..cf83ce90237614 100644
--- a/homeassistant/components/frontend/manifest.json
+++ b/homeassistant/components/frontend/manifest.json
@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
- "requirements": ["home-assistant-frontend==20250626.0"]
+ "requirements": ["home-assistant-frontend==20250627.0"]
}
diff --git a/homeassistant/components/husqvarna_automower/button.py b/homeassistant/components/husqvarna_automower/button.py
index 1f7ed7127e0b8a..281669aad04734 100644
--- a/homeassistant/components/husqvarna_automower/button.py
+++ b/homeassistant/components/husqvarna_automower/button.py
@@ -90,7 +90,9 @@ def __init__(
@property
def available(self) -> bool:
"""Return the available attribute of the entity."""
- return self.entity_description.available_fn(self.mower_attributes)
+ return super().available and self.entity_description.available_fn(
+ self.mower_attributes
+ )
@handle_sending_exception()
async def async_press(self) -> None:
diff --git a/homeassistant/components/husqvarna_automower/calendar.py b/homeassistant/components/husqvarna_automower/calendar.py
index 26e939ec7d9f8e..a26b9bf72bd7ae 100644
--- a/homeassistant/components/husqvarna_automower/calendar.py
+++ b/homeassistant/components/husqvarna_automower/calendar.py
@@ -2,15 +2,18 @@
from datetime import datetime
import logging
+from typing import TYPE_CHECKING
from aioautomower.model import make_name_string
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.core import HomeAssistant
+from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from . import AutomowerConfigEntry
+from .const import DOMAIN
from .coordinator import AutomowerDataUpdateCoordinator
from .entity import AutomowerBaseEntity
@@ -51,6 +54,19 @@ def __init__(
self._attr_unique_id = mower_id
self._event: CalendarEvent | None = None
+ @property
+ def device_name(self) -> str:
+ """Return the prefix for the event summary."""
+ device_registry = dr.async_get(self.hass)
+ device_entry = device_registry.async_get_device(
+ identifiers={(DOMAIN, self.mower_id)}
+ )
+ if TYPE_CHECKING:
+ assert device_entry is not None
+ assert device_entry.name is not None
+
+ return device_entry.name_by_user or device_entry.name
+
@property
def event(self) -> CalendarEvent | None:
"""Return the current or next upcoming event."""
@@ -66,7 +82,7 @@ def event(self) -> CalendarEvent | None:
program_event.work_area_id
]
return CalendarEvent(
- summary=make_name_string(work_area_name, program_event.schedule_no),
+ summary=f"{self.device_name} {make_name_string(work_area_name, program_event.schedule_no)}",
start=program_event.start,
end=program_event.end,
rrule=program_event.rrule_str,
@@ -93,7 +109,7 @@ async def async_get_events(
]
calendar_events.append(
CalendarEvent(
- summary=make_name_string(work_area_name, program_event.schedule_no),
+ summary=f"{self.device_name} {make_name_string(work_area_name, program_event.schedule_no)}",
start=program_event.start.replace(tzinfo=start_date.tzinfo),
end=program_event.end.replace(tzinfo=start_date.tzinfo),
rrule=program_event.rrule_str,
diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json
index 0fc05c56fb5378..34ec6693865b3b 100644
--- a/homeassistant/components/husqvarna_automower/manifest.json
+++ b/homeassistant/components/husqvarna_automower/manifest.json
@@ -8,5 +8,5 @@
"iot_class": "cloud_push",
"loggers": ["aioautomower"],
"quality_scale": "silver",
- "requirements": ["aioautomower==1.0.0"]
+ "requirements": ["aioautomower==1.0.1"]
}
diff --git a/homeassistant/components/matter/number.py b/homeassistant/components/matter/number.py
index 4b469fa85e47d9..b811a3c19d3898 100644
--- a/homeassistant/components/matter/number.py
+++ b/homeassistant/components/matter/number.py
@@ -2,9 +2,12 @@
from __future__ import annotations
+from collections.abc import Callable
from dataclasses import dataclass
+from typing import Any, cast
from chip.clusters import Objects as clusters
+from chip.clusters.ClusterObjects import ClusterAttributeDescriptor, ClusterCommand
from matter_server.common import custom_clusters
from homeassistant.components.number import (
@@ -44,6 +47,23 @@ class MatterNumberEntityDescription(NumberEntityDescription, MatterEntityDescrip
"""Describe Matter Number Input entities."""
+@dataclass(frozen=True, kw_only=True)
+class MatterRangeNumberEntityDescription(
+ NumberEntityDescription, MatterEntityDescription
+):
+ """Describe Matter Number Input entities with min and max values."""
+
+ ha_to_native_value: Callable[[Any], Any]
+
+ # attribute descriptors to get the min and max value
+ min_attribute: type[ClusterAttributeDescriptor]
+ max_attribute: type[ClusterAttributeDescriptor]
+
+ # command: a custom callback to create the command to send to the device
+ # the callback's argument will be the index of the selected list value
+ command: Callable[[int], ClusterCommand]
+
+
class MatterNumber(MatterEntity, NumberEntity):
"""Representation of a Matter Attribute as a Number entity."""
@@ -67,6 +87,42 @@ def _update_from_device(self) -> None:
self._attr_native_value = value
+class MatterRangeNumber(MatterEntity, NumberEntity):
+ """Representation of a Matter Attribute as a Number entity with min and max values."""
+
+ entity_description: MatterRangeNumberEntityDescription
+
+ async def async_set_native_value(self, value: float) -> None:
+ """Update the current value."""
+ send_value = self.entity_description.ha_to_native_value(value)
+ # custom command defined to set the new value
+ await self.send_device_command(
+ self.entity_description.command(send_value),
+ )
+
+ @callback
+ def _update_from_device(self) -> None:
+ """Update from device."""
+ value = self.get_matter_attribute_value(self._entity_info.primary_attribute)
+ if value_convert := self.entity_description.measurement_to_ha:
+ value = value_convert(value)
+ self._attr_native_value = value
+ self._attr_native_min_value = (
+ cast(
+ int,
+ self.get_matter_attribute_value(self.entity_description.min_attribute),
+ )
+ / 100
+ )
+ self._attr_native_max_value = (
+ cast(
+ int,
+ self.get_matter_attribute_value(self.entity_description.max_attribute),
+ )
+ / 100
+ )
+
+
# Discovery schema(s) to map Matter Attributes to HA entities
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
@@ -213,4 +269,27 @@ def _update_from_device(self) -> None:
entity_class=MatterNumber,
required_attributes=(clusters.DoorLock.Attributes.AutoRelockTime,),
),
+ MatterDiscoverySchema(
+ platform=Platform.NUMBER,
+ entity_description=MatterRangeNumberEntityDescription(
+ key="TemperatureControlTemperatureSetpoint",
+ name=None,
+ translation_key="temperature_setpoint",
+ command=lambda value: clusters.TemperatureControl.Commands.SetTemperature(
+ targetTemperature=value
+ ),
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+ measurement_to_ha=lambda x: None if x is None else x / 100,
+ ha_to_native_value=lambda x: round(x * 100),
+ min_attribute=clusters.TemperatureControl.Attributes.MinTemperature,
+ max_attribute=clusters.TemperatureControl.Attributes.MaxTemperature,
+ mode=NumberMode.SLIDER,
+ ),
+ entity_class=MatterRangeNumber,
+ required_attributes=(
+ clusters.TemperatureControl.Attributes.TemperatureSetpoint,
+ clusters.TemperatureControl.Attributes.MinTemperature,
+ clusters.TemperatureControl.Attributes.MaxTemperature,
+ ),
+ ),
]
diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json
index 35a9daa23706ea..d1367ba66e2fac 100644
--- a/homeassistant/components/matter/strings.json
+++ b/homeassistant/components/matter/strings.json
@@ -183,6 +183,9 @@
"temperature_offset": {
"name": "Temperature offset"
},
+ "temperature_setpoint": {
+ "name": "Temperature setpoint"
+ },
"pir_occupied_to_unoccupied_delay": {
"name": "Occupied to unoccupied delay"
},
diff --git a/homeassistant/components/pegel_online/strings.json b/homeassistant/components/pegel_online/strings.json
index 7d0702754afac9..65fecbfb8250a5 100644
--- a/homeassistant/components/pegel_online/strings.json
+++ b/homeassistant/components/pegel_online/strings.json
@@ -2,17 +2,23 @@
"config": {
"step": {
"user": {
- "description": "Select the area in which you want to search for water measuring stations",
"data": {
"location": "[%key:common::config_flow::data::location%]",
"radius": "Search radius"
+ },
+ "data_description": {
+ "location": "Pick the location where to search for water measuring stations.",
+ "radius": "The radius to search for water measuring stations around the selected location."
}
},
"select_station": {
- "title": "Select the measuring station to add",
+ "title": "Select the station to add",
"description": "Found {stations_count} stations in radius",
"data": {
"station": "Station"
+ },
+ "data_description": {
+ "station": "Select the water measuring station you want to add to Home Assistant."
}
}
},
diff --git a/homeassistant/components/playstation_network/config_flow.py b/homeassistant/components/playstation_network/config_flow.py
index 29ba8d4de904f8..b4a4a9374faa6f 100644
--- a/homeassistant/components/playstation_network/config_flow.py
+++ b/homeassistant/components/playstation_network/config_flow.py
@@ -14,7 +14,7 @@
from psnawp_api.utils.misc import parse_npsso_token
import voluptuous as vol
-from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
+from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_NAME
from .const import CONF_NPSSO, DOMAIN, NPSSO_LINK, PSN_LINK
@@ -76,13 +76,23 @@ async def async_step_reauth(
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
+ async def async_step_reconfigure(
+ self, user_input: dict[str, Any] | None = None
+ ) -> ConfigFlowResult:
+ """Reconfigure flow for PlayStation Network integration."""
+ return await self.async_step_reauth_confirm(user_input)
+
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reauthentication dialog."""
errors: dict[str, str] = {}
- entry = self._get_reauth_entry()
+ entry = (
+ self._get_reauth_entry()
+ if self.source == SOURCE_REAUTH
+ else self._get_reconfigure_entry()
+ )
if user_input is not None:
try:
@@ -113,7 +123,7 @@ async def async_step_reauth_confirm(
)
return self.async_show_form(
- step_id="reauth_confirm",
+ step_id="reauth_confirm" if self.source == SOURCE_REAUTH else "reconfigure",
data_schema=self.add_suggested_values_to_schema(
data_schema=STEP_USER_DATA_SCHEMA, suggested_values=user_input
),
diff --git a/homeassistant/components/playstation_network/quality_scale.yaml b/homeassistant/components/playstation_network/quality_scale.yaml
index a98c30a7667287..954276e72437d0 100644
--- a/homeassistant/components/playstation_network/quality_scale.yaml
+++ b/homeassistant/components/playstation_network/quality_scale.yaml
@@ -63,7 +63,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
- reconfiguration-flow: todo
+ reconfiguration-flow: done
repair-issues: todo
stale-devices: todo
# Platinum
diff --git a/homeassistant/components/playstation_network/strings.json b/homeassistant/components/playstation_network/strings.json
index 5d8333e785fb89..a26f45d8973dc0 100644
--- a/homeassistant/components/playstation_network/strings.json
+++ b/homeassistant/components/playstation_network/strings.json
@@ -19,6 +19,16 @@
"data_description": {
"npsso": "[%key:component::playstation_network::config::step::user::data_description::npsso%]"
}
+ },
+ "reconfigure": {
+ "title": "Update PlayStation Network configuration",
+ "description": "[%key:component::playstation_network::config::step::user::description%]",
+ "data": {
+ "npsso": "[%key:component::playstation_network::config::step::user::data::npsso%]"
+ },
+ "data_description": {
+ "npsso": "[%key:component::playstation_network::config::step::user::data_description::npsso%]"
+ }
}
},
"error": {
@@ -30,7 +40,8 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
- "unique_id_mismatch": "The provided NPSSO token corresponds to the account {wrong_account}. Please re-authenticate with the account **{name}**"
+ "unique_id_mismatch": "The provided NPSSO token corresponds to the account {wrong_account}. Please re-authenticate with the account **{name}**",
+ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}
},
"exceptions": {
diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py
index af8d2c76844368..7eb53433d881f1 100644
--- a/homeassistant/components/rflink/light.py
+++ b/homeassistant/components/rflink/light.py
@@ -221,8 +221,8 @@ def _handle_event(self, event):
elif command in ["off", "alloff"]:
self._state = False
# dimmable device accept 'set_level=(0-15)' commands
- elif re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE):
- self._brightness = rflink_to_brightness(int(command.split("=")[1]))
+ elif match := re.search("^set_level=(0?[0-9]|1[0-5])$", command, re.IGNORECASE):
+ self._brightness = rflink_to_brightness(int(match.group(1)))
self._state = True
@property
diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py
index 5a420a4543b8e0..587eb00b979b6c 100644
--- a/homeassistant/components/shelly/entity.py
+++ b/homeassistant/components/shelly/entity.py
@@ -192,8 +192,12 @@ def async_setup_rpc_attribute_entities(
if description.removal_condition and description.removal_condition(
coordinator.device.config, coordinator.device.status, key
):
- domain = sensor_class.__module__.split(".")[-1]
- unique_id = f"{coordinator.mac}-{key}-{sensor_id}"
+ entity_class = get_entity_class(sensor_class, description)
+ domain = entity_class.__module__.split(".")[-1]
+ unique_id = entity_class(
+ coordinator, key, sensor_id, description
+ ).unique_id
+ LOGGER.debug("Removing Shelly entity with unique_id: %s", unique_id)
async_remove_shelly_entity(hass, domain, unique_id)
elif description.use_polling_coordinator:
if not sleep_period:
diff --git a/homeassistant/components/switchbot_cloud/__init__.py b/homeassistant/components/switchbot_cloud/__init__.py
index 7b7f60589f036d..b87a569abda27a 100644
--- a/homeassistant/components/switchbot_cloud/__init__.py
+++ b/homeassistant/components/switchbot_cloud/__init__.py
@@ -153,7 +153,12 @@ async def make_device_data(
)
devices_data.vacuums.append((device, coordinator))
- if isinstance(device, Device) and device.device_type.startswith("Smart Lock"):
+ if isinstance(device, Device) and device.device_type in [
+ "Smart Lock",
+ "Smart Lock Lite",
+ "Smart Lock Pro",
+ "Smart Lock Ultra",
+ ]:
coordinator = await coordinator_for_device(
hass, entry, api, device, coordinators_by_id
)
diff --git a/homeassistant/components/switchbot_cloud/binary_sensor.py b/homeassistant/components/switchbot_cloud/binary_sensor.py
index 752c428fa6c062..cd0e6e8968c385 100644
--- a/homeassistant/components/switchbot_cloud/binary_sensor.py
+++ b/homeassistant/components/switchbot_cloud/binary_sensor.py
@@ -48,10 +48,18 @@ class SwitchBotCloudBinarySensorEntityDescription(BinarySensorEntityDescription)
CALIBRATION_DESCRIPTION,
DOOR_OPEN_DESCRIPTION,
),
+ "Smart Lock Lite": (
+ CALIBRATION_DESCRIPTION,
+ DOOR_OPEN_DESCRIPTION,
+ ),
"Smart Lock Pro": (
CALIBRATION_DESCRIPTION,
DOOR_OPEN_DESCRIPTION,
),
+ "Smart Lock Ultra": (
+ CALIBRATION_DESCRIPTION,
+ DOOR_OPEN_DESCRIPTION,
+ ),
}
@@ -69,7 +77,6 @@ async def async_setup_entry(
for description in BINARY_SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES[
device.device_type
]
- if device.device_type in BINARY_SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES
)
diff --git a/homeassistant/components/switchbot_cloud/sensor.py b/homeassistant/components/switchbot_cloud/sensor.py
index 9920717a8d7278..5a424ea78920fc 100644
--- a/homeassistant/components/switchbot_cloud/sensor.py
+++ b/homeassistant/components/switchbot_cloud/sensor.py
@@ -134,8 +134,10 @@
BATTERY_DESCRIPTION,
CO2_DESCRIPTION,
),
- "Smart Lock Pro": (BATTERY_DESCRIPTION,),
"Smart Lock": (BATTERY_DESCRIPTION,),
+ "Smart Lock Lite": (BATTERY_DESCRIPTION,),
+ "Smart Lock Pro": (BATTERY_DESCRIPTION,),
+ "Smart Lock Ultra": (BATTERY_DESCRIPTION,),
}
@@ -151,7 +153,6 @@ async def async_setup_entry(
SwitchBotCloudSensor(data.api, device, coordinator, description)
for device, coordinator in data.devices.sensors
for description in SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES[device.device_type]
- if device.device_type in SENSOR_DESCRIPTIONS_BY_DEVICE_TYPES
)
diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py
index 7ab6d77e13786b..37e9ee3d929c99 100644
--- a/homeassistant/components/system_health/__init__.py
+++ b/homeassistant/components/system_health/__init__.py
@@ -231,7 +231,7 @@ async def handle_info(
"Error fetching system info for %s - %s",
domain,
key,
- exc_info=(type(exception), exception, exception.__traceback__), # noqa: LOG014
+ exc_info=(type(exception), exception, exception.__traceback__),
)
event_msg["success"] = False
event_msg["error"] = {"type": "failed", "error": "unknown"}
diff --git a/homeassistant/components/telegram_bot/manifest.json b/homeassistant/components/telegram_bot/manifest.json
index 27c10602350a65..7a01f43c5286b7 100644
--- a/homeassistant/components/telegram_bot/manifest.json
+++ b/homeassistant/components/telegram_bot/manifest.json
@@ -1,7 +1,7 @@
{
"domain": "telegram_bot",
"name": "Telegram bot",
- "codeowners": [],
+ "codeowners": ["@hanwg"],
"config_flow": true,
"dependencies": ["http"],
"documentation": "https://www.home-assistant.io/integrations/telegram_bot",
diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py
index c31bdc2a34e34b..e6b453402e971c 100644
--- a/homeassistant/components/teslemetry/coordinator.py
+++ b/homeassistant/components/teslemetry/coordinator.py
@@ -194,14 +194,14 @@ async def _async_update_data(self) -> dict[str, Any]:
except TeslaFleetError as e:
raise UpdateFailed(e.message) from e
+ if not data or not isinstance(data.get("time_series"), list):
+ raise UpdateFailed("Received invalid data")
+
# Add all time periods together
- output = dict.fromkeys(ENERGY_HISTORY_FIELDS, None)
- for period in data.get("time_series", []):
+ output = dict.fromkeys(ENERGY_HISTORY_FIELDS, 0)
+ for period in data["time_series"]:
for key in ENERGY_HISTORY_FIELDS:
if key in period:
- if output[key] is None:
- output[key] = period[key]
- else:
- output[key] += period[key]
+ output[key] += period[key]
return output
diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py
index a109719965cbf1..7e95e274713793 100644
--- a/homeassistant/components/zwave_js/config_flow.py
+++ b/homeassistant/components/zwave_js/config_flow.py
@@ -40,7 +40,6 @@
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from homeassistant.helpers.service_info.usb import UsbServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
-from homeassistant.helpers.typing import VolDictType
from .addon import get_addon_manager
from .const import (
@@ -90,6 +89,9 @@
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool})
MIN_MIGRATION_SDK_VERSION = AwesomeVersion("6.61")
+NETWORK_TYPE_NEW = "new"
+NETWORK_TYPE_EXISTING = "existing"
+
def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
"""Return a schema for the manual step."""
@@ -632,6 +634,81 @@ async def async_step_configure_addon_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
+
+ if user_input is not None:
+ self.usb_path = user_input[CONF_USB_PATH]
+ return await self.async_step_network_type()
+
+ if self._usb_discovery:
+ return await self.async_step_network_type()
+
+ usb_path = self.usb_path or ""
+
+ try:
+ ports = await async_get_usb_ports(self.hass)
+ except OSError as err:
+ _LOGGER.error("Failed to get USB ports: %s", err)
+ return self.async_abort(reason="usb_ports_failed")
+
+ data_schema = vol.Schema(
+ {
+ vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
+ }
+ )
+
+ return self.async_show_form(
+ step_id="configure_addon_user", data_schema=data_schema
+ )
+
+ async def async_step_network_type(
+ self, user_input: dict[str, Any] | None = None
+ ) -> ConfigFlowResult:
+ """Ask for network type (new or existing)."""
+ # For recommended installation, automatically set network type to "new"
+ if self._recommended_install:
+ user_input = {"network_type": NETWORK_TYPE_NEW}
+
+ if user_input is not None:
+ if user_input["network_type"] == NETWORK_TYPE_NEW:
+ # Set all keys to empty strings for new network
+ self.s0_legacy_key = ""
+ self.s2_access_control_key = ""
+ self.s2_authenticated_key = ""
+ self.s2_unauthenticated_key = ""
+ self.lr_s2_access_control_key = ""
+ self.lr_s2_authenticated_key = ""
+
+ addon_config_updates = {
+ CONF_ADDON_DEVICE: self.usb_path,
+ CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key,
+ CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_key,
+ CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_key,
+ CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
+ CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_key,
+ CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
+ }
+
+ await self._async_set_addon_config(addon_config_updates)
+ return await self.async_step_start_addon()
+
+ # Network already exists, go to security keys step
+ return await self.async_step_configure_security_keys()
+
+ return self.async_show_form(
+ step_id="network_type",
+ data_schema=vol.Schema(
+ {
+ vol.Required("network_type", default=""): vol.In(
+ [NETWORK_TYPE_NEW, NETWORK_TYPE_EXISTING]
+ )
+ }
+ ),
+ )
+
+ async def async_step_configure_security_keys(
+ self, user_input: dict[str, Any] | None = None
+ ) -> ConfigFlowResult:
+ """Ask for security keys for existing Z-Wave network."""
addon_info = await self._async_get_addon_info()
addon_config = addon_info.options
@@ -654,10 +731,6 @@ async def async_step_configure_addon_user(
CONF_ADDON_LR_S2_AUTHENTICATED_KEY, self.lr_s2_authenticated_key or ""
)
- if self._recommended_install and self._usb_discovery:
- # Recommended installation with USB discovery, skip asking for keys
- user_input = {}
-
if user_input is not None:
self.s0_legacy_key = user_input.get(CONF_S0_LEGACY_KEY, s0_legacy_key)
self.s2_access_control_key = user_input.get(
@@ -675,8 +748,6 @@ async def async_step_configure_addon_user(
self.lr_s2_authenticated_key = user_input.get(
CONF_LR_S2_AUTHENTICATED_KEY, lr_s2_authenticated_key
)
- if not self._usb_discovery:
- self.usb_path = user_input[CONF_USB_PATH]
addon_config_updates = {
CONF_ADDON_DEVICE: self.usb_path,
@@ -689,14 +760,10 @@ async def async_step_configure_addon_user(
}
await self._async_set_addon_config(addon_config_updates)
-
return await self.async_step_start_addon()
- usb_path = self.usb_path or addon_config.get(CONF_ADDON_DEVICE) or ""
- schema: VolDictType = (
- {}
- if self._recommended_install
- else {
+ data_schema = vol.Schema(
+ {
vol.Optional(CONF_S0_LEGACY_KEY, default=s0_legacy_key): str,
vol.Optional(
CONF_S2_ACCESS_CONTROL_KEY, default=s2_access_control_key
@@ -716,22 +783,8 @@ async def async_step_configure_addon_user(
}
)
- if not self._usb_discovery:
- try:
- ports = await async_get_usb_ports(self.hass)
- except OSError as err:
- _LOGGER.error("Failed to get USB ports: %s", err)
- return self.async_abort(reason="usb_ports_failed")
-
- schema = {
- vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
- **schema,
- }
-
- data_schema = vol.Schema(schema)
-
return self.async_show_form(
- step_id="configure_addon_user", data_schema=data_schema
+ step_id="configure_security_keys", data_schema=data_schema
)
async def async_step_finish_addon_setup_user(
diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json
index f61d871cfb90b5..b7f9b180624f8d 100644
--- a/homeassistant/components/zwave_js/strings.json
+++ b/homeassistant/components/zwave_js/strings.json
@@ -38,26 +38,38 @@
},
"step": {
"configure_addon_user": {
+ "data": {
+ "usb_path": "[%key:common::config_flow::data::usb_path%]"
+ },
+ "description": "Select your Z-Wave adapter",
+ "title": "Enter the Z-Wave add-on configuration"
+ },
+ "network_type": {
+ "data": {
+ "network_type": "Is your network new or does it already exist?"
+ },
+ "title": "Z-Wave network"
+ },
+ "configure_security_keys": {
"data": {
"lr_s2_access_control_key": "Long Range S2 Access Control Key",
"lr_s2_authenticated_key": "Long Range S2 Authenticated Key",
"s0_legacy_key": "S0 Key (Legacy)",
"s2_access_control_key": "S2 Access Control Key",
"s2_authenticated_key": "S2 Authenticated Key",
- "s2_unauthenticated_key": "S2 Unauthenticated Key",
- "usb_path": "[%key:common::config_flow::data::usb_path%]"
+ "s2_unauthenticated_key": "S2 Unauthenticated Key"
},
- "description": "Select your Z-Wave adapter",
- "title": "Enter the Z-Wave add-on configuration"
+ "description": "Enter the security keys for your existing Z-Wave network",
+ "title": "Security keys"
},
"configure_addon_reconfigure": {
"data": {
- "lr_s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::lr_s2_access_control_key%]",
- "lr_s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::lr_s2_authenticated_key%]",
- "s0_legacy_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s0_legacy_key%]",
- "s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_access_control_key%]",
- "s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_authenticated_key%]",
- "s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_unauthenticated_key%]",
+ "lr_s2_access_control_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::lr_s2_access_control_key%]",
+ "lr_s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::lr_s2_authenticated_key%]",
+ "s0_legacy_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s0_legacy_key%]",
+ "s2_access_control_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_access_control_key%]",
+ "s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_authenticated_key%]",
+ "s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_unauthenticated_key%]",
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"description": "[%key:component::zwave_js::config::step::configure_addon_user::description%]",
@@ -622,5 +634,13 @@
},
"name": "Set a value (advanced)"
}
+ },
+ "selector": {
+ "network_type": {
+ "options": {
+ "new": "It's new",
+ "existing": "It already exists"
+ }
+ }
}
}
diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py
index 832bbf219f8bfc..39629d07494b11 100644
--- a/homeassistant/helpers/entity.py
+++ b/homeassistant/helpers/entity.py
@@ -92,7 +92,11 @@ def async_setup(hass: HomeAssistant) -> None:
@bind_hass
@singleton.singleton(DATA_ENTITY_SOURCE)
def entity_sources(hass: HomeAssistant) -> dict[str, EntityInfo]:
- """Get the entity sources."""
+ """Get the entity sources.
+
+ Items are added to this dict by Entity.async_internal_added_to_hass and
+ removed by Entity.async_internal_will_remove_from_hass.
+ """
return {}
diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt
index 5839a3ae0145fd..80fccb1bf78aa7 100644
--- a/homeassistant/package_constraints.txt
+++ b/homeassistant/package_constraints.txt
@@ -38,7 +38,7 @@ habluetooth==3.49.0
hass-nabucasa==0.104.0
hassil==2.2.3
home-assistant-bluetooth==1.13.1
-home-assistant-frontend==20250626.0
+home-assistant-frontend==20250627.0
home-assistant-intents==2025.6.23
httpx==0.28.1
ifaddr==0.2.0
diff --git a/requirements_all.txt b/requirements_all.txt
index bc60bd0e008632..c1048afcebb433 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -185,7 +185,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
-aioamazondevices==3.1.19
+aioamazondevices==3.1.22
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@@ -204,7 +204,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower
-aioautomower==1.0.0
+aioautomower==1.0.1
# homeassistant.components.azure_devops
aioazuredevops==2.2.1
@@ -1168,7 +1168,7 @@ hole==0.8.0
holidays==0.75
# homeassistant.components.frontend
-home-assistant-frontend==20250626.0
+home-assistant-frontend==20250627.0
# homeassistant.components.conversation
home-assistant-intents==2025.6.23
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 8a04a84adde99f..bb63020e4de795 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -173,7 +173,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
-aioamazondevices==3.1.19
+aioamazondevices==3.1.22
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@@ -192,7 +192,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower
-aioautomower==1.0.0
+aioautomower==1.0.1
# homeassistant.components.azure_devops
aioazuredevops==2.2.1
@@ -1017,7 +1017,7 @@ hole==0.8.0
holidays==0.75
# homeassistant.components.frontend
-home-assistant-frontend==20250626.0
+home-assistant-frontend==20250627.0
# homeassistant.components.conversation
home-assistant-intents==2025.6.23
diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt
index 1abbf3977cf92c..b9c800be3ca834 100644
--- a/requirements_test_pre_commit.txt
+++ b/requirements_test_pre_commit.txt
@@ -1,5 +1,5 @@
# Automatically generated from .pre-commit-config.yaml by gen_requirements_all.py, do not edit
codespell==2.4.1
-ruff==0.12.0
+ruff==0.12.1
yamllint==1.37.1
diff --git a/script/hassfest/docker/Dockerfile b/script/hassfest/docker/Dockerfile
index afd58539853457..5168388c934e04 100644
--- a/script/hassfest/docker/Dockerfile
+++ b/script/hassfest/docker/Dockerfile
@@ -27,7 +27,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.7.1,source=/uv,target=/bin/uv \
stdlib-list==0.10.0 \
pipdeptree==2.26.1 \
tqdm==4.67.1 \
- ruff==0.12.0 \
+ ruff==0.12.1 \
PyTurboJPEG==1.8.0 \
go2rtc-client==0.2.1 \
ha-ffmpeg==3.2.2 \
diff --git a/tests/components/backup/common.py b/tests/components/backup/common.py
index 3197cbfadeb753..e6c5aab08ccba1 100644
--- a/tests/components/backup/common.py
+++ b/tests/components/backup/common.py
@@ -138,6 +138,10 @@ async def setup_backup_integration(
patch(
"homeassistant.components.backup.backup.is_hassio", return_value=with_hassio
),
+ patch(
+ "homeassistant.components.backup.services.is_hassio",
+ return_value=with_hassio,
+ ),
):
remote_agents = remote_agents or []
remote_agents_dict = {}
diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py
index f75b0e7f2b0781..5e018e73f2a1ef 100644
--- a/tests/components/huawei_lte/test_config_flow.py
+++ b/tests/components/huawei_lte/test_config_flow.py
@@ -330,24 +330,25 @@ async def test_ssdp(
url = FIXTURE_USER_INPUT[CONF_URL][:-1] # strip trailing slash for appending port
context = {"source": config_entries.SOURCE_SSDP}
login_requests_mock.request(**requests_mock_request_kwargs)
+ service_info = SsdpServiceInfo(
+ ssdp_usn="mock_usn",
+ ssdp_st="upnp:rootdevice",
+ ssdp_location=f"{url}:60957/rootDesc.xml",
+ upnp={
+ ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
+ ATTR_UPNP_MANUFACTURER: "Huawei",
+ ATTR_UPNP_MANUFACTURER_URL: "http://www.huawei.com/",
+ ATTR_UPNP_MODEL_NAME: "Huawei router",
+ ATTR_UPNP_MODEL_NUMBER: "12345678",
+ ATTR_UPNP_PRESENTATION_URL: url,
+ ATTR_UPNP_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
+ **upnp_data,
+ },
+ )
result = await hass.config_entries.flow.async_init(
DOMAIN,
context=context,
- data=SsdpServiceInfo(
- ssdp_usn="mock_usn",
- ssdp_st="upnp:rootdevice",
- ssdp_location=f"{url}:60957/rootDesc.xml",
- upnp={
- ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
- ATTR_UPNP_MANUFACTURER: "Huawei",
- ATTR_UPNP_MANUFACTURER_URL: "http://www.huawei.com/",
- ATTR_UPNP_MODEL_NAME: "Huawei router",
- ATTR_UPNP_MODEL_NUMBER: "12345678",
- ATTR_UPNP_PRESENTATION_URL: url,
- ATTR_UPNP_UDN: "uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
- **upnp_data,
- },
- ),
+ data=service_info,
)
for k, v in expected_result.items():
@@ -356,6 +357,23 @@ async def test_ssdp(
assert result["data_schema"] is not None
assert result["data_schema"]({})[CONF_URL] == url + "/"
+ if result["type"] == FlowResultType.ABORT:
+ return
+
+ login_requests_mock.request(
+ ANY,
+ f"{FIXTURE_USER_INPUT[CONF_URL]}api/user/login",
+ text="OK",
+ )
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ user_input={},
+ )
+ await hass.async_block_till_done()
+
+ assert result["type"] == FlowResultType.CREATE_ENTRY
+ assert result["title"] == service_info.upnp[ATTR_UPNP_MODEL_NAME]
+
@pytest.mark.parametrize(
("login_response_text", "expected_result", "expected_entry_data"),
diff --git a/tests/components/husqvarna_automower/snapshots/test_calendar.ambr b/tests/components/husqvarna_automower/snapshots/test_calendar.ambr
index 7cd8c68b624d6c..7ff32f69df0c3d 100644
--- a/tests/components/husqvarna_automower/snapshots/test_calendar.ambr
+++ b/tests/components/husqvarna_automower/snapshots/test_calendar.ambr
@@ -6,72 +6,72 @@
dict({
'end': '2023-06-05T09:00:00+02:00',
'start': '2023-06-05T01:00:00+02:00',
- 'summary': 'Back lawn schedule 2',
+ 'summary': 'Test Mower 1 Back lawn schedule 2',
}),
dict({
'end': '2023-06-06T00:00:00+02:00',
'start': '2023-06-05T19:00:00+02:00',
- 'summary': 'Front lawn schedule 1',
+ 'summary': 'Test Mower 1 Front lawn schedule 1',
}),
dict({
'end': '2023-06-06T08:00:00+02:00',
'start': '2023-06-06T00:00:00+02:00',
- 'summary': 'Back lawn schedule 1',
+ 'summary': 'Test Mower 1 Back lawn schedule 1',
}),
dict({
'end': '2023-06-06T08:00:00+02:00',
'start': '2023-06-06T00:00:00+02:00',
- 'summary': 'Front lawn schedule 2',
+ 'summary': 'Test Mower 1 Front lawn schedule 2',
}),
dict({
'end': '2023-06-06T09:00:00+02:00',
'start': '2023-06-06T01:00:00+02:00',
- 'summary': 'Back lawn schedule 2',
+ 'summary': 'Test Mower 1 Back lawn schedule 2',
}),
dict({
'end': '2023-06-08T00:00:00+02:00',
'start': '2023-06-07T19:00:00+02:00',
- 'summary': 'Front lawn schedule 1',
+ 'summary': 'Test Mower 1 Front lawn schedule 1',
}),
dict({
'end': '2023-06-08T08:00:00+02:00',
'start': '2023-06-08T00:00:00+02:00',
- 'summary': 'Back lawn schedule 1',
+ 'summary': 'Test Mower 1 Back lawn schedule 1',
}),
dict({
'end': '2023-06-08T08:00:00+02:00',
'start': '2023-06-08T00:00:00+02:00',
- 'summary': 'Front lawn schedule 2',
+ 'summary': 'Test Mower 1 Front lawn schedule 2',
}),
dict({
'end': '2023-06-08T09:00:00+02:00',
'start': '2023-06-08T01:00:00+02:00',
- 'summary': 'Back lawn schedule 2',
+ 'summary': 'Test Mower 1 Back lawn schedule 2',
}),
dict({
'end': '2023-06-10T00:00:00+02:00',
'start': '2023-06-09T19:00:00+02:00',
- 'summary': 'Front lawn schedule 1',
+ 'summary': 'Test Mower 1 Front lawn schedule 1',
}),
dict({
'end': '2023-06-10T08:00:00+02:00',
'start': '2023-06-10T00:00:00+02:00',
- 'summary': 'Back lawn schedule 1',
+ 'summary': 'Test Mower 1 Back lawn schedule 1',
}),
dict({
'end': '2023-06-10T08:00:00+02:00',
'start': '2023-06-10T00:00:00+02:00',
- 'summary': 'Front lawn schedule 2',
+ 'summary': 'Test Mower 1 Front lawn schedule 2',
}),
dict({
'end': '2023-06-10T09:00:00+02:00',
'start': '2023-06-10T01:00:00+02:00',
- 'summary': 'Back lawn schedule 2',
+ 'summary': 'Test Mower 1 Back lawn schedule 2',
}),
dict({
'end': '2023-06-12T09:00:00+02:00',
'start': '2023-06-12T01:00:00+02:00',
- 'summary': 'Back lawn schedule 2',
+ 'summary': 'Test Mower 1 Back lawn schedule 2',
}),
]),
}),
@@ -80,7 +80,7 @@
dict({
'end': '2023-06-05T02:49:00+02:00',
'start': '2023-06-05T02:00:00+02:00',
- 'summary': 'Schedule 1',
+ 'summary': 'Test Mower 2 Schedule 1',
}),
]),
}),
diff --git a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr
index 772eef761db1bc..2c3352ecf8e2c1 100644
--- a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr
+++ b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr
@@ -80,7 +80,7 @@
'work_area_name': 'Front lawn',
}),
'planner': dict({
- 'external_reason': 'iftt_wildlife',
+ 'external_reason': 'ifttt_wildlife',
'next_start_datetime': '2023-06-05T19:00:00+02:00',
'override': dict({
'action': 'not_active',
diff --git a/tests/components/matter/snapshots/test_number.ambr b/tests/components/matter/snapshots/test_number.ambr
index 5ba0f275f8da24..d71980c06138d1 100644
--- a/tests/components/matter/snapshots/test_number.ambr
+++ b/tests/components/matter/snapshots/test_number.ambr
@@ -1846,6 +1846,64 @@
'state': '0.0',
})
# ---
+# name: test_numbers[oven][number.mock_oven_temperature_setpoint-entry]
+ EntityRegistryEntrySnapshot({
+ 'aliases': set({
+ }),
+ 'area_id': None,
+ 'capabilities': dict({
+ 'max': 288.0,
+ 'min': 76.0,
+ 'mode': ,
+ 'step': 1.0,
+ }),
+ 'config_entry_id': ,
+ 'config_subentry_id': ,
+ 'device_class': None,
+ 'device_id': ,
+ 'disabled_by': None,
+ 'domain': 'number',
+ 'entity_category': None,
+ 'entity_id': 'number.mock_oven_temperature_setpoint',
+ 'has_entity_name': True,
+ 'hidden_by': None,
+ 'icon': None,
+ 'id': ,
+ 'labels': set({
+ }),
+ 'name': None,
+ 'options': dict({
+ }),
+ 'original_device_class': None,
+ 'original_icon': None,
+ 'original_name': 'Temperature setpoint',
+ 'platform': 'matter',
+ 'previous_unique_id': None,
+ 'suggested_object_id': None,
+ 'supported_features': 0,
+ 'translation_key': 'temperature_setpoint',
+ 'unique_id': '00000000000004D2-0000000000000002-MatterNodeDevice-2-TemperatureControlTemperatureSetpoint-86-0',
+ 'unit_of_measurement': ,
+ })
+# ---
+# name: test_numbers[oven][number.mock_oven_temperature_setpoint-state]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Mock Oven Temperature setpoint',
+ 'max': 288.0,
+ 'min': 76.0,
+ 'mode': ,
+ 'step': 1.0,
+ 'unit_of_measurement': ,
+ }),
+ 'context': ,
+ 'entity_id': 'number.mock_oven_temperature_setpoint',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': '76.0',
+ })
+# ---
# name: test_numbers[pump][number.mock_pump_on_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -1903,3 +1961,177 @@
'state': '0',
})
# ---
+# name: test_numbers[silabs_laundrywasher][number.laundrywasher_temperature_setpoint-entry]
+ EntityRegistryEntrySnapshot({
+ 'aliases': set({
+ }),
+ 'area_id': None,
+ 'capabilities': dict({
+ 'max': 0.0,
+ 'min': 0.0,
+ 'mode': ,
+ 'step': 1.0,
+ }),
+ 'config_entry_id': ,
+ 'config_subentry_id': ,
+ 'device_class': None,
+ 'device_id': ,
+ 'disabled_by': None,
+ 'domain': 'number',
+ 'entity_category': None,
+ 'entity_id': 'number.laundrywasher_temperature_setpoint',
+ 'has_entity_name': True,
+ 'hidden_by': None,
+ 'icon': None,
+ 'id': ,
+ 'labels': set({
+ }),
+ 'name': None,
+ 'options': dict({
+ }),
+ 'original_device_class': None,
+ 'original_icon': None,
+ 'original_name': 'Temperature setpoint',
+ 'platform': 'matter',
+ 'previous_unique_id': None,
+ 'suggested_object_id': None,
+ 'supported_features': 0,
+ 'translation_key': 'temperature_setpoint',
+ 'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-1-TemperatureControlTemperatureSetpoint-86-0',
+ 'unit_of_measurement': ,
+ })
+# ---
+# name: test_numbers[silabs_laundrywasher][number.laundrywasher_temperature_setpoint-state]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'LaundryWasher Temperature setpoint',
+ 'max': 0.0,
+ 'min': 0.0,
+ 'mode': ,
+ 'step': 1.0,
+ 'unit_of_measurement': ,
+ }),
+ 'context': ,
+ 'entity_id': 'number.laundrywasher_temperature_setpoint',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': '0.0',
+ })
+# ---
+# name: test_numbers[silabs_refrigerator][number.refrigerator_temperature_setpoint_2-entry]
+ EntityRegistryEntrySnapshot({
+ 'aliases': set({
+ }),
+ 'area_id': None,
+ 'capabilities': dict({
+ 'max': -15.0,
+ 'min': -18.0,
+ 'mode': ,
+ 'step': 1.0,
+ }),
+ 'config_entry_id': ,
+ 'config_subentry_id': ,
+ 'device_class': None,
+ 'device_id': ,
+ 'disabled_by': None,
+ 'domain': 'number',
+ 'entity_category': None,
+ 'entity_id': 'number.refrigerator_temperature_setpoint_2',
+ 'has_entity_name': True,
+ 'hidden_by': None,
+ 'icon': None,
+ 'id': ,
+ 'labels': set({
+ }),
+ 'name': None,
+ 'options': dict({
+ }),
+ 'original_device_class': None,
+ 'original_icon': None,
+ 'original_name': 'Temperature setpoint (2)',
+ 'platform': 'matter',
+ 'previous_unique_id': None,
+ 'suggested_object_id': None,
+ 'supported_features': 0,
+ 'translation_key': 'temperature_setpoint',
+ 'unique_id': '00000000000004D2-000000000000003A-MatterNodeDevice-2-TemperatureControlTemperatureSetpoint-86-0',
+ 'unit_of_measurement': ,
+ })
+# ---
+# name: test_numbers[silabs_refrigerator][number.refrigerator_temperature_setpoint_2-state]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Refrigerator Temperature setpoint (2)',
+ 'max': -15.0,
+ 'min': -18.0,
+ 'mode': ,
+ 'step': 1.0,
+ 'unit_of_measurement': ,
+ }),
+ 'context': ,
+ 'entity_id': 'number.refrigerator_temperature_setpoint_2',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': '-18.0',
+ })
+# ---
+# name: test_numbers[silabs_refrigerator][number.refrigerator_temperature_setpoint_3-entry]
+ EntityRegistryEntrySnapshot({
+ 'aliases': set({
+ }),
+ 'area_id': None,
+ 'capabilities': dict({
+ 'max': 4.0,
+ 'min': 1.0,
+ 'mode': ,
+ 'step': 1.0,
+ }),
+ 'config_entry_id': ,
+ 'config_subentry_id': ,
+ 'device_class': None,
+ 'device_id': ,
+ 'disabled_by': None,
+ 'domain': 'number',
+ 'entity_category': None,
+ 'entity_id': 'number.refrigerator_temperature_setpoint_3',
+ 'has_entity_name': True,
+ 'hidden_by': None,
+ 'icon': None,
+ 'id': ,
+ 'labels': set({
+ }),
+ 'name': None,
+ 'options': dict({
+ }),
+ 'original_device_class': None,
+ 'original_icon': None,
+ 'original_name': 'Temperature setpoint (3)',
+ 'platform': 'matter',
+ 'previous_unique_id': None,
+ 'suggested_object_id': None,
+ 'supported_features': 0,
+ 'translation_key': 'temperature_setpoint',
+ 'unique_id': '00000000000004D2-000000000000003A-MatterNodeDevice-3-TemperatureControlTemperatureSetpoint-86-0',
+ 'unit_of_measurement': ,
+ })
+# ---
+# name: test_numbers[silabs_refrigerator][number.refrigerator_temperature_setpoint_3-state]
+ StateSnapshot({
+ 'attributes': ReadOnlyDict({
+ 'friendly_name': 'Refrigerator Temperature setpoint (3)',
+ 'max': 4.0,
+ 'min': 1.0,
+ 'mode': ,
+ 'step': 1.0,
+ 'unit_of_measurement': ,
+ }),
+ 'context': ,
+ 'entity_id': 'number.refrigerator_temperature_setpoint_3',
+ 'last_changed': ,
+ 'last_reported': ,
+ 'last_updated': ,
+ 'state': '4.0',
+ })
+# ---
diff --git a/tests/components/matter/test_number.py b/tests/components/matter/test_number.py
index c94b92dbc46738..d1ccc1a229b06b 100644
--- a/tests/components/matter/test_number.py
+++ b/tests/components/matter/test_number.py
@@ -2,6 +2,7 @@
from unittest.mock import MagicMock, call
+from chip.clusters import Objects as clusters
from matter_server.client.models.node import MatterNode
from matter_server.common import custom_clusters
from matter_server.common.errors import MatterError
@@ -101,6 +102,44 @@ async def test_eve_weather_sensor_altitude(
)
+@pytest.mark.parametrize("node_fixture", ["silabs_refrigerator"])
+async def test_temperature_control_temperature_setpoint(
+ hass: HomeAssistant,
+ matter_client: MagicMock,
+ matter_node: MatterNode,
+) -> None:
+ """Test TemperatureSetpoint from TemperatureControl."""
+ # TemperatureSetpoint
+ state = hass.states.get("number.refrigerator_temperature_setpoint_2")
+ assert state
+ assert state.state == "-18.0"
+
+ set_node_attribute(matter_node, 2, 86, 0, -1600)
+ await trigger_subscription_callback(hass, matter_client)
+ state = hass.states.get("number.refrigerator_temperature_setpoint_2")
+ assert state
+ assert state.state == "-16.0"
+
+ # test set value
+ await hass.services.async_call(
+ "number",
+ "set_value",
+ {
+ "entity_id": "number.refrigerator_temperature_setpoint_2",
+ "value": -17,
+ },
+ blocking=True,
+ )
+ assert matter_client.send_device_command.call_count == 1
+ assert matter_client.send_device_command.call_args == call(
+ node_id=matter_node.node_id,
+ endpoint_id=2,
+ command=clusters.TemperatureControl.Commands.SetTemperature(
+ targetTemperature=-1700
+ ),
+ )
+
+
@pytest.mark.parametrize("node_fixture", ["dimmable_light"])
async def test_matter_exception_on_write_attribute(
hass: HomeAssistant,
diff --git a/tests/components/playstation_network/test_config_flow.py b/tests/components/playstation_network/test_config_flow.py
index 981e459d283d71..dc3ad55c64f664 100644
--- a/tests/components/playstation_network/test_config_flow.py
+++ b/tests/components/playstation_network/test_config_flow.py
@@ -296,3 +296,32 @@ async def test_flow_reauth_account_mismatch(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unique_id_mismatch"
+
+
+@pytest.mark.usefixtures("mock_psnawpapi")
+async def test_flow_reconfigure(
+ hass: HomeAssistant,
+ config_entry: MockConfigEntry,
+) -> None:
+ """Test reconfigure flow."""
+ config_entry.add_to_hass(hass)
+ assert await hass.config_entries.async_setup(config_entry.entry_id)
+ await hass.async_block_till_done()
+
+ assert config_entry.state is ConfigEntryState.LOADED
+
+ result = await config_entry.start_reconfigure_flow(hass)
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "reconfigure"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {CONF_NPSSO: "NEW_NPSSO_TOKEN"},
+ )
+
+ assert result["type"] is FlowResultType.ABORT
+ assert result["reason"] == "reconfigure_successful"
+
+ assert config_entry.data[CONF_NPSSO] == "NEW_NPSSO_TOKEN"
+
+ assert len(hass.config_entries.async_entries()) == 1
diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py
index 54923b538f6861..3234e3eb0b9e23 100644
--- a/tests/components/shelly/test_switch.py
+++ b/tests/components/shelly/test_switch.py
@@ -1,15 +1,18 @@
"""Tests for Shelly switch platform."""
from copy import deepcopy
+from datetime import timedelta
from unittest.mock import AsyncMock, Mock
from aioshelly.const import MODEL_1PM, MODEL_GAS, MODEL_MOTION
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
+from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.shelly.const import (
DOMAIN,
+ ENTRY_RELOAD_COOLDOWN,
MODEL_WALL_DISPLAY,
MOTION_MODELS,
)
@@ -28,9 +31,14 @@
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.entity_registry import EntityRegistry
-from . import init_integration, register_device, register_entity
+from . import (
+ init_integration,
+ inject_rpc_device_event,
+ register_device,
+ register_entity,
+)
-from tests.common import mock_restore_cache
+from tests.common import async_fire_time_changed, mock_restore_cache
RELAY_BLOCK_ID = 0
GAS_VALVE_BLOCK_ID = 6
@@ -374,15 +382,57 @@ async def test_rpc_device_unique_ids(
async def test_rpc_device_switch_type_lights_mode(
- hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch
+ hass: HomeAssistant,
+ freezer: FrozenDateTimeFactory,
+ mock_rpc_device: Mock,
+ monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test RPC device with switch in consumption type lights mode."""
+ switch_entity_id = "switch.test_name_test_switch_0"
+ light_entity_id = "light.test_name_test_switch_0"
+
+ monkeypatch.delitem(mock_rpc_device.status, "cover:0")
+ monkeypatch.setitem(mock_rpc_device.status["sys"], "relay_in_thermostat", False)
+ await init_integration(hass, 2)
+
+ # Entity is created as switch
+ assert hass.states.get(switch_entity_id)
+ assert hass.states.get(light_entity_id) is None
+
+ # Generate config change from switch to light
monkeypatch.setitem(
mock_rpc_device.config["sys"]["ui_data"], "consumption_types", ["lights"]
)
- await init_integration(hass, 2)
+ inject_rpc_device_event(
+ monkeypatch,
+ mock_rpc_device,
+ {
+ "events": [
+ {
+ "data": [],
+ "event": "config_changed",
+ "id": 1,
+ "ts": 1668522399.2,
+ },
+ {
+ "data": [],
+ "id": 2,
+ "ts": 1668522399.2,
+ },
+ ],
+ "ts": 1668522399.2,
+ },
+ )
+ await hass.async_block_till_done()
+
+ # Wait for debouncer
+ freezer.tick(timedelta(seconds=ENTRY_RELOAD_COOLDOWN))
+ async_fire_time_changed(hass)
+ await hass.async_block_till_done()
- assert hass.states.get("switch.test_switch_0") is None
+ # Switch entity should be removed and light entity created
+ assert hass.states.get(switch_entity_id) is None
+ assert hass.states.get(light_entity_id)
@pytest.mark.parametrize(
diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py
index 122801e6c59cd0..29ef524a4ab585 100644
--- a/tests/components/template/test_binary_sensor.py
+++ b/tests/components/template/test_binary_sensor.py
@@ -1,9 +1,10 @@
"""The tests for the Template Binary sensor platform."""
+from collections.abc import Generator
from copy import deepcopy
from datetime import UTC, datetime, timedelta
import logging
-from unittest.mock import patch
+from unittest.mock import Mock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
@@ -22,8 +23,8 @@
from homeassistant.core import Context, CoreState, HomeAssistant, State
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_component import async_update_entity
+from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
-from homeassistant.util import dt as dt_util
from tests.common import (
MockConfigEntry,
@@ -33,6 +34,16 @@
mock_restore_cache_with_extra_data,
)
+_BEER_TRIGGER_VALUE_TEMPLATE = (
+ "{% if trigger.event.data.beer < 0 %}"
+ "{{ 1 / 0 == 10 }}"
+ "{% elif trigger.event.data.beer == 0 %}"
+ "{{ None }}"
+ "{% else %}"
+ "{{ trigger.event.data.beer == 2 }}"
+ "{% endif %}"
+)
+
@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
@@ -70,7 +81,9 @@
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_setup_minimal(hass: HomeAssistant, entity_id, name, attributes) -> None:
+async def test_setup_minimal(
+ hass: HomeAssistant, entity_id: str, name: str, attributes: dict[str, str]
+) -> None:
"""Test the setup."""
state = hass.states.get(entity_id)
assert state is not None
@@ -115,7 +128,7 @@ async def test_setup_minimal(hass: HomeAssistant, entity_id, name, attributes) -
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_setup(hass: HomeAssistant, entity_id) -> None:
+async def test_setup(hass: HomeAssistant, entity_id: str) -> None:
"""Test the setup."""
state = hass.states.get(entity_id)
assert state is not None
@@ -232,11 +245,59 @@ async def test_setup_config_entry(
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_setup_invalid_sensors(hass: HomeAssistant, count) -> None:
+async def test_setup_invalid_sensors(hass: HomeAssistant, count: int) -> None:
"""Test setup with no sensors."""
assert len(hass.states.async_entity_ids("binary_sensor")) == count
+@pytest.mark.parametrize(
+ ("state_template", "expected_result"),
+ [
+ ("{{ None }}", STATE_OFF),
+ ("{{ True }}", STATE_ON),
+ ("{{ False }}", STATE_OFF),
+ ("{{ 1 }}", STATE_ON),
+ (
+ "{% if states('binary_sensor.three') in ('unknown','unavailable') %}"
+ "{{ None }}"
+ "{% else %}"
+ "{{ states('binary_sensor.three') == 'off' }}"
+ "{% endif %}",
+ STATE_OFF,
+ ),
+ ("{{ 1 / 0 == 10 }}", STATE_UNAVAILABLE),
+ ],
+)
+async def test_state(
+ hass: HomeAssistant,
+ state_template: str,
+ expected_result: str,
+) -> None:
+ """Test the config flow."""
+ hass.states.async_set("binary_sensor.one", "on")
+ hass.states.async_set("binary_sensor.two", "off")
+ hass.states.async_set("binary_sensor.three", "unknown")
+
+ template_config_entry = MockConfigEntry(
+ data={},
+ domain=template.DOMAIN,
+ options={
+ "name": "My template",
+ "state": state_template,
+ "template_type": binary_sensor.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("binary_sensor.my_template")
+ assert state is not None
+ assert state.state == expected_result
+
+
@pytest.mark.parametrize("count", [1])
@pytest.mark.parametrize(
("config", "domain", "entity_id"),
@@ -279,7 +340,7 @@ async def test_setup_invalid_sensors(hass: HomeAssistant, count) -> None:
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_icon_template(hass: HomeAssistant, entity_id) -> None:
+async def test_icon_template(hass: HomeAssistant, entity_id: str) -> None:
"""Test icon template."""
state = hass.states.get(entity_id)
assert state.attributes.get("icon") == ""
@@ -332,7 +393,7 @@ async def test_icon_template(hass: HomeAssistant, entity_id) -> None:
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_entity_picture_template(hass: HomeAssistant, entity_id) -> None:
+async def test_entity_picture_template(hass: HomeAssistant, entity_id: str) -> None:
"""Test entity_picture template."""
state = hass.states.get(entity_id)
assert state.attributes.get("entity_picture") == ""
@@ -381,7 +442,7 @@ async def test_entity_picture_template(hass: HomeAssistant, entity_id) -> None:
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_attribute_templates(hass: HomeAssistant, entity_id) -> None:
+async def test_attribute_templates(hass: HomeAssistant, entity_id: str) -> None:
"""Test attribute_templates template."""
state = hass.states.get(entity_id)
assert state.attributes.get("test_attribute") == "It ."
@@ -394,7 +455,7 @@ async def test_attribute_templates(hass: HomeAssistant, entity_id) -> None:
@pytest.fixture
-async def setup_mock():
+def setup_mock() -> Generator[Mock]:
"""Do setup of sensor mock."""
with patch(
"homeassistant.components.template.binary_sensor."
@@ -426,7 +487,7 @@ async def setup_mock():
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_match_all(hass: HomeAssistant, setup_mock) -> None:
+async def test_match_all(hass: HomeAssistant, setup_mock: Mock) -> None:
"""Test template that is rerendered on any state lifecycle."""
init_calls = len(setup_mock.mock_calls)
@@ -565,7 +626,9 @@ async def test_event(hass: HomeAssistant) -> None:
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_template_delay_on_off(hass: HomeAssistant) -> None:
+async def test_template_delay_on_off(
+ hass: HomeAssistant, freezer: FrozenDateTimeFactory
+) -> None:
"""Test binary sensor template delay on."""
# Ensure the initial state is not on
assert hass.states.get("binary_sensor.test_on").state != STATE_ON
@@ -577,8 +640,8 @@ async def test_template_delay_on_off(hass: HomeAssistant) -> None:
assert hass.states.get("binary_sensor.test_on").state == STATE_OFF
assert hass.states.get("binary_sensor.test_off").state == STATE_ON
- future = dt_util.utcnow() + timedelta(seconds=5)
- async_fire_time_changed(hass, future)
+ freezer.tick(timedelta(seconds=5))
+ async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.test_on").state == STATE_ON
assert hass.states.get("binary_sensor.test_off").state == STATE_ON
@@ -599,8 +662,8 @@ async def test_template_delay_on_off(hass: HomeAssistant) -> None:
assert hass.states.get("binary_sensor.test_on").state == STATE_OFF
assert hass.states.get("binary_sensor.test_off").state == STATE_ON
- future = dt_util.utcnow() + timedelta(seconds=5)
- async_fire_time_changed(hass, future)
+ freezer.tick(timedelta(seconds=5))
+ async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.test_on").state == STATE_OFF
assert hass.states.get("binary_sensor.test_off").state == STATE_OFF
@@ -645,7 +708,7 @@ async def test_template_delay_on_off(hass: HomeAssistant) -> None:
)
@pytest.mark.usefixtures("start_ha")
async def test_available_without_availability_template(
- hass: HomeAssistant, entity_id
+ hass: HomeAssistant, entity_id: str
) -> None:
"""Ensure availability is true without an availability_template."""
state = hass.states.get(entity_id)
@@ -694,7 +757,7 @@ async def test_available_without_availability_template(
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_availability_template(hass: HomeAssistant, entity_id) -> None:
+async def test_availability_template(hass: HomeAssistant, entity_id: str) -> None:
"""Test availability template."""
hass.states.async_set("sensor.test_state", STATE_OFF)
await hass.async_block_till_done()
@@ -731,7 +794,7 @@ async def test_availability_template(hass: HomeAssistant, entity_id) -> None:
)
@pytest.mark.usefixtures("start_ha")
async def test_invalid_attribute_template(
- hass: HomeAssistant, caplog_setup_text
+ hass: HomeAssistant, caplog_setup_text: str
) -> None:
"""Test that errors are logged if rendering template fails."""
hass.states.async_set("binary_sensor.test_sensor", STATE_ON)
@@ -759,7 +822,7 @@ async def test_invalid_attribute_template(
)
@pytest.mark.usefixtures("start_ha")
async def test_invalid_availability_template_keeps_component_available(
- hass: HomeAssistant, caplog_setup_text
+ hass: HomeAssistant, caplog_setup_text: str
) -> None:
"""Test that an invalid availability keeps the device available."""
@@ -767,9 +830,7 @@ async def test_invalid_availability_template_keeps_component_available(
assert "UndefinedError: 'x' is undefined" in caplog_setup_text
-async def test_no_update_template_match_all(
- hass: HomeAssistant, caplog: pytest.LogCaptureFixture
-) -> None:
+async def test_no_update_template_match_all(hass: HomeAssistant) -> None:
"""Test that we do not update sensors that match on all."""
hass.set_state(CoreState.not_running)
@@ -966,7 +1027,7 @@ async def test_template_validation_error(
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_availability_icon_picture(hass: HomeAssistant, entity_id) -> None:
+async def test_availability_icon_picture(hass: HomeAssistant, entity_id: str) -> None:
"""Test name, icon and picture templates are rendered at setup."""
state = hass.states.get(entity_id)
assert state.state == "unavailable"
@@ -996,7 +1057,7 @@ async def test_availability_icon_picture(hass: HomeAssistant, entity_id) -> None
"template": {
"binary_sensor": {
"name": "test",
- "state": "{{ states.sensor.test_state.state == 'on' }}",
+ "state": "{{ states.sensor.test_state.state }}",
},
},
},
@@ -1029,17 +1090,29 @@ async def test_availability_icon_picture(hass: HomeAssistant, entity_id) -> None
({"delay_on": 5}, STATE_ON, STATE_OFF, STATE_OFF),
({"delay_on": 5}, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN),
({"delay_on": 5}, STATE_ON, STATE_UNKNOWN, STATE_UNKNOWN),
+ ({}, None, STATE_ON, STATE_OFF),
+ ({}, None, STATE_OFF, STATE_OFF),
+ ({}, None, STATE_UNAVAILABLE, STATE_OFF),
+ ({}, None, STATE_UNKNOWN, STATE_OFF),
+ ({"delay_off": 5}, None, STATE_ON, STATE_ON),
+ ({"delay_off": 5}, None, STATE_OFF, STATE_OFF),
+ ({"delay_off": 5}, None, STATE_UNAVAILABLE, STATE_UNKNOWN),
+ ({"delay_off": 5}, None, STATE_UNKNOWN, STATE_UNKNOWN),
+ ({"delay_on": 5}, None, STATE_ON, STATE_OFF),
+ ({"delay_on": 5}, None, STATE_OFF, STATE_OFF),
+ ({"delay_on": 5}, None, STATE_UNAVAILABLE, STATE_OFF),
+ ({"delay_on": 5}, None, STATE_UNKNOWN, STATE_OFF),
],
)
async def test_restore_state(
hass: HomeAssistant,
- count,
- domain,
- config,
- extra_config,
- source_state,
- restored_state,
- initial_state,
+ count: int,
+ domain: str,
+ config: ConfigType,
+ extra_config: ConfigType,
+ source_state: str | None,
+ restored_state: str,
+ initial_state: str,
) -> None:
"""Test restoring template binary sensor."""
@@ -1088,7 +1161,7 @@ async def test_restore_state(
"friendly_name": "Hello Name",
"unique_id": "hello_name-id",
"device_class": "battery",
- "value_template": "{{ trigger.event.data.beer == 2 }}",
+ "value_template": _BEER_TRIGGER_VALUE_TEMPLATE,
"entity_picture_template": "{{ '/local/dogs.png' }}",
"icon_template": "{{ 'mdi:pirate' }}",
"attribute_templates": {
@@ -1101,7 +1174,7 @@ async def test_restore_state(
"name": "via list",
"unique_id": "via_list-id",
"device_class": "battery",
- "state": "{{ trigger.event.data.beer == 2 }}",
+ "state": _BEER_TRIGGER_VALUE_TEMPLATE,
"picture": "{{ '/local/dogs.png' }}",
"icon": "{{ 'mdi:pirate' }}",
"attributes": {
@@ -1123,9 +1196,34 @@ async def test_restore_state(
},
],
)
+@pytest.mark.parametrize(
+ (
+ "beer_count",
+ "final_state",
+ "icon_attr",
+ "entity_picture_attr",
+ "plus_one_attr",
+ "another_attr",
+ "another_attr_update",
+ ),
+ [
+ (2, STATE_ON, "mdi:pirate", "/local/dogs.png", 3, 1, "si"),
+ (1, STATE_OFF, "mdi:pirate", "/local/dogs.png", 2, 1, "si"),
+ (0, STATE_OFF, "mdi:pirate", "/local/dogs.png", 1, 1, "si"),
+ (-1, STATE_UNAVAILABLE, None, None, None, None, None),
+ ],
+)
@pytest.mark.usefixtures("start_ha")
async def test_trigger_entity(
- hass: HomeAssistant, entity_registry: er.EntityRegistry
+ hass: HomeAssistant,
+ beer_count: int,
+ final_state: str,
+ icon_attr: str | None,
+ entity_picture_attr: str | None,
+ plus_one_attr: int | None,
+ another_attr: int | None,
+ another_attr_update: str | None,
+ entity_registry: er.EntityRegistry,
) -> None:
"""Test trigger entity works."""
await hass.async_block_till_done()
@@ -1138,15 +1236,15 @@ async def test_trigger_entity(
assert state.state == STATE_UNKNOWN
context = Context()
- hass.bus.async_fire("test_event", {"beer": 2}, context=context)
+ hass.bus.async_fire("test_event", {"beer": beer_count}, context=context)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.hello_name")
- assert state.state == STATE_ON
+ assert state.state == final_state
assert state.attributes.get("device_class") == "battery"
- assert state.attributes.get("icon") == "mdi:pirate"
- assert state.attributes.get("entity_picture") == "/local/dogs.png"
- assert state.attributes.get("plus_one") == 3
+ assert state.attributes.get("icon") == icon_attr
+ assert state.attributes.get("entity_picture") == entity_picture_attr
+ assert state.attributes.get("plus_one") == plus_one_attr
assert state.context is context
assert len(entity_registry.entities) == 2
@@ -1160,20 +1258,20 @@ async def test_trigger_entity(
)
state = hass.states.get("binary_sensor.via_list")
- assert state.state == STATE_ON
+ assert state.state == final_state
assert state.attributes.get("device_class") == "battery"
- assert state.attributes.get("icon") == "mdi:pirate"
- assert state.attributes.get("entity_picture") == "/local/dogs.png"
- assert state.attributes.get("plus_one") == 3
- assert state.attributes.get("another") == 1
+ assert state.attributes.get("icon") == icon_attr
+ assert state.attributes.get("entity_picture") == entity_picture_attr
+ assert state.attributes.get("plus_one") == plus_one_attr
+ assert state.attributes.get("another") == another_attr
assert state.context is context
# Even if state itself didn't change, attributes might have changed
- hass.bus.async_fire("test_event", {"beer": 2, "uno_mas": "si"})
+ hass.bus.async_fire("test_event", {"beer": beer_count, "uno_mas": "si"})
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.via_list")
- assert state.state == STATE_ON
- assert state.attributes.get("another") == "si"
+ assert state.state == final_state
+ assert state.attributes.get("another") == another_attr_update
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@@ -1185,7 +1283,7 @@ async def test_trigger_entity(
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
- "state": "{{ trigger.event.data.beer == 2 }}",
+ "state": _BEER_TRIGGER_VALUE_TEMPLATE,
"device_class": "motion",
"delay_on": '{{ ({ "seconds": 6 / 2 }) }}',
"auto_off": '{{ ({ "seconds": 1 + 1 }) }}',
@@ -1195,34 +1293,50 @@ async def test_trigger_entity(
],
)
@pytest.mark.usefixtures("start_ha")
-async def test_template_with_trigger_templated_delay_on(hass: HomeAssistant) -> None:
+@pytest.mark.parametrize(
+ ("beer_count", "first_state", "second_state", "final_state"),
+ [
+ (2, STATE_UNKNOWN, STATE_ON, STATE_OFF),
+ (1, STATE_OFF, STATE_OFF, STATE_OFF),
+ (0, STATE_OFF, STATE_OFF, STATE_OFF),
+ (-1, STATE_UNAVAILABLE, STATE_UNAVAILABLE, STATE_UNAVAILABLE),
+ ],
+)
+async def test_template_with_trigger_templated_delay_on(
+ hass: HomeAssistant,
+ beer_count: int,
+ first_state: str,
+ second_state: str,
+ final_state: str,
+ freezer: FrozenDateTimeFactory,
+) -> None:
"""Test binary sensor template with template delay on."""
state = hass.states.get("binary_sensor.test")
assert state.state == STATE_UNKNOWN
context = Context()
- hass.bus.async_fire("test_event", {"beer": 2}, context=context)
+ hass.bus.async_fire("test_event", {"beer": beer_count}, context=context)
await hass.async_block_till_done()
# State should still be unknown
state = hass.states.get("binary_sensor.test")
- assert state.state == STATE_UNKNOWN
+ assert state.state == first_state
# Now wait for the on delay
- future = dt_util.utcnow() + timedelta(seconds=3)
- async_fire_time_changed(hass, future)
+ freezer.tick(timedelta(seconds=3))
+ async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
- assert state.state == STATE_ON
+ assert state.state == second_state
# Now wait for the auto-off
- future = dt_util.utcnow() + timedelta(seconds=2)
- async_fire_time_changed(hass, future)
+ freezer.tick(timedelta(seconds=2))
+ async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
- assert state.state == STATE_OFF
+ assert state.state == final_state
@pytest.mark.parametrize(("count", "domain"), [(1, "template")])
@@ -1261,10 +1375,9 @@ async def test_template_with_trigger_templated_delay_on(hass: HomeAssistant) ->
)
@pytest.mark.usefixtures("start_ha")
async def test_trigger_template_delay_with_multiple_triggers(
- hass: HomeAssistant, delay_state: str
+ hass: HomeAssistant, delay_state: str, freezer: FrozenDateTimeFactory
) -> None:
"""Test trigger based binary sensor with multiple triggers occurring during the delay."""
- future = dt_util.utcnow()
for _ in range(10):
# State should still be unknown
state = hass.states.get("binary_sensor.test")
@@ -1273,8 +1386,8 @@ async def test_trigger_template_delay_with_multiple_triggers(
hass.bus.async_fire("test_event", {"beer": 2}, context=Context())
await hass.async_block_till_done()
- future += timedelta(seconds=1)
- async_fire_time_changed(hass, future)
+ freezer.tick(timedelta(seconds=1))
+ async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test")
@@ -1290,7 +1403,7 @@ async def test_trigger_template_delay_with_multiple_triggers(
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
- "state": "{{ trigger.event.data.beer == 2 }}",
+ "state": _BEER_TRIGGER_VALUE_TEMPLATE,
"device_class": "motion",
"picture": "{{ '/local/dogs.png' }}",
"icon": "{{ 'mdi:pirate' }}",
@@ -1314,12 +1427,12 @@ async def test_trigger_template_delay_with_multiple_triggers(
)
async def test_trigger_entity_restore_state(
hass: HomeAssistant,
- count,
- domain,
- config,
- restored_state,
- initial_state,
- initial_attributes,
+ count: int,
+ domain: str,
+ config: ConfigType,
+ restored_state: str,
+ initial_state: str,
+ initial_attributes: list[str],
) -> None:
"""Test restoring trigger template binary sensor."""
@@ -1378,7 +1491,7 @@ async def test_trigger_entity_restore_state(
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
- "state": "{{ trigger.event.data.beer == 2 }}",
+ "state": _BEER_TRIGGER_VALUE_TEMPLATE,
"device_class": "motion",
"auto_off": '{{ ({ "seconds": 1 + 1 }) }}',
},
@@ -1389,10 +1502,10 @@ async def test_trigger_entity_restore_state(
@pytest.mark.parametrize("restored_state", [STATE_ON, STATE_OFF])
async def test_trigger_entity_restore_state_auto_off(
hass: HomeAssistant,
- count,
- domain,
- config,
- restored_state,
+ count: int,
+ domain: str,
+ config: ConfigType,
+ restored_state: str,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test restoring trigger template binary sensor."""
@@ -1442,7 +1555,7 @@ async def test_trigger_entity_restore_state_auto_off(
"trigger": {"platform": "event", "event_type": "test_event"},
"binary_sensor": {
"name": "test",
- "state": "{{ trigger.event.data.beer == 2 }}",
+ "state": _BEER_TRIGGER_VALUE_TEMPLATE,
"device_class": "motion",
"auto_off": '{{ ({ "seconds": 1 + 1 }) }}',
},
@@ -1451,7 +1564,11 @@ async def test_trigger_entity_restore_state_auto_off(
],
)
async def test_trigger_entity_restore_state_auto_off_expired(
- hass: HomeAssistant, count, domain, config, freezer: FrozenDateTimeFactory
+ hass: HomeAssistant,
+ count: int,
+ domain: str,
+ config: ConfigType,
+ freezer: FrozenDateTimeFactory,
) -> None:
"""Test restoring trigger template binary sensor."""
diff --git a/tests/components/teslemetry/const.py b/tests/components/teslemetry/const.py
index b658c1e2271b99..3bfa452e38de23 100644
--- a/tests/components/teslemetry/const.py
+++ b/tests/components/teslemetry/const.py
@@ -20,6 +20,7 @@
LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN)
SITE_INFO = load_json_object_fixture("site_info.json", DOMAIN)
ENERGY_HISTORY = load_json_object_fixture("energy_history.json", DOMAIN)
+ENERGY_HISTORY_EMPTY = load_json_object_fixture("energy_history_empty.json", DOMAIN)
COMMAND_OK = {"response": {"result": True, "reason": ""}}
COMMAND_REASON = {"response": {"result": False, "reason": "already closed"}}
diff --git a/tests/components/teslemetry/fixtures/energy_history_empty.json b/tests/components/teslemetry/fixtures/energy_history_empty.json
new file mode 100644
index 00000000000000..cc54000115a66b
--- /dev/null
+++ b/tests/components/teslemetry/fixtures/energy_history_empty.json
@@ -0,0 +1,8 @@
+{
+ "response": {
+ "serial_number": "xxxxxx",
+ "period": "day",
+ "installation_time_zone": "Australia/Brisbane",
+ "time_series": null
+ }
+}
diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py
index f50dc93bde47b9..d2d6d88b3e3106 100644
--- a/tests/components/teslemetry/test_sensor.py
+++ b/tests/components/teslemetry/test_sensor.py
@@ -8,12 +8,13 @@
from teslemetry_stream import Signal
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
-from homeassistant.const import Platform
+from homeassistant.config_entries import ConfigEntryState
+from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import assert_entities, assert_entities_alt, setup_platform
-from .const import VEHICLE_DATA_ALT
+from .const import ENERGY_HISTORY_EMPTY, VEHICLE_DATA_ALT
from tests.common import async_fire_time_changed
@@ -101,3 +102,28 @@ async def test_sensors_streaming(
):
state = hass.states.get(entity_id)
assert state.state == snapshot(name=f"{entity_id}-state")
+
+
+async def test_energy_history_no_time_series(
+ hass: HomeAssistant,
+ freezer: FrozenDateTimeFactory,
+ mock_energy_history: AsyncMock,
+) -> None:
+ """Test energy history coordinator when time_series is not a list."""
+ # Mock energy history to return data without time_series as a list
+
+ entry = await setup_platform(hass, [Platform.SENSOR])
+ assert entry.state is ConfigEntryState.LOADED
+
+ entity_id = "sensor.energy_site_battery_discharged"
+ state = hass.states.get(entity_id)
+ assert state.state == "0.036"
+
+ mock_energy_history.return_value = ENERGY_HISTORY_EMPTY
+
+ freezer.tick(VEHICLE_INTERVAL)
+ async_fire_time_changed(hass)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(entity_id)
+ assert state.state == STATE_UNAVAILABLE
diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py
index e99cedbdcbae86..a1642746d0335f 100644
--- a/tests/components/zwave_js/test_config_flow.py
+++ b/tests/components/zwave_js/test_config_flow.py
@@ -29,12 +29,6 @@
CONF_ADDON_S2_ACCESS_CONTROL_KEY,
CONF_ADDON_S2_AUTHENTICATED_KEY,
CONF_ADDON_S2_UNAUTHENTICATED_KEY,
- CONF_LR_S2_ACCESS_CONTROL_KEY,
- CONF_LR_S2_AUTHENTICATED_KEY,
- CONF_S0_LEGACY_KEY,
- CONF_S2_ACCESS_CONTROL_KEY,
- CONF_S2_AUTHENTICATED_KEY,
- CONF_S2_UNAUTHENTICATED_KEY,
CONF_USB_PATH,
DOMAIN,
)
@@ -687,7 +681,17 @@ async def test_usb_discovery(
assert install_addon.call_args == call("core_zwave_js")
assert result["type"] is FlowResultType.FORM
- assert result["step_id"] == "configure_addon_user"
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -778,9 +782,18 @@ async def test_usb_discovery_addon_not_running(
)
assert result["type"] is FlowResultType.FORM
- assert result["step_id"] == "configure_addon_user"
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
- # Make sure the discovered usb device is preferred.
data_schema = result["data_schema"]
assert data_schema is not None
assert data_schema({}) == {
@@ -1126,6 +1139,25 @@ async def test_discovery_addon_not_running(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -1226,6 +1258,25 @@ async def test_discovery_addon_not_installed(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -1728,6 +1779,25 @@ async def test_addon_installed(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -1822,6 +1892,25 @@ async def test_addon_installed_start_failure(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -1911,6 +2000,25 @@ async def test_addon_installed_failures(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -1981,6 +2089,25 @@ async def test_addon_installed_set_options_failure(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -2091,6 +2218,25 @@ async def test_addon_installed_already_configured(
result["flow_id"],
{
"usb_path": "/new",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -2178,6 +2324,25 @@ async def test_addon_not_installed(
result["flow_id"],
{
"usb_path": "/test",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "network_type"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
+ "network_type": "existing",
+ },
+ )
+
+ assert result["type"] is FlowResultType.FORM
+ assert result["step_id"] == "configure_security_keys"
+
+ result = await hass.config_entries.flow.async_configure(
+ result["flow_id"],
+ {
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@@ -4229,13 +4394,8 @@ async def test_intent_recommended_user(
assert result["step_id"] == "configure_addon_user"
data_schema = result["data_schema"]
assert data_schema is not None
- assert data_schema.schema[CONF_USB_PATH] is not None
- assert data_schema.schema.get(CONF_S0_LEGACY_KEY) is None
- assert data_schema.schema.get(CONF_S2_ACCESS_CONTROL_KEY) is None
- assert data_schema.schema.get(CONF_S2_AUTHENTICATED_KEY) is None
- assert data_schema.schema.get(CONF_S2_UNAUTHENTICATED_KEY) is None
- assert data_schema.schema.get(CONF_LR_S2_ACCESS_CONTROL_KEY) is None
- assert data_schema.schema.get(CONF_LR_S2_AUTHENTICATED_KEY) is None
+ assert len(data_schema.schema) == 1
+ assert data_schema.schema.get(CONF_USB_PATH) is not None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],