core/homeassistant/components/husqvarna_automower/sensor.py

354 lines
12 KiB
Python

"""Creates a the sensor entities for the mower."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
import logging
from aioautomower.model import MowerAttributes, MowerModes, RestrictedReasons
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfLength, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import AutomowerDataUpdateCoordinator
from .entity import AutomowerBaseEntity
_LOGGER = logging.getLogger(__name__)
ERROR_KEY_LIST = [
"no_error",
"alarm_mower_in_motion",
"alarm_mower_lifted",
"alarm_mower_stopped",
"alarm_mower_switched_off",
"alarm_mower_tilted",
"alarm_outside_geofence",
"angular_sensor_problem",
"battery_problem",
"battery_problem",
"battery_restriction_due_to_ambient_temperature",
"can_error",
"charging_current_too_high",
"charging_station_blocked",
"charging_system_problem",
"charging_system_problem",
"collision_sensor_defect",
"collision_sensor_error",
"collision_sensor_problem_front",
"collision_sensor_problem_rear",
"com_board_not_available",
"communication_circuit_board_sw_must_be_updated",
"complex_working_area",
"connection_changed",
"connection_not_changed",
"connectivity_problem",
"connectivity_problem",
"connectivity_problem",
"connectivity_problem",
"connectivity_problem",
"connectivity_problem",
"connectivity_settings_restored",
"cutting_drive_motor_1_defect",
"cutting_drive_motor_2_defect",
"cutting_drive_motor_3_defect",
"cutting_height_blocked",
"cutting_height_problem",
"cutting_height_problem_curr",
"cutting_height_problem_dir",
"cutting_height_problem_drive",
"cutting_motor_problem",
"cutting_stopped_slope_too_steep",
"cutting_system_blocked",
"cutting_system_blocked",
"cutting_system_imbalance_warning",
"cutting_system_major_imbalance",
"destination_not_reachable",
"difficult_finding_home",
"docking_sensor_defect",
"electronic_problem",
"empty_battery",
"folding_cutting_deck_sensor_defect",
"folding_sensor_activated",
"geofence_problem",
"geofence_problem",
"gps_navigation_problem",
"guide_1_not_found",
"guide_2_not_found",
"guide_3_not_found",
"guide_calibration_accomplished",
"guide_calibration_failed",
"high_charging_power_loss",
"high_internal_power_loss",
"high_internal_temperature",
"internal_voltage_error",
"invalid_battery_combination_invalid_combination_of_different_battery_types",
"invalid_sub_device_combination",
"invalid_system_configuration",
"left_brush_motor_overloaded",
"lift_sensor_defect",
"lifted",
"limited_cutting_height_range",
"limited_cutting_height_range",
"loop_sensor_defect",
"loop_sensor_problem_front",
"loop_sensor_problem_left",
"loop_sensor_problem_rear",
"loop_sensor_problem_right",
"low_battery",
"memory_circuit_problem",
"mower_lifted",
"mower_tilted",
"no_accurate_position_from_satellites",
"no_confirmed_position",
"no_drive",
"no_loop_signal",
"no_power_in_charging_station",
"no_response_from_charger",
"outside_working_area",
"poor_signal_quality",
"reference_station_communication_problem",
"right_brush_motor_overloaded",
"safety_function_faulty",
"settings_restored",
"sim_card_locked",
"sim_card_locked",
"sim_card_locked",
"sim_card_locked",
"sim_card_not_found",
"sim_card_requires_pin",
"slipped_mower_has_slipped_situation_not_solved_with_moving_pattern",
"slope_too_steep",
"sms_could_not_be_sent",
"stop_button_problem",
"stuck_in_charging_station",
"switch_cord_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"temporary_battery_problem",
"tilt_sensor_problem",
"too_high_discharge_current",
"too_high_internal_current",
"trapped",
"ultrasonic_problem",
"ultrasonic_sensor_1_defect",
"ultrasonic_sensor_2_defect",
"ultrasonic_sensor_3_defect",
"ultrasonic_sensor_4_defect",
"unexpected_cutting_height_adj",
"unexpected_error",
"upside_down",
"weak_gps_signal",
"wheel_drive_problem_left",
"wheel_drive_problem_rear_left",
"wheel_drive_problem_rear_right",
"wheel_drive_problem_right",
"wheel_motor_blocked_left",
"wheel_motor_blocked_rear_left",
"wheel_motor_blocked_rear_right",
"wheel_motor_blocked_right",
"wheel_motor_overloaded_left",
"wheel_motor_overloaded_rear_left",
"wheel_motor_overloaded_rear_right",
"wheel_motor_overloaded_right",
"work_area_not_valid",
"wrong_loop_signal",
"wrong_pin_code",
"zone_generator_problem",
]
RESTRICTED_REASONS: list = [
RestrictedReasons.ALL_WORK_AREAS_COMPLETED.lower(),
RestrictedReasons.DAILY_LIMIT.lower(),
RestrictedReasons.EXTERNAL.lower(),
RestrictedReasons.FOTA.lower(),
RestrictedReasons.FROST.lower(),
RestrictedReasons.NONE.lower(),
RestrictedReasons.NOT_APPLICABLE.lower(),
RestrictedReasons.PARK_OVERRIDE.lower(),
RestrictedReasons.SENSOR.lower(),
RestrictedReasons.WEEK_SCHEDULE.lower(),
]
@dataclass(frozen=True, kw_only=True)
class AutomowerSensorEntityDescription(SensorEntityDescription):
"""Describes Automower sensor entity."""
exists_fn: Callable[[MowerAttributes], bool] = lambda _: True
value_fn: Callable[[MowerAttributes], StateType | datetime]
SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = (
AutomowerSensorEntityDescription(
key="battery_percent",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: data.battery.battery_percent,
),
AutomowerSensorEntityDescription(
key="mode",
translation_key="mode",
device_class=SensorDeviceClass.ENUM,
options=[option.lower() for option in list(MowerModes)],
value_fn=(
lambda data: data.mower.mode.lower()
if data.mower.mode != MowerModes.UNKNOWN
else None
),
),
AutomowerSensorEntityDescription(
key="cutting_blade_usage_time",
translation_key="cutting_blade_usage_time",
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.cutting_blade_usage_time is not None,
value_fn=lambda data: data.statistics.cutting_blade_usage_time,
),
AutomowerSensorEntityDescription(
key="total_charging_time",
translation_key="total_charging_time",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_charging_time is not None,
value_fn=lambda data: data.statistics.total_charging_time,
),
AutomowerSensorEntityDescription(
key="total_cutting_time",
translation_key="total_cutting_time",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_cutting_time is not None,
value_fn=lambda data: data.statistics.total_cutting_time,
),
AutomowerSensorEntityDescription(
key="total_running_time",
translation_key="total_running_time",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_running_time is not None,
value_fn=lambda data: data.statistics.total_running_time,
),
AutomowerSensorEntityDescription(
key="total_searching_time",
translation_key="total_searching_time",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
suggested_unit_of_measurement=UnitOfTime.HOURS,
exists_fn=lambda data: data.statistics.total_searching_time is not None,
value_fn=lambda data: data.statistics.total_searching_time,
),
AutomowerSensorEntityDescription(
key="number_of_charging_cycles",
translation_key="number_of_charging_cycles",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
exists_fn=lambda data: data.statistics.number_of_charging_cycles is not None,
value_fn=lambda data: data.statistics.number_of_charging_cycles,
),
AutomowerSensorEntityDescription(
key="number_of_collisions",
translation_key="number_of_collisions",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
exists_fn=lambda data: data.statistics.number_of_collisions is not None,
value_fn=lambda data: data.statistics.number_of_collisions,
),
AutomowerSensorEntityDescription(
key="total_drive_distance",
translation_key="total_drive_distance",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.METERS,
suggested_unit_of_measurement=UnitOfLength.KILOMETERS,
exists_fn=lambda data: data.statistics.total_drive_distance is not None,
value_fn=lambda data: data.statistics.total_drive_distance,
),
AutomowerSensorEntityDescription(
key="next_start_timestamp",
translation_key="next_start_timestamp",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: data.planner.next_start_datetime,
),
AutomowerSensorEntityDescription(
key="error",
translation_key="error",
device_class=SensorDeviceClass.ENUM,
value_fn=lambda data: (
"no_error" if data.mower.error_key is None else data.mower.error_key
),
options=ERROR_KEY_LIST,
),
AutomowerSensorEntityDescription(
key="restricted_reason",
translation_key="restricted_reason",
device_class=SensorDeviceClass.ENUM,
options=RESTRICTED_REASONS,
value_fn=lambda data: data.planner.restricted_reason.lower(),
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up sensor platform."""
coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
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])
)
class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
"""Defining the Automower Sensors with AutomowerSensorEntityDescription."""
entity_description: AutomowerSensorEntityDescription
def __init__(
self,
mower_id: str,
coordinator: AutomowerDataUpdateCoordinator,
description: AutomowerSensorEntityDescription,
) -> None:
"""Set up AutomowerSensors."""
super().__init__(mower_id, coordinator)
self.entity_description = description
self._attr_unique_id = f"{mower_id}_{description.key}"
@property
def native_value(self) -> StateType | datetime:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.mower_attributes)