Add work area sensors to Husqvarna Automower (#126931)
* Add work area sensors to Husqvarna Automower * add exists function * fix tests * add icons * docstring * Update homeassistant/components/husqvarna_automower/sensor.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
e705ca83b2
commit
3cda93d001
8 changed files with 318 additions and 16 deletions
|
@ -155,8 +155,8 @@ class AutomowerControlEntity(AutomowerAvailableEntity):
|
|||
return super().available and _check_error_free(self.mower_attributes)
|
||||
|
||||
|
||||
class WorkAreaControlEntity(AutomowerControlEntity):
|
||||
"""Base entity work work areas with control function."""
|
||||
class WorkAreaAvailableEntity(AutomowerAvailableEntity):
|
||||
"""Base entity for work work areas."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -184,3 +184,7 @@ class WorkAreaControlEntity(AutomowerControlEntity):
|
|||
def available(self) -> bool:
|
||||
"""Return True if the work area is available and the mower has no errors."""
|
||||
return super().available and self.work_area_id in self.work_areas
|
||||
|
||||
|
||||
class WorkAreaControlEntity(WorkAreaAvailableEntity, AutomowerControlEntity):
|
||||
"""Base entity work work areas with control function."""
|
||||
|
|
|
@ -27,6 +27,12 @@
|
|||
"error": {
|
||||
"default": "mdi:alert-circle-outline"
|
||||
},
|
||||
"my_lawn_last_time_completed": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"my_lawn_progress": {
|
||||
"default": "mdi:collage"
|
||||
},
|
||||
"number_of_charging_cycles": {
|
||||
"default": "mdi:battery-sync-outline"
|
||||
},
|
||||
|
@ -35,6 +41,12 @@
|
|||
},
|
||||
"restricted_reason": {
|
||||
"default": "mdi:tooltip-question"
|
||||
},
|
||||
"work_area_last_time_completed": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"work_area_progress": {
|
||||
"default": "mdi:collage"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ from aioautomower.model import (
|
|||
MowerModes,
|
||||
MowerStates,
|
||||
RestrictedReasons,
|
||||
WorkArea,
|
||||
)
|
||||
from aioautomower.utils import naive_to_aware
|
||||
|
||||
|
@ -29,7 +30,11 @@ from homeassistant.util import dt as dt_util
|
|||
|
||||
from . import AutomowerConfigEntry
|
||||
from .coordinator import AutomowerDataUpdateCoordinator
|
||||
from .entity import AutomowerBaseEntity
|
||||
from .entity import (
|
||||
AutomowerBaseEntity,
|
||||
WorkAreaAvailableEntity,
|
||||
_work_area_translation_key,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -261,7 +266,7 @@ class AutomowerSensorEntityDescription(SensorEntityDescription):
|
|||
value_fn: Callable[[MowerAttributes], StateType | datetime]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
|
||||
MOWER_SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
|
||||
AutomowerSensorEntityDescription(
|
||||
key="battery_percent",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
|
@ -396,6 +401,37 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
|
|||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class WorkAreaSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes the work area sensor entities."""
|
||||
|
||||
exists_fn: Callable[[WorkArea], bool] = lambda _: True
|
||||
value_fn: Callable[[WorkArea], StateType | datetime]
|
||||
translation_key_fn: Callable[[int, str], str]
|
||||
|
||||
|
||||
WORK_AREA_SENSOR_TYPES: tuple[WorkAreaSensorEntityDescription, ...] = (
|
||||
WorkAreaSensorEntityDescription(
|
||||
key="progress",
|
||||
translation_key_fn=_work_area_translation_key,
|
||||
exists_fn=lambda data: data.progress is not None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda data: data.progress,
|
||||
),
|
||||
WorkAreaSensorEntityDescription(
|
||||
key="last_time_completed",
|
||||
translation_key_fn=_work_area_translation_key,
|
||||
exists_fn=lambda data: data.last_time_completed_naive is not None,
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: naive_to_aware(
|
||||
data.last_time_completed_naive,
|
||||
ZoneInfo(str(dt_util.DEFAULT_TIME_ZONE)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AutomowerConfigEntry,
|
||||
|
@ -403,12 +439,25 @@ async def async_setup_entry(
|
|||
) -> None:
|
||||
"""Set up sensor platform."""
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
AutomowerSensorEntity(mower_id, coordinator, description)
|
||||
for mower_id in coordinator.data
|
||||
for description in SENSOR_TYPES
|
||||
if description.exists_fn(coordinator.data[mower_id])
|
||||
)
|
||||
entities: list[SensorEntity] = []
|
||||
for mower_id in coordinator.data:
|
||||
if coordinator.data[mower_id].capabilities.work_areas:
|
||||
_work_areas = coordinator.data[mower_id].work_areas
|
||||
if _work_areas is not None:
|
||||
entities.extend(
|
||||
WorkAreaSensorEntity(
|
||||
mower_id, coordinator, description, work_area_id
|
||||
)
|
||||
for description in WORK_AREA_SENSOR_TYPES
|
||||
for work_area_id in _work_areas
|
||||
if description.exists_fn(_work_areas[work_area_id])
|
||||
)
|
||||
entities.extend(
|
||||
AutomowerSensorEntity(mower_id, coordinator, description)
|
||||
for description in MOWER_SENSOR_TYPES
|
||||
if description.exists_fn(coordinator.data[mower_id])
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
|
||||
|
@ -442,3 +491,36 @@ class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
|
|||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
"""Return the state attributes."""
|
||||
return self.entity_description.extra_state_attributes_fn(self.mower_attributes)
|
||||
|
||||
|
||||
class WorkAreaSensorEntity(WorkAreaAvailableEntity, SensorEntity):
|
||||
"""Defining the Work area sensors with WorkAreaSensorEntityDescription."""
|
||||
|
||||
entity_description: WorkAreaSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mower_id: str,
|
||||
coordinator: AutomowerDataUpdateCoordinator,
|
||||
description: WorkAreaSensorEntityDescription,
|
||||
work_area_id: int,
|
||||
) -> None:
|
||||
"""Set up AutomowerSensors."""
|
||||
super().__init__(mower_id, coordinator, work_area_id)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{mower_id}_{work_area_id}_{description.key}"
|
||||
self._attr_translation_placeholders = {
|
||||
"work_area": self.work_area_attributes.name
|
||||
}
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.work_area_attributes)
|
||||
|
||||
@property
|
||||
def translation_key(self) -> str:
|
||||
"""Return the translation key of the work area."""
|
||||
return self.entity_description.translation_key_fn(
|
||||
self.work_area_id, self.entity_description.key
|
||||
)
|
||||
|
|
|
@ -204,6 +204,12 @@
|
|||
"zone_generator_problem": "Zone generator problem"
|
||||
}
|
||||
},
|
||||
"my_lawn_last_time_completed": {
|
||||
"name": "My lawn last time completed"
|
||||
},
|
||||
"my_lawn_progress": {
|
||||
"name": "My lawn progress"
|
||||
},
|
||||
"number_of_charging_cycles": {
|
||||
"name": "Number of charging cycles"
|
||||
},
|
||||
|
@ -266,6 +272,12 @@
|
|||
"name": "Work area ID assignment"
|
||||
}
|
||||
}
|
||||
},
|
||||
"work_area_last_time_completed": {
|
||||
"name": "{work_area} last time completed"
|
||||
},
|
||||
"work_area_progress": {
|
||||
"name": "{work_area} progress"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
|
|
@ -105,9 +105,7 @@
|
|||
"workAreaId": 654321,
|
||||
"name": "Back lawn",
|
||||
"cuttingHeight": 25,
|
||||
"enabled": true,
|
||||
"progress": 30,
|
||||
"lastTimeCompleted": 1722449269
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"workAreaId": 0,
|
||||
|
|
|
@ -152,9 +152,9 @@
|
|||
'654321': dict({
|
||||
'cutting_height': 25,
|
||||
'enabled': True,
|
||||
'last_time_completed_naive': '2024-07-31T18:07:49',
|
||||
'last_time_completed_naive': None,
|
||||
'name': 'Back lawn',
|
||||
'progress': 30,
|
||||
'progress': None,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -448,6 +448,103 @@
|
|||
'state': 'no_error',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_front_lawn_last_time_completed-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_mower_1_front_lawn_last_time_completed',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Front lawn last time completed',
|
||||
'platform': 'husqvarna_automower',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'work_area_last_time_completed',
|
||||
'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_123456_last_time_completed',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_front_lawn_last_time_completed-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Test Mower 1 Front lawn last time completed',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_mower_1_front_lawn_last_time_completed',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2024-08-12T05:54:29+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_front_lawn_progress-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_mower_1_front_lawn_progress',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Front lawn progress',
|
||||
'platform': 'husqvarna_automower',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'work_area_progress',
|
||||
'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_123456_progress',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_front_lawn_progress-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test Mower 1 Front lawn progress',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_mower_1_front_lawn_progress',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '40',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -510,6 +607,103 @@
|
|||
'state': 'main_area',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_my_lawn_last_time_completed-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_mower_1_my_lawn_last_time_completed',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'My lawn last time completed',
|
||||
'platform': 'husqvarna_automower',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'my_lawn_last_time_completed',
|
||||
'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_0_last_time_completed',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_my_lawn_last_time_completed-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Test Mower 1 My lawn last time completed',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_mower_1_my_lawn_last_time_completed',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2024-08-12T03:07:49+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_my_lawn_progress-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.test_mower_1_my_lawn_progress',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'My lawn progress',
|
||||
'platform': 'husqvarna_automower',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'my_lawn_progress',
|
||||
'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_0_progress',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_my_lawn_progress-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Test Mower 1 My lawn progress',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_mower_1_my_lawn_progress',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '20',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.test_mower_1_next_start-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
|
|
@ -221,7 +221,7 @@ async def test_coordinator_automatic_registry_cleanup(
|
|||
|
||||
assert (
|
||||
len(er.async_entries_for_config_entry(entity_registry, entry.entry_id))
|
||||
== current_entites - 33
|
||||
== current_entites - 37
|
||||
)
|
||||
assert (
|
||||
len(dr.async_entries_for_config_entry(device_registry, entry.entry_id))
|
||||
|
|
Loading…
Add table
Reference in a new issue