From 23177f2abfcfb4fce1f018746317e10db7f5ef76 Mon Sep 17 00:00:00 2001 From: ia74 <68617740+ia74@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:17:57 -0500 Subject: [PATCH] Add favorites --- README.md | 2 + custom_components/roomba_rest980/CloudApi.py | 6 +++ custom_components/roomba_rest980/__init__.py | 4 +- custom_components/roomba_rest980/button.py | 57 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 custom_components/roomba_rest980/button.py diff --git a/README.md b/README.md index 66d8456..7496b0f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Still work in progress, but the vacuum entity has been fully ported over. - [ ] Cloud MQTT connection - [x] Actions - [x] Start + - [x] Favorites + - This feature requires more testing, to make sure it's actually initiating a favorite cycle. - [ ] Clean all rooms by default - [x] Selective room cleaning - [ ] Two pass feature diff --git a/custom_components/roomba_rest980/CloudApi.py b/custom_components/roomba_rest980/CloudApi.py index f26ad84..9957de2 100644 --- a/custom_components/roomba_rest980/CloudApi.py +++ b/custom_components/roomba_rest980/CloudApi.py @@ -439,6 +439,11 @@ class iRobotCloudApi: return await self._aws_request(url, params) + async def get_favorites(self) -> dict[str, Any]: + """Get favorite cleaning routines.""" + url = f"{self.deployment['httpBaseAuth']}/v1/user/favorites" + return await self._aws_request(url) + async def get_robot_data(self, blid: str) -> dict[str, Any]: """Get comprehensive robot data including pmaps and mission history.""" if blid not in self.robots: @@ -484,6 +489,7 @@ class iRobotCloudApi: _LOGGER.error("Failed to get data for robot %s: %s", blid, e) all_data[blid] = {"error": str(e)} + all_data["favorites"] = await self.get_favorites() return all_data diff --git a/custom_components/roomba_rest980/__init__.py b/custom_components/roomba_rest980/__init__.py index 5580d47..a710425 100644 --- a/custom_components/roomba_rest980/__init__.py +++ b/custom_components/roomba_rest980/__init__.py @@ -124,7 +124,9 @@ async def _async_setup_cloud( if "blid" not in entry.data: await _async_match_blid(hass, entry, coordinator, cloud_coordinator) - await hass.config_entries.async_forward_entry_setups(entry, ["switch"]) + await hass.config_entries.async_forward_entry_setups( + entry, ["switch", "button"] + ) except Exception as e: # pylint: disable=broad-except _LOGGER.error("Failed to set up cloud coordinator: %s", e) diff --git a/custom_components/roomba_rest980/button.py b/custom_components/roomba_rest980/button.py new file mode 100644 index 0000000..0f56810 --- /dev/null +++ b/custom_components/roomba_rest980/button.py @@ -0,0 +1,57 @@ +"""Buttons needed.""" + +from homeassistant.components.button import ButtonEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +import logging +from .const import DOMAIN, regionTypeMappings + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): + """Create the switches to identify cleanable rooms.""" + cloudCoordinator = hass.data[DOMAIN][entry.entry_id + "_cloud"] + entities = [] + if cloudCoordinator and cloudCoordinator.data: + blid = hass.data[DOMAIN][entry.entry_id + "_blid"] + # Get cloud data for the specific robot + if blid in cloudCoordinator.data: + cloud_data = cloudCoordinator.data + # Create button entities from cloud data + if "favorites" in cloud_data: + entities.extend( + [FavoriteButton(entry, fav) for fav in cloud_data["favorites"]] + ) + async_add_entities(entities) + + +class FavoriteButton(ButtonEntity): + """A button entity to initiate iRobot favorite routines.""" + + def __init__(self, entry, data) -> None: + """Creates a button entity for entries.""" + self._attr_name = f"{data['name']}" + self._entry = entry + self._attr_unique_id = f"{entry.entry_id}_{data['favorite_id']}" + self._attr_entity_category = EntityCategory.CONFIG + self._attr_extra_state_attributes = data + self._data = data + self._attr_icon = "mdi:star" + self._attr_entity_registry_enabled_default = not data["hidden"] + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.unique_id)}, + "name": entry.title, + "manufacturer": "iRobot", + } + + async def async_press(self): + """Send command out to clean with the ID.""" + await self.hass.services.async_call( + DOMAIN, + "rest980_clean", + service_data={ + "base_url": self._entry.data["base_url"], + "payload": {"cmd": f"favorite_id: {self._data['favorite_id']}"}, + }, + )