Skip to content

Latest commit

 

History

History
126 lines (120 loc) · 26 KB

File metadata and controls

126 lines (120 loc) · 26 KB

xiao — Backlog & Roadmap

Priority Legend

  • 🔴 P1 — Bug / broken functionality
  • 🟠 P2 — Important improvement
  • 🔵 P3 — Nice to have
  • ⚪ P4 — Research / exploration

Active Bugs

  • 🔴 Room-specific cleaning still needs on-device verification — clean_rooms_miot() now preloads siid 4 piid 10 clean-extend-data and then calls official siid 2 aiid 3, and xiao now warns/returns exit code 80 when the action is accepted but the robot still looks docked/charging. Actual X20+ hardware behavior still needs confirmation after this sequence change. (Updated 2026-05-01)
  • 🔴 FIXED: set_fan_speed() was writing to siid 2 piid 2 (device fault, read-only!) instead of piid 3 (mode/fan). This means every xiao speed set command was attempting to write a fault code. Also status() was mapping piid 2 (fault) → fan_speed display, causing garbage values. Fixed 2026-03-31.
  • 🔴 FIXED: set_water_level() was writing to siid 18 piid 1 (mop-life-level, READ-ONLY %) instead of siid 4 piid 5 (mop-mode, 1=Low, 2=Medium, 3=High). Every water level command was corrupting mop consumable tracking. Also water_level() returned a stub string instead of reading from the device. Fixed 2026-04-02.
  • 🔴 FIXED: status() mapped state 13 → "In Dock" but official MIoT spec says 13 = "Charging Completed". Fixed 2026-04-02.

Improvements Planned

