Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use entity descriptions in Roomba #102323

Merged
merged 8 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions homeassistant/components/roomba/irobot_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,23 @@ def device_info(self):
)

@property
def _battery_level(self):
def battery_level(self):
"""Return the battery level of the vacuum cleaner."""
return self.vacuum_state.get("batPct")

@property
def _run_stats(self):
def run_stats(self):
"""Return the run stats."""
return self.vacuum_state.get("bbrun")

@property
def _mission_stats(self):
def mission_stats(self):
"""Return the mission stats."""
return self.vacuum_state.get("bbmssn")

@property
def _battery_stats(self):
def battery_stats(self):
"""Return the battery stats."""
return self.vacuum_state.get("bbchg3")

@property
Expand Down
298 changes: 122 additions & 176 deletions homeassistant/components/roomba/sensor.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,121 @@
"""Sensor for checking the battery level of Roomba."""
"""Sensor platform for Roomba."""
from collections.abc import Callable
from dataclasses import dataclass

from roombapy import Roomba

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import BLID, DOMAIN, ROOMBA_SESSION
from .irobot_base import IRobotEntity


@dataclass
class RoombaSensorEntityDescriptionMixin:
"""Mixin for describing Roomba data."""

value_fn: Callable[[IRobotEntity], StateType]


@dataclass
class RoombaSensorEntityDescription(
SensorEntityDescription, RoombaSensorEntityDescriptionMixin
):
"""Immutable class for describing Roomba data."""


