diff --git a/BACKLOG.md b/BACKLOG.md index 5ead68f..3e41a66 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -84,8 +84,9 @@ Sources: Valetudo, python-miio, hass-xiaomi-miot, r/Xiaomi, r/homeassistant - 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) -- 🔜 Next planned item: Mission Control vacuum-extend value decoding — translate the known raw `siid 4` enum values in the advanced diagnostics panel (for example `mop-mode`, `waterbox-status`, `break-point-restart`, and `carpet-press`) into human-readable labels while preserving the raw values for debugging. (Planned 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) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4c478..cf10207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.2](https://github.com/dacrypt/xiao/compare/v0.7.1...v0.7.2) (2026-05-26) + +### Fixed + +* **dashboard:** decode the known X20+ `vacuum-extend` diagnostics enums into human labels while preserving each raw `siid 4` value for debugging + ## [0.7.1](https://github.com/dacrypt/xiao/compare/v0.7.0...v0.7.1) (2026-05-14) ### Fixed diff --git a/pyproject.toml b/pyproject.toml index d3e26ef..791cd65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xiao-cli" -version = "0.7.1" +version = "0.7.2" description = "CLI tool and web dashboard to control Xiaomi/Roborock robot vacuums via cloud API" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/xiao/__init__.py b/src/xiao/__init__.py index 3245fc5..ea7b610 100644 --- a/src/xiao/__init__.py +++ b/src/xiao/__init__.py @@ -1,3 +1,3 @@ """xiao - CLI tool to control Xiaomi/Roborock robot vacuums.""" -__version__ = "0.6.5" +__version__ = "0.7.2" diff --git a/src/xiao/dashboard/index.html b/src/xiao/dashboard/index.html index ab9d202..bcb4f1a 100644 --- a/src/xiao/dashboard/index.html +++ b/src/xiao/dashboard/index.html @@ -2900,6 +2900,20 @@

Start cleaning?

`; + const vacuumExtendCleaningModeMap = { 0: 'Quiet', 1: 'Standard', 2: 'Medium', 3: 'Strong' }; + const vacuumExtendMopModeMap = { 1: 'Low', 2: 'Medium', 3: 'High' }; + const vacuumExtendWaterboxStatusMap = { 0: 'Detached', 1: 'Attached' }; + const vacuumExtendToggleMap = { 0: 'Disabled', 1: 'Enabled' }; + + function formatVacuumExtendEnum(value, labels) { + if (value === null || value === undefined || value === '') return '--'; + const numericValue = Number(value); + const hasNumericLabel = Number.isFinite(numericValue) && Object.hasOwn(labels, numericValue); + const label = hasNumericLabel ? labels[numericValue] : labels[value]; + if (label === undefined) return `${value}`; + return `${label} · raw ${value}`; + } + // Clean Log extended (siid 4) const cleanLogArea = document.getElementById('cleanLogArea'); const cl = d.clean_log || {}; @@ -2946,15 +2960,15 @@

Start cleaning?

Total Area
-
${cl.prop4 ?? '--'}
+
${formatVacuumExtendEnum(cl.prop4, vacuumExtendCleaningModeMap)}
4.4 Cleaning Mode
-
${cl.prop5 ?? '--'}
+
${formatVacuumExtendEnum(cl.prop5, vacuumExtendMopModeMap)}
4.5 Mop Mode
-
${cl.prop6 ?? '--'}
+
${formatVacuumExtendEnum(cl.prop6, vacuumExtendWaterboxStatusMap)}
4.6 Waterbox Status
@@ -2962,11 +2976,11 @@

Start cleaning?

4.7 Task Status
-
${cl.prop11 ?? '--'}
+
${formatVacuumExtendEnum(cl.prop11, vacuumExtendToggleMap)}
4.11 Resume After Charge
-
${cl.prop12 ?? '--'}
+
${formatVacuumExtendEnum(cl.prop12, vacuumExtendToggleMap)}
4.12 Carpet Boost
diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 470c427..23cca44 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -127,3 +127,17 @@ def test_dashboard_relabels_known_vacuum_extend_fields_in_clean_log_panel(self): assert "4.7 Unknown" not in html assert "4.11 Unknown" not in html assert "4.12 Unknown" not in html + + def test_dashboard_decodes_known_vacuum_extend_enum_values_while_preserving_raw_values(self): + html = _dashboard_html() + + assert "const vacuumExtendCleaningModeMap = { 0: 'Quiet', 1: 'Standard', 2: 'Medium', 3: 'Strong' };" in html + assert "const vacuumExtendMopModeMap = { 1: 'Low', 2: 'Medium', 3: 'High' };" in html + assert "const vacuumExtendWaterboxStatusMap = { 0: 'Detached', 1: 'Attached' };" in html + assert "const vacuumExtendToggleMap = { 0: 'Disabled', 1: 'Enabled' };" in html + assert "return `${label} · raw ${value}`;" in html + assert "formatVacuumExtendEnum(cl.prop4, vacuumExtendCleaningModeMap)" in html + assert "formatVacuumExtendEnum(cl.prop5, vacuumExtendMopModeMap)" in html + assert "formatVacuumExtendEnum(cl.prop6, vacuumExtendWaterboxStatusMap)" in html + assert "formatVacuumExtendEnum(cl.prop11, vacuumExtendToggleMap)" in html + assert "formatVacuumExtendEnum(cl.prop12, vacuumExtendToggleMap)" in html diff --git a/uv.lock b/uv.lock index 1bf6c18..91f2465 100644 --- a/uv.lock +++ b/uv.lock @@ -1762,7 +1762,7 @@ wheels = [ [[package]] name = "xiao-cli" -version = "0.5.3" +version = "0.7.2" source = { editable = "." } dependencies = [ { name = "ddddocr" },