Research / Exploration

  • ⚪ Map extraction — MITM Mi Home app traffic to find decryption key for cloud map data
  • ⚪ Room IDs verification — map room IDs to actual room names from Mi Home app
  • clean-logs (siid 12) corrected from official miot-spec.org JSON (2026-04-07):
    • piid 1 = first-clean-time (unix timestamp) — NOT last-clean-time
    • piid 2 = total-clean-time (minutes)
    • piid 3 = total-clean-times (count)
    • piid 4 = total-clean-area
    • Implication: current cloud API/spec does not expose a dedicated last-clean-date field for c102gl via siid 12; any real per-run history likely needs app/cloud log endpoints or map/log filenames from siid 4 piid 9.
  • vacuum-extend (siid 4) — fully mapped from official miot-spec.org JSON (2026-04-02):
    • piid 4 = cleaning-mode (0=Quiet, 1=Standard, 2=Medium, 3=Strong) — READ-ONLY mirror of fan mode
    • piid 5 = mop-mode / water level (1=Low, 2=Medium, 3=High) — WRITABLE ✅ Now implemented
    • piid 6 = waterbox-status (0=No, 1=Yes) — reads whether water box is attached
    • piid 7 = task-status (uint8 0-255) — unknown semantics
    • piid 10 = clean-extend-data (string, write-only) — likely the room cleaning JSON param!
    • piid 11 = break-point-restart (0=Off, 1=On) — resume after charging — writable
    • piid 12 = carpet-press (0=Off, 1=On) — carpet boost — writable
    • piid 16 = clean-rags-tip (0-120 minutes) — mop wash reminder — writable
    • piid 17 = keep-sweeper-time (int32 minutes) — unknown
    • piid 18 = faults (string) — extended fault string
    • piid 27 = child-lock (0=Off, 1=On) — writable
    • piid 34 = smart-wash-switch (0=Off, 1=On) — smart mop wash
    • piid 36 = carpet-escape (1=Escape, 2=Auto) — carpet avoidance mode
  • Room cleaning deep dive: Official spec: siid 2 aiid 3 = start-room-sweep, in=[piid 4 (Room IDs string)]. The piid 4 (room-ids) has access: [] meaning NO standalone read/write — it only works as an action param. Next step: test clean_rooms_miot() with piid 4 value as comma-separated string like "3,8,7,6". Also try clean-extend-data (siid 4 piid 10) as alternative path — write JSON before calling start-sweep.
  • Room cleaning research update (2026-04-08):
    • Official spec page for xiaomi.vacuum.c102gl still points to siid 2 aiid 3 (start-room-sweep) with room IDs passed as piid 4 string.
    • hass-xiaomi-miot README example shows Dreame/Xiaomi custom room cleaning via siid 4 aiid 1 with params piid 1 = 18 and piid 10 = clean-extend-data, where clean-extend-data is JSON under selects.
    • dreame-vacuum issue #910 logs show the selects rows encode at least room id, repeat count, fan mode, water mode, and explicit order: e.g. [[1,1,3,1,1],[3,1,3,1,2], ...].
    • openHAB miio binding docs list both vacuum-start-room-sweep and vacuum-extend-start-clean actions for xiaomi.vacuum.c102gl, which supports the idea that both paths exist on X20+.
    • Community reports still describe code=0 / accepted actions that do nothing, so success responses alone are not proof of movement.
  • X20+ / c102gl community notes (2026-04-10):
    • hass-xiaomi-miot issue #2767 reports X20+ room-cleaning action (siid 2 aiid 3) still returning accepted responses while not actually starting room jobs for some users; reinforces that code=0 is not enough without on-device movement verification.
    • python-miio issue #2071 tracks new X20 Max model gaps (xiaomi.vacuum.d109gl) and timeout/auth pain in HA stacks; X20 family support is still uneven across ecosystem tools.
    • Xiaomi Cloud Map Extractor issue #697 continues to show cloud login/session fragility (captcha/2FA/token churn), supporting our token-refresh-first strategy.
    • No clear Valetudo support signal for xiaomi.vacuum.c102gl turned up in this pass; keep this as watch-only research, not an actionable integration path yet.
  • Dashboard diagnostics UX research (2026-04-21):
    • home-assistant/frontend uses native HTML <details>/<summary> for dense system-information and repair diagnostics blocks, which keeps advanced data accessible without overwhelming the default view.
    • openhab/openhab-webui documents accordion lists specifically for collapsible technical sections, reinforcing that diagnostics/debug data should be progressively disclosed rather than always expanded.
    • For xiao, the lowest-risk ship is to collapse the three verbose lower panels (Audio, Clean Log Raw, All Properties) by default while preserving their existing live data and DOM ids.
  • Session blocker (2026-04-08):
    • This sandbox allows editing tracked files but denies writes inside .git/. git add / git commit failed with fatal: Unable to create '.git/index.lock': Operation not permitted, so this session could not create the requested local commit.
  • Test-suite findings (2026-04-09):
    • Full pytest tests/ -v currently still has 8 unrelated failures in tests/test_cloud_vacuum.py.
    • Failing areas: status parsing / fan-speed decoding regressions, plus missing total_clean_duration_display in clean_history().
    • These pre-existing failures are outside the token-refresh scope and should be handled as a separate bugfix pass.
  • ⚪ Local UDP fallback — periodically check if vacuum becomes reachable locally
  • ⚪ Valetudo compatibility — monitor if X20+ (c102gl) gets rooting support
  • ⚪ Home Assistant integration — MQTT or REST sensor for HA
  • ⚪ Notification system — Telegram alerts for cleaning complete, errors, consumable low
  • ⚪ Floor plan editor — manual room layout in dashboard (drag & drop)
  • ⚪ vacuum-extend service (siid 4) — investigate writable properties found in official spec:
    • piid 5 = mop-mode (1=Low, 2=Medium, 3=High) — actual water level control!
    • piid 11 = break-point-restart (0=Close, 1=Open) — resume after charging
    • piid 12 = carpet-press (0=Close, 1=Open) — carpet boost
    • piid 27 = child-lock (0=Close, 1=Open)
    • piid 16 = clean-rags-tip (minutes, 0-120) — mop wash reminder interval
  • ⚪ vacuum-extend piid 10 (clean-extend-data, write-only string) — this is likely the room cleaning param! Format: probably JSON with segments, fan level, water level. Worth testing with clean_rooms_miot().

Community Features to Research

Sources: Valetudo, python-miio, hass-xiaomi-miot, r/Xiaomi, r/homeassistant

  • Zone cleaning with coordinates
  • Virtual walls / no-go zones
  • Carpet detection behavior
  • Multi-floor map support
  • Cleaning history heatmap
  • Custom voice packs
  • OTA firmware management