SENSORS: list[RoombaSensorEntityDescription] = [
RoombaSensorEntityDescription(
key="battery",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.battery_level,
),
RoombaSensorEntityDescription(
key="battery_cycles",
translation_key="battery_cycles",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:counter",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.battery_stats.get("nLithChrg")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use parenthesis to add indentation so that the lambda is easier to read?

or self.battery_stats.get("nNimhChrg"),
),
RoombaSensorEntityDescription(
key="total_cleaning_time",
translation_key="total_cleaning_time",
icon="mdi:clock",
native_unit_of_measurement=UnitOfTime.HOURS,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.run_stats.get("hr"),
),
RoombaSensorEntityDescription(
key="average_mission_time",
translation_key="average_mission_time",
icon="mdi:clock",
native_unit_of_measurement=UnitOfTime.MINUTES,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.mission_stats.get("aMssnM"),
),
RoombaSensorEntityDescription(
key="total_missions",
translation_key="total_missions",
icon="mdi:counter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="Missions",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.mission_stats.get("nMssn"),
),
RoombaSensorEntityDescription(
key="successful_missions",
translation_key="successful_missions",
icon="mdi:counter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="Missions",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.mission_stats.get("nMssnOk"),
),
RoombaSensorEntityDescription(
key="canceled_missions",
translation_key="canceled_missions",
icon="mdi:counter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="Missions",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.mission_stats.get("nMssnC"),
),
RoombaSensorEntityDescription(
key="failed_missions",
translation_key="failed_missions",
icon="mdi:counter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="Missions",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.mission_stats.get("nMssnF"),
),
RoombaSensorEntityDescription(
key="scrubs_count",
translation_key="scrubs",
icon="mdi:counter",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="Scrubs",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda self: self.run_stats.get("nScrubs"),
entity_registry_enabled_default=False,
),
]


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
Expand All @@ -23,189 +126,32 @@ async def async_setup_entry(
roomba = domain_data[ROOMBA_SESSION]
blid = domain_data[BLID]

roomba_vac = RoombaBattery(roomba, blid)
roomba_battery_cycles = BatteryCycles(roomba, blid)
roomba_cleaning_time = CleaningTime(roomba, blid)
roomba_average_mission_time = AverageMissionTime(roomba, blid)
roomba_total_missions = MissionSensor(roomba, blid, "total", "nMssn")
roomba_success_missions = MissionSensor(roomba, blid, "successful", "nMssnOk")
roomba_canceled_missions = MissionSensor(roomba, blid, "canceled", "nMssnC")
roomba_failed_missions = MissionSensor(roomba, blid, "failed", "nMssnF")
roomba_scrubs_count = ScrubsCount(roomba, blid)

async_add_entities(
[
roomba_vac,
roomba_battery_cycles,
roomba_cleaning_time,
roomba_average_mission_time,
roomba_total_missions,
roomba_success_missions,
roomba_canceled_missions,
roomba_failed_missions,
roomba_scrubs_count,
],
(
RoombaSensor(roomba, blid, entity_description)
for entity_description in SENSORS
),
True,
joostlek marked this conversation as resolved.
Show resolved Hide resolved
)


class RoombaBattery(IRobotEntity, SensorEntity):
"""Class to hold Roomba Sensor basic info."""

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"battery_{self._blid}"

@property
def native_value(self):
"""Return the state of the sensor."""
return self._battery_level


class BatteryCycles(IRobotEntity, SensorEntity):
"""Class to hold Roomba Sensor basic info."""

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:counter"

@property
def name(self):
"""Return the name of the sensor."""
return "Battery cycles"

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"battery_cycles_{self._blid}"

@property
def state_class(self):
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return SensorStateClass.MEASUREMENT

@property
def native_value(self):
"""Return the state of the sensor."""
return self._battery_stats.get("nLithChrg") or self._battery_stats.get(
"nNimhChrg"
)


class CleaningTime(IRobotEntity, SensorEntity):
"""Class to hold Roomba Sensor basic info."""

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:clock"
_attr_native_unit_of_measurement = UnitOfTime.HOURS

@property
def name(self):
"""Return the name of the sensor."""
return f"{self._name} cleaning time total"

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"total_cleaning_time_{self._blid}"

@property
def native_value(self):
"""Return the state of the sensor."""
return self._run_stats.get("hr")

class RoombaSensor(IRobotEntity, SensorEntity):
"""Roomba sensor."""

class AverageMissionTime(IRobotEntity, SensorEntity):
"""Class to hold Roomba Sensor basic info."""
entity_description: RoombaSensorEntityDescription

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:clock"

@property
def name(self):
"""Return the name of the sensor."""
return "Average mission time"

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"average_mission_time_{self._blid}"

@property
def native_unit_of_measurement(self):
"""Return the unit_of_measurement of the device."""
return UnitOfTime.MINUTES

@property
def native_value(self):
"""Return the state of the sensor."""
return self._mission_stats.get("aMssnM")


class MissionSensor(IRobotEntity, SensorEntity):
"""Class to hold the Roomba missions info."""

def __init__(self, roomba, blid, mission_type, mission_value_string):
"""Initialise iRobot sensor with mission details."""
def __init__(
self,
roomba: Roomba,
blid: str,
entity_description: RoombaSensorEntityDescription,
) -> None:
"""Initialize Roomba sensor."""
super().__init__(roomba, blid)
self._mission_type = mission_type
self._mission_value_string = mission_value_string

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:counter"

@property
def name(self):
"""Return the name of the sensor."""
return f"Missions {self._mission_type}"

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"{self._mission_type}_missions_{self._blid}"

@property
def state_class(self):
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return SensorStateClass.MEASUREMENT

@property
def native_value(self):
"""Return the state of the sensor."""
return self._mission_stats.get(self._mission_value_string)


class ScrubsCount(IRobotEntity, SensorEntity):
"""Class to hold Roomba Sensor basic info."""

_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_icon = "mdi:counter"

@property
def name(self):
"""Return the name of the sensor."""
return "Scrubs count"

@property
def unique_id(self):
"""Return the ID of this sensor."""
return f"scrubs_count_{self._blid}"

@property
def state_class(self):
"""Return the state class of this entity, from STATE_CLASSES, if any."""
return SensorStateClass.MEASUREMENT

@property
def entity_registry_enabled_default(self):
"""Disable sensor by default."""
return False
self.entity_description = entity_description
self._attr_unique_id = f"{entity_description.key}_{blid}"

@property
def native_value(self):
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self._run_stats.get("nScrubs")
return self.entity_description.value_fn(self)
26 changes: 26 additions & 0 deletions homeassistant/components/roomba/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@
"bin_full": {
"name": "Bin full"
}
},
"sensor": {
"battery_cycles": {
"name": "Battery cycles"
},
"total_cleaning_time": {
"name": "Total cleaning time"
},
"average_mission_time": {
"name": "Average mission time"
},
"total_missions": {
"name": "Total missions"
},
"successful_missions": {
"name": "Successful missions"
},
"canceled_missions": {
"name": "Canceled missions"
},
"failed_missions": {
"name": "Failed missions"
},
"scrubs_count": {
"name": "Scrubs"
}
}
}
}