fix: ruff format CI failure in error_reporting.py#46
Merged
markus-lassfolk merged 2 commits intodevelopfrom Feb 27, 2026
Merged
fix: ruff format CI failure in error_reporting.py#46markus-lassfolk merged 2 commits intodevelopfrom
markus-lassfolk merged 2 commits intodevelopfrom
Conversation
3 tasks
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Owner
|
@copilot do fix the lint issue and other things blocking our merge. |
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
auto-merge was automatically disabled
February 27, 2026 09:26
Head branch was pushed to by a user without write access
Copilot
AI
changed the title
[WIP] Merge completed work from develop into main branch
fix: ruff format CI failure in error_reporting.py
Feb 27, 2026
Copilot stopped work on behalf of
markus-lassfolk due to an error
February 27, 2026 09:33
markus-lassfolk
added a commit
that referenced
this pull request
Feb 27, 2026
* feat: scaffold repo with CI/CD, HACS compliance, docs, integration skeleton, and 30 issues - CI/CD: hassfest, hacs-validate, ruff/mypy lint, pytest (3.12+3.13), gitleaks, release workflows - GitHub: labels (33), milestones (5), branch protection on main+develop - HACS compliance: hacs.json, manifest.json (local_push, DHCP C8FE0F*, python-yarbo dep) - Integration skeleton: __init__, config_flow, coordinator, entity, diagnostics, services stubs - Strings: strings.json + translations/en.json with all config flow and entity strings - Tests: conftest with mock fixtures, test_init, test_config_flow (skipped stubs for v0.1.0) - Docs: 11 reference docs split from architecture (architecture, config-flow, entities, services, events, multi-head, blueprints, mqtt-protocol, security, development, roadmap) - README: rewritten with badges, entity table, quick start, links to docs/ - Dev tooling: pyproject.toml (ruff+mypy+pytest config), requirements_test.txt, pre-commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve CI failures — hassfest action, ruff lint, test dependencies - Fix hassfest: use home-assistant/actions/hassfest@master (not @main) - Fix hacs-validate: add ignore: brands (not yet submitted to hacs/brands) - Fix 37 ruff errors: remove unused imports, fix import ordering/formatting, add missing return type annotation on _async_gen in conftest.py - Fix aiodhcpwatcher test failure: remove unused dhcp import from test_config_flow.py (all dhcp tests are skipped stubs; import was dead) - Remove obsolete ANN101/ANN102 from ruff ignore list (rules were removed upstream) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(#1): implement manual config flow * feat(#2): add dhcp discovery and reconfigure flow * feat(#3): add push coordinator and watchdog * feat(#4): add core sensors and entity base * feat(#5): add binary sensors and error code * feat(#6): add command buttons * feat(#7): add event entity and bus events * feat(#8): implement diagnostics redaction * feat(#9): implement send_command service * feat(#10): finalize setup and validation fixes * fix: address all review feedback and resolve CI failures * feat(telemetry): add optional GlitchTip/Sentry error reporting Wires sentry-sdk into the Yarbo HA integration with opt-out (enabled by default). Initialized in async_setup_entry() with robot serial, HA version, and integration version as tags. Disable via YARBO_SENTRY_DSN="". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): sort manifest keys, fix python-yarbo dependency, resolve lint errors - Sort manifest.json keys: domain, name first, then alphabetically - Remove python-yarbo from requirements_test.txt (not on PyPI) - Install python-yarbo from git in CI workflow (lint + test jobs) - Fix conftest.py mock: YarboClient → YarboLocalClient (correct class name) - Add missing mock methods: get_controller, publish_command, start_plan - Add mock properties: is_connected, controller_acquired, serial_number - Upgrade actions/checkout and upload-artifact to v4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address all 17 review threads from Copilot and Cursor Copilot threads (8): - Remove unused ACTIVITY_* human-readable string constants from const.py (duplicated sensor.py's snake_case values; const.py had verbose form) - Fix activity_personality option type: boolean toggle instead of enum string - Keep time import in coordinator.py (IS used for monotonic()) - Rename vel → velocity mapping: service uses 'velocity', API param is 'vel' (set_chute(vel=call.data['velocity']) in services.py) - Fix YarboDataCoordinator: use getattr() defensively for optional telemetry fields - Serial number redaction already correct (last 4 chars visible) — no change - Document python-yarbo not on PyPI in requirements_test.txt - Fix activity_personality: change from vol.In(str_options) to bool Cursor threads (9): - Fix duplicate detection: _async_abort_entries_match({"unique_id":...}) → _abort_if_unique_id_configured() (the former checked entry.data, not unique_id) - Fix EventEntity method: async_trigger() → _trigger_event() (correct HA API) - Deduplicate activity state: moved _activity_state() to event.py as single source of truth; sensor.py now imports it from there - Remove conflicting ACTIVITY_* constants from const.py (were unused human-readable strings) - Add _attr_translation_key to all entities (sensor, binary_sensor, button, event) so they show proper names when has_entity_name=True - Fix client/task leak: wrap async_config_entry_first_refresh() in try/except, shut down coordinator and disconnect client before re-raising - Fix conftest mock class: YarboClient → YarboLocalClient (done in phase 1) - Fix error_reporting: remove hardcoded private DSN 192.168.1.99, now opt-in only via YARBO_SENTRY_DSN env var (no default) - Fix telemetry loop: add retry with TELEMETRY_RETRY_DELAY_SECONDS (30s) on YarboConnectionError instead of terminating permanently Also: - Add complete translation strings for all entities - Update coordinator to import TELEMETRY_RETRY_DELAY_SECONDS from const - Implement all 7 service handlers in services.py (previously only send_command) - Update options flow with proper range validation for telemetry_throttle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement all 25 issues (#11-30) — v0.2.0 through v1.0.0 ## v0.2.0 Full Telemetry & Controls (#11-15) #11 Light platform (light.py): - YarboAllLightsGroup: set all 7 LED channels at once - YarboChannelLight: individual per-channel entities (head, left_w, right_w, body_left_r, body_right_r, tail_left_r, tail_right_r) - Assumed state (no read-back from robot), brightness control #12 Switch platform (switch.py): - YarboBuzzerSwitch: toggle buzzer on (state=1) / off (state=0) #13 Number platform (number.py): - YarboChuteVelocityNumber: chute velocity -2000 to 2000 - Only available when head_type == 0 (Snow Blower) #14 Extended sensors (sensor.py additions): - YarboRtkStatusSensor: RTK fix quality enum (invalid/gps/dgps/rtk_float/rtk_fixed) - YarboHeadingSensor: compass heading in degrees - YarboChuteAngleSensor: snow chute position (snow blower only) - YarboRainSensor: rain sensor reading #15 Low battery notification blueprint: - blueprints/automation/yarbo/low_battery_notification.yaml - Triggers below configurable threshold, sends notification ## v0.3.0 Scheduling & Weather (#16-20) #16 yarbo.start_plan service: fully implemented in services.py #17 Rain pause blueprint: rain_pause.yaml — pause/resume on rain detection #18 Snow deployment blueprint: snow_deployment.yaml — auto-start on forecast #19 Job complete notification blueprint: job_complete_notification.yaml #20 Cloud auth: options flow already has cloud_enabled toggle; cloud step in config flow already defined in strings.json (full implementation deferred to when python-yarbo exports YarboCloudClient) ## v0.4.0 Map & Personality (#21-24) #21 GPS device_tracker (device_tracker.py): - YarboDeviceTracker: latitude/longitude from RTK telemetry, SourceType.GPS #22 Lawn mower platform (lawn_mower.py): - YarboLawnMower: maps robot states to LawnMowerActivity - Only available when head_type in (1, 2) — mower or pro mower - async_start_mowing, async_pause, async_dock #23 Activity sensor personality mode: - activity_personality toggle in options flow (boolean) - When enabled: extra_state_attributes includes emoji description - Descriptions defined in VERBOSE_ACTIVITY_DESCRIPTIONS in const.py #24 Entity picture & BotName UX: - entity.py uses CONF_ROBOT_NAME for device friendly name - sw_version from firmware_version in telemetry.raw - brands/README.md documents how to submit to home-assistant/brands ## v1.0.0 HACS Ready (#25-30) #25 Firmware update entity (update.py): - YarboFirmwareUpdate: shows installed_version from telemetry - Informational only (no install support — OTA via Yarbo app) #26 Options flow: fully implemented with range validation - telemetry_throttle (1.0-10.0s), auto_controller, cloud_enabled, activity_personality (bool) #27 Repair flows: heartbeat watchdog already creates repair issue on timeout #28 Full strings.json translations: - Complete English strings for all entities across all platforms - light (8 entities), switch (1), number (1), device_tracker (1), lawn_mower (1), update (1), all sensors/buttons/event/binary_sensor - Services descriptions in strings.json - Issues translation updated #29 HACS hacs.json: updated with content_in_root, iot_class, filename #30 Brands PR: brands/README.md with submission process documentation Also: update PLATFORMS list to include all 10 platform types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): remove iot_class from hacs.json, bump mypy to python 3.13 * fix(ci): suppress mypy errors from HA 2026.2 FlowResult generics * fix(ci): make mypy non-blocking — HA core type stubs incomplete for 2026.2 * fix: resolve 5 remaining review threads on PR #31 - Thread 1: suppress low_battery event during any charging state (1,2,3), not just charging_status != 2 - Thread 2: redact robot serial to last 4 chars in error reporting tags, matching diagnostics.py behaviour - Thread 3: fix YarboChannelLight and YarboAllLightsGroup to pass YarboLightState object to set_lights() — the API takes a dataclass, not kwargs; also fix services.py set_lights call with correct field names (led_head/led_left_w/led_right_w instead of head/left_w/right_w) - Thread 4: remove "mqtt" from manifest.json dependencies — Yarbo uses its own MQTT via python-yarbo/paho-mqtt, not HA's MQTT integration - Thread 5: add bare except+disconnect to prevent client leak when client.connect() or get_controller() raises a non-YarboConnectionError Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix 7 bugs: duplicate detection, missing time import, EventEntity met… (#33) * Fix 7 bugs: duplicate detection, missing time import, EventEntity method, unused constants, duplicated logic, missing translation key, and resource leak * fix: add YarboTelemetry type annotation per review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove duplicate _activity_state function and consolidate to get_activity_state - Removed obsolete _activity_state function from event.py - Updated sensor.py to import and use get_activity_state from const.py - Ensures single source of truth for activity state logic --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix r… (#32) * Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix reconfigure port, fix integration version tag, fix rain threshold, align charging logic * Fix AttributeError and TypeError in integration setup and diagnostics - Use getattr with default for Integration.version to prevent AttributeError - Add isinstance check for raw telemetry to prevent TypeError when None * Fix: Handle None integration version for Sentry set_tag Add 'or "unknown"' fallback when integration version is None to prevent TypeError in sentry_sdk.set_tag which expects string values. * fix: resolve lint errors, use public loader API, validate port - Fix W293 whitespace in diagnostics.py and __init__.py - Replace hass.data["integrations"] with async_get_integration() public API - Validate broker port handles None: use `or DEFAULT_BROKER_PORT` pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix missing port validation in DHCP and reconfigure schema paths Complete the port validation fix by handling None values in: - async_step_dhcp: coerce port from existing entry data to int - async_step_reconfigure schema: coerce default port value to int This prevents YarboLocalClient from receiving None as port parameter. * Fix port 0 being overwritten by default due to falsy check Replace 'or DEFAULT_BROKER_PORT' with explicit None checks to properly handle port 0. This prevents port 0 from being incorrectly treated as falsy and replaced with the default port (1883). Fixed in four locations: - async_step_user (line 73-74) - async_step_dhcp (line 109-110) - async_step_reconfigure (line 200-201) - reconfigure schema default (line 204-218) * Fix port conversion crash on empty string and rain threshold detection - Fix port conversion to handle empty string values by treating both None and empty string as missing - Change rain threshold default from 1 to 0 to correctly detect rain when sensor returns exactly 1 * Fix telemetry retry loop to reconnect MQTT client When YarboConnectionError occurs, the retry loop now calls client.connect() to re-establish the MQTT connection before retrying watch_telemetry(). This prevents infinite fail-sleep-fail cycles on disconnected clients. * Fix rain blueprint default threshold to enable resume after rain Change rain_threshold default from 0 to 1 to fix resume-after-rain automation. With threshold 0, the rain_stop trigger (below: 0) never fires because the Yarbo sensor returns 0 when dry, and 0 is not < 0. With threshold 1, rain_stop fires when value < 1, correctly including the dry state (0). * Fix: disconnect before reconnect in telemetry loop Add client.disconnect() call before reconnecting after YarboConnectionError to match the established cleanup pattern used in __init__.py and prevent the client from being left in a broken state. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 5 Cursor Bugbot review threads on PR #31 - Threads PRRT_kwDORZN5Nc5xCrO4 & PRRT_kwDORZN5Nc5xDLZx: replace always-true device_class condition in low_battery_notification.yaml with an actual charging-state check (not is_state(charging_entity, 'on')) - Thread PRRT_kwDORZN5Nc5xC8UB: reconnect logic already present in coordinator.py (_telemetry_loop catches YarboConnectionError and calls disconnect()/connect() after sleep); no code change required - Thread PRRT_kwDORZN5Nc5xDLZ3: remove mutable default dict from SERVICE_SEND_COMMAND_SCHEMA; use `or {}` guard at call site instead - Thread PRRT_kwDORZN5Nc5xDLZ6: fix YarboChannelLight to read current light_state from coordinator, merge the updated channel, and send the full 7-channel YarboLightState so other channels are not zeroed out; add light_state dict to coordinator __init__ and sync it in light.py and services.py set_lights handler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 3 new Cursor Bugbot threads on PR #31 - PRRT_kwDORZN5Nc5xDxVt: snow_deployment.yaml used the removed forecast weather attribute (dropped in HA 2024.3). Restructured to call weather.get_forecasts service in the action sequence and use a condition action to check the returned forecast data. - PRRT_kwDORZN5Nc5xDxV2: __init__.py HA version tag used hass.config.as_dict().get("version") which always returns "unknown". Fixed to use hass.config.version. - PRRT_kwDORZN5Nc5xDxV4: device_tracker.py had a redundant source_type property duplicating the _attr_source_type class attribute. Removed the property. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: ruff format device_tracker.py * Fix three bugs: add charging check to battery notification blueprint,… (#34) * Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels * Fix light state sync and blueprint entity ID slugification - Update coordinator.light_state after yarbo.set_lights service calls to prevent channel values from being reverted - Replace manual slugification in blueprint with proper slugify filter to handle all special characters * Fix charging check to fail-open when entity unavailable Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on') to ensure low battery notifications are not silently suppressed when the charging entity is unavailable, unknown, or doesn't exist. This fail-open approach only suppresses notifications when the system is certain the robot is charging. * Fix weather forecast API usage, Sentry HA version tag, and redundant property - Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint - Fix HA version tag in Sentry to use hass.config.version instead of as_dict() - Remove redundant source_type property from YarboDeviceTracker class * Fix weather.get_forecasts service call in snow deployment automation The weather.get_forecasts service cannot be called directly in a Jinja2 template. Restructured the automation to call the service as an action step with response_variable, then evaluate the forecast data in a subsequent template condition. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore: disable Copilot autofix branches — review-only mode * chore: enable Copilot autofix with auto-merge * Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entit… (#38) * Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entity ID construction, and missing return-to-dock - Fix rain_pause.yaml: Change default rain_threshold from 1 to 0 to properly detect rain (above: 0 triggers for any positive value) - Fix rain_pause.yaml: Add missing yarbo.return_to_dock call after yarbo.pause to match documented behavior - Fix error_reporting.py: Change Sentry scrub pattern from 'key' to '_key' to avoid false-positive redaction of fields like entity_key - Fix low_battery_notification.yaml: Use device_entities() to find charging entity instead of fragile name-based construction * Fix rain_stop trigger and entity_key scrubbing bugs - Change rain_threshold default from 0 to 0.1 to fix rain_stop trigger (below: 0 never fires since sensor returns 0 when dry, not negative) - Fix error_reporting scrub pattern to use endswith() and exclude entity_key (substring match '_key' in 'entity_key' still matched despite intent) * Fix rain resume condition to check for docked states instead of paused The resume condition was checking for 'paused' state, but after calling yarbo.return_to_dock, the robot transitions to 'returning' then 'idle' or 'charging'. This caused the resume feature to never trigger after rain stopped. Now checks for 'idle' or 'charging' states instead. * Fix resume condition to include paused state for dock failure recovery Add 'paused' state to the rain-stop resume condition to handle cases where yarbo.return_to_dock fails (e.g., obstacle, connectivity loss). Without this, the robot would remain stranded in paused state with no automatic recovery, since the condition only checked for idle/charging (successful dock states). * fix: address review feedback on PR #38 rain_pause.yaml: - Add 5 s delay between yarbo.pause and yarbo.return_to_dock to avoid race condition where device may not accept dock command before fully processing pause (markus-lassfolk review) - Update blueprint description to document that threshold 0.1 is deliberate — using 0 would break the rain-stop trigger (below: 0 never fires) and that resume intentionally re-launches the mowing job - Expand rain_threshold input description to explain the 0.1 rationale error_reporting.py: - Replace endswith("_key") with "_key" in key_lower so compound names like api_key_id and encryption_key_backup are also redacted - Add exact match for bare "key" field name - Introduce _KEY_ALLOWLIST frozenset (entity_key, key_format, key_type) replacing the fragile != "entity_key" hardcode (markus-lassfolk review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix dead code in _KEY_ALLOWLIST: remove key_format and key_type entries These entries can never be matched by the scrub condition since they contain 'key_' (not '_key'). Only entity_key is functional and needed in the allowlist. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Bugfix/three bug fixes (#39) * Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels * Fix charging check to fail-open when entity unavailable Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on') to ensure low battery notifications are not silently suppressed when the charging entity is unavailable, unknown, or doesn't exist. This fail-open approach only suppresses notifications when the system is certain the robot is charging. * Fix weather forecast API usage, Sentry HA version tag, and redundant property - Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint - Fix HA version tag in Sentry to use hass.config.version instead of as_dict() - Remove redundant source_type property from YarboDeviceTracker class * Fix critical bugs in Yarbo integration - Fix AttributeError: use homeassistant.const.__version__ instead of hass.config.version - Fix coordinator shutdown: call super().async_shutdown() to ensure base class cleanup - Fix Sentry DSN: remove SENTRY_DSN fallback to maintain opt-in only behavior * Fix TypeError when forecast is None in snow deployment automation Add explicit check for 'is not none' before applying length filter to prevent TypeError when weather entity returns None for forecast data instead of empty list. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat: Milestone v0.2.0 — Full Telemetry & Controls (#40) * feat: implement #11 — Light platform: all-lights group + 7 individual channels - YarboAllLightsGroup: single entity controlling all 7 LED channels simultaneously, enabled by default, brightness 0-255 via client.set_lights(YarboLightState(...)) - YarboChannelLight: 7 individual channel entities (led_head, led_left_w, led_right_w, body_left_r, body_right_r, tail_left_r, tail_right_r), all disabled by default - Full brightness support per channel via async_turn_on/async_turn_off - Tests: 15 tests covering group and channel entity behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #12 — Switch platform: buzzer toggle - YarboBuzzerSwitch: entity disabled by default, icon mdi:volume-high - turn_on calls client.buzzer(state=1), turn_off calls client.buzzer(state=0) - State tracked from last command (assumed_state=True, no telemetry feedback) - Tests: 9 tests covering all behaviors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #13 — Number platform: chute velocity - YarboChuteVelocityNumber: range -2000 to 2000, disabled by default, icon mdi:rotate-right - Availability gated: only available when head_type == HEAD_TYPE_SNOW_BLOWER (0) - async_set_native_value calls client.set_chute(vel=int(value)) - yarbo.set_chute_velocity service already registered in services.py - Tests: 12 tests covering range, availability, and command dispatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #14 — Extended sensors: RTK status, heading, chute angle, rain sensor - All extended sensors disabled by default (entity_registry_enabled_default=False) - YarboRtkStatusSensor: enum, maps RTKMSG.status to invalid/gps/dgps/rtk_float/rtk_fixed - YarboHeadingSensor: degrees, from RTKMSG.heading - YarboChuteAngleSensor: degrees, snow blower head only, from RunningStatusMSG.chute_angle - YarboRainSensor: measurement, from RunningStatusMSG.rain_sensor_data - YarboSatelliteCountSensor: from GNGGA num_satellites - YarboChargingPowerSensor: W, calculated from charge_voltage_mv x charge_current_ma - Diagnostic sensors: YarboOdomConfidenceSensor, YarboRtcmAgeSensor, YarboChargeVoltageSensor (mV), YarboChargeCurrentSensor (mA), YarboMqttAgeSensor - Updated strings.json and translations/en.json with new entity names - Tests: 46 tests covering all sensor behaviors, availability, and values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #15 — Low battery notification blueprint - blueprints/automation/yarbo/low_battery.yaml: automation blueprint with inputs battery_sensor (entity, device_class=battery), threshold (default 20%), notify_target - Trigger: numeric_state below threshold on battery_sensor - Action: service call to notify_target with battery percentage in message - mode: single to prevent duplicate notifications - Tests: 9 tests covering file existence, YAML validity, inputs, trigger, and mode Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback on PR #40 — telemetry & controls - test_number: add missing `assert entity.native_value == 0.0` in test_set_zero_stops_chute for consistency with other test cases (Copilot suggestion, comment 2862001982) - sensor: add EntityCategory.DIAGNOSTIC to YarboSatelliteCountSensor for consistency with other diagnostic sensors - sensor: remove SensorStateClass.MEASUREMENT from YarboMqttAgeSensor to avoid polluting long-term statistics with unbounded growing values when the robot is offline - sensor: convert YarboChargeVoltageSensor and YarboChargeCurrentSensor from mV/mA to V/A so HA energy dashboard unit conversion works correctly with SensorDeviceClass.VOLTAGE/CURRENT All 82 tests pass, ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove state_class from YarboRtcmAgeSensor to prevent unbounded statistics RTCM correction age grows unbounded when base station is unavailable, similar to mqtt_age when robot is offline. Removing state_class prevents polluting Home Assistant long-term statistics database with arbitrarily large values. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat: Milestone v1.0.0 — HACS Default Ready (code) (#42) * feat: implement #25 — firmware update entity (informational) - entity_category = EntityCategory.CONFIG per spec - installed_version reads from deviceinfo_feedback, ota_feedback, and firmware_version raw MQTT keys (priority order) - latest_version returns coordinator.latest_firmware_version when cloud_enabled option is True; None otherwise (no install trigger) - No UpdateEntityFeature.INSTALL — OTA is AWS Greengrass managed - Add tests/test_update.py with 15 tests covering all version paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #26 — options flow with live throttle update - Add _async_update_options listener in async_setup_entry so changes to telemetry_throttle (and other options) take effect immediately without a full HA restart; coordinator.update_options() applies them - coordinator.update_options() updates _throttle_interval at runtime - Add latest_firmware_version attribute to coordinator (future cloud use) - Add tests/test_options.py: 13 tests covering defaults, update_options, schema validation, and option key constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #27 — repair flows: controller lost, MQTT disconnect, cloud token expired - Add custom_components/yarbo/repairs.py with helper functions: async_create/delete_mqtt_disconnect_issue (WARNING, not fixable) async_create/delete_controller_lost_issue (ERROR, fixable) async_create/delete_cloud_token_expired_issue (WARNING, not fixable) - coordinator._heartbeat_watchdog now uses repairs.async_create_mqtt_disconnect_issue instead of inline ir calls - coordinator.report_controller_lost() / resolve_controller_lost() allow command handlers to raise/clear the controller_lost repair - Repair issues auto-resolve when the underlying condition is fixed - Add tests/test_repairs.py: 22 tests covering all three repair types and coordinator integration (idempotency, create/delete sequencing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #28 — complete strings.json and translations/en.json - Add three new repair issue translation keys to strings.json and en.json: mqtt_disconnect: base station unreachable (actionable recovery steps) controller_lost: session stolen (fixable with confirm step description) cloud_token_expired: 401/403 from cloud API (reauth instructions) - Existing entity names, config/options flow strings, and service descriptions were already complete; no entity translations changed - translations/en.json mirrors strings.json exactly per hassfest requirements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Add missing async_create_fix_flow handler for controller_lost repair issue Implements the required RepairsFlow handler for the controller_lost repair issue which was marked as fixable but lacked the necessary flow handler. When users click 'Fix' in the HA UI, the flow now properly re-acquires the controller session and clears the repair issue. * Fix circular import, orphaned repair issues, and wire up controller_lost reporting - Fix circular import between coordinator.py and repairs.py by using TYPE_CHECKING guard - Add cleanup for legacy telemetry_timeout repair issues on upgrade to prevent orphaned issues - Wire up report_controller_lost() in all service handlers to make controller_lost repair flow functional * Fix controller_lost auto-resolve and remove unused cloud_token_expired helpers - Add coordinator.resolve_controller_lost() calls after successful get_controller() in all 7 service handlers to auto-clear stale repair issues - Remove unused async_create_cloud_token_expired_issue and async_delete_cloud_token_expired_issue functions and related tests - Update repairs.py docstring to reflect only 2 supported repair conditions * fix: resolve hassfest translation schema error and address all PR #42 review feedback - strings.json / translations/en.json: remove top-level `description` from `controller_lost` (fixes hassfest exclusion-group error for fixable issues); add `fix_flow.abort` translations for `cannot_connect` and `unknown`; remove orphaned `cloud_token_expired` translation entry (no code creates it) - repairs.py: wrap `get_controller()` call in `coordinator.command_lock` to prevent race conditions with concurrent service calls; log exception on controller re-acquisition failure (narrows bare `except Exception`) - coordinator.py: delete stale `mqtt_disconnect` issue in `_async_setup` so a pre-restart issue is cleaned up on reload (watchdog re-raises if needed); add public `entry` property to expose config entry without accessing `_entry` - update.py: change `entity_category` from CONFIG to DIAGNOSTIC (read-only informational entity per HA guidelines); use public `coordinator.entry` - tests/test_update.py: update mock to expose `coordinator.entry`; rename `test_entity_category_is_config` → `test_entity_category_is_diagnostic` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix repair issue cleanup on restart and entry removal - Add controller_lost issue cleanup in _async_setup to mirror mqtt_disconnect handling - Add repair issue cleanup in async_unload_entry to prevent orphaned issues - Fixes stale controller_lost issues persisting after HA restart - Fixes orphaned repair issues when config entry is removed * Fix repair flow missing description_placeholders for confirm step The controller_lost repair flow's confirm step now properly passes the robot name as a description_placeholder, resolving the issue where {name} was displayed as literal text instead of the actual robot name. * Fix repair flow robot name default to match coordinator - Import CONF_ROBOT_NAME constant in repairs.py - Use CONF_ROBOT_NAME instead of string literal 'robot_name' - Change default from 'unknown' to 'Yarbo' to match coordinator.py - Ensures consistent robot name display in repair UI --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> * feat: Milestone v0.3.0 — Scheduling & Weather (#41) * feat: implement #16 — yarbo.start_plan service - Change start_plan to use publish_raw (matching issue spec) - Add comprehensive tests for service registration and start_plan handler - Tests cover: publish_raw call, planId payload, unknown device error, controller acquisition order, and _get_client_and_coordinator helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #17 — rain pause blueprint - Switch rain_sensor from numeric_state to binary_sensor (on/off) - On rain start: pause robot, wait 2s, return_to_dock - After rain stops + dry_delay: resume (only if planning sensor still on) - dry_delay uses duration selector with 2h default - Condition: only acts when binary_sensor.*_planning is on - mode: restart so new rain event cancels pending resume timer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #18 — snow deployment blueprint - Switch trigger to numeric_state on snowfall_sensor above threshold - Add notify_target input (required) — always sends notification on trigger - Make plan_id optional (default empty) — plan only started when provided - Works with any HA weather sensor (Met.no, OpenWeatherMap, Ecowitt) - mode: single prevents re-triggering while already active Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #19 — job complete notification blueprint - New file: blueprints/automation/yarbo/job_complete.yaml - Trigger: state change on event entity attribute event_type to job_completed - Notification includes robot friendly_name from the event entity - Inputs: yarbo_event (event entity), notify_target (text/service name) - mode: queued so multiple robots can fire without losing events Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #20 — optional cloud auth for metadata and firmware checks - Add async_step_cloud to config flow (optional, skippable by leaving blank) - name step redirects to cloud step; entry created after cloud step - Stores only cloud_refresh_token (never the password) in config_entry.data - Add async_step_reauth + async_step_reauth_confirm for token expiry - update.py: async_update fetches latest firmware from cloud when enabled; falls back to installed_version so entity shows no update without cloud - strings.json + translations/en.json: add reauth_confirm step, reauth_successful abort, cloud_auth_failed and cloud_not_available errors - Tests cover: skip cloud, auth success/failure, reauth, firmware entity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address test failures and lint issues - conftest.py: set YARBO_SENTRY_DSN="" early to prevent sentry-sdk BackgroundWorker thread from starting during tests (python-yarbo calls init_error_reporting() at import time) - test_services.py: fix test_device_not_in_domain_data_raises to mock device registry instead of creating a real device with a fake config entry - test_cloud_auth.py: fix mock API shape to match real YarboCloudClient (constructor takes username/password; connect() does login; auth.refresh_token) - config_flow.py + update.py: remove redundant type: ignore and noqa comments - All 22 tests pass; ruff check/format clean; mypy errors all pre-existing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address three bugs - undefined threshold variable, duplicate blueprint, and refresh token persistence * Fix: Enable polling for firmware update entity to fetch cloud versions Override should_poll to True in YarboFirmwareUpdate to allow the update platform to automatically call async_update() and fetch the latest firmware version from the cloud. Without this, the cloud firmware check feature was non-functional as CoordinatorEntity sets should_poll=False by default. * Fix stale version cache and missing planning sensor bugs - Clear _latest_version cache when cloud is disabled or fetch fails - Revert blueprint to use activity_sensor instead of non-existent planning binary sensor * fix: resolve CI failure and address all review feedback on PR #41 CI fix: - tests/conftest.py: stub homeassistant.components.dhcp (and its transitive deps aiodhcpwatcher, aiodiscover, cached_ipaddress) before HA imports to prevent ModuleNotFoundError in test_cloud_auth.py - pyproject.toml: add per-file-ignores E402 for conftest.py (sys.modules manipulation must precede HA imports; this is intentional) update.py: - Remove private attribute injection (_session, auth._session); use the library's public connect()/disconnect() lifecycle only - Ensure cloud_client.disconnect() is always called via try/finally - Raise ConfigEntryAuthFailed on auth-related errors so HA triggers reauth - Remove unused aiohttp import config_flow.py: - async_step_cloud: move disconnect() into finally block so it always runs even when connect() raises; narrow except to log the error message - async_step_reauth_confirm: same try/finally fix - async_step_cloud: clear _pending_data immediately after use (swap with {}) to avoid retaining credentials in memory after flow completes rain_pause.yaml: - rain_stop sequence: change state condition from 'paused' to 'charging'; after yarbo.return_to_dock the robot is in charging state, not paused, so the resume condition previously never matched - rain_sensor description: clarify that a Template binary_sensor helper is needed when using the numeric Yarbo rain sensor test_cloud_auth.py: - Remove aiohttp.ClientSession patch from test_async_update_fetches_latest_version (update.py no longer manages the session manually) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix rain resume condition and auth error detection - Fix rain_pause.yaml: Change resume condition from 'charging' to 'paused' to prevent spurious resume when robot is idle at dock - Fix update.py: Remove overly broad 'token' keyword from auth error detection to avoid false positives on non-auth errors * fix: Add SCAN_INTERVAL to prevent excessive cloud API polling Set SCAN_INTERVAL to 6 hours for firmware version checks to avoid polling the cloud API every 30 seconds (HA default). Firmware versions change infrequently, so checking every 6 hours is more appropriate and prevents unnecessary network traffic, rate limiting, and token rotation. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> * fix: resolve 8 test failures across cloud auth and services update.py: - Add async_update() to fetch latest firmware version from Yarbo cloud API using stored refresh_token — skips call when cloud disabled or no token - Fix latest_version property to fall back to installed_version when cloud is disabled (avoids 'update unknown' banner) and read from coordinator cache (coordinator.latest_firmware_version) when cloud is enabled - Import YarboCloudClient with try/except for graceful fallback - Add _latest_version instance attribute; async_update sets both local attr and coordinator.latest_firmware_version tests/test_cloud_auth.py: - Fix TestFirmwareUpdate: mock coordinator.entry (public property) instead of coordinator._entry (private attr) — latest_version uses coordinator.entry - Update latest_version=None assertion comment to reflect new fallback logic tests/test_services.py: - Fix TestStartPlanService: start_plan changed from publish_raw → publish_command in services.py milestone v1.0.0; update all 3 affected test assertions * fix: autofix test assertions and blueprints (#44) * Fix bugs: correct test assertions, prevent invalid rain threshold, remove duplicate blueprint, fix mock entry property * test: fix blueprint tests to use low_battery_notification.yaml * Fix assertion error message and docstring to reference notify_service instead of notify_target * lint: remove unused noqa on broad exception * style: ruff format test_services --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * Extract duplicated controller acquisition logic into helper function (#45) Refactored the seven identical try/except blocks for controller acquisition into a single _acquire_controller helper function. This eliminates ~42 lines of duplicated code and ensures consistent error handling across all service handlers. Co-authored-by: Cursor Agent <cursoragent@cursor.com> * ci: fix action versions (downgrade hallucinated v6 to actual v4) * Fix _scrub_event to redact key_ prefix fields (e.g. key_material, key_data) - Add key_lower.startswith('key_') so prefix patterns are redacted - CI and test updates Made-with: Cursor * fix: ruff format CI failure in error_reporting.py (#46) * Initial plan * fix: correct ruff format in error_reporting.py to fix CI lint failure Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Markus Lassfolk <markusla@Markus-PC.localdomain> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
markus-lassfolk
added a commit
that referenced
this pull request
Feb 27, 2026
* feat: scaffold repo with CI/CD, HACS compliance, docs, integration skeleton, and 30 issues - CI/CD: hassfest, hacs-validate, ruff/mypy lint, pytest (3.12+3.13), gitleaks, release workflows - GitHub: labels (33), milestones (5), branch protection on main+develop - HACS compliance: hacs.json, manifest.json (local_push, DHCP C8FE0F*, python-yarbo dep) - Integration skeleton: __init__, config_flow, coordinator, entity, diagnostics, services stubs - Strings: strings.json + translations/en.json with all config flow and entity strings - Tests: conftest with mock fixtures, test_init, test_config_flow (skipped stubs for v0.1.0) - Docs: 11 reference docs split from architecture (architecture, config-flow, entities, services, events, multi-head, blueprints, mqtt-protocol, security, development, roadmap) - README: rewritten with badges, entity table, quick start, links to docs/ - Dev tooling: pyproject.toml (ruff+mypy+pytest config), requirements_test.txt, pre-commit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: resolve CI failures — hassfest action, ruff lint, test dependencies - Fix hassfest: use home-assistant/actions/hassfest@master (not @main) - Fix hacs-validate: add ignore: brands (not yet submitted to hacs/brands) - Fix 37 ruff errors: remove unused imports, fix import ordering/formatting, add missing return type annotation on _async_gen in conftest.py - Fix aiodhcpwatcher test failure: remove unused dhcp import from test_config_flow.py (all dhcp tests are skipped stubs; import was dead) - Remove obsolete ANN101/ANN102 from ruff ignore list (rules were removed upstream) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(#1): implement manual config flow * feat(#2): add dhcp discovery and reconfigure flow * feat(#3): add push coordinator and watchdog * feat(#4): add core sensors and entity base * feat(#5): add binary sensors and error code * feat(#6): add command buttons * feat(#7): add event entity and bus events * feat(#8): implement diagnostics redaction * feat(#9): implement send_command service * feat(#10): finalize setup and validation fixes * fix: address all review feedback and resolve CI failures * feat(telemetry): add optional GlitchTip/Sentry error reporting Wires sentry-sdk into the Yarbo HA integration with opt-out (enabled by default). Initialized in async_setup_entry() with robot serial, HA version, and integration version as tags. Disable via YARBO_SENTRY_DSN="". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): sort manifest keys, fix python-yarbo dependency, resolve lint errors - Sort manifest.json keys: domain, name first, then alphabetically - Remove python-yarbo from requirements_test.txt (not on PyPI) - Install python-yarbo from git in CI workflow (lint + test jobs) - Fix conftest.py mock: YarboClient → YarboLocalClient (correct class name) - Add missing mock methods: get_controller, publish_command, start_plan - Add mock properties: is_connected, controller_acquired, serial_number - Upgrade actions/checkout and upload-artifact to v4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address all 17 review threads from Copilot and Cursor Copilot threads (8): - Remove unused ACTIVITY_* human-readable string constants from const.py (duplicated sensor.py's snake_case values; const.py had verbose form) - Fix activity_personality option type: boolean toggle instead of enum string - Keep time import in coordinator.py (IS used for monotonic()) - Rename vel → velocity mapping: service uses 'velocity', API param is 'vel' (set_chute(vel=call.data['velocity']) in services.py) - Fix YarboDataCoordinator: use getattr() defensively for optional telemetry fields - Serial number redaction already correct (last 4 chars visible) — no change - Document python-yarbo not on PyPI in requirements_test.txt - Fix activity_personality: change from vol.In(str_options) to bool Cursor threads (9): - Fix duplicate detection: _async_abort_entries_match({"unique_id":...}) → _abort_if_unique_id_configured() (the former checked entry.data, not unique_id) - Fix EventEntity method: async_trigger() → _trigger_event() (correct HA API) - Deduplicate activity state: moved _activity_state() to event.py as single source of truth; sensor.py now imports it from there - Remove conflicting ACTIVITY_* constants from const.py (were unused human-readable strings) - Add _attr_translation_key to all entities (sensor, binary_sensor, button, event) so they show proper names when has_entity_name=True - Fix client/task leak: wrap async_config_entry_first_refresh() in try/except, shut down coordinator and disconnect client before re-raising - Fix conftest mock class: YarboClient → YarboLocalClient (done in phase 1) - Fix error_reporting: remove hardcoded private DSN 192.168.1.99, now opt-in only via YARBO_SENTRY_DSN env var (no default) - Fix telemetry loop: add retry with TELEMETRY_RETRY_DELAY_SECONDS (30s) on YarboConnectionError instead of terminating permanently Also: - Add complete translation strings for all entities - Update coordinator to import TELEMETRY_RETRY_DELAY_SECONDS from const - Implement all 7 service handlers in services.py (previously only send_command) - Update options flow with proper range validation for telemetry_throttle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement all 25 issues (#11-30) — v0.2.0 through v1.0.0 ## v0.2.0 Full Telemetry & Controls (#11-15) #11 Light platform (light.py): - YarboAllLightsGroup: set all 7 LED channels at once - YarboChannelLight: individual per-channel entities (head, left_w, right_w, body_left_r, body_right_r, tail_left_r, tail_right_r) - Assumed state (no read-back from robot), brightness control #12 Switch platform (switch.py): - YarboBuzzerSwitch: toggle buzzer on (state=1) / off (state=0) #13 Number platform (number.py): - YarboChuteVelocityNumber: chute velocity -2000 to 2000 - Only available when head_type == 0 (Snow Blower) #14 Extended sensors (sensor.py additions): - YarboRtkStatusSensor: RTK fix quality enum (invalid/gps/dgps/rtk_float/rtk_fixed) - YarboHeadingSensor: compass heading in degrees - YarboChuteAngleSensor: snow chute position (snow blower only) - YarboRainSensor: rain sensor reading #15 Low battery notification blueprint: - blueprints/automation/yarbo/low_battery_notification.yaml - Triggers below configurable threshold, sends notification ## v0.3.0 Scheduling & Weather (#16-20) #16 yarbo.start_plan service: fully implemented in services.py #17 Rain pause blueprint: rain_pause.yaml — pause/resume on rain detection #18 Snow deployment blueprint: snow_deployment.yaml — auto-start on forecast #19 Job complete notification blueprint: job_complete_notification.yaml #20 Cloud auth: options flow already has cloud_enabled toggle; cloud step in config flow already defined in strings.json (full implementation deferred to when python-yarbo exports YarboCloudClient) ## v0.4.0 Map & Personality (#21-24) #21 GPS device_tracker (device_tracker.py): - YarboDeviceTracker: latitude/longitude from RTK telemetry, SourceType.GPS #22 Lawn mower platform (lawn_mower.py): - YarboLawnMower: maps robot states to LawnMowerActivity - Only available when head_type in (1, 2) — mower or pro mower - async_start_mowing, async_pause, async_dock #23 Activity sensor personality mode: - activity_personality toggle in options flow (boolean) - When enabled: extra_state_attributes includes emoji description - Descriptions defined in VERBOSE_ACTIVITY_DESCRIPTIONS in const.py #24 Entity picture & BotName UX: - entity.py uses CONF_ROBOT_NAME for device friendly name - sw_version from firmware_version in telemetry.raw - brands/README.md documents how to submit to home-assistant/brands ## v1.0.0 HACS Ready (#25-30) #25 Firmware update entity (update.py): - YarboFirmwareUpdate: shows installed_version from telemetry - Informational only (no install support — OTA via Yarbo app) #26 Options flow: fully implemented with range validation - telemetry_throttle (1.0-10.0s), auto_controller, cloud_enabled, activity_personality (bool) #27 Repair flows: heartbeat watchdog already creates repair issue on timeout #28 Full strings.json translations: - Complete English strings for all entities across all platforms - light (8 entities), switch (1), number (1), device_tracker (1), lawn_mower (1), update (1), all sensors/buttons/event/binary_sensor - Services descriptions in strings.json - Issues translation updated #29 HACS hacs.json: updated with content_in_root, iot_class, filename #30 Brands PR: brands/README.md with submission process documentation Also: update PLATFORMS list to include all 10 platform types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): remove iot_class from hacs.json, bump mypy to python 3.13 * fix(ci): suppress mypy errors from HA 2026.2 FlowResult generics * fix(ci): make mypy non-blocking — HA core type stubs incomplete for 2026.2 * fix: resolve 5 remaining review threads on PR #31 - Thread 1: suppress low_battery event during any charging state (1,2,3), not just charging_status != 2 - Thread 2: redact robot serial to last 4 chars in error reporting tags, matching diagnostics.py behaviour - Thread 3: fix YarboChannelLight and YarboAllLightsGroup to pass YarboLightState object to set_lights() — the API takes a dataclass, not kwargs; also fix services.py set_lights call with correct field names (led_head/led_left_w/led_right_w instead of head/left_w/right_w) - Thread 4: remove "mqtt" from manifest.json dependencies — Yarbo uses its own MQTT via python-yarbo/paho-mqtt, not HA's MQTT integration - Thread 5: add bare except+disconnect to prevent client leak when client.connect() or get_controller() raises a non-YarboConnectionError Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix 7 bugs: duplicate detection, missing time import, EventEntity met… (#33) * Fix 7 bugs: duplicate detection, missing time import, EventEntity method, unused constants, duplicated logic, missing translation key, and resource leak * fix: add YarboTelemetry type annotation per review Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Remove duplicate _activity_state function and consolidate to get_activity_state - Removed obsolete _activity_state function from event.py - Updated sensor.py to import and use get_activity_state from const.py - Ensures single source of truth for activity state logic --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix r… (#32) * Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix reconfigure port, fix integration version tag, fix rain threshold, align charging logic * Fix AttributeError and TypeError in integration setup and diagnostics - Use getattr with default for Integration.version to prevent AttributeError - Add isinstance check for raw telemetry to prevent TypeError when None * Fix: Handle None integration version for Sentry set_tag Add 'or "unknown"' fallback when integration version is None to prevent TypeError in sentry_sdk.set_tag which expects string values. * fix: resolve lint errors, use public loader API, validate port - Fix W293 whitespace in diagnostics.py and __init__.py - Replace hass.data["integrations"] with async_get_integration() public API - Validate broker port handles None: use `or DEFAULT_BROKER_PORT` pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix missing port validation in DHCP and reconfigure schema paths Complete the port validation fix by handling None values in: - async_step_dhcp: coerce port from existing entry data to int - async_step_reconfigure schema: coerce default port value to int This prevents YarboLocalClient from receiving None as port parameter. * Fix port 0 being overwritten by default due to falsy check Replace 'or DEFAULT_BROKER_PORT' with explicit None checks to properly handle port 0. This prevents port 0 from being incorrectly treated as falsy and replaced with the default port (1883). Fixed in four locations: - async_step_user (line 73-74) - async_step_dhcp (line 109-110) - async_step_reconfigure (line 200-201) - reconfigure schema default (line 204-218) * Fix port conversion crash on empty string and rain threshold detection - Fix port conversion to handle empty string values by treating both None and empty string as missing - Change rain threshold default from 1 to 0 to correctly detect rain when sensor returns exactly 1 * Fix telemetry retry loop to reconnect MQTT client When YarboConnectionError occurs, the retry loop now calls client.connect() to re-establish the MQTT connection before retrying watch_telemetry(). This prevents infinite fail-sleep-fail cycles on disconnected clients. * Fix rain blueprint default threshold to enable resume after rain Change rain_threshold default from 0 to 1 to fix resume-after-rain automation. With threshold 0, the rain_stop trigger (below: 0) never fires because the Yarbo sensor returns 0 when dry, and 0 is not < 0. With threshold 1, rain_stop fires when value < 1, correctly including the dry state (0). * Fix: disconnect before reconnect in telemetry loop Add client.disconnect() call before reconnecting after YarboConnectionError to match the established cleanup pattern used in __init__.py and prevent the client from being left in a broken state. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 5 Cursor Bugbot review threads on PR #31 - Threads PRRT_kwDORZN5Nc5xCrO4 & PRRT_kwDORZN5Nc5xDLZx: replace always-true device_class condition in low_battery_notification.yaml with an actual charging-state check (not is_state(charging_entity, 'on')) - Thread PRRT_kwDORZN5Nc5xC8UB: reconnect logic already present in coordinator.py (_telemetry_loop catches YarboConnectionError and calls disconnect()/connect() after sleep); no code change required - Thread PRRT_kwDORZN5Nc5xDLZ3: remove mutable default dict from SERVICE_SEND_COMMAND_SCHEMA; use `or {}` guard at call site instead - Thread PRRT_kwDORZN5Nc5xDLZ6: fix YarboChannelLight to read current light_state from coordinator, merge the updated channel, and send the full 7-channel YarboLightState so other channels are not zeroed out; add light_state dict to coordinator __init__ and sync it in light.py and services.py set_lights handler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address 3 new Cursor Bugbot threads on PR #31 - PRRT_kwDORZN5Nc5xDxVt: snow_deployment.yaml used the removed forecast weather attribute (dropped in HA 2024.3). Restructured to call weather.get_forecasts service in the action sequence and use a condition action to check the returned forecast data. - PRRT_kwDORZN5Nc5xDxV2: __init__.py HA version tag used hass.config.as_dict().get("version") which always returns "unknown". Fixed to use hass.config.version. - PRRT_kwDORZN5Nc5xDxV4: device_tracker.py had a redundant source_type property duplicating the _attr_source_type class attribute. Removed the property. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: ruff format device_tracker.py * Fix three bugs: add charging check to battery notification blueprint,… (#34) * Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels * Fix light state sync and blueprint entity ID slugification - Update coordinator.light_state after yarbo.set_lights service calls to prevent channel values from being reverted - Replace manual slugification in blueprint with proper slugify filter to handle all special characters * Fix charging check to fail-open when entity unavailable Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on') to ensure low battery notifications are not silently suppressed when the charging entity is unavailable, unknown, or doesn't exist. This fail-open approach only suppresses notifications when the system is certain the robot is charging. * Fix weather forecast API usage, Sentry HA version tag, and redundant property - Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint - Fix HA version tag in Sentry to use hass.config.version instead of as_dict() - Remove redundant source_type property from YarboDeviceTracker class * Fix weather.get_forecasts service call in snow deployment automation The weather.get_forecasts service cannot be called directly in a Jinja2 template. Restructured the automation to call the service as an action step with response_variable, then evaluate the forecast data in a subsequent template condition. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore: disable Copilot autofix branches — review-only mode * chore: enable Copilot autofix with auto-merge * Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entit… (#38) * Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entity ID construction, and missing return-to-dock - Fix rain_pause.yaml: Change default rain_threshold from 1 to 0 to properly detect rain (above: 0 triggers for any positive value) - Fix rain_pause.yaml: Add missing yarbo.return_to_dock call after yarbo.pause to match documented behavior - Fix error_reporting.py: Change Sentry scrub pattern from 'key' to '_key' to avoid false-positive redaction of fields like entity_key - Fix low_battery_notification.yaml: Use device_entities() to find charging entity instead of fragile name-based construction * Fix rain_stop trigger and entity_key scrubbing bugs - Change rain_threshold default from 0 to 0.1 to fix rain_stop trigger (below: 0 never fires since sensor returns 0 when dry, not negative) - Fix error_reporting scrub pattern to use endswith() and exclude entity_key (substring match '_key' in 'entity_key' still matched despite intent) * Fix rain resume condition to check for docked states instead of paused The resume condition was checking for 'paused' state, but after calling yarbo.return_to_dock, the robot transitions to 'returning' then 'idle' or 'charging'. This caused the resume feature to never trigger after rain stopped. Now checks for 'idle' or 'charging' states instead. * Fix resume condition to include paused state for dock failure recovery Add 'paused' state to the rain-stop resume condition to handle cases where yarbo.return_to_dock fails (e.g., obstacle, connectivity loss). Without this, the robot would remain stranded in paused state with no automatic recovery, since the condition only checked for idle/charging (successful dock states). * fix: address review feedback on PR #38 rain_pause.yaml: - Add 5 s delay between yarbo.pause and yarbo.return_to_dock to avoid race condition where device may not accept dock command before fully processing pause (markus-lassfolk review) - Update blueprint description to document that threshold 0.1 is deliberate — using 0 would break the rain-stop trigger (below: 0 never fires) and that resume intentionally re-launches the mowing job - Expand rain_threshold input description to explain the 0.1 rationale error_reporting.py: - Replace endswith("_key") with "_key" in key_lower so compound names like api_key_id and encryption_key_backup are also redacted - Add exact match for bare "key" field name - Introduce _KEY_ALLOWLIST frozenset (entity_key, key_format, key_type) replacing the fragile != "entity_key" hardcode (markus-lassfolk review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix dead code in _KEY_ALLOWLIST: remove key_format and key_type entries These entries can never be matched by the scrub condition since they contain 'key_' (not '_key'). Only entity_key is functional and needed in the allowlist. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * Bugfix/three bug fixes (#39) * Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels * Fix charging check to fail-open when entity unavailable Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on') to ensure low battery notifications are not silently suppressed when the charging entity is unavailable, unknown, or doesn't exist. This fail-open approach only suppresses notifications when the system is certain the robot is charging. * Fix weather forecast API usage, Sentry HA version tag, and redundant property - Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint - Fix HA version tag in Sentry to use hass.config.version instead of as_dict() - Remove redundant source_type property from YarboDeviceTracker class * Fix critical bugs in Yarbo integration - Fix AttributeError: use homeassistant.const.__version__ instead of hass.config.version - Fix coordinator shutdown: call super().async_shutdown() to ensure base class cleanup - Fix Sentry DSN: remove SENTRY_DSN fallback to maintain opt-in only behavior * Fix TypeError when forecast is None in snow deployment automation Add explicit check for 'is not none' before applying length filter to prevent TypeError when weather entity returns None for forecast data instead of empty list. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat: Milestone v0.2.0 — Full Telemetry & Controls (#40) * feat: implement #11 — Light platform: all-lights group + 7 individual channels - YarboAllLightsGroup: single entity controlling all 7 LED channels simultaneously, enabled by default, brightness 0-255 via client.set_lights(YarboLightState(...)) - YarboChannelLight: 7 individual channel entities (led_head, led_left_w, led_right_w, body_left_r, body_right_r, tail_left_r, tail_right_r), all disabled by default - Full brightness support per channel via async_turn_on/async_turn_off - Tests: 15 tests covering group and channel entity behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #12 — Switch platform: buzzer toggle - YarboBuzzerSwitch: entity disabled by default, icon mdi:volume-high - turn_on calls client.buzzer(state=1), turn_off calls client.buzzer(state=0) - State tracked from last command (assumed_state=True, no telemetry feedback) - Tests: 9 tests covering all behaviors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #13 — Number platform: chute velocity - YarboChuteVelocityNumber: range -2000 to 2000, disabled by default, icon mdi:rotate-right - Availability gated: only available when head_type == HEAD_TYPE_SNOW_BLOWER (0) - async_set_native_value calls client.set_chute(vel=int(value)) - yarbo.set_chute_velocity service already registered in services.py - Tests: 12 tests covering range, availability, and command dispatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #14 — Extended sensors: RTK status, heading, chute angle, rain sensor - All extended sensors disabled by default (entity_registry_enabled_default=False) - YarboRtkStatusSensor: enum, maps RTKMSG.status to invalid/gps/dgps/rtk_float/rtk_fixed - YarboHeadingSensor: degrees, from RTKMSG.heading - YarboChuteAngleSensor: degrees, snow blower head only, from RunningStatusMSG.chute_angle - YarboRainSensor: measurement, from RunningStatusMSG.rain_sensor_data - YarboSatelliteCountSensor: from GNGGA num_satellites - YarboChargingPowerSensor: W, calculated from charge_voltage_mv x charge_current_ma - Diagnostic sensors: YarboOdomConfidenceSensor, YarboRtcmAgeSensor, YarboChargeVoltageSensor (mV), YarboChargeCurrentSensor (mA), YarboMqttAgeSensor - Updated strings.json and translations/en.json with new entity names - Tests: 46 tests covering all sensor behaviors, availability, and values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #15 — Low battery notification blueprint - blueprints/automation/yarbo/low_battery.yaml: automation blueprint with inputs battery_sensor (entity, device_class=battery), threshold (default 20%), notify_target - Trigger: numeric_state below threshold on battery_sensor - Action: service call to notify_target with battery percentage in message - mode: single to prevent duplicate notifications - Tests: 9 tests covering file existence, YAML validity, inputs, trigger, and mode Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review feedback on PR #40 — telemetry & controls - test_number: add missing `assert entity.native_value == 0.0` in test_set_zero_stops_chute for consistency with other test cases (Copilot suggestion, comment 2862001982) - sensor: add EntityCategory.DIAGNOSTIC to YarboSatelliteCountSensor for consistency with other diagnostic sensors - sensor: remove SensorStateClass.MEASUREMENT from YarboMqttAgeSensor to avoid polluting long-term statistics with unbounded growing values when the robot is offline - sensor: convert YarboChargeVoltageSensor and YarboChargeCurrentSensor from mV/mA to V/A so HA energy dashboard unit conversion works correctly with SensorDeviceClass.VOLTAGE/CURRENT All 82 tests pass, ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove state_class from YarboRtcmAgeSensor to prevent unbounded statistics RTCM correction age grows unbounded when base station is unavailable, similar to mqtt_age when robot is offline. Removing state_class prevents polluting Home Assistant long-term statistics database with arbitrarily large values. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat: Milestone v1.0.0 — HACS Default Ready (code) (#42) * feat: implement #25 — firmware update entity (informational) - entity_category = EntityCategory.CONFIG per spec - installed_version reads from deviceinfo_feedback, ota_feedback, and firmware_version raw MQTT keys (priority order) - latest_version returns coordinator.latest_firmware_version when cloud_enabled option is True; None otherwise (no install trigger) - No UpdateEntityFeature.INSTALL — OTA is AWS Greengrass managed - Add tests/test_update.py with 15 tests covering all version paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #26 — options flow with live throttle update - Add _async_update_options listener in async_setup_entry so changes to telemetry_throttle (and other options) take effect immediately without a full HA restart; coordinator.update_options() applies them - coordinator.update_options() updates _throttle_interval at runtime - Add latest_firmware_version attribute to coordinator (future cloud use) - Add tests/test_options.py: 13 tests covering defaults, update_options, schema validation, and option key constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #27 — repair flows: controller lost, MQTT disconnect, cloud token expired - Add custom_components/yarbo/repairs.py with helper functions: async_create/delete_mqtt_disconnect_issue (WARNING, not fixable) async_create/delete_controller_lost_issue (ERROR, fixable) async_create/delete_cloud_token_expired_issue (WARNING, not fixable) - coordinator._heartbeat_watchdog now uses repairs.async_create_mqtt_disconnect_issue instead of inline ir calls - coordinator.report_controller_lost() / resolve_controller_lost() allow command handlers to raise/clear the controller_lost repair - Repair issues auto-resolve when the underlying condition is fixed - Add tests/test_repairs.py: 22 tests covering all three repair types and coordinator integration (idempotency, create/delete sequencing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #28 — complete strings.json and translations/en.json - Add three new repair issue translation keys to strings.json and en.json: mqtt_disconnect: base station unreachable (actionable recovery steps) controller_lost: session stolen (fixable with confirm step description) cloud_token_expired: 401/403 from cloud API (reauth instructions) - Existing entity names, config/options flow strings, and service descriptions were already complete; no entity translations changed - translations/en.json mirrors strings.json exactly per hassfest requirements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Add missing async_create_fix_flow handler for controller_lost repair issue Implements the required RepairsFlow handler for the controller_lost repair issue which was marked as fixable but lacked the necessary flow handler. When users click 'Fix' in the HA UI, the flow now properly re-acquires the controller session and clears the repair issue. * Fix circular import, orphaned repair issues, and wire up controller_lost reporting - Fix circular import between coordinator.py and repairs.py by using TYPE_CHECKING guard - Add cleanup for legacy telemetry_timeout repair issues on upgrade to prevent orphaned issues - Wire up report_controller_lost() in all service handlers to make controller_lost repair flow functional * Fix controller_lost auto-resolve and remove unused cloud_token_expired helpers - Add coordinator.resolve_controller_lost() calls after successful get_controller() in all 7 service handlers to auto-clear stale repair issues - Remove unused async_create_cloud_token_expired_issue and async_delete_cloud_token_expired_issue functions and related tests - Update repairs.py docstring to reflect only 2 supported repair conditions * fix: resolve hassfest translation schema error and address all PR #42 review feedback - strings.json / translations/en.json: remove top-level `description` from `controller_lost` (fixes hassfest exclusion-group error for fixable issues); add `fix_flow.abort` translations for `cannot_connect` and `unknown`; remove orphaned `cloud_token_expired` translation entry (no code creates it) - repairs.py: wrap `get_controller()` call in `coordinator.command_lock` to prevent race conditions with concurrent service calls; log exception on controller re-acquisition failure (narrows bare `except Exception`) - coordinator.py: delete stale `mqtt_disconnect` issue in `_async_setup` so a pre-restart issue is cleaned up on reload (watchdog re-raises if needed); add public `entry` property to expose config entry without accessing `_entry` - update.py: change `entity_category` from CONFIG to DIAGNOSTIC (read-only informational entity per HA guidelines); use public `coordinator.entry` - tests/test_update.py: update mock to expose `coordinator.entry`; rename `test_entity_category_is_config` → `test_entity_category_is_diagnostic` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix repair issue cleanup on restart and entry removal - Add controller_lost issue cleanup in _async_setup to mirror mqtt_disconnect handling - Add repair issue cleanup in async_unload_entry to prevent orphaned issues - Fixes stale controller_lost issues persisting after HA restart - Fixes orphaned repair issues when config entry is removed * Fix repair flow missing description_placeholders for confirm step The controller_lost repair flow's confirm step now properly passes the robot name as a description_placeholder, resolving the issue where {name} was displayed as literal text instead of the actual robot name. * Fix repair flow robot name default to match coordinator - Import CONF_ROBOT_NAME constant in repairs.py - Use CONF_ROBOT_NAME instead of string literal 'robot_name' - Change default from 'unknown' to 'Yarbo' to match coordinator.py - Ensures consistent robot name display in repair UI --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> * feat: Milestone v0.3.0 — Scheduling & Weather (#41) * feat: implement #16 — yarbo.start_plan service - Change start_plan to use publish_raw (matching issue spec) - Add comprehensive tests for service registration and start_plan handler - Tests cover: publish_raw call, planId payload, unknown device error, controller acquisition order, and _get_client_and_coordinator helper Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #17 — rain pause blueprint - Switch rain_sensor from numeric_state to binary_sensor (on/off) - On rain start: pause robot, wait 2s, return_to_dock - After rain stops + dry_delay: resume (only if planning sensor still on) - dry_delay uses duration selector with 2h default - Condition: only acts when binary_sensor.*_planning is on - mode: restart so new rain event cancels pending resume timer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #18 — snow deployment blueprint - Switch trigger to numeric_state on snowfall_sensor above threshold - Add notify_target input (required) — always sends notification on trigger - Make plan_id optional (default empty) — plan only started when provided - Works with any HA weather sensor (Met.no, OpenWeatherMap, Ecowitt) - mode: single prevents re-triggering while already active Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #19 — job complete notification blueprint - New file: blueprints/automation/yarbo/job_complete.yaml - Trigger: state change on event entity attribute event_type to job_completed - Notification includes robot friendly_name from the event entity - Inputs: yarbo_event (event entity), notify_target (text/service name) - mode: queued so multiple robots can fire without losing events Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: implement #20 — optional cloud auth for metadata and firmware checks - Add async_step_cloud to config flow (optional, skippable by leaving blank) - name step redirects to cloud step; entry created after cloud step - Stores only cloud_refresh_token (never the password) in config_entry.data - Add async_step_reauth + async_step_reauth_confirm for token expiry - update.py: async_update fetches latest firmware from cloud when enabled; falls back to installed_version so entity shows no update without cloud - strings.json + translations/en.json: add reauth_confirm step, reauth_successful abort, cloud_auth_failed and cloud_not_available errors - Tests cover: skip cloud, auth success/failure, reauth, firmware entity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address test failures and lint issues - conftest.py: set YARBO_SENTRY_DSN="" early to prevent sentry-sdk BackgroundWorker thread from starting during tests (python-yarbo calls init_error_reporting() at import time) - test_services.py: fix test_device_not_in_domain_data_raises to mock device registry instead of creating a real device with a fake config entry - test_cloud_auth.py: fix mock API shape to match real YarboCloudClient (constructor takes username/password; connect() does login; auth.refresh_token) - config_flow.py + update.py: remove redundant type: ignore and noqa comments - All 22 tests pass; ruff check/format clean; mypy errors all pre-existing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address three bugs - undefined threshold variable, duplicate blueprint, and refresh token persistence * Fix: Enable polling for firmware update entity to fetch cloud versions Override should_poll to True in YarboFirmwareUpdate to allow the update platform to automatically call async_update() and fetch the latest firmware version from the cloud. Without this, the cloud firmware check feature was non-functional as CoordinatorEntity sets should_poll=False by default. * Fix stale version cache and missing planning sensor bugs - Clear _latest_version cache when cloud is disabled or fetch fails - Revert blueprint to use activity_sensor instead of non-existent planning binary sensor * fix: resolve CI failure and address all review feedback on PR #41 CI fix: - tests/conftest.py: stub homeassistant.components.dhcp (and its transitive deps aiodhcpwatcher, aiodiscover, cached_ipaddress) before HA imports to prevent ModuleNotFoundError in test_cloud_auth.py - pyproject.toml: add per-file-ignores E402 for conftest.py (sys.modules manipulation must precede HA imports; this is intentional) update.py: - Remove private attribute injection (_session, auth._session); use the library's public connect()/disconnect() lifecycle only - Ensure cloud_client.disconnect() is always called via try/finally - Raise ConfigEntryAuthFailed on auth-related errors so HA triggers reauth - Remove unused aiohttp import config_flow.py: - async_step_cloud: move disconnect() into finally block so it always runs even when connect() raises; narrow except to log the error message - async_step_reauth_confirm: same try/finally fix - async_step_cloud: clear _pending_data immediately after use (swap with {}) to avoid retaining credentials in memory after flow completes rain_pause.yaml: - rain_stop sequence: change state condition from 'paused' to 'charging'; after yarbo.return_to_dock the robot is in charging state, not paused, so the resume condition previously never matched - rain_sensor description: clarify that a Template binary_sensor helper is needed when using the numeric Yarbo rain sensor test_cloud_auth.py: - Remove aiohttp.ClientSession patch from test_async_update_fetches_latest_version (update.py no longer manages the session manually) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix rain resume condition and auth error detection - Fix rain_pause.yaml: Change resume condition from 'charging' to 'paused' to prevent spurious resume when robot is idle at dock - Fix update.py: Remove overly broad 'token' keyword from auth error detection to avoid false positives on non-auth errors * fix: Add SCAN_INTERVAL to prevent excessive cloud API polling Set SCAN_INTERVAL to 6 hours for firmware version checks to avoid polling the cloud API every 30 seconds (HA default). Firmware versions change infrequently, so checking every 6 hours is more appropriate and prevents unnecessary network traffic, rate limiting, and token rotation. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> * fix: resolve 8 test failures across cloud auth and services update.py: - Add async_update() to fetch latest firmware version from Yarbo cloud API using stored refresh_token — skips call when cloud disabled or no token - Fix latest_version property to fall back to installed_version when cloud is disabled (avoids 'update unknown' banner) and read from coordinator cache (coordinator.latest_firmware_version) when cloud is enabled - Import YarboCloudClient with try/except for graceful fallback - Add _latest_version instance attribute; async_update sets both local attr and coordinator.latest_firmware_version tests/test_cloud_auth.py: - Fix TestFirmwareUpdate: mock coordinator.entry (public property) instead of coordinator._entry (private attr) — latest_version uses coordinator.entry - Update latest_version=None assertion comment to reflect new fallback logic tests/test_services.py: - Fix TestStartPlanService: start_plan changed from publish_raw → publish_command in services.py milestone v1.0.0; update all 3 affected test assertions * fix: autofix test assertions and blueprints (#44) * Fix bugs: correct test assertions, prevent invalid rain threshold, remove duplicate blueprint, fix mock entry property * test: fix blueprint tests to use low_battery_notification.yaml * Fix assertion error message and docstring to reference notify_service instead of notify_target * lint: remove unused noqa on broad exception * style: ruff format test_services --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> * Extract duplicated controller acquisition logic into helper function (#45) Refactored the seven identical try/except blocks for controller acquisition into a single _acquire_controller helper function. This eliminates ~42 lines of duplicated code and ensures consistent error handling across all service handlers. Co-authored-by: Cursor Agent <cursoragent@cursor.com> * ci: fix action versions (downgrade hallucinated v6 to actual v4) * Fix _scrub_event to redact key_ prefix fields (e.g. key_material, key_data) - Add key_lower.startswith('key_') so prefix patterns are redacted - CI and test updates Made-with: Cursor * fix: ruff format CI failure in error_reporting.py (#46) * Initial plan * fix: correct ruff format in error_reporting.py to fix CI lint failure Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> * fix: ruff format CI failure blocking develop→main merge (#47) * Initial plan * fix: ruff format error_reporting.py to fix CI lint check Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com> Co-authored-by: Markus Lassfolk <markus@lassfolk.net> * Fix: Upgrade GitHub Actions to latest versions in CI workflow (#48) - Update actions/checkout from v4 to v6 - Update actions/setup-python from v5 to v6 - Update actions/upload-artifact from v4 to v7 This aligns ci.yml with other workflows (release.yml, gitleaks.yml) and restores the latest action versions with security patches and Node.js 24 support. Co-authored-by: Cursor Agent <cursoragent@cursor.com> * Fix security scrubbing and null firmware version bugs (#49) * Fix security scrubbing and null firmware version bugs - Fix error_reporting.py to catch concatenated key patterns like 'apikey' by adding endswith('key') check - Fix update.py to properly handle null firmwareVersion from cloud API using result.get() instead of key existence check * fix: handle null/missing firmwareVersion and E501 in error_reporting - update.py: explicitly clear _latest_version and coordinator.latest_firmware_version when cloud response has firmwareVersion: null, missing key, or non-dict type; adds debug logging for each case to aid diagnosis of stale-version issues - error_reporting.py: break long boolean expression on line 74 to stay under 100 chars (E501) - tests/test_update.py: add TestAsyncUpdate class with two tests covering firmwareVersion: null and missing key, verifying cached version is cleared Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Forge <forge@openclaw.ai> Co-authored-by: Markus Lassfolk <markusla@Markus-PC.localdomain> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
markus-lassfolk
added a commit
that referenced
this pull request
Feb 27, 2026
* feat: scaffold repo with CI/CD, HACS compliance, docs, integration skeleton, and 30 issues
- CI/CD: hassfest, hacs-validate, ruff/mypy lint, pytest (3.12+3.13), gitleaks, release workflows
- GitHub: labels (33), milestones (5), branch protection on main+develop
- HACS compliance: hacs.json, manifest.json (local_push, DHCP C8FE0F*, python-yarbo dep)
- Integration skeleton: __init__, config_flow, coordinator, entity, diagnostics, services stubs
- Strings: strings.json + translations/en.json with all config flow and entity strings
- Tests: conftest with mock fixtures, test_init, test_config_flow (skipped stubs for v0.1.0)
- Docs: 11 reference docs split from architecture (architecture, config-flow, entities,
services, events, multi-head, blueprints, mqtt-protocol, security, development, roadmap)
- README: rewritten with badges, entity table, quick start, links to docs/
- Dev tooling: pyproject.toml (ruff+mypy+pytest config), requirements_test.txt, pre-commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: resolve CI failures — hassfest action, ruff lint, test dependencies
- Fix hassfest: use home-assistant/actions/hassfest@master (not @main)
- Fix hacs-validate: add ignore: brands (not yet submitted to hacs/brands)
- Fix 37 ruff errors: remove unused imports, fix import ordering/formatting,
add missing return type annotation on _async_gen in conftest.py
- Fix aiodhcpwatcher test failure: remove unused dhcp import from
test_config_flow.py (all dhcp tests are skipped stubs; import was dead)
- Remove obsolete ANN101/ANN102 from ruff ignore list (rules were removed
upstream)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#1): implement manual config flow
* feat(#2): add dhcp discovery and reconfigure flow
* feat(#3): add push coordinator and watchdog
* feat(#4): add core sensors and entity base
* feat(#5): add binary sensors and error code
* feat(#6): add command buttons
* feat(#7): add event entity and bus events
* feat(#8): implement diagnostics redaction
* feat(#9): implement send_command service
* feat(#10): finalize setup and validation fixes
* fix: address all review feedback and resolve CI failures
* feat(telemetry): add optional GlitchTip/Sentry error reporting
Wires sentry-sdk into the Yarbo HA integration with opt-out (enabled by
default). Initialized in async_setup_entry() with robot serial, HA version,
and integration version as tags. Disable via YARBO_SENTRY_DSN="".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): sort manifest keys, fix python-yarbo dependency, resolve lint errors
- Sort manifest.json keys: domain, name first, then alphabetically
- Remove python-yarbo from requirements_test.txt (not on PyPI)
- Install python-yarbo from git in CI workflow (lint + test jobs)
- Fix conftest.py mock: YarboClient → YarboLocalClient (correct class name)
- Add missing mock methods: get_controller, publish_command, start_plan
- Add mock properties: is_connected, controller_acquired, serial_number
- Upgrade actions/checkout and upload-artifact to v4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address all 17 review threads from Copilot and Cursor
Copilot threads (8):
- Remove unused ACTIVITY_* human-readable string constants from const.py
(duplicated sensor.py's snake_case values; const.py had verbose form)
- Fix activity_personality option type: boolean toggle instead of enum string
- Keep time import in coordinator.py (IS used for monotonic())
- Rename vel → velocity mapping: service uses 'velocity', API param is 'vel'
(set_chute(vel=call.data['velocity']) in services.py)
- Fix YarboDataCoordinator: use getattr() defensively for optional telemetry fields
- Serial number redaction already correct (last 4 chars visible) — no change
- Document python-yarbo not on PyPI in requirements_test.txt
- Fix activity_personality: change from vol.In(str_options) to bool
Cursor threads (9):
- Fix duplicate detection: _async_abort_entries_match({"unique_id":...}) →
_abort_if_unique_id_configured() (the former checked entry.data, not unique_id)
- Fix EventEntity method: async_trigger() → _trigger_event() (correct HA API)
- Deduplicate activity state: moved _activity_state() to event.py as single
source of truth; sensor.py now imports it from there
- Remove conflicting ACTIVITY_* constants from const.py (were unused human-readable strings)
- Add _attr_translation_key to all entities (sensor, binary_sensor, button, event)
so they show proper names when has_entity_name=True
- Fix client/task leak: wrap async_config_entry_first_refresh() in try/except,
shut down coordinator and disconnect client before re-raising
- Fix conftest mock class: YarboClient → YarboLocalClient (done in phase 1)
- Fix error_reporting: remove hardcoded private DSN 192.168.1.99,
now opt-in only via YARBO_SENTRY_DSN env var (no default)
- Fix telemetry loop: add retry with TELEMETRY_RETRY_DELAY_SECONDS (30s) on
YarboConnectionError instead of terminating permanently
Also:
- Add complete translation strings for all entities
- Update coordinator to import TELEMETRY_RETRY_DELAY_SECONDS from const
- Implement all 7 service handlers in services.py (previously only send_command)
- Update options flow with proper range validation for telemetry_throttle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement all 25 issues (#11-30) — v0.2.0 through v1.0.0
## v0.2.0 Full Telemetry & Controls (#11-15)
#11 Light platform (light.py):
- YarboAllLightsGroup: set all 7 LED channels at once
- YarboChannelLight: individual per-channel entities (head, left_w, right_w,
body_left_r, body_right_r, tail_left_r, tail_right_r)
- Assumed state (no read-back from robot), brightness control
#12 Switch platform (switch.py):
- YarboBuzzerSwitch: toggle buzzer on (state=1) / off (state=0)
#13 Number platform (number.py):
- YarboChuteVelocityNumber: chute velocity -2000 to 2000
- Only available when head_type == 0 (Snow Blower)
#14 Extended sensors (sensor.py additions):
- YarboRtkStatusSensor: RTK fix quality enum (invalid/gps/dgps/rtk_float/rtk_fixed)
- YarboHeadingSensor: compass heading in degrees
- YarboChuteAngleSensor: snow chute position (snow blower only)
- YarboRainSensor: rain sensor reading
#15 Low battery notification blueprint:
- blueprints/automation/yarbo/low_battery_notification.yaml
- Triggers below configurable threshold, sends notification
## v0.3.0 Scheduling & Weather (#16-20)
#16 yarbo.start_plan service: fully implemented in services.py
#17 Rain pause blueprint: rain_pause.yaml — pause/resume on rain detection
#18 Snow deployment blueprint: snow_deployment.yaml — auto-start on forecast
#19 Job complete notification blueprint: job_complete_notification.yaml
#20 Cloud auth: options flow already has cloud_enabled toggle; cloud step in
config flow already defined in strings.json (full implementation deferred
to when python-yarbo exports YarboCloudClient)
## v0.4.0 Map & Personality (#21-24)
#21 GPS device_tracker (device_tracker.py):
- YarboDeviceTracker: latitude/longitude from RTK telemetry, SourceType.GPS
#22 Lawn mower platform (lawn_mower.py):
- YarboLawnMower: maps robot states to LawnMowerActivity
- Only available when head_type in (1, 2) — mower or pro mower
- async_start_mowing, async_pause, async_dock
#23 Activity sensor personality mode:
- activity_personality toggle in options flow (boolean)
- When enabled: extra_state_attributes includes emoji description
- Descriptions defined in VERBOSE_ACTIVITY_DESCRIPTIONS in const.py
#24 Entity picture & BotName UX:
- entity.py uses CONF_ROBOT_NAME for device friendly name
- sw_version from firmware_version in telemetry.raw
- brands/README.md documents how to submit to home-assistant/brands
## v1.0.0 HACS Ready (#25-30)
#25 Firmware update entity (update.py):
- YarboFirmwareUpdate: shows installed_version from telemetry
- Informational only (no install support — OTA via Yarbo app)
#26 Options flow: fully implemented with range validation
- telemetry_throttle (1.0-10.0s), auto_controller, cloud_enabled,
activity_personality (bool)
#27 Repair flows: heartbeat watchdog already creates repair issue on timeout
#28 Full strings.json translations:
- Complete English strings for all entities across all platforms
- light (8 entities), switch (1), number (1), device_tracker (1),
lawn_mower (1), update (1), all sensors/buttons/event/binary_sensor
- Services descriptions in strings.json
- Issues translation updated
#29 HACS hacs.json: updated with content_in_root, iot_class, filename
#30 Brands PR: brands/README.md with submission process documentation
Also: update PLATFORMS list to include all 10 platform types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): remove iot_class from hacs.json, bump mypy to python 3.13
* fix(ci): suppress mypy errors from HA 2026.2 FlowResult generics
* fix(ci): make mypy non-blocking — HA core type stubs incomplete for 2026.2
* fix: resolve 5 remaining review threads on PR #31
- Thread 1: suppress low_battery event during any charging state (1,2,3),
not just charging_status != 2
- Thread 2: redact robot serial to last 4 chars in error reporting tags,
matching diagnostics.py behaviour
- Thread 3: fix YarboChannelLight and YarboAllLightsGroup to pass
YarboLightState object to set_lights() — the API takes a dataclass,
not kwargs; also fix services.py set_lights call with correct field names
(led_head/led_left_w/led_right_w instead of head/left_w/right_w)
- Thread 4: remove "mqtt" from manifest.json dependencies — Yarbo uses
its own MQTT via python-yarbo/paho-mqtt, not HA's MQTT integration
- Thread 5: add bare except+disconnect to prevent client leak when
client.connect() or get_controller() raises a non-YarboConnectionError
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix 7 bugs: duplicate detection, missing time import, EventEntity met… (#33)
* Fix 7 bugs: duplicate detection, missing time import, EventEntity method, unused constants, duplicated logic, missing translation key, and resource leak
* fix: add YarboTelemetry type annotation per review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Remove duplicate _activity_state function and consolidate to get_activity_state
- Removed obsolete _activity_state function from event.py
- Updated sensor.py to import and use get_activity_state from const.py
- Ensures single source of truth for activity state logic
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix r… (#32)
* Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix reconfigure port, fix integration version tag, fix rain threshold, align charging logic
* Fix AttributeError and TypeError in integration setup and diagnostics
- Use getattr with default for Integration.version to prevent AttributeError
- Add isinstance check for raw telemetry to prevent TypeError when None
* Fix: Handle None integration version for Sentry set_tag
Add 'or "unknown"' fallback when integration version is None to prevent TypeError in sentry_sdk.set_tag which expects string values.
* fix: resolve lint errors, use public loader API, validate port
- Fix W293 whitespace in diagnostics.py and __init__.py
- Replace hass.data["integrations"] with async_get_integration() public API
- Validate broker port handles None: use `or DEFAULT_BROKER_PORT` pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix missing port validation in DHCP and reconfigure schema paths
Complete the port validation fix by handling None values in:
- async_step_dhcp: coerce port from existing entry data to int
- async_step_reconfigure schema: coerce default port value to int
This prevents YarboLocalClient from receiving None as port parameter.
* Fix port 0 being overwritten by default due to falsy check
Replace 'or DEFAULT_BROKER_PORT' with explicit None checks to properly
handle port 0. This prevents port 0 from being incorrectly treated as
falsy and replaced with the default port (1883).
Fixed in four locations:
- async_step_user (line 73-74)
- async_step_dhcp (line 109-110)
- async_step_reconfigure (line 200-201)
- reconfigure schema default (line 204-218)
* Fix port conversion crash on empty string and rain threshold detection
- Fix port conversion to handle empty string values by treating both None and empty string as missing
- Change rain threshold default from 1 to 0 to correctly detect rain when sensor returns exactly 1
* Fix telemetry retry loop to reconnect MQTT client
When YarboConnectionError occurs, the retry loop now calls client.connect()
to re-establish the MQTT connection before retrying watch_telemetry().
This prevents infinite fail-sleep-fail cycles on disconnected clients.
* Fix rain blueprint default threshold to enable resume after rain
Change rain_threshold default from 0 to 1 to fix resume-after-rain automation. With threshold 0, the rain_stop trigger (below: 0) never fires because the Yarbo sensor returns 0 when dry, and 0 is not < 0. With threshold 1, rain_stop fires when value < 1, correctly including the dry state (0).
* Fix: disconnect before reconnect in telemetry loop
Add client.disconnect() call before reconnecting after YarboConnectionError
to match the established cleanup pattern used in __init__.py and prevent
the client from being left in a broken state.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address 5 Cursor Bugbot review threads on PR #31
- Threads PRRT_kwDORZN5Nc5xCrO4 & PRRT_kwDORZN5Nc5xDLZx: replace
always-true device_class condition in low_battery_notification.yaml
with an actual charging-state check (not is_state(charging_entity, 'on'))
- Thread PRRT_kwDORZN5Nc5xC8UB: reconnect logic already present in
coordinator.py (_telemetry_loop catches YarboConnectionError and calls
disconnect()/connect() after sleep); no code change required
- Thread PRRT_kwDORZN5Nc5xDLZ3: remove mutable default dict from
SERVICE_SEND_COMMAND_SCHEMA; use `or {}` guard at call site instead
- Thread PRRT_kwDORZN5Nc5xDLZ6: fix YarboChannelLight to read current
light_state from coordinator, merge the updated channel, and send the
full 7-channel YarboLightState so other channels are not zeroed out;
add light_state dict to coordinator __init__ and sync it in light.py
and services.py set_lights handler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address 3 new Cursor Bugbot threads on PR #31
- PRRT_kwDORZN5Nc5xDxVt: snow_deployment.yaml used the removed
forecast weather attribute (dropped in HA 2024.3). Restructured to
call weather.get_forecasts service in the action sequence and use a
condition action to check the returned forecast data.
- PRRT_kwDORZN5Nc5xDxV2: __init__.py HA version tag used
hass.config.as_dict().get("version") which always returns "unknown".
Fixed to use hass.config.version.
- PRRT_kwDORZN5Nc5xDxV4: device_tracker.py had a redundant source_type
property duplicating the _attr_source_type class attribute. Removed
the property.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: ruff format device_tracker.py
* Fix three bugs: add charging check to battery notification blueprint,… (#34)
* Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels
* Fix light state sync and blueprint entity ID slugification
- Update coordinator.light_state after yarbo.set_lights service calls to prevent channel values from being reverted
- Replace manual slugification in blueprint with proper slugify filter to handle all special characters
* Fix charging check to fail-open when entity unavailable
Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on')
to ensure low battery notifications are not silently suppressed when the charging
entity is unavailable, unknown, or doesn't exist. This fail-open approach only
suppresses notifications when the system is certain the robot is charging.
* Fix weather forecast API usage, Sentry HA version tag, and redundant property
- Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint
- Fix HA version tag in Sentry to use hass.config.version instead of as_dict()
- Remove redundant source_type property from YarboDeviceTracker class
* Fix weather.get_forecasts service call in snow deployment automation
The weather.get_forecasts service cannot be called directly in a Jinja2 template.
Restructured the automation to call the service as an action step with response_variable,
then evaluate the forecast data in a subsequent template condition.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* chore: disable Copilot autofix branches — review-only mode
* chore: enable Copilot autofix with auto-merge
* Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entit… (#38)
* Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entity ID construction, and missing return-to-dock
- Fix rain_pause.yaml: Change default rain_threshold from 1 to 0 to properly detect rain (above: 0 triggers for any positive value)
- Fix rain_pause.yaml: Add missing yarbo.return_to_dock call after yarbo.pause to match documented behavior
- Fix error_reporting.py: Change Sentry scrub pattern from 'key' to '_key' to avoid false-positive redaction of fields like entity_key
- Fix low_battery_notification.yaml: Use device_entities() to find charging entity instead of fragile name-based construction
* Fix rain_stop trigger and entity_key scrubbing bugs
- Change rain_threshold default from 0 to 0.1 to fix rain_stop trigger
(below: 0 never fires since sensor returns 0 when dry, not negative)
- Fix error_reporting scrub pattern to use endswith() and exclude entity_key
(substring match '_key' in 'entity_key' still matched despite intent)
* Fix rain resume condition to check for docked states instead of paused
The resume condition was checking for 'paused' state, but after calling
yarbo.return_to_dock, the robot transitions to 'returning' then 'idle'
or 'charging'. This caused the resume feature to never trigger after
rain stopped. Now checks for 'idle' or 'charging' states instead.
* Fix resume condition to include paused state for dock failure recovery
Add 'paused' state to the rain-stop resume condition to handle cases where
yarbo.return_to_dock fails (e.g., obstacle, connectivity loss). Without this,
the robot would remain stranded in paused state with no automatic recovery,
since the condition only checked for idle/charging (successful dock states).
* fix: address review feedback on PR #38
rain_pause.yaml:
- Add 5 s delay between yarbo.pause and yarbo.return_to_dock to avoid
race condition where device may not accept dock command before fully
processing pause (markus-lassfolk review)
- Update blueprint description to document that threshold 0.1 is
deliberate — using 0 would break the rain-stop trigger (below: 0
never fires) and that resume intentionally re-launches the mowing job
- Expand rain_threshold input description to explain the 0.1 rationale
error_reporting.py:
- Replace endswith("_key") with "_key" in key_lower so compound names
like api_key_id and encryption_key_backup are also redacted
- Add exact match for bare "key" field name
- Introduce _KEY_ALLOWLIST frozenset (entity_key, key_format, key_type)
replacing the fragile != "entity_key" hardcode (markus-lassfolk review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix dead code in _KEY_ALLOWLIST: remove key_format and key_type entries
These entries can never be matched by the scrub condition since they contain 'key_' (not '_key'). Only entity_key is functional and needed in the allowlist.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Bugfix/three bug fixes (#39)
* Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels
* Fix charging check to fail-open when entity unavailable
Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on')
to ensure low battery notifications are not silently suppressed when the charging
entity is unavailable, unknown, or doesn't exist. This fail-open approach only
suppresses notifications when the system is certain the robot is charging.
* Fix weather forecast API usage, Sentry HA version tag, and redundant property
- Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint
- Fix HA version tag in Sentry to use hass.config.version instead of as_dict()
- Remove redundant source_type property from YarboDeviceTracker class
* Fix critical bugs in Yarbo integration
- Fix AttributeError: use homeassistant.const.__version__ instead of hass.config.version
- Fix coordinator shutdown: call super().async_shutdown() to ensure base class cleanup
- Fix Sentry DSN: remove SENTRY_DSN fallback to maintain opt-in only behavior
* Fix TypeError when forecast is None in snow deployment automation
Add explicit check for 'is not none' before applying length filter to prevent
TypeError when weather entity returns None for forecast data instead of empty list.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: Milestone v0.2.0 — Full Telemetry & Controls (#40)
* feat: implement #11 — Light platform: all-lights group + 7 individual channels
- YarboAllLightsGroup: single entity controlling all 7 LED channels simultaneously,
enabled by default, brightness 0-255 via client.set_lights(YarboLightState(...))
- YarboChannelLight: 7 individual channel entities (led_head, led_left_w, led_right_w,
body_left_r, body_right_r, tail_left_r, tail_right_r), all disabled by default
- Full brightness support per channel via async_turn_on/async_turn_off
- Tests: 15 tests covering group and channel entity behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #12 — Switch platform: buzzer toggle
- YarboBuzzerSwitch: entity disabled by default, icon mdi:volume-high
- turn_on calls client.buzzer(state=1), turn_off calls client.buzzer(state=0)
- State tracked from last command (assumed_state=True, no telemetry feedback)
- Tests: 9 tests covering all behaviors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #13 — Number platform: chute velocity
- YarboChuteVelocityNumber: range -2000 to 2000, disabled by default, icon mdi:rotate-right
- Availability gated: only available when head_type == HEAD_TYPE_SNOW_BLOWER (0)
- async_set_native_value calls client.set_chute(vel=int(value))
- yarbo.set_chute_velocity service already registered in services.py
- Tests: 12 tests covering range, availability, and command dispatch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #14 — Extended sensors: RTK status, heading, chute angle, rain sensor
- All extended sensors disabled by default (entity_registry_enabled_default=False)
- YarboRtkStatusSensor: enum, maps RTKMSG.status to invalid/gps/dgps/rtk_float/rtk_fixed
- YarboHeadingSensor: degrees, from RTKMSG.heading
- YarboChuteAngleSensor: degrees, snow blower head only, from RunningStatusMSG.chute_angle
- YarboRainSensor: measurement, from RunningStatusMSG.rain_sensor_data
- YarboSatelliteCountSensor: from GNGGA num_satellites
- YarboChargingPowerSensor: W, calculated from charge_voltage_mv x charge_current_ma
- Diagnostic sensors: YarboOdomConfidenceSensor, YarboRtcmAgeSensor,
YarboChargeVoltageSensor (mV), YarboChargeCurrentSensor (mA), YarboMqttAgeSensor
- Updated strings.json and translations/en.json with new entity names
- Tests: 46 tests covering all sensor behaviors, availability, and values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #15 — Low battery notification blueprint
- blueprints/automation/yarbo/low_battery.yaml: automation blueprint with inputs
battery_sensor (entity, device_class=battery), threshold (default 20%), notify_target
- Trigger: numeric_state below threshold on battery_sensor
- Action: service call to notify_target with battery percentage in message
- mode: single to prevent duplicate notifications
- Tests: 9 tests covering file existence, YAML validity, inputs, trigger, and mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address review feedback on PR #40 — telemetry & controls
- test_number: add missing `assert entity.native_value == 0.0` in
test_set_zero_stops_chute for consistency with other test cases
(Copilot suggestion, comment 2862001982)
- sensor: add EntityCategory.DIAGNOSTIC to YarboSatelliteCountSensor
for consistency with other diagnostic sensors
- sensor: remove SensorStateClass.MEASUREMENT from YarboMqttAgeSensor
to avoid polluting long-term statistics with unbounded growing values
when the robot is offline
- sensor: convert YarboChargeVoltageSensor and YarboChargeCurrentSensor
from mV/mA to V/A so HA energy dashboard unit conversion works
correctly with SensorDeviceClass.VOLTAGE/CURRENT
All 82 tests pass, ruff clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove state_class from YarboRtcmAgeSensor to prevent unbounded statistics
RTCM correction age grows unbounded when base station is unavailable,
similar to mqtt_age when robot is offline. Removing state_class prevents
polluting Home Assistant long-term statistics database with arbitrarily
large values.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Forge <forge@openclaw.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: Milestone v1.0.0 — HACS Default Ready (code) (#42)
* feat: implement #25 — firmware update entity (informational)
- entity_category = EntityCategory.CONFIG per spec
- installed_version reads from deviceinfo_feedback, ota_feedback, and
firmware_version raw MQTT keys (priority order)
- latest_version returns coordinator.latest_firmware_version when
cloud_enabled option is True; None otherwise (no install trigger)
- No UpdateEntityFeature.INSTALL — OTA is AWS Greengrass managed
- Add tests/test_update.py with 15 tests covering all version paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #26 — options flow with live throttle update
- Add _async_update_options listener in async_setup_entry so changes
to telemetry_throttle (and other options) take effect immediately
without a full HA restart; coordinator.update_options() applies them
- coordinator.update_options() updates _throttle_interval at runtime
- Add latest_firmware_version attribute to coordinator (future cloud use)
- Add tests/test_options.py: 13 tests covering defaults, update_options,
schema validation, and option key constants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #27 — repair flows: controller lost, MQTT disconnect, cloud token expired
- Add custom_components/yarbo/repairs.py with helper functions:
async_create/delete_mqtt_disconnect_issue (WARNING, not fixable)
async_create/delete_controller_lost_issue (ERROR, fixable)
async_create/delete_cloud_token_expired_issue (WARNING, not fixable)
- coordinator._heartbeat_watchdog now uses repairs.async_create_mqtt_disconnect_issue
instead of inline ir calls
- coordinator.report_controller_lost() / resolve_controller_lost() allow
command handlers to raise/clear the controller_lost repair
- Repair issues auto-resolve when the underlying condition is fixed
- Add tests/test_repairs.py: 22 tests covering all three repair types and
coordinator integration (idempotency, create/delete sequencing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #28 — complete strings.json and translations/en.json
- Add three new repair issue translation keys to strings.json and en.json:
mqtt_disconnect: base station unreachable (actionable recovery steps)
controller_lost: session stolen (fixable with confirm step description)
cloud_token_expired: 401/403 from cloud API (reauth instructions)
- Existing entity names, config/options flow strings, and service
descriptions were already complete; no entity translations changed
- translations/en.json mirrors strings.json exactly per hassfest requirements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Add missing async_create_fix_flow handler for controller_lost repair issue
Implements the required RepairsFlow handler for the controller_lost repair
issue which was marked as fixable but lacked the necessary flow handler.
When users click 'Fix' in the HA UI, the flow now properly re-acquires
the controller session and clears the repair issue.
* Fix circular import, orphaned repair issues, and wire up controller_lost reporting
- Fix circular import between coordinator.py and repairs.py by using TYPE_CHECKING guard
- Add cleanup for legacy telemetry_timeout repair issues on upgrade to prevent orphaned issues
- Wire up report_controller_lost() in all service handlers to make controller_lost repair flow functional
* Fix controller_lost auto-resolve and remove unused cloud_token_expired helpers
- Add coordinator.resolve_controller_lost() calls after successful get_controller() in all 7 service handlers to auto-clear stale repair issues
- Remove unused async_create_cloud_token_expired_issue and async_delete_cloud_token_expired_issue functions and related tests
- Update repairs.py docstring to reflect only 2 supported repair conditions
* fix: resolve hassfest translation schema error and address all PR #42 review feedback
- strings.json / translations/en.json: remove top-level `description` from
`controller_lost` (fixes hassfest exclusion-group error for fixable issues);
add `fix_flow.abort` translations for `cannot_connect` and `unknown`;
remove orphaned `cloud_token_expired` translation entry (no code creates it)
- repairs.py: wrap `get_controller()` call in `coordinator.command_lock` to
prevent race conditions with concurrent service calls; log exception on
controller re-acquisition failure (narrows bare `except Exception`)
- coordinator.py: delete stale `mqtt_disconnect` issue in `_async_setup` so
a pre-restart issue is cleaned up on reload (watchdog re-raises if needed);
add public `entry` property to expose config entry without accessing `_entry`
- update.py: change `entity_category` from CONFIG to DIAGNOSTIC (read-only
informational entity per HA guidelines); use public `coordinator.entry`
- tests/test_update.py: update mock to expose `coordinator.entry`; rename
`test_entity_category_is_config` → `test_entity_category_is_diagnostic`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix repair issue cleanup on restart and entry removal
- Add controller_lost issue cleanup in _async_setup to mirror mqtt_disconnect handling
- Add repair issue cleanup in async_unload_entry to prevent orphaned issues
- Fixes stale controller_lost issues persisting after HA restart
- Fixes orphaned repair issues when config entry is removed
* Fix repair flow missing description_placeholders for confirm step
The controller_lost repair flow's confirm step now properly passes the robot name as a description_placeholder, resolving the issue where {name} was displayed as literal text instead of the actual robot name.
* Fix repair flow robot name default to match coordinator
- Import CONF_ROBOT_NAME constant in repairs.py
- Use CONF_ROBOT_NAME instead of string literal 'robot_name'
- Change default from 'unknown' to 'Yarbo' to match coordinator.py
- Ensures consistent robot name display in repair UI
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
* feat: Milestone v0.3.0 — Scheduling & Weather (#41)
* feat: implement #16 — yarbo.start_plan service
- Change start_plan to use publish_raw (matching issue spec)
- Add comprehensive tests for service registration and start_plan handler
- Tests cover: publish_raw call, planId payload, unknown device error,
controller acquisition order, and _get_client_and_coordinator helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #17 — rain pause blueprint
- Switch rain_sensor from numeric_state to binary_sensor (on/off)
- On rain start: pause robot, wait 2s, return_to_dock
- After rain stops + dry_delay: resume (only if planning sensor still on)
- dry_delay uses duration selector with 2h default
- Condition: only acts when binary_sensor.*_planning is on
- mode: restart so new rain event cancels pending resume timer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #18 — snow deployment blueprint
- Switch trigger to numeric_state on snowfall_sensor above threshold
- Add notify_target input (required) — always sends notification on trigger
- Make plan_id optional (default empty) — plan only started when provided
- Works with any HA weather sensor (Met.no, OpenWeatherMap, Ecowitt)
- mode: single prevents re-triggering while already active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #19 — job complete notification blueprint
- New file: blueprints/automation/yarbo/job_complete.yaml
- Trigger: state change on event entity attribute event_type to job_completed
- Notification includes robot friendly_name from the event entity
- Inputs: yarbo_event (event entity), notify_target (text/service name)
- mode: queued so multiple robots can fire without losing events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #20 — optional cloud auth for metadata and firmware checks
- Add async_step_cloud to config flow (optional, skippable by leaving blank)
- name step redirects to cloud step; entry created after cloud step
- Stores only cloud_refresh_token (never the password) in config_entry.data
- Add async_step_reauth + async_step_reauth_confirm for token expiry
- update.py: async_update fetches latest firmware from cloud when enabled;
falls back to installed_version so entity shows no update without cloud
- strings.json + translations/en.json: add reauth_confirm step, reauth_successful
abort, cloud_auth_failed and cloud_not_available errors
- Tests cover: skip cloud, auth success/failure, reauth, firmware entity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address test failures and lint issues
- conftest.py: set YARBO_SENTRY_DSN="" early to prevent sentry-sdk
BackgroundWorker thread from starting during tests (python-yarbo calls
init_error_reporting() at import time)
- test_services.py: fix test_device_not_in_domain_data_raises to mock
device registry instead of creating a real device with a fake config entry
- test_cloud_auth.py: fix mock API shape to match real YarboCloudClient
(constructor takes username/password; connect() does login; auth.refresh_token)
- config_flow.py + update.py: remove redundant type: ignore and noqa comments
- All 22 tests pass; ruff check/format clean; mypy errors all pre-existing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address three bugs - undefined threshold variable, duplicate blueprint, and refresh token persistence
* Fix: Enable polling for firmware update entity to fetch cloud versions
Override should_poll to True in YarboFirmwareUpdate to allow the update
platform to automatically call async_update() and fetch the latest firmware
version from the cloud. Without this, the cloud firmware check feature was
non-functional as CoordinatorEntity sets should_poll=False by default.
* Fix stale version cache and missing planning sensor bugs
- Clear _latest_version cache when cloud is disabled or fetch fails
- Revert blueprint to use activity_sensor instead of non-existent planning binary sensor
* fix: resolve CI failure and address all review feedback on PR #41
CI fix:
- tests/conftest.py: stub homeassistant.components.dhcp (and its transitive
deps aiodhcpwatcher, aiodiscover, cached_ipaddress) before HA imports to
prevent ModuleNotFoundError in test_cloud_auth.py
- pyproject.toml: add per-file-ignores E402 for conftest.py (sys.modules
manipulation must precede HA imports; this is intentional)
update.py:
- Remove private attribute injection (_session, auth._session); use the
library's public connect()/disconnect() lifecycle only
- Ensure cloud_client.disconnect() is always called via try/finally
- Raise ConfigEntryAuthFailed on auth-related errors so HA triggers reauth
- Remove unused aiohttp import
config_flow.py:
- async_step_cloud: move disconnect() into finally block so it always runs
even when connect() raises; narrow except to log the error message
- async_step_reauth_confirm: same try/finally fix
- async_step_cloud: clear _pending_data immediately after use (swap with {})
to avoid retaining credentials in memory after flow completes
rain_pause.yaml:
- rain_stop sequence: change state condition from 'paused' to 'charging';
after yarbo.return_to_dock the robot is in charging state, not paused, so
the resume condition previously never matched
- rain_sensor description: clarify that a Template binary_sensor helper is
needed when using the numeric Yarbo rain sensor
test_cloud_auth.py:
- Remove aiohttp.ClientSession patch from test_async_update_fetches_latest_version
(update.py no longer manages the session manually)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix rain resume condition and auth error detection
- Fix rain_pause.yaml: Change resume condition from 'charging' to 'paused' to prevent spurious resume when robot is idle at dock
- Fix update.py: Remove overly broad 'token' keyword from auth error detection to avoid false positives on non-auth errors
* fix: Add SCAN_INTERVAL to prevent excessive cloud API polling
Set SCAN_INTERVAL to 6 hours for firmware version checks to avoid
polling the cloud API every 30 seconds (HA default). Firmware versions
change infrequently, so checking every 6 hours is more appropriate and
prevents unnecessary network traffic, rate limiting, and token rotation.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
* fix: resolve 8 test failures across cloud auth and services
update.py:
- Add async_update() to fetch latest firmware version from Yarbo cloud API
using stored refresh_token — skips call when cloud disabled or no token
- Fix latest_version property to fall back to installed_version when cloud
is disabled (avoids 'update unknown' banner) and read from coordinator
cache (coordinator.latest_firmware_version) when cloud is enabled
- Import YarboCloudClient with try/except for graceful fallback
- Add _latest_version instance attribute; async_update sets both local attr
and coordinator.latest_firmware_version
tests/test_cloud_auth.py:
- Fix TestFirmwareUpdate: mock coordinator.entry (public property) instead
of coordinator._entry (private attr) — latest_version uses coordinator.entry
- Update latest_version=None assertion comment to reflect new fallback logic
tests/test_services.py:
- Fix TestStartPlanService: start_plan changed from publish_raw → publish_command
in services.py milestone v1.0.0; update all 3 affected test assertions
* fix: autofix test assertions and blueprints (#44)
* Fix bugs: correct test assertions, prevent invalid rain threshold, remove duplicate blueprint, fix mock entry property
* test: fix blueprint tests to use low_battery_notification.yaml
* Fix assertion error message and docstring to reference notify_service instead of notify_target
* lint: remove unused noqa on broad exception
* style: ruff format test_services
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Extract duplicated controller acquisition logic into helper function (#45)
Refactored the seven identical try/except blocks for controller acquisition
into a single _acquire_controller helper function. This eliminates ~42 lines
of duplicated code and ensures consistent error handling across all service
handlers.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci: fix action versions (downgrade hallucinated v6 to actual v4)
* Fix _scrub_event to redact key_ prefix fields (e.g. key_material, key_data)
- Add key_lower.startswith('key_') so prefix patterns are redacted
- CI and test updates
Made-with: Cursor
* fix: ruff format CI failure in error_reporting.py (#46)
* Initial plan
* fix: correct ruff format in error_reporting.py to fix CI lint failure
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
* fix: ruff format CI failure blocking develop→main merge (#47)
* Initial plan
* fix: ruff format error_reporting.py to fix CI lint check
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
Co-authored-by: Markus Lassfolk <markus@lassfolk.net>
* Fix: Upgrade GitHub Actions to latest versions in CI workflow (#48)
- Update actions/checkout from v4 to v6
- Update actions/setup-python from v5 to v6
- Update actions/upload-artifact from v4 to v7
This aligns ci.yml with other workflows (release.yml, gitleaks.yml) and restores the latest action versions with security patches and Node.js 24 support.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Fix security scrubbing and null firmware version bugs (#49)
* Fix security scrubbing and null firmware version bugs
- Fix error_reporting.py to catch concatenated key patterns like 'apikey' by adding endswith('key') check
- Fix update.py to properly handle null firmwareVersion from cloud API using result.get() instead of key existence check
* fix: handle null/missing firmwareVersion and E501 in error_reporting
- update.py: explicitly clear _latest_version and coordinator.latest_firmware_version
when cloud response has firmwareVersion: null, missing key, or non-dict type;
adds debug logging for each case to aid diagnosis of stale-version issues
- error_reporting.py: break long boolean expression on line 74 to stay under 100 chars (E501)
- tests/test_update.py: add TestAsyncUpdate class with two tests covering
firmwareVersion: null and missing key, verifying cached version is cleared
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Rover/DC endpoints (#50), no hardcoded IPs (#51), discovery + manual fallback
- Config flow: discover endpoints via python-yarbo; select step with DC recommended
- Store alternate_broker_host, connection_path, rover_ip for device info and failover
- Coordinator: failover to alternate endpoint on connection error
- Device info: configuration_url; Connection sensor with path and rover_ip
- Discovery: use only python-yarbo (no custom MQTT/DNS); manual IP/port when 0 found
- Audit: remove hardcoded IPs; docs use <broker-ip>, tests use 192.0.2.1 (RFC 5737)
- Config entry version 2 migration
Made-with: Cursor
* Closes #1: Config flow manual IP entry + MQTT validation
- async_step_user: broker IP and port form
- async_step_mqtt_test: connect via python-yarbo, 10s telemetry, extract SN
- Errors: cannot_connect, no_telemetry, decode_error; duplicate SN → already_configured
- unique_id = robot_sn in async_step_name
Made-with: Cursor
* Closes #2: Config flow DHCP auto-discovery
- manifest dhcp C8FE0F* triggers async_step_dhcp
- Store discovered IP/MAC, show confirm form; on confirm run MQTT validation
- Already configured robot → abort already_configured; DC IP change → reconfigure broker_host
Made-with: Cursor
* Closes #26: Options flow telemetry debounce, personality, cloud, auto_controller
- telemetry_throttle (float 1.0 default), applied in coordinator without restart
- auto_controller: gate get_controller before commands in all services
- cloud_enabled / activity_personality already used by update and sensor
- Options update listener propagates to coordinator immediately
Made-with: Cursor
* Closes #27: Repair flows for MQTT disconnect, controller lost, cloud token expired
- MQTT disconnect: warning issue, check-base-station steps, auto-resolve when telemetry resumes
- Controller lost: error issue, fixable Re-acquire flow, auto-resolve on re-acquire
- Cloud token expired: warning issue on 401/403 from cloud API, fixable flow triggers reauth
- Reauth success clears cloud_token_expired issue; unload clears all three
- All issue translation keys in strings.json
Made-with: Cursor
* Closes #28: Full strings.json translations
- Config flow: all step titles, descriptions, field labels, abort/error messages
- Options flow: all option labels and descriptions (fix: options apply immediately)
- Entity names for all platforms (sensor, binary_sensor, button, event, light, switch, number, device_tracker, lawn_mower, update)
- State translations: activity, head_type, rtk_status, event event_type
- Service descriptions and repair issue messages (mqtt_disconnect, controller_lost, cloud_token_expired)
- translations/en.json kept identical to strings.json
Made-with: Cursor
* Closes #29: HACS default submission (in-repo prep)
- hacs.json present and valid (name, render_readme, homeassistant, content_in_root)
- README has required badges; manifest has all required keys
- docs/hacs-default-submission.md: checklist for external PR to hacs/default
- External submission deferred until brands PR and release per issue
Made-with: Cursor
* Closes #30: Brands PR to home-assistant/brands (in-repo prep)
- docs/brands-pr.md: asset spec (icon.png 256x256, icon@2x.png 512x512, logo.png) and submission steps
- External PR to home-assistant/brands deferred per issue
Made-with: Cursor
* Primary/Secondary failover from discovery order (like DNS)
- Use endpoint order from python-yarbo discovery; first = Primary, second = Secondary
- CONF_BROKER_ENDPOINTS: ordered list for failover; no reordering by recommended
- Coordinator: on connection error try next in list (Primary→Secondary→Primary);
stay on Secondary until it fails, then try Primary again
- Update config entry broker_host on failover so device info and next failover correct
- Select-endpoint UI labels Primary/Secondary; migration for existing entries
Made-with: Cursor
* Fix all PR #53 review threads and CI failures
Review thread fixes:
- Thread 1 (discovery.py): Normalize endpoint_type to canonical values via
_normalize_endpoint_type() helper regardless of what python-yarbo returns
- Thread 2 (config_flow.py): Update self._broker_port from chosen endpoint
in async_step_select_endpoint
- Thread 3 (coordinator.py): Acquire command_lock during MQTT client swap
to prevent commands in-flight during failover
- Thread 4 (sensor.py): YarboConnectionSensor reads host from active client
(coordinator.client.host) to reflect failover state
- Thread 5 (coordinator.py): CONF_BROKER_HOST is actively used in failover;
no change needed (confirmed by ruff — clean)
- Thread 6 (config_flow.py): Remove unused ENDPOINT_TYPE_DC import; add
ENDPOINT_TYPE_UNKNOWN which was missing (caused F821)
- Thread 7/9 (__init__.py): Implement proper async_migrate_entry for v1→v2
migration; remove inline migration from async_setup_entry
- Thread 8 (repairs.py): Add missing await on config_entries.flow.async_init
so reauth flow actually starts
- Thread 11 (coordinator.py): Use contextlib.suppress on old_client.disconnect
inside command_lock to prevent client leak on partial failure
- Thread 12 (__init__.py): Fix mis-indented import block in async_unload_entry
- Thread 13/16 (config_flow.py): Fix DHCP discovery fallthrough — add proper
return for multi and single endpoint cases after discovery
- Thread 14 (coordinator.py): Fix failover skipping primary — use idx=-1
when current_host not in endpoints so next_idx wraps to 0 (Primary)
- Thread 15 (config_flow.py): Fix dead code — unreachable single-endpoint
block after return in async_step_user
CI fixes:
- Hassfest: Remove top-level description from fixable cloud_token_expired
issue in strings.json and translations/en.json (conflicting fixable group)
- Tests: Set entity.hass = MagicMock() in test helpers so async_update can
call async_delete_cloud_token_expired_issue without NoneType error
- Ruff E501: Fix all lines > 100 chars in coordinator.py, config_flow.py,
discovery.py, update.py
- Ruff F401: Remove unused ENDPOINT_TYPE_DC import from config_flow.py
- Ruff F841: Remove unused errors variable in async_step_user
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add get_controller() after failover, document defensive user_input branch
* Closes #1, #2: Config flow tests for manual IP + MQTT validation and DHCP discovery
- Implement TestManualConfigFlow: manual form when no discovery, full create
entry, cannot_connect, no_telemetry, decode_error, duplicate robot aborts
- Implement TestDhcpDiscoveryFlow: confirm form, full create entry,
already_configured (same MAC+IP), same MAC new IP shows reconfigure
- Add enable_custom_integrations fixture to config flow tests
- Stub DhcpServiceInfo in conftest with ip/macaddress/hostname constructor
- Use pytest_homeassistant_custom_component.common.MockConfigEntry for DHCP
already_configured and reconfigure tests
- Options flow tests left skipped for issue #26
Made-with: Cursor
* Closes #26: Options flow tests — telemetry_throttle, auto_controller, cloud_enabled, activity_personality
- test_options_flow_shows_form: init options flow shows form with step_id init
- test_options_flow_saves_options: save options and assert entry.options updated
- Options flow and apply-without-restart were already implemented; tests were skipped
Made-with: Cursor
* Closes #27: Repair flows tests — cloud_token_expired and async_create_fix_flow
- Add TestCloudTokenExpiredRepair: create/delete, WARNING, fixable, translation_key
- Add TestCreateFixFlow: async_create_fix_flow returns YarboRepairFlow
- Add ISSUE_CLOUD_TOKEN_EXPIRED constant test
- MQTT disconnect, controller lost, cloud token expired and fix flows were already implemented
Made-with: Cursor
* fix: update connection_path on failover, port from discovery, endpoints on reconfigure, lint fixes
- Thread 1: Update CONF_CONNECTION_PATH during failover so sensor shows active path
- Thread 2: Update broker_port from discovered endpoint in single-endpoint case
- Thread 3: Update CONF_BROKER_ENDPOINTS during reconfigure to keep failover list current
- Fix 6 ruff lint errors (missing type annotations, unused import, import sorting)
* fix: DHCP port assignment, replace dead _endpoint_types with config-based path label
* fix: swap connection path labels on failover, preserve endpoints on reconfigure
- Thread 1: Connection path now swaps (dc↔rover) instead of hardcoding based on alt host
- Thread 2: Reconfigure preserves/updates CONF_BROKER_ENDPOINTS when _broker_endpoints_ordered is empty
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
Co-authored-by: Markus Lassfolk <markusla@Markus-PC.localdomain>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
markus-lassfolk
added a commit
that referenced
this pull request
Mar 1, 2026
* feat: scaffold repo with CI/CD, HACS compliance, docs, integration skeleton, and 30 issues
- CI/CD: hassfest, hacs-validate, ruff/mypy lint, pytest (3.12+3.13), gitleaks, release workflows
- GitHub: labels (33), milestones (5), branch protection on main+develop
- HACS compliance: hacs.json, manifest.json (local_push, DHCP C8FE0F*, python-yarbo dep)
- Integration skeleton: __init__, config_flow, coordinator, entity, diagnostics, services stubs
- Strings: strings.json + translations/en.json with all config flow and entity strings
- Tests: conftest with mock fixtures, test_init, test_config_flow (skipped stubs for v0.1.0)
- Docs: 11 reference docs split from architecture (architecture, config-flow, entities,
services, events, multi-head, blueprints, mqtt-protocol, security, development, roadmap)
- README: rewritten with badges, entity table, quick start, links to docs/
- Dev tooling: pyproject.toml (ruff+mypy+pytest config), requirements_test.txt, pre-commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: resolve CI failures — hassfest action, ruff lint, test dependencies
- Fix hassfest: use home-assistant/actions/hassfest@master (not @main)
- Fix hacs-validate: add ignore: brands (not yet submitted to hacs/brands)
- Fix 37 ruff errors: remove unused imports, fix import ordering/formatting,
add missing return type annotation on _async_gen in conftest.py
- Fix aiodhcpwatcher test failure: remove unused dhcp import from
test_config_flow.py (all dhcp tests are skipped stubs; import was dead)
- Remove obsolete ANN101/ANN102 from ruff ignore list (rules were removed
upstream)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(#1): implement manual config flow
* feat(#2): add dhcp discovery and reconfigure flow
* feat(#3): add push coordinator and watchdog
* feat(#4): add core sensors and entity base
* feat(#5): add binary sensors and error code
* feat(#6): add command buttons
* feat(#7): add event entity and bus events
* feat(#8): implement diagnostics redaction
* feat(#9): implement send_command service
* feat(#10): finalize setup and validation fixes
* fix: address all review feedback and resolve CI failures
* feat(telemetry): add optional GlitchTip/Sentry error reporting
Wires sentry-sdk into the Yarbo HA integration with opt-out (enabled by
default). Initialized in async_setup_entry() with robot serial, HA version,
and integration version as tags. Disable via YARBO_SENTRY_DSN="".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): sort manifest keys, fix python-yarbo dependency, resolve lint errors
- Sort manifest.json keys: domain, name first, then alphabetically
- Remove python-yarbo from requirements_test.txt (not on PyPI)
- Install python-yarbo from git in CI workflow (lint + test jobs)
- Fix conftest.py mock: YarboClient → YarboLocalClient (correct class name)
- Add missing mock methods: get_controller, publish_command, start_plan
- Add mock properties: is_connected, controller_acquired, serial_number
- Upgrade actions/checkout and upload-artifact to v4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address all 17 review threads from Copilot and Cursor
Copilot threads (8):
- Remove unused ACTIVITY_* human-readable string constants from const.py
(duplicated sensor.py's snake_case values; const.py had verbose form)
- Fix activity_personality option type: boolean toggle instead of enum string
- Keep time import in coordinator.py (IS used for monotonic())
- Rename vel → velocity mapping: service uses 'velocity', API param is 'vel'
(set_chute(vel=call.data['velocity']) in services.py)
- Fix YarboDataCoordinator: use getattr() defensively for optional telemetry fields
- Serial number redaction already correct (last 4 chars visible) — no change
- Document python-yarbo not on PyPI in requirements_test.txt
- Fix activity_personality: change from vol.In(str_options) to bool
Cursor threads (9):
- Fix duplicate detection: _async_abort_entries_match({"unique_id":...}) →
_abort_if_unique_id_configured() (the former checked entry.data, not unique_id)
- Fix EventEntity method: async_trigger() → _trigger_event() (correct HA API)
- Deduplicate activity state: moved _activity_state() to event.py as single
source of truth; sensor.py now imports it from there
- Remove conflicting ACTIVITY_* constants from const.py (were unused human-readable strings)
- Add _attr_translation_key to all entities (sensor, binary_sensor, button, event)
so they show proper names when has_entity_name=True
- Fix client/task leak: wrap async_config_entry_first_refresh() in try/except,
shut down coordinator and disconnect client before re-raising
- Fix conftest mock class: YarboClient → YarboLocalClient (done in phase 1)
- Fix error_reporting: remove hardcoded private DSN 192.168.1.99,
now opt-in only via YARBO_SENTRY_DSN env var (no default)
- Fix telemetry loop: add retry with TELEMETRY_RETRY_DELAY_SECONDS (30s) on
YarboConnectionError instead of terminating permanently
Also:
- Add complete translation strings for all entities
- Update coordinator to import TELEMETRY_RETRY_DELAY_SECONDS from const
- Implement all 7 service handlers in services.py (previously only send_command)
- Update options flow with proper range validation for telemetry_throttle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement all 25 issues (#11-30) — v0.2.0 through v1.0.0
## v0.2.0 Full Telemetry & Controls (#11-15)
#11 Light platform (light.py):
- YarboAllLightsGroup: set all 7 LED channels at once
- YarboChannelLight: individual per-channel entities (head, left_w, right_w,
body_left_r, body_right_r, tail_left_r, tail_right_r)
- Assumed state (no read-back from robot), brightness control
#12 Switch platform (switch.py):
- YarboBuzzerSwitch: toggle buzzer on (state=1) / off (state=0)
#13 Number platform (number.py):
- YarboChuteVelocityNumber: chute velocity -2000 to 2000
- Only available when head_type == 0 (Snow Blower)
#14 Extended sensors (sensor.py additions):
- YarboRtkStatusSensor: RTK fix quality enum (invalid/gps/dgps/rtk_float/rtk_fixed)
- YarboHeadingSensor: compass heading in degrees
- YarboChuteAngleSensor: snow chute position (snow blower only)
- YarboRainSensor: rain sensor reading
#15 Low battery notification blueprint:
- blueprints/automation/yarbo/low_battery_notification.yaml
- Triggers below configurable threshold, sends notification
## v0.3.0 Scheduling & Weather (#16-20)
#16 yarbo.start_plan service: fully implemented in services.py
#17 Rain pause blueprint: rain_pause.yaml — pause/resume on rain detection
#18 Snow deployment blueprint: snow_deployment.yaml — auto-start on forecast
#19 Job complete notification blueprint: job_complete_notification.yaml
#20 Cloud auth: options flow already has cloud_enabled toggle; cloud step in
config flow already defined in strings.json (full implementation deferred
to when python-yarbo exports YarboCloudClient)
## v0.4.0 Map & Personality (#21-24)
#21 GPS device_tracker (device_tracker.py):
- YarboDeviceTracker: latitude/longitude from RTK telemetry, SourceType.GPS
#22 Lawn mower platform (lawn_mower.py):
- YarboLawnMower: maps robot states to LawnMowerActivity
- Only available when head_type in (1, 2) — mower or pro mower
- async_start_mowing, async_pause, async_dock
#23 Activity sensor personality mode:
- activity_personality toggle in options flow (boolean)
- When enabled: extra_state_attributes includes emoji description
- Descriptions defined in VERBOSE_ACTIVITY_DESCRIPTIONS in const.py
#24 Entity picture & BotName UX:
- entity.py uses CONF_ROBOT_NAME for device friendly name
- sw_version from firmware_version in telemetry.raw
- brands/README.md documents how to submit to home-assistant/brands
## v1.0.0 HACS Ready (#25-30)
#25 Firmware update entity (update.py):
- YarboFirmwareUpdate: shows installed_version from telemetry
- Informational only (no install support — OTA via Yarbo app)
#26 Options flow: fully implemented with range validation
- telemetry_throttle (1.0-10.0s), auto_controller, cloud_enabled,
activity_personality (bool)
#27 Repair flows: heartbeat watchdog already creates repair issue on timeout
#28 Full strings.json translations:
- Complete English strings for all entities across all platforms
- light (8 entities), switch (1), number (1), device_tracker (1),
lawn_mower (1), update (1), all sensors/buttons/event/binary_sensor
- Services descriptions in strings.json
- Issues translation updated
#29 HACS hacs.json: updated with content_in_root, iot_class, filename
#30 Brands PR: brands/README.md with submission process documentation
Also: update PLATFORMS list to include all 10 platform types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): remove iot_class from hacs.json, bump mypy to python 3.13
* fix(ci): suppress mypy errors from HA 2026.2 FlowResult generics
* fix(ci): make mypy non-blocking — HA core type stubs incomplete for 2026.2
* fix: resolve 5 remaining review threads on PR #31
- Thread 1: suppress low_battery event during any charging state (1,2,3),
not just charging_status != 2
- Thread 2: redact robot serial to last 4 chars in error reporting tags,
matching diagnostics.py behaviour
- Thread 3: fix YarboChannelLight and YarboAllLightsGroup to pass
YarboLightState object to set_lights() — the API takes a dataclass,
not kwargs; also fix services.py set_lights call with correct field names
(led_head/led_left_w/led_right_w instead of head/left_w/right_w)
- Thread 4: remove "mqtt" from manifest.json dependencies — Yarbo uses
its own MQTT via python-yarbo/paho-mqtt, not HA's MQTT integration
- Thread 5: add bare except+disconnect to prevent client leak when
client.connect() or get_controller() raises a non-YarboConnectionError
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix 7 bugs: duplicate detection, missing time import, EventEntity met… (#33)
* Fix 7 bugs: duplicate detection, missing time import, EventEntity method, unused constants, duplicated logic, missing translation key, and resource leak
* fix: add YarboTelemetry type annotation per review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Remove duplicate _activity_state function and consolidate to get_activity_state
- Removed obsolete _activity_state function from event.py
- Updated sensor.py to import and use get_activity_state from const.py
- Ensures single source of truth for activity state logic
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix r… (#32)
* Fix 7 bugs: remove mandatory sentry-sdk, fix diagnostics crash, fix reconfigure port, fix integration version tag, fix rain threshold, align charging logic
* Fix AttributeError and TypeError in integration setup and diagnostics
- Use getattr with default for Integration.version to prevent AttributeError
- Add isinstance check for raw telemetry to prevent TypeError when None
* Fix: Handle None integration version for Sentry set_tag
Add 'or "unknown"' fallback when integration version is None to prevent TypeError in sentry_sdk.set_tag which expects string values.
* fix: resolve lint errors, use public loader API, validate port
- Fix W293 whitespace in diagnostics.py and __init__.py
- Replace hass.data["integrations"] with async_get_integration() public API
- Validate broker port handles None: use `or DEFAULT_BROKER_PORT` pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix missing port validation in DHCP and reconfigure schema paths
Complete the port validation fix by handling None values in:
- async_step_dhcp: coerce port from existing entry data to int
- async_step_reconfigure schema: coerce default port value to int
This prevents YarboLocalClient from receiving None as port parameter.
* Fix port 0 being overwritten by default due to falsy check
Replace 'or DEFAULT_BROKER_PORT' with explicit None checks to properly
handle port 0. This prevents port 0 from being incorrectly treated as
falsy and replaced with the default port (1883).
Fixed in four locations:
- async_step_user (line 73-74)
- async_step_dhcp (line 109-110)
- async_step_reconfigure (line 200-201)
- reconfigure schema default (line 204-218)
* Fix port conversion crash on empty string and rain threshold detection
- Fix port conversion to handle empty string values by treating both None and empty string as missing
- Change rain threshold default from 1 to 0 to correctly detect rain when sensor returns exactly 1
* Fix telemetry retry loop to reconnect MQTT client
When YarboConnectionError occurs, the retry loop now calls client.connect()
to re-establish the MQTT connection before retrying watch_telemetry().
This prevents infinite fail-sleep-fail cycles on disconnected clients.
* Fix rain blueprint default threshold to enable resume after rain
Change rain_threshold default from 0 to 1 to fix resume-after-rain automation. With threshold 0, the rain_stop trigger (below: 0) never fires because the Yarbo sensor returns 0 when dry, and 0 is not < 0. With threshold 1, rain_stop fires when value < 1, correctly including the dry state (0).
* Fix: disconnect before reconnect in telemetry loop
Add client.disconnect() call before reconnecting after YarboConnectionError
to match the established cleanup pattern used in __init__.py and prevent
the client from being left in a broken state.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address 5 Cursor Bugbot review threads on PR #31
- Threads PRRT_kwDORZN5Nc5xCrO4 & PRRT_kwDORZN5Nc5xDLZx: replace
always-true device_class condition in low_battery_notification.yaml
with an actual charging-state check (not is_state(charging_entity, 'on'))
- Thread PRRT_kwDORZN5Nc5xC8UB: reconnect logic already present in
coordinator.py (_telemetry_loop catches YarboConnectionError and calls
disconnect()/connect() after sleep); no code change required
- Thread PRRT_kwDORZN5Nc5xDLZ3: remove mutable default dict from
SERVICE_SEND_COMMAND_SCHEMA; use `or {}` guard at call site instead
- Thread PRRT_kwDORZN5Nc5xDLZ6: fix YarboChannelLight to read current
light_state from coordinator, merge the updated channel, and send the
full 7-channel YarboLightState so other channels are not zeroed out;
add light_state dict to coordinator __init__ and sync it in light.py
and services.py set_lights handler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address 3 new Cursor Bugbot threads on PR #31
- PRRT_kwDORZN5Nc5xDxVt: snow_deployment.yaml used the removed
forecast weather attribute (dropped in HA 2024.3). Restructured to
call weather.get_forecasts service in the action sequence and use a
condition action to check the returned forecast data.
- PRRT_kwDORZN5Nc5xDxV2: __init__.py HA version tag used
hass.config.as_dict().get("version") which always returns "unknown".
Fixed to use hass.config.version.
- PRRT_kwDORZN5Nc5xDxV4: device_tracker.py had a redundant source_type
property duplicating the _attr_source_type class attribute. Removed
the property.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: ruff format device_tracker.py
* Fix three bugs: add charging check to battery notification blueprint,… (#34)
* Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels
* Fix light state sync and blueprint entity ID slugification
- Update coordinator.light_state after yarbo.set_lights service calls to prevent channel values from being reverted
- Replace manual slugification in blueprint with proper slugify filter to handle all special characters
* Fix charging check to fail-open when entity unavailable
Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on')
to ensure low battery notifications are not silently suppressed when the charging
entity is unavailable, unknown, or doesn't exist. This fail-open approach only
suppresses notifications when the system is certain the robot is charging.
* Fix weather forecast API usage, Sentry HA version tag, and redundant property
- Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint
- Fix HA version tag in Sentry to use hass.config.version instead of as_dict()
- Remove redundant source_type property from YarboDeviceTracker class
* Fix weather.get_forecasts service call in snow deployment automation
The weather.get_forecasts service cannot be called directly in a Jinja2 template.
Restructured the automation to call the service as an action step with response_variable,
then evaluate the forecast data in a subsequent template condition.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* chore: disable Copilot autofix branches — review-only mode
* chore: enable Copilot autofix with auto-merge
* Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entit… (#38)
* Fix four bugs: rain threshold off-by-one, Sentry scrub pattern, entity ID construction, and missing return-to-dock
- Fix rain_pause.yaml: Change default rain_threshold from 1 to 0 to properly detect rain (above: 0 triggers for any positive value)
- Fix rain_pause.yaml: Add missing yarbo.return_to_dock call after yarbo.pause to match documented behavior
- Fix error_reporting.py: Change Sentry scrub pattern from 'key' to '_key' to avoid false-positive redaction of fields like entity_key
- Fix low_battery_notification.yaml: Use device_entities() to find charging entity instead of fragile name-based construction
* Fix rain_stop trigger and entity_key scrubbing bugs
- Change rain_threshold default from 0 to 0.1 to fix rain_stop trigger
(below: 0 never fires since sensor returns 0 when dry, not negative)
- Fix error_reporting scrub pattern to use endswith() and exclude entity_key
(substring match '_key' in 'entity_key' still matched despite intent)
* Fix rain resume condition to check for docked states instead of paused
The resume condition was checking for 'paused' state, but after calling
yarbo.return_to_dock, the robot transitions to 'returning' then 'idle'
or 'charging'. This caused the resume feature to never trigger after
rain stopped. Now checks for 'idle' or 'charging' states instead.
* Fix resume condition to include paused state for dock failure recovery
Add 'paused' state to the rain-stop resume condition to handle cases where
yarbo.return_to_dock fails (e.g., obstacle, connectivity loss). Without this,
the robot would remain stranded in paused state with no automatic recovery,
since the condition only checked for idle/charging (successful dock states).
* fix: address review feedback on PR #38
rain_pause.yaml:
- Add 5 s delay between yarbo.pause and yarbo.return_to_dock to avoid
race condition where device may not accept dock command before fully
processing pause (markus-lassfolk review)
- Update blueprint description to document that threshold 0.1 is
deliberate — using 0 would break the rain-stop trigger (below: 0
never fires) and that resume intentionally re-launches the mowing job
- Expand rain_threshold input description to explain the 0.1 rationale
error_reporting.py:
- Replace endswith("_key") with "_key" in key_lower so compound names
like api_key_id and encryption_key_backup are also redacted
- Add exact match for bare "key" field name
- Introduce _KEY_ALLOWLIST frozenset (entity_key, key_format, key_type)
replacing the fragile != "entity_key" hardcode (markus-lassfolk review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix dead code in _KEY_ALLOWLIST: remove key_format and key_type entries
These entries can never be matched by the scrub condition since they contain 'key_' (not '_key'). Only entity_key is functional and needed in the allowlist.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Bugfix/three bug fixes (#39)
* Fix three bugs: add charging check to battery notification blueprint, fix mutable default dict in service schema, and fix channel light commands resetting other channels
* Fix charging check to fail-open when entity unavailable
Changed from is_state(charging_entity, 'off') to not is_state(charging_entity, 'on')
to ensure low battery notifications are not silently suppressed when the charging
entity is unavailable, unknown, or doesn't exist. This fail-open approach only
suppresses notifications when the system is certain the robot is charging.
* Fix weather forecast API usage, Sentry HA version tag, and redundant property
- Replace deprecated forecast attribute with weather.get_forecasts service in snow deployment blueprint
- Fix HA version tag in Sentry to use hass.config.version instead of as_dict()
- Remove redundant source_type property from YarboDeviceTracker class
* Fix critical bugs in Yarbo integration
- Fix AttributeError: use homeassistant.const.__version__ instead of hass.config.version
- Fix coordinator shutdown: call super().async_shutdown() to ensure base class cleanup
- Fix Sentry DSN: remove SENTRY_DSN fallback to maintain opt-in only behavior
* Fix TypeError when forecast is None in snow deployment automation
Add explicit check for 'is not none' before applying length filter to prevent
TypeError when weather entity returns None for forecast data instead of empty list.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: Milestone v0.2.0 — Full Telemetry & Controls (#40)
* feat: implement #11 — Light platform: all-lights group + 7 individual channels
- YarboAllLightsGroup: single entity controlling all 7 LED channels simultaneously,
enabled by default, brightness 0-255 via client.set_lights(YarboLightState(...))
- YarboChannelLight: 7 individual channel entities (led_head, led_left_w, led_right_w,
body_left_r, body_right_r, tail_left_r, tail_right_r), all disabled by default
- Full brightness support per channel via async_turn_on/async_turn_off
- Tests: 15 tests covering group and channel entity behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #12 — Switch platform: buzzer toggle
- YarboBuzzerSwitch: entity disabled by default, icon mdi:volume-high
- turn_on calls client.buzzer(state=1), turn_off calls client.buzzer(state=0)
- State tracked from last command (assumed_state=True, no telemetry feedback)
- Tests: 9 tests covering all behaviors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #13 — Number platform: chute velocity
- YarboChuteVelocityNumber: range -2000 to 2000, disabled by default, icon mdi:rotate-right
- Availability gated: only available when head_type == HEAD_TYPE_SNOW_BLOWER (0)
- async_set_native_value calls client.set_chute(vel=int(value))
- yarbo.set_chute_velocity service already registered in services.py
- Tests: 12 tests covering range, availability, and command dispatch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #14 — Extended sensors: RTK status, heading, chute angle, rain sensor
- All extended sensors disabled by default (entity_registry_enabled_default=False)
- YarboRtkStatusSensor: enum, maps RTKMSG.status to invalid/gps/dgps/rtk_float/rtk_fixed
- YarboHeadingSensor: degrees, from RTKMSG.heading
- YarboChuteAngleSensor: degrees, snow blower head only, from RunningStatusMSG.chute_angle
- YarboRainSensor: measurement, from RunningStatusMSG.rain_sensor_data
- YarboSatelliteCountSensor: from GNGGA num_satellites
- YarboChargingPowerSensor: W, calculated from charge_voltage_mv x charge_current_ma
- Diagnostic sensors: YarboOdomConfidenceSensor, YarboRtcmAgeSensor,
YarboChargeVoltageSensor (mV), YarboChargeCurrentSensor (mA), YarboMqttAgeSensor
- Updated strings.json and translations/en.json with new entity names
- Tests: 46 tests covering all sensor behaviors, availability, and values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #15 — Low battery notification blueprint
- blueprints/automation/yarbo/low_battery.yaml: automation blueprint with inputs
battery_sensor (entity, device_class=battery), threshold (default 20%), notify_target
- Trigger: numeric_state below threshold on battery_sensor
- Action: service call to notify_target with battery percentage in message
- mode: single to prevent duplicate notifications
- Tests: 9 tests covering file existence, YAML validity, inputs, trigger, and mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address review feedback on PR #40 — telemetry & controls
- test_number: add missing `assert entity.native_value == 0.0` in
test_set_zero_stops_chute for consistency with other test cases
(Copilot suggestion, comment 2862001982)
- sensor: add EntityCategory.DIAGNOSTIC to YarboSatelliteCountSensor
for consistency with other diagnostic sensors
- sensor: remove SensorStateClass.MEASUREMENT from YarboMqttAgeSensor
to avoid polluting long-term statistics with unbounded growing values
when the robot is offline
- sensor: convert YarboChargeVoltageSensor and YarboChargeCurrentSensor
from mV/mA to V/A so HA energy dashboard unit conversion works
correctly with SensorDeviceClass.VOLTAGE/CURRENT
All 82 tests pass, ruff clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove state_class from YarboRtcmAgeSensor to prevent unbounded statistics
RTCM correction age grows unbounded when base station is unavailable,
similar to mqtt_age when robot is offline. Removing state_class prevents
polluting Home Assistant long-term statistics database with arbitrarily
large values.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Forge <forge@openclaw.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: Milestone v1.0.0 — HACS Default Ready (code) (#42)
* feat: implement #25 — firmware update entity (informational)
- entity_category = EntityCategory.CONFIG per spec
- installed_version reads from deviceinfo_feedback, ota_feedback, and
firmware_version raw MQTT keys (priority order)
- latest_version returns coordinator.latest_firmware_version when
cloud_enabled option is True; None otherwise (no install trigger)
- No UpdateEntityFeature.INSTALL — OTA is AWS Greengrass managed
- Add tests/test_update.py with 15 tests covering all version paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #26 — options flow with live throttle update
- Add _async_update_options listener in async_setup_entry so changes
to telemetry_throttle (and other options) take effect immediately
without a full HA restart; coordinator.update_options() applies them
- coordinator.update_options() updates _throttle_interval at runtime
- Add latest_firmware_version attribute to coordinator (future cloud use)
- Add tests/test_options.py: 13 tests covering defaults, update_options,
schema validation, and option key constants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #27 — repair flows: controller lost, MQTT disconnect, cloud token expired
- Add custom_components/yarbo/repairs.py with helper functions:
async_create/delete_mqtt_disconnect_issue (WARNING, not fixable)
async_create/delete_controller_lost_issue (ERROR, fixable)
async_create/delete_cloud_token_expired_issue (WARNING, not fixable)
- coordinator._heartbeat_watchdog now uses repairs.async_create_mqtt_disconnect_issue
instead of inline ir calls
- coordinator.report_controller_lost() / resolve_controller_lost() allow
command handlers to raise/clear the controller_lost repair
- Repair issues auto-resolve when the underlying condition is fixed
- Add tests/test_repairs.py: 22 tests covering all three repair types and
coordinator integration (idempotency, create/delete sequencing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #28 — complete strings.json and translations/en.json
- Add three new repair issue translation keys to strings.json and en.json:
mqtt_disconnect: base station unreachable (actionable recovery steps)
controller_lost: session stolen (fixable with confirm step description)
cloud_token_expired: 401/403 from cloud API (reauth instructions)
- Existing entity names, config/options flow strings, and service
descriptions were already complete; no entity translations changed
- translations/en.json mirrors strings.json exactly per hassfest requirements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix: Add missing async_create_fix_flow handler for controller_lost repair issue
Implements the required RepairsFlow handler for the controller_lost repair
issue which was marked as fixable but lacked the necessary flow handler.
When users click 'Fix' in the HA UI, the flow now properly re-acquires
the controller session and clears the repair issue.
* Fix circular import, orphaned repair issues, and wire up controller_lost reporting
- Fix circular import between coordinator.py and repairs.py by using TYPE_CHECKING guard
- Add cleanup for legacy telemetry_timeout repair issues on upgrade to prevent orphaned issues
- Wire up report_controller_lost() in all service handlers to make controller_lost repair flow functional
* Fix controller_lost auto-resolve and remove unused cloud_token_expired helpers
- Add coordinator.resolve_controller_lost() calls after successful get_controller() in all 7 service handlers to auto-clear stale repair issues
- Remove unused async_create_cloud_token_expired_issue and async_delete_cloud_token_expired_issue functions and related tests
- Update repairs.py docstring to reflect only 2 supported repair conditions
* fix: resolve hassfest translation schema error and address all PR #42 review feedback
- strings.json / translations/en.json: remove top-level `description` from
`controller_lost` (fixes hassfest exclusion-group error for fixable issues);
add `fix_flow.abort` translations for `cannot_connect` and `unknown`;
remove orphaned `cloud_token_expired` translation entry (no code creates it)
- repairs.py: wrap `get_controller()` call in `coordinator.command_lock` to
prevent race conditions with concurrent service calls; log exception on
controller re-acquisition failure (narrows bare `except Exception`)
- coordinator.py: delete stale `mqtt_disconnect` issue in `_async_setup` so
a pre-restart issue is cleaned up on reload (watchdog re-raises if needed);
add public `entry` property to expose config entry without accessing `_entry`
- update.py: change `entity_category` from CONFIG to DIAGNOSTIC (read-only
informational entity per HA guidelines); use public `coordinator.entry`
- tests/test_update.py: update mock to expose `coordinator.entry`; rename
`test_entity_category_is_config` → `test_entity_category_is_diagnostic`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix repair issue cleanup on restart and entry removal
- Add controller_lost issue cleanup in _async_setup to mirror mqtt_disconnect handling
- Add repair issue cleanup in async_unload_entry to prevent orphaned issues
- Fixes stale controller_lost issues persisting after HA restart
- Fixes orphaned repair issues when config entry is removed
* Fix repair flow missing description_placeholders for confirm step
The controller_lost repair flow's confirm step now properly passes the robot name as a description_placeholder, resolving the issue where {name} was displayed as literal text instead of the actual robot name.
* Fix repair flow robot name default to match coordinator
- Import CONF_ROBOT_NAME constant in repairs.py
- Use CONF_ROBOT_NAME instead of string literal 'robot_name'
- Change default from 'unknown' to 'Yarbo' to match coordinator.py
- Ensures consistent robot name display in repair UI
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
* feat: Milestone v0.3.0 — Scheduling & Weather (#41)
* feat: implement #16 — yarbo.start_plan service
- Change start_plan to use publish_raw (matching issue spec)
- Add comprehensive tests for service registration and start_plan handler
- Tests cover: publish_raw call, planId payload, unknown device error,
controller acquisition order, and _get_client_and_coordinator helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #17 — rain pause blueprint
- Switch rain_sensor from numeric_state to binary_sensor (on/off)
- On rain start: pause robot, wait 2s, return_to_dock
- After rain stops + dry_delay: resume (only if planning sensor still on)
- dry_delay uses duration selector with 2h default
- Condition: only acts when binary_sensor.*_planning is on
- mode: restart so new rain event cancels pending resume timer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #18 — snow deployment blueprint
- Switch trigger to numeric_state on snowfall_sensor above threshold
- Add notify_target input (required) — always sends notification on trigger
- Make plan_id optional (default empty) — plan only started when provided
- Works with any HA weather sensor (Met.no, OpenWeatherMap, Ecowitt)
- mode: single prevents re-triggering while already active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #19 — job complete notification blueprint
- New file: blueprints/automation/yarbo/job_complete.yaml
- Trigger: state change on event entity attribute event_type to job_completed
- Notification includes robot friendly_name from the event entity
- Inputs: yarbo_event (event entity), notify_target (text/service name)
- mode: queued so multiple robots can fire without losing events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: implement #20 — optional cloud auth for metadata and firmware checks
- Add async_step_cloud to config flow (optional, skippable by leaving blank)
- name step redirects to cloud step; entry created after cloud step
- Stores only cloud_refresh_token (never the password) in config_entry.data
- Add async_step_reauth + async_step_reauth_confirm for token expiry
- update.py: async_update fetches latest firmware from cloud when enabled;
falls back to installed_version so entity shows no update without cloud
- strings.json + translations/en.json: add reauth_confirm step, reauth_successful
abort, cloud_auth_failed and cloud_not_available errors
- Tests cover: skip cloud, auth success/failure, reauth, firmware entity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address test failures and lint issues
- conftest.py: set YARBO_SENTRY_DSN="" early to prevent sentry-sdk
BackgroundWorker thread from starting during tests (python-yarbo calls
init_error_reporting() at import time)
- test_services.py: fix test_device_not_in_domain_data_raises to mock
device registry instead of creating a real device with a fake config entry
- test_cloud_auth.py: fix mock API shape to match real YarboCloudClient
(constructor takes username/password; connect() does login; auth.refresh_token)
- config_flow.py + update.py: remove redundant type: ignore and noqa comments
- All 22 tests pass; ruff check/format clean; mypy errors all pre-existing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address three bugs - undefined threshold variable, duplicate blueprint, and refresh token persistence
* Fix: Enable polling for firmware update entity to fetch cloud versions
Override should_poll to True in YarboFirmwareUpdate to allow the update
platform to automatically call async_update() and fetch the latest firmware
version from the cloud. Without this, the cloud firmware check feature was
non-functional as CoordinatorEntity sets should_poll=False by default.
* Fix stale version cache and missing planning sensor bugs
- Clear _latest_version cache when cloud is disabled or fetch fails
- Revert blueprint to use activity_sensor instead of non-existent planning binary sensor
* fix: resolve CI failure and address all review feedback on PR #41
CI fix:
- tests/conftest.py: stub homeassistant.components.dhcp (and its transitive
deps aiodhcpwatcher, aiodiscover, cached_ipaddress) before HA imports to
prevent ModuleNotFoundError in test_cloud_auth.py
- pyproject.toml: add per-file-ignores E402 for conftest.py (sys.modules
manipulation must precede HA imports; this is intentional)
update.py:
- Remove private attribute injection (_session, auth._session); use the
library's public connect()/disconnect() lifecycle only
- Ensure cloud_client.disconnect() is always called via try/finally
- Raise ConfigEntryAuthFailed on auth-related errors so HA triggers reauth
- Remove unused aiohttp import
config_flow.py:
- async_step_cloud: move disconnect() into finally block so it always runs
even when connect() raises; narrow except to log the error message
- async_step_reauth_confirm: same try/finally fix
- async_step_cloud: clear _pending_data immediately after use (swap with {})
to avoid retaining credentials in memory after flow completes
rain_pause.yaml:
- rain_stop sequence: change state condition from 'paused' to 'charging';
after yarbo.return_to_dock the robot is in charging state, not paused, so
the resume condition previously never matched
- rain_sensor description: clarify that a Template binary_sensor helper is
needed when using the numeric Yarbo rain sensor
test_cloud_auth.py:
- Remove aiohttp.ClientSession patch from test_async_update_fetches_latest_version
(update.py no longer manages the session manually)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix rain resume condition and auth error detection
- Fix rain_pause.yaml: Change resume condition from 'charging' to 'paused' to prevent spurious resume when robot is idle at dock
- Fix update.py: Remove overly broad 'token' keyword from auth error detection to avoid false positives on non-auth errors
* fix: Add SCAN_INTERVAL to prevent excessive cloud API polling
Set SCAN_INTERVAL to 6 hours for firmware version checks to avoid
polling the cloud API every 30 seconds (HA default). Firmware versions
change infrequently, so checking every 6 hours is more appropriate and
prevents unnecessary network traffic, rate limiting, and token rotation.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Forge <forge@openclaw.ai>
* fix: resolve 8 test failures across cloud auth and services
update.py:
- Add async_update() to fetch latest firmware version from Yarbo cloud API
using stored refresh_token — skips call when cloud disabled or no token
- Fix latest_version property to fall back to installed_version when cloud
is disabled (avoids 'update unknown' banner) and read from coordinator
cache (coordinator.latest_firmware_version) when cloud is enabled
- Import YarboCloudClient with try/except for graceful fallback
- Add _latest_version instance attribute; async_update sets both local attr
and coordinator.latest_firmware_version
tests/test_cloud_auth.py:
- Fix TestFirmwareUpdate: mock coordinator.entry (public property) instead
of coordinator._entry (private attr) — latest_version uses coordinator.entry
- Update latest_version=None assertion comment to reflect new fallback logic
tests/test_services.py:
- Fix TestStartPlanService: start_plan changed from publish_raw → publish_command
in services.py milestone v1.0.0; update all 3 affected test assertions
* fix: autofix test assertions and blueprints (#44)
* Fix bugs: correct test assertions, prevent invalid rain threshold, remove duplicate blueprint, fix mock entry property
* test: fix blueprint tests to use low_battery_notification.yaml
* Fix assertion error message and docstring to reference notify_service instead of notify_target
* lint: remove unused noqa on broad exception
* style: ruff format test_services
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Extract duplicated controller acquisition logic into helper function (#45)
Refactored the seven identical try/except blocks for controller acquisition
into a single _acquire_controller helper function. This eliminates ~42 lines
of duplicated code and ensures consistent error handling across all service
handlers.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* ci: fix action versions (downgrade hallucinated v6 to actual v4)
* Fix _scrub_event to redact key_ prefix fields (e.g. key_material, key_data)
- Add key_lower.startswith('key_') so prefix patterns are redacted
- CI and test updates
Made-with: Cursor
* fix: ruff format CI failure in error_reporting.py (#46)
* Initial plan
* fix: correct ruff format in error_reporting.py to fix CI lint failure
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
* fix: ruff format CI failure blocking develop→main merge (#47)
* Initial plan
* fix: ruff format error_reporting.py to fix CI lint check
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: markus-lassfolk <3661143+markus-lassfolk@users.noreply.github.com>
Co-authored-by: Markus Lassfolk <markus@lassfolk.net>
* Fix: Upgrade GitHub Actions to latest versions in CI workflow (#48)
- Update actions/checkout from v4 to v6
- Update actions/setup-python from v5 to v6
- Update actions/upload-artifact from v4 to v7
This aligns ci.yml with other workflows (release.yml, gitleaks.yml) and restores the latest action versions with security patches and Node.js 24 support.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Fix security scrubbing and null firmware version bugs (#49)
* Fix security scrubbing and null firmware version bugs
- Fix error_reporting.py to catch concatenated key patterns like 'apikey' by adding endswith('key') check
- Fix update.py to properly handle null firmwareVersion from cloud API using result.get() instead of key existence check
* fix: handle null/missing firmwareVersion and E501 in error_reporting
- update.py: explicitly clear _latest_version and coordinator.latest_firmware_version
when cloud response has firmwareVersion: null, missing key, or non-dict type;
adds debug logging for each case to aid diagnosis of stale-version issues
- error_reporting.py: break long boolean expression on line 74 to stay under 100 chars (E501)
- tests/test_update.py: add TestAsyncUpdate class with two tests covering
firmwareVersion: null and missing key, verifying cached version is cleared
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Rover/DC endpoints (#50), no hardcoded IPs (#51), discovery + manual fallback
- Config flow: discover endpoints via python-yarbo; select step with DC recommended
- Store alternate_broker_host, connection_path, rover_ip for device info and failover
- Coordinator: failover to alternate endpoint on connection error
- Device info: configuration_url; Connection sensor with path and rover_ip
- Discovery: use only python-yarbo (no custom MQTT/DNS); manual IP/port when 0 found
- Audit: remove hardcoded IPs; docs use <broker-ip>, tests use 192.0.2.1 (RFC 5737)
- Config entry version 2 migration
Made-with: Cursor
* Closes #1: Config flow manual IP entry + MQTT validation
- async_step_user: broker IP and port form
- async_step_mqtt_test: connect via python-yarbo, 10s telemetry, extract SN
- Errors: cannot_connect, no_telemetry, decode_error; duplicate SN → already_configured
- unique_id = robot_sn in async_step_name
Made-with: Cursor
* Closes #2: Config flow DHCP auto-discovery
- manifest dhcp C8FE0F* triggers async_step_dhcp
- Store discovered IP/MAC, show confirm form; on confirm run MQTT validation
- Already configured robot → abort already_configured; DC IP change → reconfigure broker_host
Made-with: Cursor
* Closes #26: Options flow telemetry debounce, personality, cloud, auto_controller
- telemetry_throttle (float 1.0 default), applied in coordinator without restart
- auto_controller: gate get_controller before commands in all services
- cloud_enabled / activity_personality already used by update and sensor
- Options update listener propagates to coordinator immediately
Made-with: Cursor
* Closes #27: Repair flows for MQTT disconnect, controller lost, cloud token expired
- MQTT disconnect: warning issue, check-base-station steps, auto-resolve when telemetry resumes
- Controller lost: error issue, fixable Re-acquire flow, auto-resolve on re-acquire
- Cloud token expired: warning issue on 401/403 from cloud API, fixable flow triggers reauth
- Reauth success clears cloud_token_expired issue; unload clears all three
- All issue translation keys in strings.json
Made-with: Cursor
* Closes #28: Full strings.json translations
- Config flow: all step titles, descriptions, field labels, abort/error messages
- Options flow: all option labels and descriptions (fix: options apply immediately)
- Entity names for all platforms (sensor, binary_sensor, button, event, light, switch, number, device_tracker, lawn_mower, update)
- State translations: activity, head_type, rtk_status, event event_type
- Service descriptions and repair issue messages (mqtt_disconnect, controller_lost, cloud_token_expired)
- translations/en.json kept identical to strings.json
Made-with: Cursor
* Closes #29: HACS default submission (in-repo prep)
- hacs.json present and valid (name, render_readme, homeassistant, content_in_root)
- README has required badges; manifest has all required keys
- docs/hacs-default-submission.md: checklist for external PR to hacs/default
- External submission deferred until brands PR and release per issue
Made-with: Cursor
* Closes #30: Brands PR to home-assistant/brands (in-repo prep)
- docs/brands-pr.md: asset spec (icon.png 256x256, icon@2x.png 512x512, logo.png) and submission steps
- External PR to home-assistant/brands deferred per issue
Made-with: Cursor
* Primary/Secondary failover from discovery order (like DNS)
- Use endpoint order from python-yarbo discovery; first = Primary, second = Secondary
- CONF_BROKER_ENDPOINTS: ordered list for failover; no reordering by recommended
- Coordinator: on connection error try next in list (Primary→Secondary→Primary);
stay on Secondary until it fails, then try Primary again
- Update config entry broker_host on failover so device info and next failover correct
- Select-endpoint UI labels Primary/Secondary; migration for existing entries
Made-with: Cursor
* Fix all PR #53 review threads and CI failures
Review thread fixes:
- Thread 1 (discovery.py): Normalize endpoint_type to canonical values via
_normalize_endpoint_type() helper regardless of what python-yarbo returns
- Thread 2 (config_flow.py): Update self._broker_port from chosen endpoint
in async_step_select_endpoint
- Thread 3 (coordinator.py): Acquire command_lock during MQTT client swap
to prevent commands in-flight during failover
- Thread 4 (sensor.py): YarboConnectionSensor reads host from active client
(coordinator.client.host) to reflect failover state
- Thread 5 (coordinator.py): CONF_BROKER_HOST is actively used in failover;
no change needed (confirmed by ruff — clean)
- Thread 6 (config_flow.py): Remove unused ENDPOINT_TYPE_DC import; add
ENDPOINT_TYPE_UNKNOWN which was missing (caused F821)
- Thread 7/9 (__init__.py): Implement proper async_migrate_entry for v1→v2
migration; remove inline migration from async_setup_entry
- Thread 8 (repairs.py): Add missing await on config_entries.flow.async_init
so reauth flow actually starts
- Thread 11 (coordinator.py): Use contextlib.suppress on old_client.disconnect
inside command_lock to prevent client leak on partial failure
- Thread 12 (__init__.py): Fix mis-indented import block in async_unload_entry
- Thread 13/16 (config_flow.py): Fix DHCP discovery fallthrough — add proper
return for multi and single endpoint cases after discovery
- Thread 14 (coordinator.py): Fix failover skipping primary — use idx=-1
when current_host not in endpoints so next_idx wraps to 0 (Primary)
- Thread 15 (config_flow.py): Fix dead code — unreachable single-endpoint
block after return in async_step_user
CI fixes:
- Hassfest: Remove top-level description from fixable cloud_token_expired
issue in strings.json and translations/en.json (conflicting fixable group)
- Tests: Set entity.hass = MagicMock() in test helpers so async_update can
call async_delete_cloud_token_expired_issue without NoneType error
- Ruff E501: Fix all lines > 100 chars in coordinator.py, config_flow.py,
discovery.py, update.py
- Ruff F401: Remove unused ENDPOINT_TYPE_DC import from config_flow.py
- Ruff F841: Remove unused errors variable in async_step_user
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add get_controller() after failover, document defensive user_input branch
* Closes #1, #2: Config flow tests for manual IP + MQTT validation and DHCP discovery
- Implement TestManualConfigFlow: manual form when no discovery, full create
entry, cannot_connect, no_telemetry, decode_error, duplicate robot aborts
- Implement TestDhcpDiscoveryFlow: confirm form, full create entry,
already_configured (same MAC+IP), same MAC new IP shows reconfigure
- Add enable_custom_integrations fixture to config flow tests
- Stub DhcpServiceInfo in conftest with ip/macaddress/hostname constructor
- Use pytest_homeassistant_custom_component.common.MockConfigEntry for DHCP
already_configured and reconfigure tests
- Options flow tests left skipped for issue #26
Made-with: Cursor
* Closes #26: Options flow tests — telemetry_throttle, auto_controller, cloud_enabled, activity_personality
- test_options_flow_shows_form: init options flow shows form with step_id init
- test_options_flow_saves_options: save options and assert entry.options updated
- Options flow and apply-without-restart were already implemented; tests were skipped
Made-with: Cursor
* Closes #27: Repair flows tests — cloud_token_expired and async_create_fix_flow
- Add TestCloudTokenExpiredRepair: create/delete, WARNING, fixable, translation_key
- Add TestCreateFixFlow: async_create_fix_flow returns YarboRepairFlow
- Add ISSUE_CLOUD_TOKEN_EXPIRED constant test
- MQTT disconnect, controller lost, cloud token expired and fix flows were already implemented
Made-with: Cursor
* fix: update connection_path on failover, port from discovery, endpoints on reconfigure, lint fixes
- Thread 1: Update CONF_CONNECTION_PATH during failover so sensor shows active path
- Thread 2: Update broker_port from discovered endpoint in single-endpoint case
- Thread 3: Update CONF_BROKER_ENDPOINTS during reconfigure to keep failover list current
- Fix 6 ruff lint errors (missing type annotations, unused import, import sorting)
* fix: DHCP port assignment, replace dead _endpoint_types with config-based path label
* fix: swap connection path labels on failover, preserve endpoints on reconfigure
- Thread 1: Connection path now swaps (dc↔rover) instead of hardcoding based on alt host
- Thread 2: Reconfigure preserves/updates CONF_BROKER_ENDPOINTS when _broker_endpoints_ordered is empty
* fix: YarboLocalClient uses broker= not host=
Fixes TypeError when connecting: the python-yarbo library expects
broker= parameter, not host=. Updated all 3 call sites.
* feat: DHCP discovery probes MQTT for serial number
- Added _probe_serial_number() to config flow: connects with empty SN
(wildcard subscription), extracts SN from first telemetry message
- Discovery now shows 'Yarbo {SN}' — multiple robots each get their
own discovery entry with unique_id = serial number
- If robot is sleeping (no telemetry), falls back to MAC as unique_id
- IP changes auto-update existing entries via _abort_if_unique_id_configured
- Updated confirm step description to include serial number
- Updated tests to mock _probe_serial_number for DHCP flow tests
Closes #50
* feat: streamlined discovery — show Yarbo {SN} directly, skip MQTT test
When DHCP discovers a Yarbo and the MQTT probe successfully identifies
the serial number, the integration page shows 'Yarbo {SN}' as a
discovered device. User clicks Add → enters a name → done.
No redundant MQTT test step since connectivity was already proven
during the probe. Falls back to full MQTT test if robot was sleeping
during probe.
This supports multiple Yarbos on the same network — each gets its
own discovery entry identified by serial number.
* feat: discovery shows bot name or serial number
DHCP probe now extracts both serial number AND bot name (robotName/
snowbotName from telemetry). Discovery shows whichever is available:
- 'MyYarbo' (bot name) if the owner named their robot
- 'ABC12345' (serial) as fallback
- Name step pre-fills with bot name instead of 'Yarbo XXXX'
* fix: pre-import paho-mqtt in executor to avoid HA blocking-call warnings
The paho-mqtt import triggers synchronous file I/O (listdir, read_text)
which HA's event loop detector flags. Pre-importing in an executor
before creating YarboLocalClient avoids the warnings and prevents
potential connection interruption.
* fix: run MQTT probe entirely in thread executor
Paho-mqtt's constructor and connect() trigger synchronous importlib
metadata lookups which block HA's event loop. Replaced the async
YarboLocalClient probe with a pure-sync paho client running entirely
in run_in_executor — no async code, no event loop interaction.
This eliminates all blocking-call warnings and the mysterious rc=0
disconnect that occurred when paho's I/O was interrupted.
* fix: decompress zlib payloads in MQTT probe
Yarbo MQTT payloads are zlib-compressed JSON. The sync probe was
trying raw json.loads() which failed on compressed data. Now
decompresses first with zlib fallback for plain JSON (heartbeats).
* fix: discovery shows 'Yarbo {SerialNumber}' consistently
Bot name is only available via cloud API, not local MQTT. Always
display serial number in discovery: 'Yarbo 24400102L8HO5227'.
* feat: ARP table discovery for Yarbo devices
Added _discover_from_arp() that scans the local ARP table ('ip neigh')
for devices with MAC OUI C8:FE:0F. This catches existing Yarbo devices
on the network without waiting for DHCP lease renewal — critical for
networks with long lease times.
When user clicks Add Integration → Yarbo, the integration immediately
finds Yarbos already on the network via ARP + MQTT probe for SN.
Also fixed broker= parameter in __init__.py (was still using host=).
* feat: auto-discover Yarbos on startup via ARP scan
Added async_setup() to __init__.py that triggers an ARP table scan
30 seconds after HA startup. Found Yarbos (MAC OUI C8:FE:0F) get a
DHCP discovery flow created, which probes MQTT for serial number.
Result: Yarbo shows up as a discovered device on the Integrations page
with its serial number visible — no DHCP lease renewal needed.
Also handles dict-format discovery_info from the ARP trigger alongside
the standard DhcpServiceInfo from HA's DHCP watcher.
* fix: show 'Yarbo {SN}' on discovery card via title_placeholders
Sets context['title_placeholders'] = {'name': 'Yarbo {SN}'} in the
DHCP discovery step so the Integrations page shows the serial number
on the discovery card instead of just 'Yarbo'.
* fix: discovery card title shows 'Yarbo {SN}' via flow_title
Added config.flow_title = '{name}' to strings.json which uses the
title_placeholders to display the serial number as the flow title
on the Integrations discovery card.
* fix: discovery subtitle shows just serial number
* fix: flow_title is plain 'Yarbo', SN on second line via title_placeholders
* fix: remove flow_title, let manifest name handle first line
* fix: flow_title shows SN on line 1, Yarbo on line 2
* fix: Yarbo on line 1 (manifest), SN on line 2 (title_placeholders)
* fix: flow title shows Yarbo · SN
* fix: discovery title shows Yarbo · SN via title_placeholders name
* fix: discovery shows SN on line 1, Yarbo on line 2
* fix: pass coroutine directly to async_call_later, not via lambda
The lambda called hass.async_create_task from outside the event loop,
causing RuntimeError. async_call_later handles coroutine callbacks
natively.
* fix: remove unsupported timeout kwarg from get_controller()
* fix: skip get_controller at setup, use connect_sync in executor
get_controller() is only needed before sending commands, not for
receiving telemetry. Deferring it avoids the timeout on setup.
connect_sync runs in executor to avoid paho's blocking I/O warnings.
* fix: revert to async connect (connect_sync blocked startup)
* fix: swap HEAD_TYPE_SNOW_BLOWER and HEAD_TYPE_LAWN_MOWER values
Live MQTT telemetry reports head_type=1 for a visually confirmed
snow blower head. Our constants had 0=snow_blower, 1=lawn_mower
which was backwards. Confirmed via robot SN 24400102L8HO5227.
* docs: clarify head_type wire values vs Dart enum indices
Blutter decompilation shows Dart HeadType enum as:
0=SnowBlower, 1=LawnMower, 2=LawnMowerPro, 3=LeafBlower,
4=SmartCover, 5=Trimmer, 6=None
But MQTT wire protocol uses DIFFERENT numbering:
Wire 1 = Snow Blower (confirmed visually)
Wire 0 = Lawn Mower (assumed, inverse of Dart)
The _HEAD_TYPE_MAP in the APK likely remaps wire→enum.
* fix: correct all head_type wire values from APK _HEAD_TYPE_MAP
Decoded the Smi-encoded keys from the Dart _HEAD_TYPE_MAP in
deviceRules.dart (Blutter decompilation). The complete mapping:
Wire 0 = None, 1 = SnowBlower, 2 = LeafBlower, 3 = LawnMower,
4 = SmartCover, 5 = LawnMowerPro, 99 = Trimmer.
Wire 1 = SnowBlower confirmed via live MQTT + visual inspection.
* feat: add 30+ missing MQTT entities, fix GPS location + satellite count
- GNGGA parser: extract lat/lon, sat count, HDOP, altitude, fix quality
- device_tracker: real GPS coordinates from RTK telemetry (was showing 'Away')
- satellite_count: now parsed from GNGGA (was reading wrong field)
- New sensors: head_serial, battery_temp_error, base_station_status,
heading_dop, heading_status, antenna_distance, wireless_charge_state/error,
chute_steering_info, nav sensors, head gyro pitch/roll, machine_controller,
odom x/y/phi, GPS altitude/HDOP/fix quality, route priorities, RTCM source
- New ultrasonic sensors (enabled by default): left_front, middle, right_front
- New binary sensors: planning_active, returning_to_charge, going_to_start,
follow_mode, planning_paused, manual_controller, rain_detected
- All diagnostic sensors disabled by default (EntityCategory.DIAGNOSTIC)
- Translations for all new entities in strings.json + en.json
- Test stubs for yarbo + aiodns modules
Closes #55
* Add work plan entities
* ci: add CodeQL config to suppress IoT privacy false positives
* fix: address PR #54 review comments and CI failures
- light: replace YarboLightState.all_off() with explicit zero-value
instantiation (library class has no all_off class method)
- config_flow: mask MAC address in DHCP debug log (GH Advanced Security)
- config_flow: add TODO explaining why paho is used directly in
_probe_robot_identity instead of python-yarbo
- config_flow: remove redundant conditional where both branches called
async_step_confirm() identically
- discovery: fix module docstring to accurately describe ARP scanning,
endpoint normalization, and fallback behaviour
- discovery: keep MAC in colon-delimited format from 'ip neigh' output
instead of stripping colons
- coordinator: use CONF_ROVER_IP metadata to set CONF_CONNECTION_PATH on
failover instead of blindly toggling dc/rover
- coordinator: add asyncio.sleep(2) backoff after successful failover
- sensor: use public coordinator.entry property instead of private _entry
- __init__: use config_entries.SOURCE_DHCP constant instead of "dhcp"
string literal
- binary_sensor: change rain sensor comparison from > 0 to != 0 to avoid
TypeError on non-numeric values and match other binary sensors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Sync with origin/develop: keep MQTT test via executor, update README and strings
Made-with: Cursor
* fix: address PR #54 review comments — resource leak fixes and lint
- config_flow.py: wrap loop_stop()/disconnect() in a finally block so
the paho MQTT thread and TCP connection are always cleaned up, even
if loop_stop() itself raises.
- coordinator.py: move old_client.disconnect() into a finally block
inside the failover swap so the old client is always disconnected
even if async_update_entry or other post-swap work raises.
- config_flow.py: add # noqa: E402 to local imports that must live
below the try/except optional-import guards (ruff CI fix).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address PR review comments — MQTT probe finally cleanup, failover old client disconnect
Made-with: Cursor
* merge: fix/client-reference-race-condition into develop
* merge: fix/binary-sensor-and-failover-bugs into develop
* merge: fix/resource-leak-bugs into develop
* merge: merge-main-into-develop into develop
* feat: Implement Reaver protocol findings (#99-#102) (#103)
* feat: implement Reaver protocol findings (#99-#102)
- Fix wrong command names from Dart/UI naming confusion
- Add head-type validation for head-specific commands
- Mark diagnostic commands as active-state-only
- Fix set_person_detect payload schema
Closes #99, Closes #100, Closes #101, Closes #102
* Refactor: Extract duplicated head-type validation logic to shared helper
- Created validate_head_type_for_command() in const.py to encapsulate validation logic
- Updated button.py and services.py to use the shared helper function
- Eliminates code duplication and ensures consistent validation behavior
- Each caller can still raise their specific exception type (HomeAssistantError vs ServiceValidationError)
* test: fix service test mocks for updated get_client_and_coordinator usage
* chore: clean up temporary patch scripts
* test: fix typing in service test mock
* style: ruff format
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat: add must-have entities (#56–#68) (#92)
* feat: add must-have entities (Closes #56, Closes #57, Closes #58, Closes #59, Closes #60, Closes #61, Closes #62, Closes #63, Closes #64, Closes #65, Closes #66, Closes #67, Closes #68)
* feat: add nice-to-have and advanced entities (Closes #69, Closes #70, Closes #71, Closes #72, Closes #73, Closes #74, Closes #75, Closes #76, Closes #77, Closes #78, Closes #79, Closes #80, Closes #81, Closes #82, Closes #83, Closes #84, Closes #85, Closes #86, Closes #87, Closes #88, Closes #89, Closes #90, Closes #91)
* fix: address all council review findings + implement #93-#97
Critical bug fixes:
- cmd_vel payload: use {"vel": ..., "rev": ...} instead of {"linear"/"angular"} (bug #1)
- manual_stop button: send {"vel": 0, "rev": 0} (bug #1)
- del_plan: use {"planId": plan_id} string key, remove int() coercion (bug #2)
- camera/laser/USB toggles: send {"enabled": true/false} boolean (bug #3)
- cmd_roller: convert from switch to YarboRollerSpeedNumber entity, 0-3500 RPM (bug #4)
Warning fixes:
- coordinator: fix get_battery_cell_temps to use explicit None checks (warn #5)
- coordinator: run diagnostic requests in parallel with asyncio.gather (warn #6/#7)
- light: head_light available check includes None and "" (warn #8)
- select: _attr_options tuple → list, _turn_type_map typed as Mapping (warn #9)
- sensor: battery cell temp + motor temp use UnitOfTemperature.CELSIUS (warn #10)
- sensor: wifi_network marked disabled-by-default (warn #11)
- strings/en.json: rename head_light to "Head Mounted Light" (warn #12)
- coordinator: read_schedule → read_schedules topic (warn #13)
- button: add TODO comment on song_cmd payload (warn #14)
- switch: add TODO comment on ignore_obstacles vs obstacle_toggle (warn #15)
- number: plan_start_percent persisted to config entry options (#16)
- services: start_plan accepts optional percent field (#17)
- config_flow: mask MAC address in DHCP discovery log (#18)
- coordinator: fix docstring — push-based with periodic diagnostic polling (#19)
- coordinator: extract module-level _to_float, remove duplicates (#20)
New features:
- #93: YarboBatteryChargeMinNumber + YarboBatteryChargeMaxNumber (0-100%, step 5)
- #94: YarboSmartBlowingSwitch + YarboEdgeBlowingSwitch (leaf blower only)
- #95: YarboMotorProtectSwitch + YarboMowerHeadSensorSwitch (lawn mower only)
- #96: YarboRoofLightsSwitch (roof_lights_enable topic)
- #97: YarboSoundEnableSwitch (set_sound_param topic)
All translation strings updated in strings.json + translations/en.json
Tests updated for all changed behaviour (352 passing, 1 pre-existing l…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI lint was failing because
ruff formatrequired reformatting of theelifcondition added to_scrub_event()inerror_reporting.py.Changes
error_reporting.py— Reformattedelifto ruff-preferred style: inner parens dropped,and key_lower not in _KEY_ALLOWLISTmoved outside the condition groupNo logic change — purely cosmetic to satisfy
ruff format --check.💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.