Completed

  • ✅ Mission Control vacuum-extend value decoding — the advanced diagnostics panel now translates the known siid 4 enum-backed values into human-readable labels while still showing the raw number for debugging (for example Quiet · raw 0, Attached · raw 1, Enabled · raw 1). Added a TDD dashboard regression that failed before the new formatter/mappings were introduced. Research notes: the official xiaomi.vacuum.c102gl MIoT/OpenHAB mappings still identify cleaning-mode as 0=Quiet/1=Standard/2=Medium/3=Strong, mop-mode as 1=Low/2=Medium/3=High, waterbox-status as 0=No/1=Yes, and both break-point-restart plus carpet-press as 0=Close/1=Open; openhab/openhab-addons mirrors the same enums for the X20+ database; and hass-xiaomi-miot issue #2767 still keeps hardware-dependent room-cleaning changes in the higher-risk bucket, which made another diagnostics-only UX ship the safer unattended choice. (2026-05-26)
  • 🔜 Next planned item: Mission Control vacuum-extend diagnostics expansion — add the next confirmed siid 4 controls (child-lock, smart-wash, and carpet-avoidance) to the advanced diagnostics panel with the same human-readable-plus-raw formatting so dashboard users can inspect the full known writable settings set at a glance. (Planned 2026-05-26)
  • ✅ Mission Control vacuum-extend diagnostics copy cleanup — the advanced diagnostics panel now relabels the known siid 4 fields as Cleaning Mode, Mop Mode, Waterbox Status, Task Status, Resume After Charge, and Carpet Boost instead of showing generic Unknown placeholders, while still leaving the underlying raw values untouched for debugging. Added a TDD dashboard regression that failed on the old labels before updating the frontend copy. Research notes: the official xiaomi.vacuum.c102gl MIoT spec still exposes these vacuum-extend properties under siid 4 (mop-mode, waterbox-status, task-status, break-point-restart, carpet-press); openhab/openhab-addons mirrors the same channel names for the X20+ model; and hass-xiaomi-miot issue #2767 still shows room-cleaning behavior is the riskier area, which kept this run focused on a safe diagnostics UX win instead of hardware-dependent action changes. (2026-05-14)
  • ✅ Waterbox attachment status surfaced across CLI + Mission Control — status() now reads the official X20+ vacuum-extend waterbox-status property (siid 4 piid 6) and exposes it as attached/detached, while the rich CLI formatters and Mission Control status tags now show whether the mop water box is present without forcing users into raw property dumps. Added TDD regression coverage for the cloud status decoder, full-status formatter, and dashboard HTML hook before implementation. Research notes: the local backlog/spec mapping already identified siid 4 piid 6 as waterbox-status; the official xiaomi.vacuum.c102gl MIoT/OpenHAB database maps it as a read-only 0 = No, 1 = Yes enum; and that made this a safe, user-visible diagnostics improvement for mopping/setup issues without depending on on-device room-clean verification. (2026-05-12)
  • ✅ State 21 naming fix (WashingMopPause) — status(), full_status(), Mission Control API responses, and dashboard state maps now keep the official X20+ state-21 name instead of mislabeling it as Water Tank Alert, while still surfacing separate recovery guidance to check/refill the base tanks when that pause appears. Also updated the tank auto-reset heuristic to key off the real state-21 name so the existing estimate-reset behavior keeps working after the label correction. Added TDD regression coverage for the cloud status decoder, full_status() alert wiring, Mission Control /api/status, tank-update auto-reset logic, and the dashboard HTML state map before implementation. Research notes: the local X20+ MIoT mapping/tests already called state 21 WashingMopPause, and the adjacent openhab/openhab-addons MIIO binding docs list the same status enum label for xiaomi.vacuum.c102gl; that made this a safe terminology/UX fix while preserving the existing tank-service recovery advice. (2026-05-11)
  • ✅ Estimated cleaning energy from clean logs — clean_history() now derives a clearly labeled lifetime energy estimate from the X20+ clean-log totals, and Mission Control plus the rich CLI history surfaces now show that estimate as kWh @ 75W so users do not have to do the math manually. Added TDD regression coverage for the cloud-history payload, full-status formatter, report formatter, and dashboard history card before implementation. Research notes: Xiaomi's published Xiaomi Robot Vacuum X20+ specs list Rated Power 75W, the official xiaomi.vacuum.c102gl clean-log fields still expose only aggregate cleaning time/count/area, and adjacent Home Assistant energy tooling (Vortitron/energy-sensor-generator) reinforced keeping the output explicitly in kWh while labeling it as an estimate rather than measured telemetry. (2026-05-08)
  • ✅ Room-clean accepted-vs-started diagnostics — xiao clean, Mission Control, and the MCP server no longer treat X20+ room-clean code=0 responses as guaranteed movement. They now share a verification helper that polls follow-up status, warns when the robot still looks docked/charging, and emits CLI exit code 80 for the clearly unresponsive case. Added TDD regression coverage for the shared verification helper plus CLI, dashboard, dashboard API, and MCP surfaces before implementation. Research notes: the official xiaomi.vacuum.c102gl MIoT spec still points room cleaning at siid 2 aiid 3 with room IDs passed as a string param; the hass-xiaomi-miot README continues to document a separate vacuum-extend clean-extend-data path (siid 4 aiid 1 + piid 10 JSON payload) for adjacent Dreame/Xiaomi models; and hass-xiaomi-miot issue #2767 shows X20+ users still getting accepted room-clean calls with no actual movement, which confirmed that shipping an accepted-vs-started diagnostic was safer than pretending success. (2026-05-01)
  • ✅ Clean-history surfaces now match the official X20+ clean-log fields — Mission Control history cards, xiao status --full, xiao report, and xiao device history --full now show first_clean_date plus aggregate totals instead of stale last_clean_* wording that the cloud backend no longer provides. Added TDD regression coverage for the dashboard history card copy, full-status formatter output, and the cloud-history merge path before implementation. Research notes: the official xiaomi.vacuum.c102gl MIoT clean-log spec still exposes only first-clean-time, total-clean-time, total-clean-times, and total-clean-area; openhab/openhab-addons mirrors those same four labels for xiaomi.vacuum.c102gl; and that confirmed our remaining Last Clean UI copy was frontend drift, not missing device data. (2026-04-30)
  • ✅ Mission Control advanced settings parity — the web dashboard now reads and writes the full shipped X20+ vacuum-extend controls (resume-after-charge, carpet-boost, child-lock, smart-wash, carpet-avoidance, and clean-rags-tip) instead of forcing users back to CLI-only commands. Added TDD regression coverage for the expanded /api/settings snapshot, the new write endpoints, invalid minutes rejection, and the dashboard DOM/API wiring before implementation. Research notes: the official xiaomi.vacuum.c102gl MIoT spec continues to expose these properties under siid 4 as stable cloud-backed controls; openhab/openhab-addons#17796 still reinforces that X20+ support is cloud-centric, which made dashboard parity a safer unattended ship than hardware-dependent room-cleaning work; and openhab/openhab-addons also surfaces adjacent vacuum-extend channels like child_lock, carpet-press, and clean_rags_tip, which supported treating these as first-class UI settings rather than hidden expert-only knobs. (2026-04-29)
  • ✅ Carpet avoidance mode — added first-class CLI support for xiao settings carpet-avoidance [avoid|auto], backed by cloud-service helpers for the X20+ vacuum-extend carpet-excape property (siid 4 piid 36), and included it in the dashboard /api/settings snapshot for programmatic consumers. Added TDD regression coverage for the cloud enum reads/writes, validation, the new CLI command, and the settings snapshot before implementation. Research notes: the official miot-spec.org JSON for xiaomi.vacuum.c102gl exposes siid 4 piid 36 with enum values 1 = Escape and 2 = Auto; openhab/openhab-addons#17796 continues to reinforce that X20+ support is cloud-centric, which kept this as a safer cloud-backed settings release than hardware-dependent room-cleaning work; and al-one/hass-xiaomi-miot#2767 shows users still struggling with room-cleaning action parameters, which further supported prioritizing a spec-backed setting over more experimental cleaning flows. CLI wording ships as avoid|auto, while still accepting the raw Xiaomi escape term for compatibility. (2026-04-28)
  • ✅ Clean rags tip interval — added first-class CLI support for xiao settings clean-rags-tip [minutes], backed by cloud-service helpers for the X20+ vacuum-extend clean-rags-tip property (siid 4 piid 16), with strict 0-120 minute validation and inclusion in the dashboard settings snapshot API for programmatic consumers. Added TDD regression coverage for the cloud property reads/writes, range validation, and the new CLI command before implementation. Research notes: the local MIoT mapping already identified piid 16 as writable clean-rags-tip, the official home.miot-spec.com/spec/xiaomi.vacuum.c102gl page continues to expose it under vacuum-extend with a 0-120 minute range, and openhab/openhab-addons surfaces the same field as Number:Time clean_rags_tip, which reinforced shipping a raw minutes control instead of guessing unsupported presets. (2026-04-27)
  • ✅ Smart wash switch toggle — added first-class CLI support for xiao settings smart-wash plus cloud-service helpers for the X20+ vacuum-extend smart-wash-switch boolean (siid 4 piid 34), and included it in the dashboard settings snapshot API for programmatic consumers. Added TDD regression coverage for both the cloud property reads/writes and the new CLI command before implementation. Research notes: the local MIoT mapping already identified piid 34 as writable, the official home.miot-spec.com/spec/xiaomi.vacuum.c102gl page still exposes it under vacuum-extend, and adjacent ecosystem threads (al-one/hass-xiaomi-miot#2767, openhab/openhab-addons#17796) continue to reinforce that X20+ support remains cloud-centric, making another spec-backed boolean toggle a safer unattended release than room-cleaning experiments. (2026-04-26)
  • ✅ Advanced vacuum-extend settings toggles — added first-class CLI support for xiao settings resume-after-charge, xiao settings carpet-boost, and xiao settings child-lock, all backed by the official X20+ MIoT vacuum-extend booleans (siid 4 piid 11/12/27). Added TDD regression coverage for both the cloud property reads/writes and the new CLI commands before implementing the feature. Research notes: the local MIoT mapping already identified these three properties as writable booleans, the official home.miot-spec.com/spec/xiaomi.vacuum.c102gl page still exposes them under vacuum-extend, and adjacent ecosystem threads (al-one/hass-xiaomi-miot#2767, openhab/openhab-addons#17796) continue to show X20+ users relying on cloud-only MIoT paths, which made CLI exposure of stable spec-backed toggles a safer unattended ship than more experimental room-cleaning work. (2026-04-25)
  • ✅ Sweep type decoding cleanup — removed the stale dashboard sweep_type wording and now surface dry_left_time_min explicitly as dry-time-left in both Mission Control and CLI rich status output, including a drying-specific status tag. Added TDD regression coverage for the dashboard copy and formatter label before implementing the fix. Research notes: the official miot-spec.org JSON for xiaomi.vacuum.c102gl confirms siid 2 piid 5 is dry-left-time in minutes, hass-xiaomi-miot examples continue to use clean-extend-data for room cleaning instead of any sweep-type property, and openhab still exposes vacuum-start-room-sweep plus vacuum-extend-start-clean, which reinforced that our old sweep-type label was legacy/spec drift rather than a real X20+ field. Also corrected the dashboard's lingering state-13 label to Charging Completed for consistency with the already-fixed backend mapping. (2026-04-24)
  • ✅ Dashboard dark/light theme toggle — Mission Control now keeps the current dark glassmorphism default, but adds an explicit light theme switch in the header and persists the user's choice in local storage so daytime use is less fatiguing without re-theming every visit. Added TDD regression coverage for the toggle markup and persistence hook. Research notes: the local README already positions Mission Control as a day-to-day dashboard surface, MDN's prefers-color-scheme guidance reinforced shipping named light/dark themes instead of ad-hoc color overrides, and home-assistant/frontend persists selectedTheme in browser storage with defensive fallbacks, which informed our saved-preference approach and storage guards. (2026-04-23)
  • ✅ CLI schedule default view + compact cadence labels — xiao schedule now shows the parsed schedule table directly without requiring the extra list subcommand, and schedule cadence text now compacts common masks to Every day, Weekdays, Weekends, and One time for faster scanning. Added TDD regression coverage for both the parser labels and the root CLI behavior. Research notes: local README/docs already positioned schedule viewing as a primary CLI workflow, and Home Assistant's schedule model explicitly treats weekdays as first-class recurrence data rather than a raw bitmask, which reinforced shipping human-readable cadence labels instead of the previous verbose day list. (2026-04-22)
  • ✅ Dashboard diagnostics cleanup — collapsed the lower-noise Audio, Clean Log (Raw), and All Properties panels by default using native <details>/<summary> sections, while keeping the same content and live refresh hooks once expanded. Added a regression test that failed before the markup change and now locks the collapsed-by-default contract. (2026-04-21)
  • ✅ CLI rooms rename command — added xiao rooms rename <id> <name> as a first-class command (same underlying behavior as alias set) so room naming flows are more discoverable. Added TDD regression test (TestCLIRooms::test_rooms_rename) that failed before command implementation and passes now. (2026-04-10)
  • ✅ Device list token auto-refresh — XiaomiCloud.get_devices() now catches TokenExpiredError, refreshes cloud tokens via browser CDP helper, and retries once before surfacing the error. This closes the remaining gap where setup/device listing could still fail on expired Xiaomi cloud sessions even though RPC/property helpers already retried. Added regression tests for both refresh-success and refresh-failure paths. (2026-04-09)
  • ✅ Room cleaning reliability improvement — clean_rooms_miot() now preloads vacuum-extend clean-extend-data (siid 4 piid 10) using a selects payload derived from room order, repeat count, current fan mode, and current water mode, then calls the official siid 2 aiid 3 room-sweep action. CLI xiao clean --room ... --repeat N now forwards repeat into that MIoT path instead of silently dropping it. Added regression tests. Hardware verification still pending. (2026-04-08)
  • ✅ Clean history / first-clean-time mapping fix — siid 12 piid 1 was incorrectly treated as last-clean-time, which made last_clean_date stale/misleading. Official MIoT spec confirms siid 12 is clean-logs: piid 1 = first-clean-time, piid 2 = total-clean-time, piid 3 = total-clean-times, piid 4 = total-clean-area. Updated clean_history(), last_clean(), and history scan output to expose first_clean_date and correct aggregate totals instead of fake last-run fields. Added regression tests. (2026-04-07)
  • ✅ Water level MIoT property bug fix — set_water_level() was writing to siid 18 piid 1 (mop-life-level, READ-ONLY %) instead of siid 4 piid 5 (mop-mode, writable). Official MIoT spec confirms: siid 4 piid 5 = mop-mode (1=Low, 2=Medium, 3=High). Also fixed water_level() to actually read from the device instead of returning stub string. Added 7 new tests. (2026-04-02)
  • ✅ Status 13 corrected — status() mapped state 13 → "In Dock" but official MIoT spec says 13 = "Charging Completed". Fixed with test. (2026-04-02)
  • ✅ MIOT_SPEC extended — Added mop-mode, break-point-restart, carpet-press, child-lock, clean-rags-tip, clean-extend-data to MIOT_SPEC dict. Updated module comments to reflect siid 4 = vacuum-extend (not clean-log). (2026-04-02)
  • ✅ Fan speed MIoT property bug fix — set_fan_speed() was writing to siid 2 piid 2 (device fault, read-only!). Official MIoT spec confirms: piid 2 = fault, piid 3 = mode/fan speed (0=Silent, 1=Basic, 2=Strong, 3=Full Speed). Fixed read and write paths. Added 5 new tests confirming correct piid usage. (2026-03-31)
  • ✅ Status fan_speed parsing corrected — was mapping piid 2 (fault code) to fan speed display. Now correctly reads piid 3 for fan speed and stores piid 2 as fault_code. (2026-03-31)
  • ✅ MIOT_SPEC corrected — fan_level now points to piid 3, fault_code added for piid 2, dry_left_time added for piid 5 (was wrongly called sweep_type). (2026-03-31)
  • ✅ Dashboard history section: fixed "--" for zero/falsy values — added total_clean_duration_display field to clean_history() (e.g. "2h 10min"), fixed JS to use ?? null-coalescing so 0 shows as 0 not --, fixed total-time label (was incorrectly appending h to a minutes value).
  • ✅ Cloud mode control (all basic actions)
  • ✅ Mission Control dashboard (glassmorphism, real-time)
  • ✅ Water Tank Alert detection (state 21) + auto-reset
  • ✅ Water tank level estimator (area-based)
  • ✅ Mop consumable tracking + reset
  • ✅ Network info in dashboard (IP, WiFi, RSSI, MAC)
  • ✅ All 4 consumable resets (main brush, side brush, filter, mop)
  • ✅ Full MIoT property scan (39 properties documented)
  • ✅ Schedule parser with room names
  • ✅ Base station controls (wash, dry, dust, eject)
  • ✅ Fan speed / water level / volume / DND settings
  • ✅ Token refresh via browser CDP