diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 68029190439c98..059b378b9a83cc 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -153,8 +153,8 @@ class IntentCacheKey: language: str """Language of text.""" - device_id: str | None - """Device id from user input.""" + satellite_id: str | None + """Satellite id from user input.""" @dataclass(frozen=True) @@ -443,9 +443,15 @@ async def _async_process_intent_result( } for entity in result.entities_list } - device_area = self._get_device_area(user_input.device_id) - if device_area: - slots["preferred_area_id"] = {"value": device_area.id} + + satellite_id = user_input.satellite_id + device_id = user_input.device_id + satellite_area, device_id = self._get_satellite_area_and_device( + satellite_id, device_id + ) + if satellite_area is not None: + slots["preferred_area_id"] = {"value": satellite_area.id} + async_conversation_trace_append( ConversationTraceEventType.TOOL_CALL, { @@ -467,8 +473,8 @@ async def _async_process_intent_result( user_input.context, language, assistant=DOMAIN, - device_id=user_input.device_id, - satellite_id=user_input.satellite_id, + device_id=device_id, + satellite_id=satellite_id, conversation_agent_id=user_input.agent_id, ) except intent.MatchFailedError as match_error: @@ -534,7 +540,9 @@ def _recognize( # Try cache first cache_key = IntentCacheKey( - text=user_input.text, language=language, device_id=user_input.device_id + text=user_input.text, + language=language, + satellite_id=user_input.satellite_id, ) cache_value = self._intent_cache.get(cache_key) if cache_value is not None: @@ -1304,28 +1312,40 @@ def _make_intent_context( self, user_input: ConversationInput ) -> dict[str, Any] | None: """Return intent recognition context for user input.""" - if not user_input.device_id: + satellite_area, _ = self._get_satellite_area_and_device( + user_input.satellite_id, user_input.device_id + ) + if satellite_area is None: return None - device_area = self._get_device_area(user_input.device_id) - if device_area is None: - return None + return {"area": {"value": satellite_area.name, "text": satellite_area.name}} - return {"area": {"value": device_area.name, "text": device_area.name}} + def _get_satellite_area_and_device( + self, satellite_id: str | None, device_id: str | None = None + ) -> tuple[ar.AreaEntry | None, str | None]: + """Return area entry and device id.""" + hass = self.hass - def _get_device_area(self, device_id: str | None) -> ar.AreaEntry | None: - """Return area object for given device identifier.""" - if device_id is None: - return None + area_id: str | None = None - devices = dr.async_get(self.hass) - device = devices.async_get(device_id) - if (device is None) or (device.area_id is None): - return None + if ( + satellite_id is not None + and (entity_entry := er.async_get(hass).async_get(satellite_id)) is not None + ): + area_id = entity_entry.area_id + device_id = entity_entry.device_id - areas = ar.async_get(self.hass) + if ( + area_id is None + and device_id is not None + and (device_entry := dr.async_get(hass).async_get(device_id)) is not None + ): + area_id = device_entry.area_id + + if area_id is None: + return None, device_id - return areas.async_get_area(device.area_id) + return ar.async_get(hass).async_get_area(area_id), device_id def _get_error_text( self, diff --git a/homeassistant/components/conversation/trigger.py b/homeassistant/components/conversation/trigger.py index 36f8b2246776c4..b6b1273f1ab92a 100644 --- a/homeassistant/components/conversation/trigger.py +++ b/homeassistant/components/conversation/trigger.py @@ -15,7 +15,7 @@ from homeassistant.const import CONF_COMMAND, CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.script import ScriptRunResult from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import UNDEFINED, ConfigType @@ -71,6 +71,8 @@ async def async_attach_trigger( trigger_data = trigger_info["trigger_data"] sentences = config.get(CONF_COMMAND, []) + ent_reg = er.async_get(hass) + job = HassJob(action) async def call_action( @@ -92,6 +94,14 @@ async def call_action( for entity_name, entity in result.entities.items() } + satellite_id = user_input.satellite_id + device_id = user_input.device_id + if ( + satellite_id is not None + and (satellite_entry := ent_reg.async_get(satellite_id)) is not None + ): + device_id = satellite_entry.device_id + trigger_input: dict[str, Any] = { # Satisfy type checker **trigger_data, "platform": DOMAIN, @@ -100,8 +110,8 @@ async def call_action( "slots": { # direct access to values entity_name: entity["value"] for entity_name, entity in details.items() }, - "device_id": user_input.device_id, - "satellite_id": user_input.satellite_id, + "device_id": device_id, + "satellite_id": satellite_id, "user_input": user_input.as_dict(), } diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index f621c74642b316..cb1a3d10c97ea2 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -51,6 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ESPHomeConfigEntry) -> b client_info=CLIENT_INFO, zeroconf_instance=zeroconf_instance, noise_psk=noise_psk, + timezone=hass.config.time_zone, ) domain_data = DomainData.get(hass) diff --git a/homeassistant/components/ezviz/sensor.py b/homeassistant/components/ezviz/sensor.py index ec631e8e5c157b..c441b34b42dd1f 100644 --- a/homeassistant/components/ezviz/sensor.py +++ b/homeassistant/components/ezviz/sensor.py @@ -66,26 +66,6 @@ key="last_alarm_type_name", translation_key="last_alarm_type_name", ), - "Record_Mode": SensorEntityDescription( - key="Record_Mode", - translation_key="record_mode", - entity_registry_enabled_default=False, - ), - "battery_camera_work_mode": SensorEntityDescription( - key="battery_camera_work_mode", - translation_key="battery_camera_work_mode", - entity_registry_enabled_default=False, - ), - "powerStatus": SensorEntityDescription( - key="powerStatus", - translation_key="power_status", - entity_registry_enabled_default=False, - ), - "OnlineStatus": SensorEntityDescription( - key="OnlineStatus", - translation_key="online_status", - entity_registry_enabled_default=False, - ), } @@ -96,26 +76,16 @@ async def async_setup_entry( ) -> None: """Set up EZVIZ sensors based on a config entry.""" coordinator = entry.runtime_data - entities: list[EzvizSensor] = [] - for camera, sensors in coordinator.data.items(): - entities.extend( + async_add_entities( + [ EzvizSensor(coordinator, camera, sensor) - for sensor, value in sensors.items() - if sensor in SENSOR_TYPES and value is not None - ) - - optionals = sensors.get("optionals", {}) - entities.extend( - EzvizSensor(coordinator, camera, optional_key) - for optional_key in ("powerStatus", "OnlineStatus") - if optional_key in optionals - ) - - if "mode" in optionals.get("Record_Mode", {}): - entities.append(EzvizSensor(coordinator, camera, "mode")) - - async_add_entities(entities) + for camera in coordinator.data + for sensor, value in coordinator.data[camera].items() + if sensor in SENSOR_TYPES + if value is not None + ] + ) class EzvizSensor(EzvizEntity, SensorEntity): diff --git a/homeassistant/components/ezviz/strings.json b/homeassistant/components/ezviz/strings.json index ad8f7114407c72..b03a5dbc61a89d 100644 --- a/homeassistant/components/ezviz/strings.json +++ b/homeassistant/components/ezviz/strings.json @@ -147,18 +147,6 @@ }, "last_alarm_type_name": { "name": "Last alarm type name" - }, - "record_mode": { - "name": "Record mode" - }, - "battery_camera_work_mode": { - "name": "Battery work mode" - }, - "power_status": { - "name": "Power status" - }, - "online_status": { - "name": "Online status" } }, "switch": { diff --git a/homeassistant/components/iron_os/manifest.json b/homeassistant/components/iron_os/manifest.json index be2309ab340568..fb4d3fc15cda74 100644 --- a/homeassistant/components/iron_os/manifest.json +++ b/homeassistant/components/iron_os/manifest.json @@ -14,5 +14,5 @@ "iot_class": "local_polling", "loggers": ["pynecil"], "quality_scale": "platinum", - "requirements": ["pynecil==4.1.1"] + "requirements": ["pynecil==4.2.0"] } diff --git a/homeassistant/components/mcp/manifest.json b/homeassistant/components/mcp/manifest.json index 7ff64d29aa47f7..dfc180f70228ba 100644 --- a/homeassistant/components/mcp/manifest.json +++ b/homeassistant/components/mcp/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/mcp", "iot_class": "local_polling", "quality_scale": "silver", - "requirements": ["mcp==1.5.0"] + "requirements": ["mcp==1.14.1"] } diff --git a/homeassistant/components/mcp_server/http.py b/homeassistant/components/mcp_server/http.py index 07c8ff39f62fe1..76867b6c85db9b 100644 --- a/homeassistant/components/mcp_server/http.py +++ b/homeassistant/components/mcp_server/http.py @@ -22,6 +22,7 @@ import anyio from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from mcp import types +from mcp.shared.message import SessionMessage from homeassistant.components import conversation from homeassistant.components.http import KEY_HASS, HomeAssistantView @@ -98,12 +99,12 @@ async def get(self, request: web.Request) -> web.StreamResponse: server.create_initialization_options # Reads package for version info ) - read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception] - read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception] + read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] read_stream_writer, read_stream = anyio.create_memory_object_stream(0) - write_stream: MemoryObjectSendStream[types.JSONRPCMessage] - write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage] + write_stream: MemoryObjectSendStream[SessionMessage] + write_stream_reader: MemoryObjectReceiveStream[SessionMessage] write_stream, write_stream_reader = anyio.create_memory_object_stream(0) async with ( @@ -116,10 +117,12 @@ async def get(self, request: web.Request) -> web.StreamResponse: async def sse_reader() -> None: """Forward MCP server responses to the client.""" - async for message in write_stream_reader: - _LOGGER.debug("Sending SSE message: %s", message) + async for session_message in write_stream_reader: + _LOGGER.debug("Sending SSE message: %s", session_message) await response.send( - message.model_dump_json(by_alias=True, exclude_none=True), + session_message.message.model_dump_json( + by_alias=True, exclude_none=True + ), event="message", ) @@ -163,5 +166,5 @@ async def post( raise HTTPBadRequest(text="Could not parse message") from err _LOGGER.debug("Received client message: %s", message) - await session.read_stream_writer.send(message) + await session.read_stream_writer.send(SessionMessage(message)) return web.Response(status=200) diff --git a/homeassistant/components/mcp_server/manifest.json b/homeassistant/components/mcp_server/manifest.json index 452714f14cdab1..abc43ffffeb26e 100644 --- a/homeassistant/components/mcp_server/manifest.json +++ b/homeassistant/components/mcp_server/manifest.json @@ -8,6 +8,6 @@ "integration_type": "service", "iot_class": "local_push", "quality_scale": "silver", - "requirements": ["mcp==1.5.0", "aiohttp_sse==2.2.0", "anyio==4.10.0"], + "requirements": ["mcp==1.14.1", "aiohttp_sse==2.2.0", "anyio==4.10.0"], "single_config_entry": true } diff --git a/homeassistant/components/mcp_server/server.py b/homeassistant/components/mcp_server/server.py index 953fc1314dafd0..85bcd407fef9e5 100644 --- a/homeassistant/components/mcp_server/server.py +++ b/homeassistant/components/mcp_server/server.py @@ -96,7 +96,7 @@ async def list_tools() -> list[types.Tool]: llm_api = await get_api_instance() return [_format_tool(tool, llm_api.custom_serializer) for tool in llm_api.tools] - @server.call_tool() # type: ignore[no-untyped-call, misc] + @server.call_tool() # type: ignore[misc] async def call_tool(name: str, arguments: dict) -> Sequence[types.TextContent]: """Handle calling tools.""" llm_api = await get_api_instance() diff --git a/homeassistant/components/mcp_server/session.py b/homeassistant/components/mcp_server/session.py index 4c586fd32a0e91..e4bfe25eaf5238 100644 --- a/homeassistant/components/mcp_server/session.py +++ b/homeassistant/components/mcp_server/session.py @@ -11,7 +11,7 @@ import logging from anyio.streams.memory import MemoryObjectSendStream -from mcp import types +from mcp.shared.message import SessionMessage from homeassistant.util import ulid as ulid_util @@ -22,7 +22,7 @@ class Session: """A session for the Model Context Protocol.""" - read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception] + read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] class SessionManager: diff --git a/homeassistant/components/music_assistant/config_flow.py b/homeassistant/components/music_assistant/config_flow.py index b00924c97a5d75..09931040d6a915 100644 --- a/homeassistant/components/music_assistant/config_flow.py +++ b/homeassistant/components/music_assistant/config_flow.py @@ -68,7 +68,7 @@ async def async_step_user( self.server_info.server_id, raise_on_progress=False ) self._abort_if_unique_id_configured( - updates={CONF_URL: self.server_info.base_url}, + updates={CONF_URL: user_input[CONF_URL]}, reload_on_update=True, ) except CannotConnect: @@ -82,7 +82,7 @@ async def async_step_user( return self.async_create_entry( title=DEFAULT_TITLE, data={ - CONF_URL: self.server_info.base_url, + CONF_URL: user_input[CONF_URL], }, ) @@ -103,14 +103,36 @@ async def async_step_zeroconf( # abort if discovery info is not what we expect if "server_id" not in discovery_info.properties: return self.async_abort(reason="missing_server_id") - # abort if we already have exactly this server_id - # reload the integration if the host got updated + self.server_info = ServerInfoMessage.from_dict(discovery_info.properties) await self.async_set_unique_id(self.server_info.server_id) - self._abort_if_unique_id_configured( - updates={CONF_URL: self.server_info.base_url}, - reload_on_update=True, + + # Check if we already have a config entry for this server_id + existing_entry = self.hass.config_entries.async_entry_for_domain_unique_id( + DOMAIN, self.server_info.server_id ) + + if existing_entry: + # Test connectivity to the current URL first + current_url = existing_entry.data[CONF_URL] + try: + await get_server_info(self.hass, current_url) + # Current URL is working, no need to update + return self.async_abort(reason="already_configured") + except CannotConnect: + # Current URL is not working, update to the discovered URL + # and continue to discovery confirm + self.hass.config_entries.async_update_entry( + existing_entry, + data={**existing_entry.data, CONF_URL: self.server_info.base_url}, + ) + # Schedule reload since URL changed + self.hass.config_entries.async_schedule_reload(existing_entry.entry_id) + else: + # No existing entry, proceed with normal flow + self._abort_if_unique_id_configured() + + # Test connectivity to the discovered URL try: await get_server_info(self.hass, self.server_info.base_url) except CannotConnect: diff --git a/homeassistant/components/nordpool/coordinator.py b/homeassistant/components/nordpool/coordinator.py index 0cda1923125d0b..51bc0e638dd82f 100644 --- a/homeassistant/components/nordpool/coordinator.py +++ b/homeassistant/components/nordpool/coordinator.py @@ -71,21 +71,34 @@ async def fetch_data(self, now: datetime, initial: bool = False) -> None: self.unsub = async_track_point_in_utc_time( self.hass, self.fetch_data, self.get_next_interval(dt_util.utcnow()) ) + if self.config_entry.pref_disable_polling and not initial: + self.async_update_listeners() + return + try: + data = await self.handle_data(initial) + except UpdateFailed as err: + self.async_set_update_error(err) + return + self.async_set_updated_data(data) + + async def handle_data(self, initial: bool = False) -> DeliveryPeriodsData: + """Fetch data from Nord Pool.""" data = await self.api_call() if data and data.entries: current_day = dt_util.utcnow().strftime("%Y-%m-%d") for entry in data.entries: if entry.requested_date == current_day: LOGGER.debug("Data for current day found") - self.async_set_updated_data(data) - return + return data if data and not data.entries and not initial: # Empty response, use cache LOGGER.debug("No data entries received") - return - self.async_set_update_error( - UpdateFailed(translation_domain=DOMAIN, translation_key="no_day_data") - ) + return self.data + raise UpdateFailed(translation_domain=DOMAIN, translation_key="no_day_data") + + async def _async_update_data(self) -> DeliveryPeriodsData: + """Fetch the latest data from the source.""" + return await self.handle_data() async def api_call(self, retry: int = 3) -> DeliveryPeriodsData | None: """Make api call to retrieve data with retry if failure.""" diff --git a/homeassistant/components/nordpool/sensor.py b/homeassistant/components/nordpool/sensor.py index 4bde12afc3c5c4..90b0f44c2e57dc 100644 --- a/homeassistant/components/nordpool/sensor.py +++ b/homeassistant/components/nordpool/sensor.py @@ -34,8 +34,11 @@ def validate_prices( index: int, ) -> float | None: """Validate and return.""" - if (result := func(entity)[area][index]) is not None: - return result / 1000 + try: + if (result := func(entity)[area][index]) is not None: + return result / 1000 + except KeyError: + return None return None diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 5664bba25a3cb2..99039ab98220fb 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -74,21 +74,21 @@ class ReolinkSmartAIBinarySensorEntityDescription( ), ReolinkBinarySensorEntityDescription( key=PERSON_DETECTION_TYPE, - cmd_id=33, + cmd_id=[33, 600], translation_key="person", value=lambda api, ch: api.ai_detected(ch, PERSON_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, PERSON_DETECTION_TYPE), ), ReolinkBinarySensorEntityDescription( key=VEHICLE_DETECTION_TYPE, - cmd_id=33, + cmd_id=[33, 600], translation_key="vehicle", value=lambda api, ch: api.ai_detected(ch, VEHICLE_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, VEHICLE_DETECTION_TYPE), ), ReolinkBinarySensorEntityDescription( key=PET_DETECTION_TYPE, - cmd_id=33, + cmd_id=[33, 600], translation_key="pet", value=lambda api, ch: api.ai_detected(ch, PET_DETECTION_TYPE), supported=lambda api, ch: ( @@ -98,14 +98,14 @@ class ReolinkSmartAIBinarySensorEntityDescription( ), ReolinkBinarySensorEntityDescription( key=PET_DETECTION_TYPE, - cmd_id=33, + cmd_id=[33, 600], translation_key="animal", value=lambda api, ch: api.ai_detected(ch, PET_DETECTION_TYPE), supported=lambda api, ch: api.supported(ch, "ai_animal"), ), ReolinkBinarySensorEntityDescription( key=PACKAGE_DETECTION_TYPE, - cmd_id=33, + cmd_id=[33, 600], translation_key="package", value=lambda api, ch: api.ai_detected(ch, PACKAGE_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, PACKAGE_DETECTION_TYPE), @@ -120,7 +120,7 @@ class ReolinkSmartAIBinarySensorEntityDescription( ), ReolinkBinarySensorEntityDescription( key="cry", - cmd_id=33, + cmd_id=[33, 600], translation_key="cry", value=lambda api, ch: api.ai_detected(ch, "cry"), supported=lambda api, ch: api.ai_supported(ch, "cry"), diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index a509a79eaa1b8a..634b8d909e6557 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -19,5 +19,5 @@ "iot_class": "local_push", "loggers": ["reolink_aio"], "quality_scale": "platinum", - "requirements": ["reolink-aio==0.15.1"] + "requirements": ["reolink-aio==0.15.2"] } diff --git a/homeassistant/components/systemmonitor/util.py b/homeassistant/components/systemmonitor/util.py index 2a4b889bdde867..dec0508bb646f3 100644 --- a/homeassistant/components/systemmonitor/util.py +++ b/homeassistant/components/systemmonitor/util.py @@ -21,12 +21,6 @@ def get_all_disk_mounts( """Return all disk mount points on system.""" disks: set[str] = set() for part in psutil_wrapper.psutil.disk_partitions(all=True): - if os.name == "nt": - if "cdrom" in part.opts or part.fstype == "": - # skip cd-rom drives with no disk in it; they may raise - # ENOENT, pop-up a Windows GUI error for a non-ready - # partition or just hang. - continue if part.fstype in SKIP_DISK_TYPES: # Ignore disks which are memory continue diff --git a/homeassistant/components/volvo/__init__.py b/homeassistant/components/volvo/__init__.py index fa2c7530cac801..403dce7bfe6128 100644 --- a/homeassistant/components/volvo/__init__.py +++ b/homeassistant/components/volvo/__init__.py @@ -24,8 +24,10 @@ from .const import CONF_VIN, DOMAIN, PLATFORMS from .coordinator import ( VolvoConfigEntry, + VolvoContext, VolvoFastIntervalCoordinator, VolvoMediumIntervalCoordinator, + VolvoRuntimeData, VolvoSlowIntervalCoordinator, VolvoVerySlowIntervalCoordinator, ) @@ -36,21 +38,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: VolvoConfigEntry) -> boo api = await _async_auth_and_create_api(hass, entry) vehicle = await _async_load_vehicle(api) + context = VolvoContext(api, vehicle) # Order is important! Faster intervals must come first. # Different interval coordinators are in place to keep the number # of requests under 5000 per day. This lets users use the same # API key for two vehicles (as the limit is 10000 per day). coordinators = ( - VolvoFastIntervalCoordinator(hass, entry, api, vehicle), - VolvoMediumIntervalCoordinator(hass, entry, api, vehicle), - VolvoSlowIntervalCoordinator(hass, entry, api, vehicle), - VolvoVerySlowIntervalCoordinator(hass, entry, api, vehicle), + VolvoFastIntervalCoordinator(hass, entry, context), + VolvoMediumIntervalCoordinator(hass, entry, context), + VolvoSlowIntervalCoordinator(hass, entry, context), + VolvoVerySlowIntervalCoordinator(hass, entry, context), ) await asyncio.gather(*(c.async_config_entry_first_refresh() for c in coordinators)) - entry.runtime_data = coordinators + entry.runtime_data = VolvoRuntimeData(coordinators) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/volvo/binary_sensor.py b/homeassistant/components/volvo/binary_sensor.py index 5edbcf0812698e..fe8783d933409e 100644 --- a/homeassistant/components/volvo/binary_sensor.py +++ b/homeassistant/components/volvo/binary_sensor.py @@ -366,7 +366,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up binary sensors.""" - coordinators = entry.runtime_data + coordinators = entry.runtime_data.interval_coordinators async_add_entities( VolvoBinarySensor(coordinator, description) for coordinator in coordinators diff --git a/homeassistant/components/volvo/coordinator.py b/homeassistant/components/volvo/coordinator.py index 7dc8c47eccc008..cbb4915f4a0c1e 100644 --- a/homeassistant/components/volvo/coordinator.py +++ b/homeassistant/components/volvo/coordinator.py @@ -5,9 +5,10 @@ from abc import abstractmethod import asyncio from collections.abc import Callable, Coroutine +from dataclasses import dataclass from datetime import timedelta import logging -from typing import Any, cast +from typing import Any, Generic, TypeVar, cast from volvocarsapi.api import VolvoCarsApi from volvocarsapi.models import ( @@ -34,7 +35,22 @@ _LOGGER = logging.getLogger(__name__) -type VolvoConfigEntry = ConfigEntry[tuple[VolvoBaseCoordinator, ...]] +@dataclass +class VolvoContext: + """Volvo context.""" + + api: VolvoCarsApi + vehicle: VolvoCarsVehicle + + +@dataclass +class VolvoRuntimeData: + """Volvo runtime data.""" + + interval_coordinators: tuple[VolvoBaseIntervalCoordinator, ...] + + +type VolvoConfigEntry = ConfigEntry[VolvoRuntimeData] type CoordinatorData = dict[str, VolvoCarsApiBaseModel | None] @@ -48,7 +64,10 @@ def _is_invalid_api_field(field: VolvoCarsApiBaseModel | None) -> bool: return False -class VolvoBaseCoordinator(DataUpdateCoordinator[CoordinatorData]): +T = TypeVar("T", bound=dict, default=dict[str, Any]) + + +class VolvoBaseCoordinator(DataUpdateCoordinator[T], Generic[T]): """Volvo base coordinator.""" config_entry: VolvoConfigEntry @@ -57,9 +76,8 @@ def __init__( self, hass: HomeAssistant, entry: VolvoConfigEntry, - api: VolvoCarsApi, - vehicle: VolvoCarsVehicle, - update_interval: timedelta, + context: VolvoContext, + update_interval: timedelta | None, name: str, ) -> None: """Initialize the coordinator.""" @@ -72,8 +90,34 @@ def __init__( update_interval=update_interval, ) - self.api = api - self.vehicle = vehicle + self.context = context + + def get_api_field(self, api_field: str | None) -> VolvoCarsApiBaseModel | None: + """Get the API field based on the entity description.""" + + return self.data.get(api_field) if api_field else None + + +class VolvoBaseIntervalCoordinator(VolvoBaseCoordinator[CoordinatorData]): + """Volvo base interval coordinator.""" + + def __init__( + self, + hass: HomeAssistant, + entry: VolvoConfigEntry, + context: VolvoContext, + update_interval: timedelta, + name: str, + ) -> None: + """Initialize the coordinator.""" + + super().__init__( + hass, + entry, + context, + update_interval, + name, + ) self._api_calls: list[Callable[[], Coroutine[Any, Any, Any]]] = [] @@ -151,11 +195,6 @@ async def _async_update_data(self) -> CoordinatorData: return data - def get_api_field(self, api_field: str | None) -> VolvoCarsApiBaseModel | None: - """Get the API field based on the entity description.""" - - return self.data.get(api_field) if api_field else None - @abstractmethod async def _async_determine_api_calls( self, @@ -163,23 +202,21 @@ async def _async_determine_api_calls( raise NotImplementedError -class VolvoVerySlowIntervalCoordinator(VolvoBaseCoordinator): +class VolvoVerySlowIntervalCoordinator(VolvoBaseIntervalCoordinator): """Volvo coordinator with very slow update rate.""" def __init__( self, hass: HomeAssistant, entry: VolvoConfigEntry, - api: VolvoCarsApi, - vehicle: VolvoCarsVehicle, + context: VolvoContext, ) -> None: """Initialize the coordinator.""" super().__init__( hass, entry, - api, - vehicle, + context, timedelta(minutes=VERY_SLOW_INTERVAL), "Volvo very slow interval coordinator", ) @@ -187,47 +224,47 @@ def __init__( async def _async_determine_api_calls( self, ) -> list[Callable[[], Coroutine[Any, Any, Any]]]: + api = self.context.api + return [ - self.api.async_get_brakes_status, - self.api.async_get_diagnostics, - self.api.async_get_engine_warnings, - self.api.async_get_odometer, - self.api.async_get_statistics, - self.api.async_get_tyre_states, - self.api.async_get_warnings, + api.async_get_brakes_status, + api.async_get_diagnostics, + api.async_get_engine_warnings, + api.async_get_odometer, + api.async_get_statistics, + api.async_get_tyre_states, + api.async_get_warnings, ] async def _async_update_data(self) -> CoordinatorData: data = await super()._async_update_data() # Add static values - if self.vehicle.has_battery_engine(): + if self.context.vehicle.has_battery_engine(): data[DATA_BATTERY_CAPACITY] = VolvoCarsValue.from_dict( { - "value": self.vehicle.battery_capacity_kwh, + "value": self.context.vehicle.battery_capacity_kwh, } ) return data -class VolvoSlowIntervalCoordinator(VolvoBaseCoordinator): +class VolvoSlowIntervalCoordinator(VolvoBaseIntervalCoordinator): """Volvo coordinator with slow update rate.""" def __init__( self, hass: HomeAssistant, entry: VolvoConfigEntry, - api: VolvoCarsApi, - vehicle: VolvoCarsVehicle, + context: VolvoContext, ) -> None: """Initialize the coordinator.""" super().__init__( hass, entry, - api, - vehicle, + context, timedelta(minutes=SLOW_INTERVAL), "Volvo slow interval coordinator", ) @@ -235,32 +272,32 @@ def __init__( async def _async_determine_api_calls( self, ) -> list[Callable[[], Coroutine[Any, Any, Any]]]: - if self.vehicle.has_combustion_engine(): + api = self.context.api + + if self.context.vehicle.has_combustion_engine(): return [ - self.api.async_get_command_accessibility, - self.api.async_get_fuel_status, + api.async_get_command_accessibility, + api.async_get_fuel_status, ] - return [self.api.async_get_command_accessibility] + return [api.async_get_command_accessibility] -class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator): +class VolvoMediumIntervalCoordinator(VolvoBaseIntervalCoordinator): """Volvo coordinator with medium update rate.""" def __init__( self, hass: HomeAssistant, entry: VolvoConfigEntry, - api: VolvoCarsApi, - vehicle: VolvoCarsVehicle, + context: VolvoContext, ) -> None: """Initialize the coordinator.""" super().__init__( hass, entry, - api, - vehicle, + context, timedelta(minutes=MEDIUM_INTERVAL), "Volvo medium interval coordinator", ) @@ -271,9 +308,11 @@ async def _async_determine_api_calls( self, ) -> list[Callable[[], Coroutine[Any, Any, Any]]]: api_calls: list[Any] = [] + api = self.context.api + vehicle = self.context.vehicle - if self.vehicle.has_battery_engine(): - capabilities = await self.api.async_get_energy_capabilities() + if vehicle.has_battery_engine(): + capabilities = await api.async_get_energy_capabilities() if capabilities.get("isSupported", False): @@ -288,8 +327,8 @@ def _normalize_key(key: str) -> str: api_calls.append(self._async_get_energy_state) - if self.vehicle.has_combustion_engine(): - api_calls.append(self.api.async_get_engine_status) + if vehicle.has_combustion_engine(): + api_calls.append(api.async_get_engine_status) return api_calls @@ -304,7 +343,7 @@ def _mark_ok( return field - energy_state = await self.api.async_get_energy_state() + energy_state = await self.context.api.async_get_energy_state() return { key: _mark_ok(value) @@ -313,23 +352,21 @@ def _mark_ok( } -class VolvoFastIntervalCoordinator(VolvoBaseCoordinator): +class VolvoFastIntervalCoordinator(VolvoBaseIntervalCoordinator): """Volvo coordinator with fast update rate.""" def __init__( self, hass: HomeAssistant, entry: VolvoConfigEntry, - api: VolvoCarsApi, - vehicle: VolvoCarsVehicle, + context: VolvoContext, ) -> None: """Initialize the coordinator.""" super().__init__( hass, entry, - api, - vehicle, + context, timedelta(minutes=FAST_INTERVAL), "Volvo fast interval coordinator", ) @@ -337,7 +374,9 @@ def __init__( async def _async_determine_api_calls( self, ) -> list[Callable[[], Coroutine[Any, Any, Any]]]: + api = self.context.api + return [ - self.api.async_get_doors_status, - self.api.async_get_window_states, + api.async_get_doors_status, + api.async_get_window_states, ] diff --git a/homeassistant/components/volvo/entity.py b/homeassistant/components/volvo/entity.py index f23bd714870f56..a8960a5f68f88c 100644 --- a/homeassistant/components/volvo/entity.py +++ b/homeassistant/components/volvo/entity.py @@ -54,7 +54,7 @@ def __init__( coordinator.config_entry.data[CONF_VIN], description.key ) - vehicle = coordinator.vehicle + vehicle = coordinator.context.vehicle model = ( f"{vehicle.description.model} ({vehicle.model_year})" if vehicle.fuel_type == "NONE" diff --git a/homeassistant/components/volvo/sensor.py b/homeassistant/components/volvo/sensor.py index 2d1274c17c0612..13614ff2830239 100644 --- a/homeassistant/components/volvo/sensor.py +++ b/homeassistant/components/volvo/sensor.py @@ -354,7 +354,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up sensors.""" - coordinators = entry.runtime_data + coordinators = entry.runtime_data.interval_coordinators async_add_entities( VolvoSensor(coordinator, description) for coordinator in coordinators diff --git a/requirements_all.txt b/requirements_all.txt index dccd226ac18c09..c667bf72bc3547 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1416,7 +1416,7 @@ mbddns==0.1.2 # homeassistant.components.mcp # homeassistant.components.mcp_server -mcp==1.5.0 +mcp==1.14.1 # homeassistant.components.minecraft_server mcstatus==12.0.1 @@ -2183,7 +2183,7 @@ pymsteams==0.1.12 pymysensors==0.26.0 # homeassistant.components.iron_os -pynecil==4.1.1 +pynecil==4.2.0 # homeassistant.components.netgear pynetgear==0.10.10 @@ -2688,7 +2688,7 @@ renault-api==0.4.0 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.15.1 +reolink-aio==0.15.2 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be4803bd890cf2..9d5f97ea5bbe3f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1214,7 +1214,7 @@ mbddns==0.1.2 # homeassistant.components.mcp # homeassistant.components.mcp_server -mcp==1.5.0 +mcp==1.14.1 # homeassistant.components.minecraft_server mcstatus==12.0.1 @@ -1822,7 +1822,7 @@ pymonoprice==0.4 pymysensors==0.26.0 # homeassistant.components.iron_os -pynecil==4.1.1 +pynecil==4.2.0 # homeassistant.components.netgear pynetgear==0.10.10 @@ -2237,7 +2237,7 @@ renault-api==0.4.0 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.15.1 +reolink-aio==0.15.2 # homeassistant.components.rflink rflink==0.0.67 diff --git a/tests/components/conversation/test_default_agent.py b/tests/components/conversation/test_default_agent.py index 6dcb032c0d3dd4..69fbe3caf820ea 100644 --- a/tests/components/conversation/test_default_agent.py +++ b/tests/components/conversation/test_default_agent.py @@ -522,13 +522,13 @@ async def test_respond_intent(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("init_components") -async def test_device_area_context( +async def test_satellite_area_context( hass: HomeAssistant, area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: - """Test that including a device_id will target a specific area.""" + """Test that including a satellite will target a specific area.""" turn_on_calls = async_mock_service(hass, "light", "turn_on") turn_off_calls = async_mock_service(hass, "light", "turn_off") @@ -560,12 +560,12 @@ async def test_device_area_context( entry = MockConfigEntry() entry.add_to_hass(hass) - kitchen_satellite = device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - connections=set(), - identifiers={("demo", "id-satellite-kitchen")}, + kitchen_satellite = entity_registry.async_get_or_create( + "assist_satellite", "demo", "kitchen" + ) + entity_registry.async_update_entity( + kitchen_satellite.entity_id, area_id=area_kitchen.id ) - device_registry.async_update_device(kitchen_satellite.id, area_id=area_kitchen.id) bedroom_satellite = device_registry.async_get_or_create( config_entry_id=entry.entry_id, @@ -581,7 +581,7 @@ async def test_device_area_context( None, Context(), None, - device_id=kitchen_satellite.id, + satellite_id=kitchen_satellite.entity_id, ) await hass.async_block_till_done() assert result.response.response_type == intent.IntentResponseType.ACTION_DONE @@ -605,7 +605,7 @@ async def test_device_area_context( None, Context(), None, - device_id=kitchen_satellite.id, + satellite_id=kitchen_satellite.entity_id, ) await hass.async_block_till_done() assert result.response.response_type == intent.IntentResponseType.ACTION_DONE diff --git a/tests/components/esphome/conftest.py b/tests/components/esphome/conftest.py index f9383d3b4f78ef..9fe709322af40e 100644 --- a/tests/components/esphome/conftest.py +++ b/tests/components/esphome/conftest.py @@ -187,13 +187,15 @@ def mock_constructor( zeroconf_instance: Zeroconf = None, noise_psk: str | None = None, expected_name: str | None = None, - ): + timezone: str | None = None, + ) -> None: """Fake the client constructor.""" mock_client.host = address mock_client.port = port mock_client.password = password mock_client.zeroconf_instance = zeroconf_instance mock_client.noise_psk = noise_psk + mock_client.timezone = timezone return mock_client mock_client.side_effect = mock_constructor diff --git a/tests/components/music_assistant/test_config_flow.py b/tests/components/music_assistant/test_config_flow.py index 2f623c1188d206..57eafd72ecf9a0 100644 --- a/tests/components/music_assistant/test_config_flow.py +++ b/tests/components/music_assistant/test_config_flow.py @@ -215,3 +215,150 @@ async def test_flow_zeroconf_connect_issue( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "cannot_connect" + + +async def test_user_url_different_from_server_base_url( + hass: HomeAssistant, + mock_get_server_info: AsyncMock, +) -> None: + """Test that user-provided URL is used even when different from server base_url.""" + # Mock server info with a different base_url than what user will provide + server_info = ServerInfoMessage.from_json( + await async_load_fixture(hass, "server_info_message.json", DOMAIN) + ) + server_info.base_url = "http://different-server:8095" + mock_get_server_info.return_value = server_info + + user_url = "http://user-provided-server:8095" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_URL: user_url}, + ) + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == DEFAULT_NAME + # Verify that the user-provided URL is stored, not the server's base_url + assert result["data"] == { + CONF_URL: user_url, + } + assert result["result"].unique_id == "1234" + + +async def test_duplicate_user_with_different_urls( + hass: HomeAssistant, + mock_get_server_info: AsyncMock, +) -> None: + """Test duplicate detection works with different user URLs.""" + # Set up existing config entry with one URL + existing_url = "http://existing-server:8095" + existing_config_entry = MockConfigEntry( + domain=DOMAIN, + title="Music Assistant", + data={CONF_URL: existing_url}, + unique_id="1234", + ) + existing_config_entry.add_to_hass(hass) + + # Mock server info with different base_url + server_info = ServerInfoMessage.from_json( + await async_load_fixture(hass, "server_info_message.json", DOMAIN) + ) + server_info.base_url = "http://server-reported-url:8095" + mock_get_server_info.return_value = server_info + + # Try to configure with a different user URL but same server_id + new_user_url = "http://new-user-url:8095" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + await hass.async_block_till_done() + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_URL: new_user_url}, + ) + await hass.async_block_till_done() + + # Should detect as duplicate because server_id is the same + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_zeroconf_existing_entry_working_url( + hass: HomeAssistant, + mock_get_server_info: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test zeroconf flow when existing entry has working URL.""" + mock_config_entry.add_to_hass(hass) + + # Mock server info with different base_url + server_info = ServerInfoMessage.from_json( + await async_load_fixture(hass, "server_info_message.json", DOMAIN) + ) + server_info.base_url = "http://different-discovered-url:8095" + mock_get_server_info.return_value = server_info + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=ZEROCONF_DATA, + ) + await hass.async_block_till_done() + + # Should abort because current URL is working + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + # Verify the URL was not changed + assert mock_config_entry.data[CONF_URL] == "http://localhost:8095" + + +async def test_zeroconf_existing_entry_broken_url( + hass: HomeAssistant, + mock_get_server_info: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test zeroconf flow when existing entry has broken URL.""" + mock_config_entry.add_to_hass(hass) + + # Create modified zeroconf data with different base_url + modified_zeroconf_data = deepcopy(ZEROCONF_DATA) + modified_zeroconf_data.properties["base_url"] = "http://discovered-working-url:8095" + + # Mock server info with the discovered URL + server_info = ServerInfoMessage.from_json( + await async_load_fixture(hass, "server_info_message.json", DOMAIN) + ) + server_info.base_url = "http://discovered-working-url:8095" + mock_get_server_info.return_value = server_info + + # First call (testing current URL) should fail, second call (testing discovered URL) should succeed + mock_get_server_info.side_effect = [ + CannotConnect("cannot_connect"), # Current URL fails + server_info, # Discovered URL works + ] + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=modified_zeroconf_data, + ) + await hass.async_block_till_done() + + # Should proceed to discovery confirm because current URL is broken + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "discovery_confirm" + # Verify the URL was updated in the config entry + updated_entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id) + assert updated_entry.data[CONF_URL] == "http://discovered-working-url:8095" diff --git a/tests/components/nordpool/test_coordinator.py b/tests/components/nordpool/test_coordinator.py index 0f6b4341b938be..e9af70d05bc870 100644 --- a/tests/components/nordpool/test_coordinator.py +++ b/tests/components/nordpool/test_coordinator.py @@ -16,10 +16,15 @@ ) import pytest +from homeassistant.components.homeassistant import ( + DOMAIN as HOMEASSISTANT_DOMAIN, + SERVICE_UPDATE_ENTITY, +) from homeassistant.components.nordpool.const import DOMAIN from homeassistant.config_entries import SOURCE_USER -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component from . import ENTRY_CONFIG @@ -34,12 +39,12 @@ async def test_coordinator( caplog: pytest.LogCaptureFixture, ) -> None: """Test the Nord Pool coordinator with errors.""" + await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {}) config_entry = MockConfigEntry( domain=DOMAIN, source=SOURCE_USER, data=ENTRY_CONFIG, ) - config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) @@ -88,7 +93,7 @@ async def test_coordinator( # Empty responses does not raise assert mock_data.call_count == 3 state = hass.states.get("sensor.nord_pool_se3_current_price") - assert state.state == "0.94949" + assert state.state == "1.04203" assert "Empty response" in caplog.text with ( @@ -142,6 +147,50 @@ async def test_coordinator( state = hass.states.get("sensor.nord_pool_se3_current_price") assert state.state == "1.81983" + # Test manual polling + hass.config_entries.async_update_entry( + entry=config_entry, pref_disable_polling=True + ) + await hass.config_entries.async_reload(config_entry.entry_id) + freezer.tick(timedelta(hours=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("sensor.nord_pool_se3_current_price") + assert state.state == "1.01177" + + # Prices should update without any polling made (read from cache) + freezer.tick(timedelta(hours=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("sensor.nord_pool_se3_current_price") + assert state.state == "0.83553" + + # Test manually updating the data + with ( + patch( + "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_periods", + wraps=get_client.async_get_delivery_periods, + ) as mock_data, + ): + await hass.services.async_call( + HOMEASSISTANT_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: "sensor.nord_pool_se3_current_price"}, + blocking=True, + ) + assert mock_data.call_count == 1 + + freezer.tick(timedelta(hours=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("sensor.nord_pool_se3_current_price") + assert state.state == "0.79619" + + hass.config_entries.async_update_entry( + entry=config_entry, pref_disable_polling=False + ) + await hass.config_entries.async_reload(config_entry.entry_id) + with ( patch( "homeassistant.components.nordpool.coordinator.NordPoolClient.async_get_delivery_period", diff --git a/tests/components/systemmonitor/conftest.py b/tests/components/systemmonitor/conftest.py index 5f0a7a5c76dadb..a5aa15d8b0a5e4 100644 --- a/tests/components/systemmonitor/conftest.py +++ b/tests/components/systemmonitor/conftest.py @@ -176,7 +176,6 @@ def mock_psutil(mock_process: list[MockProcess]) -> Generator: mock_psutil.disk_partitions.return_value = [ sdiskpart("test", "/", "ext4", ""), sdiskpart("test2", "/media/share", "ext4", ""), - sdiskpart("test3", "/incorrect", "", ""), sdiskpart("hosts", "/etc/hosts", "bind", ""), sdiskpart("proc", "/proc/run", "proc", ""), ] @@ -197,7 +196,6 @@ def isdir(path: str) -> bool: patch("homeassistant.components.systemmonitor.coordinator.os") as mock_os, patch("homeassistant.components.systemmonitor.util.os") as mock_os_util, ): - mock_os_util.name = "nt" mock_os.getloadavg.return_value = (1, 2, 3) mock_os_util.path.isdir = isdir yield mock_os diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index a5f5e7623e9d17..9b942257ec17ac 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -313,19 +313,6 @@ async def test_processor_temperature( assert await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.async_block_till_done() - with patch("sys.platform", "nt"): - mock_psutil.sensors_temperatures.return_value = None - mock_psutil.sensors_temperatures.side_effect = AttributeError( - "sensors_temperatures not exist" - ) - mock_config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - temp_entity = hass.states.get("sensor.system_monitor_processor_temperature") - assert temp_entity.state == STATE_UNAVAILABLE - assert await hass.config_entries.async_unload(mock_config_entry.entry_id) - await hass.async_block_till_done() - with patch("sys.platform", "darwin"): mock_psutil.sensors_temperatures.return_value = { "cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)] diff --git a/tests/components/systemmonitor/test_util.py b/tests/components/systemmonitor/test_util.py index 582707f3574bc4..471f2f9e2cb49b 100644 --- a/tests/components/systemmonitor/test_util.py +++ b/tests/components/systemmonitor/test_util.py @@ -52,7 +52,6 @@ async def test_disk_util( mock_psutil.psutil.disk_partitions.return_value = [ sdiskpart("test", "/", "ext4", ""), # Should be ok sdiskpart("test2", "/media/share", "ext4", ""), # Should be ok - sdiskpart("test3", "/incorrect", "", ""), # Should be skipped as no type sdiskpart( "proc", "/proc/run", "proc", "" ), # Should be skipped as in skipped disk types @@ -62,7 +61,6 @@ async def test_disk_util( "tmpfs", "", ), # Should be skipped as in skipped disk types - sdiskpart("test5", "E:", "cd", "cdrom"), # Should be skipped as cdrom ] mock_config_entry.add_to_hass(hass) @@ -71,13 +69,9 @@ async def test_disk_util( disk_sensor1 = hass.states.get("sensor.system_monitor_disk_free") disk_sensor2 = hass.states.get("sensor.system_monitor_disk_free_media_share") - disk_sensor3 = hass.states.get("sensor.system_monitor_disk_free_incorrect") - disk_sensor4 = hass.states.get("sensor.system_monitor_disk_free_proc_run") - disk_sensor5 = hass.states.get("sensor.system_monitor_disk_free_tmpfs") - disk_sensor6 = hass.states.get("sensor.system_monitor_disk_free_e") + disk_sensor3 = hass.states.get("sensor.system_monitor_disk_free_proc_run") + disk_sensor4 = hass.states.get("sensor.system_monitor_disk_free_tmpfs") assert disk_sensor1 is not None assert disk_sensor2 is not None assert disk_sensor3 is None assert disk_sensor4 is None - assert disk_sensor5 is None - assert disk_sensor6 is None