mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Add and remove entities during runtime in Husqvarna Automower (#127878)
This commit is contained in:
parent
2236ca3e12
commit
983cd9c3fc
@ -13,6 +13,7 @@ from homeassistant.helpers import (
|
|||||||
aiohttp_client,
|
aiohttp_client,
|
||||||
config_entry_oauth2_flow,
|
config_entry_oauth2_flow,
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
|
entity_registry as er,
|
||||||
)
|
)
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
@ -99,3 +100,20 @@ def cleanup_removed_devices(
|
|||||||
device_reg.async_update_device(
|
device_reg.async_update_device(
|
||||||
device.id, remove_config_entry_id=config_entry.entry_id
|
device.id, remove_config_entry_id=config_entry.entry_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_work_area_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
removed_work_areas: set[int],
|
||||||
|
mower_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Remove all unused work area entities for the specified mower."""
|
||||||
|
entity_reg = er.async_get(hass)
|
||||||
|
for entity_entry in er.async_entries_for_config_entry(
|
||||||
|
entity_reg, config_entry.entry_id
|
||||||
|
):
|
||||||
|
for work_area_id in removed_work_areas:
|
||||||
|
if entity_entry.unique_id.startswith(f"{mower_id}_{work_area_id}_"):
|
||||||
|
_LOGGER.info("Deleting: %s", entity_entry.entity_id)
|
||||||
|
entity_reg.async_remove(entity_entry.entity_id)
|
||||||
|
@ -9,13 +9,12 @@ from typing import TYPE_CHECKING, Any
|
|||||||
from aioautomower.exceptions import ApiException
|
from aioautomower.exceptions import ApiException
|
||||||
from aioautomower.model import MowerActivities, MowerAttributes, MowerStates, WorkArea
|
from aioautomower.model import MowerActivities, MowerAttributes, MowerStates, WorkArea
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import AutomowerConfigEntry, AutomowerDataUpdateCoordinator
|
from . import AutomowerDataUpdateCoordinator
|
||||||
from .const import DOMAIN, EXECUTION_TIME_DELAY
|
from .const import DOMAIN, EXECUTION_TIME_DELAY
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -53,30 +52,6 @@ def _work_area_translation_key(work_area_id: int, key: str) -> str:
|
|||||||
return f"work_area_{key}"
|
return f"work_area_{key}"
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_remove_work_area_entities(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
coordinator: AutomowerDataUpdateCoordinator,
|
|
||||||
entry: AutomowerConfigEntry,
|
|
||||||
mower_id: str,
|
|
||||||
) -> None:
|
|
||||||
"""Remove deleted work areas from Home Assistant."""
|
|
||||||
entity_reg = er.async_get(hass)
|
|
||||||
active_work_areas = set()
|
|
||||||
_work_areas = coordinator.data[mower_id].work_areas
|
|
||||||
if _work_areas is not None:
|
|
||||||
for work_area_id in _work_areas:
|
|
||||||
uid = f"{mower_id}_{work_area_id}_cutting_height_work_area"
|
|
||||||
active_work_areas.add(uid)
|
|
||||||
for entity_entry in er.async_entries_for_config_entry(entity_reg, entry.entry_id):
|
|
||||||
if (
|
|
||||||
(split := entity_entry.unique_id.split("_"))[0] == mower_id
|
|
||||||
and split[-1] == "area"
|
|
||||||
and entity_entry.unique_id not in active_work_areas
|
|
||||||
):
|
|
||||||
entity_reg.async_remove(entity_entry.entity_id)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_sending_exception(
|
def handle_sending_exception(
|
||||||
poll_after_sending: bool = False,
|
poll_after_sending: bool = False,
|
||||||
) -> Callable[
|
) -> Callable[
|
||||||
|
@ -13,13 +13,12 @@ from homeassistant.const import PERCENTAGE, EntityCategory
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from . import AutomowerConfigEntry
|
from . import AutomowerConfigEntry, remove_work_area_entities
|
||||||
from .coordinator import AutomowerDataUpdateCoordinator
|
from .coordinator import AutomowerDataUpdateCoordinator
|
||||||
from .entity import (
|
from .entity import (
|
||||||
AutomowerControlEntity,
|
AutomowerControlEntity,
|
||||||
WorkAreaControlEntity,
|
WorkAreaControlEntity,
|
||||||
_work_area_translation_key,
|
_work_area_translation_key,
|
||||||
async_remove_work_area_entities,
|
|
||||||
handle_sending_exception,
|
handle_sending_exception,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -110,26 +109,44 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up number platform."""
|
"""Set up number platform."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
entities: list[NumberEntity] = []
|
current_work_areas: dict[str, set[int]] = {}
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
AutomowerNumberEntity(mower_id, coordinator, description)
|
||||||
|
for mower_id in coordinator.data
|
||||||
|
for description in MOWER_NUMBER_TYPES
|
||||||
|
if description.exists_fn(coordinator.data[mower_id])
|
||||||
|
)
|
||||||
|
|
||||||
|
def _async_work_area_listener() -> None:
|
||||||
|
"""Listen for new work areas and add/remove entities as needed."""
|
||||||
for mower_id in coordinator.data:
|
for mower_id in coordinator.data:
|
||||||
if coordinator.data[mower_id].capabilities.work_areas:
|
if (
|
||||||
_work_areas = coordinator.data[mower_id].work_areas
|
coordinator.data[mower_id].capabilities.work_areas
|
||||||
if _work_areas is not None:
|
and (_work_areas := coordinator.data[mower_id].work_areas) is not None
|
||||||
entities.extend(
|
):
|
||||||
|
received_work_areas = set(_work_areas.keys())
|
||||||
|
current_work_area_set = current_work_areas.setdefault(mower_id, set())
|
||||||
|
|
||||||
|
new_work_areas = received_work_areas - current_work_area_set
|
||||||
|
removed_work_areas = current_work_area_set - received_work_areas
|
||||||
|
|
||||||
|
if new_work_areas:
|
||||||
|
current_work_area_set.update(new_work_areas)
|
||||||
|
async_add_entities(
|
||||||
WorkAreaNumberEntity(
|
WorkAreaNumberEntity(
|
||||||
mower_id, coordinator, description, work_area_id
|
mower_id, coordinator, description, work_area_id
|
||||||
)
|
)
|
||||||
for description in WORK_AREA_NUMBER_TYPES
|
for description in WORK_AREA_NUMBER_TYPES
|
||||||
for work_area_id in _work_areas
|
for work_area_id in new_work_areas
|
||||||
)
|
)
|
||||||
async_remove_work_area_entities(hass, coordinator, entry, mower_id)
|
|
||||||
entities.extend(
|
if removed_work_areas:
|
||||||
AutomowerNumberEntity(mower_id, coordinator, description)
|
remove_work_area_entities(hass, entry, removed_work_areas, mower_id)
|
||||||
for description in MOWER_NUMBER_TYPES
|
current_work_area_set.difference_update(removed_work_areas)
|
||||||
if description.exists_fn(coordinator.data[mower_id])
|
|
||||||
)
|
coordinator.async_add_listener(_async_work_area_listener)
|
||||||
async_add_entities(entities)
|
_async_work_area_listener()
|
||||||
|
|
||||||
|
|
||||||
class AutomowerNumberEntity(AutomowerControlEntity, NumberEntity):
|
class AutomowerNumberEntity(AutomowerControlEntity, NumberEntity):
|
||||||
|
@ -431,25 +431,44 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up sensor platform."""
|
"""Set up sensor platform."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
entities: list[SensorEntity] = []
|
current_work_areas: dict[str, set[int]] = {}
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
AutomowerSensorEntity(mower_id, coordinator, description)
|
||||||
|
for mower_id, data in coordinator.data.items()
|
||||||
|
for description in MOWER_SENSOR_TYPES
|
||||||
|
if description.exists_fn(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _async_work_area_listener() -> None:
|
||||||
|
"""Listen for new work areas and add sensor entities if they did not exist.
|
||||||
|
|
||||||
|
Listening for deletable work areas is managed in the number platform.
|
||||||
|
"""
|
||||||
for mower_id in coordinator.data:
|
for mower_id in coordinator.data:
|
||||||
if coordinator.data[mower_id].capabilities.work_areas:
|
if (
|
||||||
_work_areas = coordinator.data[mower_id].work_areas
|
coordinator.data[mower_id].capabilities.work_areas
|
||||||
if _work_areas is not None:
|
and (_work_areas := coordinator.data[mower_id].work_areas) is not None
|
||||||
entities.extend(
|
):
|
||||||
|
received_work_areas = set(_work_areas.keys())
|
||||||
|
new_work_areas = received_work_areas - current_work_areas.get(
|
||||||
|
mower_id, set()
|
||||||
|
)
|
||||||
|
if new_work_areas:
|
||||||
|
current_work_areas.setdefault(mower_id, set()).update(
|
||||||
|
new_work_areas
|
||||||
|
)
|
||||||
|
async_add_entities(
|
||||||
WorkAreaSensorEntity(
|
WorkAreaSensorEntity(
|
||||||
mower_id, coordinator, description, work_area_id
|
mower_id, coordinator, description, work_area_id
|
||||||
)
|
)
|
||||||
for description in WORK_AREA_SENSOR_TYPES
|
for description in WORK_AREA_SENSOR_TYPES
|
||||||
for work_area_id in _work_areas
|
for work_area_id in new_work_areas
|
||||||
if description.exists_fn(_work_areas[work_area_id])
|
if description.exists_fn(_work_areas[work_area_id])
|
||||||
)
|
)
|
||||||
entities.extend(
|
|
||||||
AutomowerSensorEntity(mower_id, coordinator, description)
|
coordinator.async_add_listener(_async_work_area_listener)
|
||||||
for description in MOWER_SENSOR_TYPES
|
_async_work_area_listener()
|
||||||
if description.exists_fn(coordinator.data[mower_id])
|
|
||||||
)
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
|
|
||||||
class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
|
class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
|
||||||
|
@ -6,8 +6,7 @@ from typing import TYPE_CHECKING, Any
|
|||||||
from aioautomower.model import MowerModes, StayOutZones, Zone
|
from aioautomower.model import MowerModes, StayOutZones, Zone
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.const import Platform
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.core import HomeAssistant, callback
|
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
@ -30,28 +29,82 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up switch platform."""
|
"""Set up switch platform."""
|
||||||
coordinator = entry.runtime_data
|
coordinator = entry.runtime_data
|
||||||
entities: list[SwitchEntity] = []
|
current_work_areas: dict[str, set[int]] = {}
|
||||||
entities.extend(
|
current_stay_out_zones: dict[str, set[str]] = {}
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
AutomowerScheduleSwitchEntity(mower_id, coordinator)
|
AutomowerScheduleSwitchEntity(mower_id, coordinator)
|
||||||
for mower_id in coordinator.data
|
for mower_id in coordinator.data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _async_work_area_listener() -> None:
|
||||||
|
"""Listen for new work areas and add switch entities if they did not exist.
|
||||||
|
|
||||||
|
Listening for deletable work areas is managed in the number platform.
|
||||||
|
"""
|
||||||
for mower_id in coordinator.data:
|
for mower_id in coordinator.data:
|
||||||
if coordinator.data[mower_id].capabilities.stay_out_zones:
|
if (
|
||||||
_stay_out_zones = coordinator.data[mower_id].stay_out_zones
|
coordinator.data[mower_id].capabilities.work_areas
|
||||||
if _stay_out_zones is not None:
|
and (_work_areas := coordinator.data[mower_id].work_areas) is not None
|
||||||
entities.extend(
|
):
|
||||||
StayOutZoneSwitchEntity(coordinator, mower_id, stay_out_zone_uid)
|
received_work_areas = set(_work_areas.keys())
|
||||||
for stay_out_zone_uid in _stay_out_zones.zones
|
new_work_areas = received_work_areas - current_work_areas.get(
|
||||||
|
mower_id, set()
|
||||||
)
|
)
|
||||||
async_remove_entities(hass, coordinator, entry, mower_id)
|
if new_work_areas:
|
||||||
if coordinator.data[mower_id].capabilities.work_areas:
|
current_work_areas.setdefault(mower_id, set()).update(
|
||||||
_work_areas = coordinator.data[mower_id].work_areas
|
new_work_areas
|
||||||
if _work_areas is not None:
|
)
|
||||||
entities.extend(
|
async_add_entities(
|
||||||
WorkAreaSwitchEntity(coordinator, mower_id, work_area_id)
|
WorkAreaSwitchEntity(coordinator, mower_id, work_area_id)
|
||||||
for work_area_id in _work_areas
|
for work_area_id in new_work_areas
|
||||||
)
|
)
|
||||||
async_add_entities(entities)
|
|
||||||
|
def _remove_stay_out_zone_entities(
|
||||||
|
removed_stay_out_zones: set, mower_id: str
|
||||||
|
) -> None:
|
||||||
|
"""Remove all unused stay-out zones for all platforms."""
|
||||||
|
entity_reg = er.async_get(hass)
|
||||||
|
for entity_entry in er.async_entries_for_config_entry(
|
||||||
|
entity_reg, entry.entry_id
|
||||||
|
):
|
||||||
|
for stay_out_zone_uid in removed_stay_out_zones:
|
||||||
|
if entity_entry.unique_id.startswith(f"{mower_id}_{stay_out_zone_uid}"):
|
||||||
|
entity_reg.async_remove(entity_entry.entity_id)
|
||||||
|
|
||||||
|
def _async_stay_out_zone_listener() -> None:
|
||||||
|
"""Listen for new stay-out zones and add/remove switch entities if they did not exist."""
|
||||||
|
for mower_id in coordinator.data:
|
||||||
|
if (
|
||||||
|
coordinator.data[mower_id].capabilities.stay_out_zones
|
||||||
|
and (_stay_out_zones := coordinator.data[mower_id].stay_out_zones)
|
||||||
|
is not None
|
||||||
|
):
|
||||||
|
received_stay_out_zones = set(_stay_out_zones.zones)
|
||||||
|
current_stay_out_zones_set = current_stay_out_zones.get(mower_id, set())
|
||||||
|
new_stay_out_zones = (
|
||||||
|
received_stay_out_zones - current_stay_out_zones_set
|
||||||
|
)
|
||||||
|
removed_stay_out_zones = (
|
||||||
|
current_stay_out_zones_set - received_stay_out_zones
|
||||||
|
)
|
||||||
|
if new_stay_out_zones:
|
||||||
|
current_stay_out_zones.setdefault(mower_id, set()).update(
|
||||||
|
new_stay_out_zones
|
||||||
|
)
|
||||||
|
async_add_entities(
|
||||||
|
StayOutZoneSwitchEntity(
|
||||||
|
coordinator, mower_id, stay_out_zone_uid
|
||||||
|
)
|
||||||
|
for stay_out_zone_uid in new_stay_out_zones
|
||||||
|
)
|
||||||
|
if removed_stay_out_zones:
|
||||||
|
_remove_stay_out_zone_entities(removed_stay_out_zones, mower_id)
|
||||||
|
|
||||||
|
coordinator.async_add_listener(_async_work_area_listener)
|
||||||
|
coordinator.async_add_listener(_async_stay_out_zone_listener)
|
||||||
|
_async_work_area_listener()
|
||||||
|
_async_stay_out_zone_listener()
|
||||||
|
|
||||||
|
|
||||||
class AutomowerScheduleSwitchEntity(AutomowerControlEntity, SwitchEntity):
|
class AutomowerScheduleSwitchEntity(AutomowerControlEntity, SwitchEntity):
|
||||||
@ -180,28 +233,3 @@ class WorkAreaSwitchEntity(WorkAreaControlEntity, SwitchEntity):
|
|||||||
await self.coordinator.api.commands.workarea_settings(
|
await self.coordinator.api.commands.workarea_settings(
|
||||||
self.mower_id, self.work_area_id, enabled=True
|
self.mower_id, self.work_area_id, enabled=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_remove_entities(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
coordinator: AutomowerDataUpdateCoordinator,
|
|
||||||
entry: AutomowerConfigEntry,
|
|
||||||
mower_id: str,
|
|
||||||
) -> None:
|
|
||||||
"""Remove deleted stay-out-zones from Home Assistant."""
|
|
||||||
entity_reg = er.async_get(hass)
|
|
||||||
active_zones = set()
|
|
||||||
_zones = coordinator.data[mower_id].stay_out_zones
|
|
||||||
if _zones is not None:
|
|
||||||
for zones_uid in _zones.zones:
|
|
||||||
uid = f"{mower_id}_{zones_uid}_stay_out_zones"
|
|
||||||
active_zones.add(uid)
|
|
||||||
for entity_entry in er.async_entries_for_config_entry(entity_reg, entry.entry_id):
|
|
||||||
if (
|
|
||||||
entity_entry.domain == Platform.SWITCH
|
|
||||||
and (split := entity_entry.unique_id.split("_"))[0] == mower_id
|
|
||||||
and split[-1] == "zones"
|
|
||||||
and entity_entry.unique_id not in active_zones
|
|
||||||
):
|
|
||||||
entity_reg.async_remove(entity_entry.entity_id)
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Tests for init module."""
|
"""Tests for init module."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
import http
|
import http
|
||||||
import time
|
import time
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
@ -10,15 +10,17 @@ from aioautomower.exceptions import (
|
|||||||
AuthException,
|
AuthException,
|
||||||
HusqvarnaWSServerHandshakeError,
|
HusqvarnaWSServerHandshakeError,
|
||||||
)
|
)
|
||||||
from aioautomower.model import MowerAttributes
|
from aioautomower.model import MowerAttributes, WorkArea
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN
|
from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN
|
||||||
|
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import setup_integration
|
from . import setup_integration
|
||||||
from .const import TEST_MOWER_ID
|
from .const import TEST_MOWER_ID
|
||||||
@ -26,6 +28,10 @@ from .const import TEST_MOWER_ID
|
|||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
ADDITIONAL_NUMBER_ENTITIES = 1
|
||||||
|
ADDITIONAL_SENSOR_ENTITIES = 2
|
||||||
|
ADDITIONAL_SWITCH_ENTITIES = 1
|
||||||
|
|
||||||
|
|
||||||
async def test_load_unload_entry(
|
async def test_load_unload_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -163,29 +169,6 @@ async def test_device_info(
|
|||||||
assert reg_device == snapshot
|
assert reg_device == snapshot
|
||||||
|
|
||||||
|
|
||||||
async def test_workarea_deleted(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_automower_client: AsyncMock,
|
|
||||||
mock_config_entry: MockConfigEntry,
|
|
||||||
entity_registry: er.EntityRegistry,
|
|
||||||
values: dict[str, MowerAttributes],
|
|
||||||
) -> None:
|
|
||||||
"""Test if work area is deleted after removed."""
|
|
||||||
|
|
||||||
await setup_integration(hass, mock_config_entry)
|
|
||||||
current_entries = len(
|
|
||||||
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
del values[TEST_MOWER_ID].work_areas[123456]
|
|
||||||
mock_automower_client.get_status.return_value = values
|
|
||||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert len(
|
|
||||||
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)
|
|
||||||
) == (current_entries - 2)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_automatic_registry_cleanup(
|
async def test_coordinator_automatic_registry_cleanup(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_automower_client: AsyncMock,
|
mock_automower_client: AsyncMock,
|
||||||
@ -219,3 +202,70 @@ async def test_coordinator_automatic_registry_cleanup(
|
|||||||
len(dr.async_entries_for_config_entry(device_registry, entry.entry_id))
|
len(dr.async_entries_for_config_entry(device_registry, entry.entry_id))
|
||||||
== current_devices - 1
|
== current_devices - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_add_and_remove_work_area(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_automower_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
values: dict[str, MowerAttributes],
|
||||||
|
) -> None:
|
||||||
|
"""Test adding a work area in runtime."""
|
||||||
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||||
|
current_entites_start = len(
|
||||||
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
|
)
|
||||||
|
values[TEST_MOWER_ID].work_area_names.append("new work area")
|
||||||
|
values[TEST_MOWER_ID].work_area_dict.update({1: "new work area"})
|
||||||
|
values[TEST_MOWER_ID].work_areas.update(
|
||||||
|
{
|
||||||
|
1: WorkArea(
|
||||||
|
name="new work area",
|
||||||
|
cutting_height=12,
|
||||||
|
enabled=True,
|
||||||
|
progress=12,
|
||||||
|
last_time_completed=datetime(
|
||||||
|
2024, 10, 1, 11, 11, 0, tzinfo=dt_util.get_default_time_zone()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mock_automower_client.get_status.return_value = values
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
current_entites_after_addition = len(
|
||||||
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
current_entites_after_addition
|
||||||
|
== current_entites_start
|
||||||
|
+ ADDITIONAL_NUMBER_ENTITIES
|
||||||
|
+ ADDITIONAL_SENSOR_ENTITIES
|
||||||
|
+ ADDITIONAL_SWITCH_ENTITIES
|
||||||
|
)
|
||||||
|
|
||||||
|
values[TEST_MOWER_ID].work_area_names.remove("new work area")
|
||||||
|
del values[TEST_MOWER_ID].work_area_dict[1]
|
||||||
|
del values[TEST_MOWER_ID].work_areas[1]
|
||||||
|
values[TEST_MOWER_ID].work_area_names.remove("Front lawn")
|
||||||
|
del values[TEST_MOWER_ID].work_area_dict[123456]
|
||||||
|
del values[TEST_MOWER_ID].work_areas[123456]
|
||||||
|
del values[TEST_MOWER_ID].calendar.tasks[:2]
|
||||||
|
mock_automower_client.get_status.return_value = values
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
current_entites_after_deletion = len(
|
||||||
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
current_entites_after_deletion
|
||||||
|
== current_entites_start
|
||||||
|
- ADDITIONAL_SWITCH_ENTITIES
|
||||||
|
- ADDITIONAL_NUMBER_ENTITIES
|
||||||
|
- ADDITIONAL_SENSOR_ENTITIES
|
||||||
|
)
|
||||||
|
@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, patch
|
|||||||
import zoneinfo
|
import zoneinfo
|
||||||
|
|
||||||
from aioautomower.exceptions import ApiException
|
from aioautomower.exceptions import ApiException
|
||||||
from aioautomower.model import MowerAttributes, MowerModes
|
from aioautomower.model import MowerAttributes, MowerModes, Zone
|
||||||
from aioautomower.utils import mower_list_to_dictionary_dataclass
|
from aioautomower.utils import mower_list_to_dictionary_dataclass
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
@ -38,8 +38,9 @@ from tests.common import (
|
|||||||
snapshot_platform,
|
snapshot_platform,
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_ZONE_ID = "AAAAAAAA-BBBB-CCCC-DDDD-123456789101"
|
|
||||||
TEST_AREA_ID = 0
|
TEST_AREA_ID = 0
|
||||||
|
TEST_VARIABLE_ZONE_ID = "203F6359-AB56-4D57-A6DC-703095BB695D"
|
||||||
|
TEST_ZONE_ID = "AAAAAAAA-BBBB-CCCC-DDDD-123456789101"
|
||||||
|
|
||||||
|
|
||||||
async def test_switch_states(
|
async def test_switch_states(
|
||||||
@ -179,6 +180,7 @@ async def test_work_area_switch_commands(
|
|||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
mower_time_zone: zoneinfo.ZoneInfo,
|
mower_time_zone: zoneinfo.ZoneInfo,
|
||||||
|
values: dict[str, MowerAttributes],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test switch commands."""
|
"""Test switch commands."""
|
||||||
entity_id = "switch.test_mower_1_my_lawn"
|
entity_id = "switch.test_mower_1_my_lawn"
|
||||||
@ -219,26 +221,46 @@ async def test_work_area_switch_commands(
|
|||||||
assert len(mocked_method.mock_calls) == 2
|
assert len(mocked_method.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
async def test_zones_deleted(
|
async def test_add_stay_out_zone(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_automower_client: AsyncMock,
|
mock_automower_client: AsyncMock,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
values: dict[str, MowerAttributes],
|
values: dict[str, MowerAttributes],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test if stay-out-zone is deleted after removed."""
|
"""Test adding a stay out zone in runtime."""
|
||||||
await setup_integration(hass, mock_config_entry)
|
await setup_integration(hass, mock_config_entry)
|
||||||
current_entries = len(
|
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||||
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)
|
current_entites = len(
|
||||||
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
|
)
|
||||||
|
values[TEST_MOWER_ID].stay_out_zones.zones.update(
|
||||||
|
{
|
||||||
|
TEST_VARIABLE_ZONE_ID: Zone(
|
||||||
|
name="future_zone",
|
||||||
|
enabled=True,
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
del values[TEST_MOWER_ID].stay_out_zones.zones[TEST_ZONE_ID]
|
|
||||||
mock_automower_client.get_status.return_value = values
|
mock_automower_client.get_status.return_value = values
|
||||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(
|
current_entites_after_addition = len(
|
||||||
er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
) == (current_entries - 1)
|
)
|
||||||
|
assert current_entites_after_addition == current_entites + 1
|
||||||
|
values[TEST_MOWER_ID].stay_out_zones.zones.pop(TEST_VARIABLE_ZONE_ID)
|
||||||
|
values[TEST_MOWER_ID].stay_out_zones.zones.pop(TEST_ZONE_ID)
|
||||||
|
mock_automower_client.get_status.return_value = values
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
current_entites_after_deletion = len(
|
||||||
|
er.async_entries_for_config_entry(entity_registry, entry.entry_id)
|
||||||
|
)
|
||||||
|
assert current_entites_after_deletion == current_entites - 1
|
||||||
|
|
||||||
|
|
||||||
async def test_switch_snapshot(
|
async def test_switch_snapshot(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user