From 599ddb68a8ce1a590d6ca6a4aaa2648f8020feba Mon Sep 17 00:00:00 2001 From: brott Date: Sun, 18 Jan 2026 12:36:21 +1100 Subject: [PATCH 1/3] - Ensure images are retained per configuration - Allow changes to configuration workflow --- custom_components/cuboai/config_flow.py | 12 ++++++++++-- custom_components/cuboai/sensor.py | 8 ++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/custom_components/cuboai/config_flow.py b/custom_components/cuboai/config_flow.py index 8f175c2..9c846ba 100644 --- a/custom_components/cuboai/config_flow.py +++ b/custom_components/cuboai/config_flow.py @@ -3,6 +3,7 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.core import callback from .api import cuboai_functions as api from .const import DOMAIN @@ -245,6 +246,7 @@ async def async_step_mfa(self, user_input=None): ) @staticmethod + @callback def async_get_options_flow(config_entry): return CuboAIOptionsFlowHandler(config_entry) @@ -255,7 +257,7 @@ def __init__(self, config_entry): async def async_step_init(self, user_input=None): if user_input is not None: - return self.async_create_entry(title="", data={"download_images": user_input.get("download_images", True)}) + return self.async_create_entry(title="", data=user_input) return self.async_show_form( step_id="init", @@ -266,7 +268,13 @@ async def async_step_init(self, user_input=None): default=self.config_entry.options.get( "download_images", self.config_entry.data.get("download_images", True) ), - ): bool + ): bool, + vol.Optional( + "alerts_count", + default=self.config_entry.options.get( + "alerts_count", self.config_entry.data.get("alerts_count", 5) + ), + ): vol.All(vol.Coerce(int), vol.Range(min=1, max=50)), } ), ) diff --git a/custom_components/cuboai/sensor.py b/custom_components/cuboai/sensor.py index c3519b1..1955473 100644 --- a/custom_components/cuboai/sensor.py +++ b/custom_components/cuboai/sensor.py @@ -330,8 +330,8 @@ def device_info(self): # ---------- Update logic ---------- - def _cleanup_old_images(self): - """Cleanup old images, keeping only the latest 5 per device. + def _cleanup_old_images(self, limit): + """Cleanup old images, keeping only the latest N per device. This is a blocking operation and should be run via executor. """ @@ -342,7 +342,7 @@ def _cleanup_old_images(self): key=lambda f: f.stat().st_mtime, reverse=True, ) - for old_file in files[5:]: + for old_file in files[limit:]: try: old_file.unlink(missing_ok=True) except Exception: @@ -444,7 +444,7 @@ async def async_update(self): if self.download_images: try: # Run cleanup in executor since it uses blocking pathlib - await self.hass.async_add_executor_job(self._cleanup_old_images) + await self.hass.async_add_executor_job(self._cleanup_old_images, self.max_alerts) except Exception as e: log_to_file(f"[CuboLastAlertSensor] Error cleaning images: {e}") From 16bf7cd70a05e19c46daff2cb96b5fd4a216cecc Mon Sep 17 00:00:00 2001 From: brott Date: Tue, 27 Jan 2026 15:25:46 +1100 Subject: [PATCH 2/3] Add hours_back as a config value and update config flow. --- custom_components/cuboai/config_flow.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/custom_components/cuboai/config_flow.py b/custom_components/cuboai/config_flow.py index 9c846ba..d8fc735 100644 --- a/custom_components/cuboai/config_flow.py +++ b/custom_components/cuboai/config_flow.py @@ -253,6 +253,7 @@ def async_get_options_flow(config_entry): class CuboAIOptionsFlowHandler(config_entries.OptionsFlow): def __init__(self, config_entry): + super().__init__() self.config_entry = config_entry async def async_step_init(self, user_input=None): @@ -275,6 +276,12 @@ async def async_step_init(self, user_input=None): "alerts_count", self.config_entry.data.get("alerts_count", 5) ), ): vol.All(vol.Coerce(int), vol.Range(min=1, max=50)), + vol.Optional( + "hours_back", + default=self.config_entry.options.get( + "hours_back", self.config_entry.data.get("hours_back", 12) + ), + ): vol.All(vol.Coerce(int), vol.Range(min=1, max=72)), } ), ) From f4beaa4372f8f8638deefd0d15e6167967094051 Mon Sep 17 00:00:00 2001 From: brott Date: Tue, 27 Jan 2026 15:38:42 +1100 Subject: [PATCH 3/3] Updated config flow to expose on the UI --- custom_components/cuboai/config_flow.py | 76 +++++++++++++++---------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/custom_components/cuboai/config_flow.py b/custom_components/cuboai/config_flow.py index d8fc735..a8255db 100644 --- a/custom_components/cuboai/config_flow.py +++ b/custom_components/cuboai/config_flow.py @@ -120,21 +120,19 @@ async def async_step_user(self, user_input=None): for baby_name, device_id in device_map.items(): cameras.append({"device_id": device_id, "baby_name": baby_name}) - return self.async_create_entry( - title=f"CuboAI ({user_input['username']})", - data={ - "uuid": uuid, - "username": user_input["username"], - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "pool_id": POOL_ID, - "region": REGION, - "access_token": access_token, - "refresh_token": refresh_token, - "user_agent": user_agent, - "cameras": cameras, - }, - ) + self._auth_data = { + "uuid": uuid, + "username": user_input["username"], + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "pool_id": POOL_ID, + "region": REGION, + "access_token": access_token, + "refresh_token": refresh_token, + "user_agent": user_agent, + "cameras": cameras, + } + return await self.async_step_config() except Exception as e: _LOGGER.exception("CuboAI authentication failed: %s", e) @@ -207,21 +205,19 @@ async def async_step_mfa(self, user_input=None): for baby_name, device_id in device_map.items(): cameras.append({"device_id": device_id, "baby_name": baby_name}) - return self.async_create_entry( - title=f"CuboAI ({self._username_input})", - data={ - "uuid": uuid, - "username": self._username_input, - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "pool_id": POOL_ID, - "region": REGION, - "access_token": access_token, - "refresh_token": refresh_token, - "user_agent": self._user_agent, - "cameras": cameras, - }, - ) + self._auth_data = { + "uuid": uuid, + "username": self._username_input, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "pool_id": POOL_ID, + "region": REGION, + "access_token": access_token, + "refresh_token": refresh_token, + "user_agent": self._user_agent, + "cameras": cameras, + } + return await self.async_step_config() except Exception as e: _LOGGER.exception("MFA verification failed: %s", e) @@ -245,6 +241,26 @@ async def async_step_mfa(self, user_input=None): step_id="mfa", data_schema=MFA_SCHEMA, errors=errors, description_placeholders=description_placeholders ) + async def async_step_config(self, user_input=None): + """Handle configuration options step.""" + if user_input is not None: + return self.async_create_entry( + title=f"CuboAI ({self._auth_data['username']})", + data=self._auth_data, + options=user_input, + ) + + return self.async_show_form( + step_id="config", + data_schema=vol.Schema( + { + vol.Optional("download_images", default=True): bool, + vol.Optional("alerts_count", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=50)), + vol.Optional("hours_back", default=12): vol.All(vol.Coerce(int), vol.Range(min=1, max=72)), + } + ), + ) + @staticmethod @callback def async_get_options_flow(config_entry):