Skip to content

v3.5.0

Choose a tag to compare

@github-actions github-actions released this 15 Jul 20:12
· 302 commits to main since this release
e8c3f58

Roomba+ v3.5.0 — Repairs Redesign, Full Backup & Restore

3,899 tests passing (test_rest980_migrate.py excluded — pre-existing, unrelated sandbox limitation, documented in that file) · Config Entry version 25 (no migration needed) · HACS: update as usual

This is the biggest non-breaking release this integration has had. The short version: of the 29 Repair Issues this integration could raise, 20 are gone or changed — most because a persistent, must-dismiss Repair was the wrong mechanism for what they were telling you, not because anything was broken. Nothing you rely on today (sensors, entities, automations on existing events) changes. If you never once opened Settings → Repairs for this integration, the only thing you'll notice is: fewer things showing up there from now on.

This release exists because of field feedback — specifically dixi83's report that Repairs fired far too often for things that weren't actually problems, and a separate, genuinely broken "Map vacuum segments to areas" prompt that kept coming back after every reboot. Thank you for pushing on both.

✨ New

Full backup & restore

Two new actions: roomba_plus.create_backup and roomba_plus.restore_backup.

The concrete problem this solves: migrating Home Assistant to new hardware (new SD card, new server, fresh install) without losing everything your robot has learned — coverage history, room identities, L-series battery learning, maintenance timestamps. create_backup writes a single ZIP under <config>/roomba_plus_backups/ containing every store this integration persists for the robot. restore_backup writes it all back and reloads every store live — no Home Assistant restart required.

service: roomba_plus.create_backup
target:
  entity_id: vacuum.living_room
service: roomba_plus.restore_backup
target:
  entity_id: vacuum.living_room
data:
  path: /config/roomba_plus_backups/roomba_plus_backup_....zip

A self-calibrating relocalisation-rate signal, with no fixed threshold

The existing (diagnostic, disabled-by-default) relocalisation_rate sensor's alert attribute — previously a fixed 3× multiplier against the robot's own baseline — is now percentile_rank: where the robot's current relocalisation rate sits within its own historical distribution, 0–100, computed fresh every time. No magic number anywhere. reLc turned out to be zero-inflated on most robots we have field data for, which made a fixed multiplier either never fire or hair-trigger on the very first nonzero reading, depending on how close to zero the personal baseline happened to sit. Percentile rank sidesteps that entirely — automate on it with whatever cutoff matters to you.

🛠 Fixed

The segmentation "Map vacuum segments to areas" prompt firing repeatedly

If you've seen HA's own native remap prompt reappear every few hours regardless of confirming it, this is the fix. The check comparing your robot's current room segments against what you last confirmed used to act on a single mismatched refresh — meaning any one-off inconsistency in iRobot's own region/pmap ID assignment (not an actual map retrain) would re-trigger the prompt immediately, every time. It now requires the mismatch to persist across three consecutive refreshes before triggering — the same window-plus-hysteresis pattern this integration already uses elsewhere (DRIFT-AUTO, MAP-RETRAIN-WF). A genuine retrain still triggers reliably; a transient blip now clears itself instead of nagging you.

Favorites with more than one command step now raise a visible warning

Favorites are sent via their commanddefs payload; only commanddefs[0] has ever been acted on. Real sample data from the official app (six example Favorites, including multi-region ones) now confirms multi-region cleaning is expressed through a single command step's nested region list, not through multiple steps — ordinary Favorites were never affected by this. The remaining edge case (a Favorite with genuinely more than one command step) now raises a Repair Issue instead of only a debug log line.

🔄 Repairs redesign

The full rationale, for anyone who wants it: a Repair Issue is meant for something a user must act on and can dismiss once fixed. Several of this integration's 29 had drifted into being used for interesting patterns instead — informational, not broken. That's a mismatch between what the mechanism communicates ("something needs your attention") and what the signal actually was ("here's something you might find interesting"). This release re-sorts all 29 against that bar.

Removed — the sensor already told you this

Six checks were pure duplicates of an already-existing sensor. The Repair added nothing beyond "look at the sensor you already have," so it's gone, no replacement needed:

performance_degradation (→ cleaning_speed_trend), health_trend_declining (→ health_score_trend), consecutive_skips (→ consecutive_clean_skips), battery_recharge_high (→ mission_recharge_minutes), dirt_correlation (→ dirt_weather_correlation), integration_health (→ the integration health sensor's own score).

Removed — no replacement needed

Three checks either had no concrete action to offer, or their data already lives elsewhere:

  • stuck_hotspot_detected — this data is already fully exposed via the ?format=hazards REST endpoint and the coverage map.
  • room_accessibility_low — its own docstring admitted the specific numbers it described were never actually computed; redundant with the hotspot data above besides.
  • coverage_frequency_overdue — a derivable nudge, not a problem.

Removed — merged into an existing Repair as corroborating evidence

smberr_high (elevated SMBus communication errors between the main board and battery) told you almost the same thing as two other checks already do (clean contacts / consider battery replacement) — so instead of a third parallel Repair, it now lowers the bar for those two: battery_contact_suspect fires on a single implausible reading instead of requiring two in a row when smberr is also elevated, and dock_contact_health's three thresholds halve under the same condition. Both issues' text notes the corroborating SMBus count when relevant. The raw count remains visible in diagnostics either way.

Removed — replaced by a self-calibrating sensor

reloc_rate_elevated's fixed multiplier is gone (see "New" above) — the signal lives on as the relocalisation_rate sensor's percentile_rank attribute instead of a Repair.

Demoted to events

Eight signals were genuine, real patterns — just not "broken" in the Repair sense. They now fire as Home Assistant events instead (Logbook entries + available as automation triggers), rather than sitting in Settings → Repairs until dismissed:

error_recurrence, cancellation_recurrence, stuck_pattern, mission_anomaly, mixed_schedule, schedule_suboptimal, map_drift_detected, map_retrain_in_progress (the "still updating, taking longer than usual" stage specifically — the escalated "genuinely stuck" stage remains a Repair).

Each fires once per sustained occurrence, not on every refresh while the underlying condition holds, and each has a matching Logbook description so the history reads the same as the old Repair text did.

Split by cause

cloud_stale used to fire regardless of why your cloud data had gone stale. It's now two different things depending on the actual cause:

  • Login/credentials failed → Home Assistant's own native reauth flow, which this integration's cloud coordinator already triggers correctly (ConfigEntryAuthFailed) — no custom Repair needed or wanted here; HA's own guided reauth is the better UX.
  • Cloud genuinely unreachable (network, iRobot's servers down, timeout) → a cloud_stale event instead of a persistent Repair. Usually transient, and not something you can act on beyond waiting.

What's left

Nine checks remain full, persistent Repair Issues, because each one genuinely needs your attention and can't resolve itself: accident_detected (safety, never suppressible), battery_contact_suspect, dock_contact_health, maintenance_baselines_reset, maintenance_due, layout_change_detected, observed_zones_detected, map_retrain_stuck, favorite_multi_commanddefs.

🐛 Bug hunt (found before release, not after)

Two issues turned up in a structured bug-hunt pass after the redesign above was otherwise complete — neither was caught by the (extensive) unit test suite, because both involve state unit tests don't naturally simulate.

  • Upgrading from a pre-v3.5.0 install with an active Repair from one of the 20 removed/changed types would leave it stuck in Settings → Repairs forever. Home Assistant's issue registry has no automatic expiry — an issue persists until something explicitly deletes it, and after this upgrade, nothing ever would again. Fixed with a one-time cleanup that runs at startup and removes any leftover issue matching one of the removed types, regardless of which version created it.

  • The cloud_stale auth/unreachable split (see above) initially had a subtle bug: Home Assistant's coordinator never clears its "last exception seen" once set, even long after a successful recovery. A naive check would have meant one historical auth failure, resolved via reauth days earlier, permanently and silently suppressed the cloud_stale event for any later, completely unrelated staleness. Fixed by additionally requiring the coordinator's current refresh to be the one that failed, not merely checking its history.

  • cloud_stale's auth split relied on a reauth flow that turned out not to exist. cloud_coordinator.py has raised ConfigEntryAuthFailed on a bad cloud login since before this release — which calls Home Assistant's config_entry.async_start_reauth(), which in turn expects the integration's config flow to implement async_step_reauth. It didn't. Every cloud auth failure since that mechanism was added would have quietly gone nowhere: no guided flow, just a config entry stuck in an error state with no way back short of removing and re-adding the whole integration. Added async_step_reauth/async_step_reauth_confirm to the config flow, reusing the same credential-validation pattern already used elsewhere in this file rather than a third slightly-different implementation of the same check.

🔍 Under the hood

  • New event constants and Logbook descriptions live in const.py/logbook.py alongside the existing ones — same one-constant-per-event-type convention this integration already used, not a new parallel pattern.
  • A shared _fire_once()/_disarm() helper in repairs.py handles the "fire once per sustained occurrence, not every refresh" debouncing for all eight demoted-to-event signals — mirrors the in-memory per-entry-id state pattern already used for MAP-RETRAIN-WF and INTEG-HEALTH.
  • battery_contact_suspect/dock_contact_health reusing smberr as a confidence input (rather than duplicating it as a third Repair) required moving the SMBus-elevated check into a small shared helper both now call.
  • create_backup/restore_backup read and write each store's Home Assistant Store file directly rather than going through each store class, keeping the backup mechanism decoupled from any one store's internal API.