From 7f9be420d2180cd44a5e08bec26bdf37e70c1239 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:54:54 +0200 Subject: [PATCH] Add details to Husqvarna Automower restricted reason sensor (#147678) Co-authored-by: Norbert Rittel --- .../components/husqvarna_automower/sensor.py | 29 ++++++++++- .../husqvarna_automower/strings.json | 26 +++++++--- .../snapshots/test_sensor.ambr | 48 +++++++++++++++++++ .../husqvarna_automower/test_sensor.py | 43 ++++++++++++++++- 4 files changed, 137 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 0ff72271cb9..7f2921f17fa 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -8,6 +8,7 @@ from operator import attrgetter from typing import TYPE_CHECKING, Any from aioautomower.model import ( + ExternalReasons, InactiveReasons, MowerAttributes, MowerModes, @@ -190,11 +191,37 @@ RESTRICTED_REASONS: list = [ RestrictedReasons.PARK_OVERRIDE, RestrictedReasons.SENSOR, RestrictedReasons.WEEK_SCHEDULE, + ExternalReasons.AMAZON_ALEXA, + ExternalReasons.DEVELOPER_PORTAL, + ExternalReasons.GARDENA_SMART_SYSTEM, + ExternalReasons.GOOGLE_ASSISTANT, + ExternalReasons.HOME_ASSISTANT, + ExternalReasons.IFTTT, + ExternalReasons.IFTTT_APPLETS, + ExternalReasons.IFTTT_CALENDAR_CONNECTION, + ExternalReasons.SMART_ROUTINE, + ExternalReasons.SMART_ROUTINE_FROST_GUARD, + ExternalReasons.SMART_ROUTINE_RAIN_GUARD, + ExternalReasons.SMART_ROUTINE_WILDLIFE_PROTECTION, ] STATE_NO_WORK_AREA_ACTIVE = "no_work_area_active" +@callback +def _get_restricted_reason(data: MowerAttributes) -> str: + """Return the restricted reason. + + If there is an external reason, return that instead, if it's available. + """ + if ( + data.planner.restricted_reason == RestrictedReasons.EXTERNAL + and data.planner.external_reason is not None + ): + return data.planner.external_reason + return data.planner.restricted_reason + + @callback def _get_work_area_names(data: MowerAttributes) -> list[str]: """Return a list with all work area names.""" @@ -400,7 +427,7 @@ MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( translation_key="restricted_reason", device_class=SensorDeviceClass.ENUM, option_fn=lambda data: RESTRICTED_REASONS, - value_fn=attrgetter("planner.restricted_reason"), + value_fn=_get_restricted_reason, ), AutomowerSensorEntityDescription( key="inactive_reason", diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index 62843d67ae2..226c9ee17f0 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -242,16 +242,28 @@ "restricted_reason": { "name": "Restricted reason", "state": { - "none": "No restrictions", - "week_schedule": "Week schedule", - "park_override": "Park override", - "sensor": "Weather timer", + "all_work_areas_completed": "All work areas completed", + "amazon_alexa": "Amazon Alexa", "daily_limit": "Daily limit", + "developer_portal": "Developer Portal", + "external": "External", "fota": "Firmware Over-the-Air update running", "frost": "Frost", - "all_work_areas_completed": "All work areas completed", - "external": "External", - "not_applicable": "Not applicable" + "gardena_smart_system": "Gardena Smart System", + "google_assistant": "Google Assistant", + "home_assistant": "Home Assistant", + "ifttt_applets": "IFTTT applets", + "ifttt_calendar_connection": "IFTTT calendar connection", + "ifttt": "IFTTT", + "none": "No restrictions", + "not_applicable": "Not applicable", + "park_override": "Park override", + "sensor": "Weather timer", + "smart_routine_frost_guard": "Frost guard", + "smart_routine_rain_guard": "Rain guard", + "smart_routine_wildlife_protection": "Wildlife protection", + "smart_routine": "Generic smart routine", + "week_schedule": "Week schedule" } }, "total_charging_time": { diff --git a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr index 0fe46c24254..3aa3504cc26 100644 --- a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr @@ -978,6 +978,18 @@ , , , + , + , + , + , + , + , + , + , + , + , + , + , ]), }), 'config_entry_id': , @@ -1025,6 +1037,18 @@ , , , + , + , + , + , + , + , + , + , + , + , + , + , ]), }), 'context': , @@ -1953,6 +1977,18 @@ , , , + , + , + , + , + , + , + , + , + , + , + , + , ]), }), 'config_entry_id': , @@ -2000,6 +2036,18 @@ , , , + , + , + , + , + , + , + , + , + , + , + , + , ]), }), 'context': , diff --git a/tests/components/husqvarna_automower/test_sensor.py b/tests/components/husqvarna_automower/test_sensor.py index d756b1b2ffa..204fba872c4 100644 --- a/tests/components/husqvarna_automower/test_sensor.py +++ b/tests/components/husqvarna_automower/test_sensor.py @@ -4,7 +4,13 @@ import datetime from unittest.mock import AsyncMock, patch import zoneinfo -from aioautomower.model import MowerAttributes, MowerModes, MowerStates +from aioautomower.model import ( + ExternalReasons, + MowerAttributes, + MowerModes, + MowerStates, + RestrictedReasons, +) from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -123,6 +129,41 @@ async def test_work_area_sensor( assert state.state == "no_work_area_active" +async def test_restricted_reason_sensor( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, + values: dict[str, MowerAttributes], +) -> None: + """Test the work area sensor.""" + sensor = "sensor.test_mower_1_restricted_reason" + await setup_integration(hass, mock_config_entry) + state = hass.states.get(sensor) + assert state is not None + assert state.state == RestrictedReasons.WEEK_SCHEDULE + + values[TEST_MOWER_ID].planner.restricted_reason = RestrictedReasons.EXTERNAL + values[TEST_MOWER_ID].planner.external_reason = None + mock_automower_client.get_status.return_value = values + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get(sensor) + assert state.state == RestrictedReasons.EXTERNAL + + values[TEST_MOWER_ID].planner.restricted_reason = RestrictedReasons.EXTERNAL + values[ + TEST_MOWER_ID + ].planner.external_reason = ExternalReasons.SMART_ROUTINE_WILDLIFE_PROTECTION + mock_automower_client.get_status.return_value = values + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get(sensor) + assert state.state == ExternalReasons.SMART_ROUTINE_WILDLIFE_PROTECTION + + @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( ("sensor_to_test"),