Skip to content

fix(r2449a): correct CleaningMode value mapping; add WATER_VOLUME_FINE; verify SuctionMax#10

Open
carvalho2707 wants to merge 1 commit into
malard:mainfrom
carvalho2707:r2449a-cleanmode-fix
Open

fix(r2449a): correct CleaningMode value mapping; add WATER_VOLUME_FINE; verify SuctionMax#10
carvalho2707 wants to merge 1 commit into
malard:mainfrom
carvalho2707:r2449a-cleanmode-fix

Conversation

@carvalho2707

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #9 from a 2026-05-25 observation probe on r2449a (Dreame X40 Ultra Complete, firmware FU174072 EU). Three findings, all r2449a-only:

  • 🚨 Breaking — CleaningMode value mapping was inverted on Sweeping / SweepAndMop. Re-verified by tapping each sub-mode in the Dreamehome app (label-before-tap protocol) and reading back the MQTT echo. Behavioural symptom that prompted the re-check: writing 0 for "Sweeping" caused the dock to attach the mop pad (the device actually entered SweepAndMop).

    App label 2.6 4.23 4.23 & 0x3
    Vac + Mop 0 5120 (0x1400) 0
    Mop 1 5121 (0x1401) 1
    Vac 2 5122 (0x1402) 2
    Mop After Vac 3 5123 (0x1403) 3

    Old mapping was round-trip-verified only (write N → echo N), which doesn't establish named-mode ↔ value pairing.

  • Add DOCK_PROP.WATER_VOLUME_FINE at siid 28 piid 1 — fine-grained per-job mop-water slider, integer 1..32. This is the axis the Dreamehome app surfaces; the existing VACUUM_PROP.WATER_VOLUME (siid 4.5) coarse 3-step Low/Medium/High is the legacy axis.

  • Promote FEATURE_CONFIG_KEYS.SuctionMax from ~ to — boolean (0/1) Max Suction Power toggle. Writeable in both Sweeping and MopAfterSweep sub-modes (the two the app surfaces it in).

r2532a scoping

Per-model, strictly. Existing r2532a paragraphs in the CLEAN_MODE_SETTING and CLEANING_MODE JSDocs were named under the inverted mapping, so the named direction of the 5120 ↔ 5122 mop-install/remove correlation is now uncertain on r2532a. The physical correlation (clean-mode change co-fires with the dock sequence) is unchanged. Both JSDocs now carry a "needs re-verification on r2532a" note instead of the previous direction claim.

Test plan

  • vitest run — all 341 tests pass (updated test/miot-spec.test.ts for the corrected enum values).
  • tsc --noEmit clean.
  • eslint . clean.
  • Behavioural: the corrected mapping is what sobreda's downstream integration has been running against for the past day with no mode/pad mis-matches.
  • r2532a re-verification — out of scope for this PR; flagged in JSDocs.

🤖 Generated with Claude Code

…NE; verify SuctionMax

A 2026-05-25 observation probe on r2449a (Dreame X40 Ultra Complete,
firmware FU174072 EU) using a label-before-tap protocol (typed marker
in terminal before each tap in the Dreamehome app, then read MQTT echo)
caught three things the v0.4.1 PR (malard#9) either got wrong or didn't
cover.

Breaking
- CleaningMode value mapping corrected on r2449a — Sweeping and
  SweepAndMop values were inverted in v0.4.1 / v0.4.2. The prior
  mapping was round-trip-verified (write N → echo N) but the named-mode
  ↔ value pairing was not established against the app. Corrected
  values:
    SweepAndMop = 0   (was 2)
    Mopping = 1       (unchanged)
    Sweeping = 2      (was 0)
    MopAfterSweep = 3 (unchanged)
  Behavioural corroboration: with the old mapping, writing 0 for
  "Sweeping" caused the dock to attach the mop pad (device actually
  entered SweepAndMop).

  r2532a — needs re-verification. Earlier r2532a notes that paired
  5120 ↔ 5122 transitions with the mop-install / remove dock sequence
  were named under the inverted mapping. The physical correlation is
  unchanged; the named direction depends on whether r2532a uses the
  same value mapping as r2449a. JSDocs on CLEAN_MODE_SETTING and
  CLEANING_MODE now carry a "needs re-verification on r2532a" note
  instead of the previous named-mode direction claim.

Added
- DOCK_PROP.WATER_VOLUME_FINE at siid 28 piid 1. Fine-grained per-job
  mop-water volume; integer slider 1..32. The axis the Dreamehome app
  surfaces in the per-mode water controls — the coarse
  VACUUM_PROP.WATER_VOLUME (siid 4 piid 5) 3-step Low/Medium/High
  enum is the legacy axis; new code should write the fine field.
  Verified on r2449a 2026-05-25 by dragging the slider through
  1, 8, 16, 32 and observing the echo. Not yet observed on r2532a.

Changed
- FEATURE_CONFIG_KEYS.SuctionMax promoted from ~ to ✓. Boolean
  (0 = off, 1 = on), writeable in both Sweeping and MopAfterSweep
  sub-modes (the two sub-modes the app surfaces the toggle in — any
  mode with a pure-vacuum phase). Verified on r2449a 2026-05-25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@malard

malard commented May 26, 2026

Copy link
Copy Markdown
Owner

🤖 AI-generated review (Claude Code, on behalf of @malard). Verified locally: checked out the branch, ran vitest run test/miot-spec.test.ts (16 pass) and tsc --noEmit (clean), and traced the enum swap through every usage. A human will still sign off.

Verdict: sound correction, well-evidenced — but it leaves one self-contradictory JSDoc that should be fixed before merge.

What's good

  • The methodology critique is the heart of the PR and it's correct. Round-trip verification (write N → echo N) never establishes a named-mode ↔ value pairing — it only proves the field is writable. The label-before-tap protocol plus the behavioural corroboration (writing 0 for "Sweeping" caused the dock to attach the mop pad) is strong, independent evidence: a vacuum-only mode would never install a pad, so 0 = SweepAndMop explains the symptom directly.
  • Good discipline on r2532a. Downgrading the named direction to "physical co-firing holds, naming needs re-verification" rather than propagating the now-suspect claim is the right call.
  • No code path hardcodes the swapped numeric values. state.ts:311 just range-casts num as CleaningMode; there are no switch/literal comparisons on the enum names. The swap is a pure semantic/contract change, correctly flagged as Breaking.
  • WATER_VOLUME_FINE at siid 28 piid 1 — no collision with existing siid 28 piids (2,4,5,8,22,27,28,29,38,63). Clean addition.

Should fix before merge — stale CleanGeniusSubMode cross-reference

The swap leaves CleanGeniusSubMode's JSDoc (src/spec/enums.ts:330-341) self-contradictory. It still claims the enum is "a 2-element subset of CleaningMode's SweepAndMop and MopAfterSweep members." But CleanGeniusSubMode.VacAndMop = 2, and after this PR CleaningMode.SweepAndMop = 0. Only MopAfterVac/MopAfterSweep = 3 still lines up — the VacAndMop half of the "subset" claim is now false.

This is also a consistency question, not just a doc nit: CleanGeniusSubMode (VacAndMop=2) was VERIFIED on the same day (2026-05-21) as the old CleaningMode mapping, presumably by the same round-trip method this PR now distrusts. Two possibilities:

  • (a) siid 28.5 genuinely uses a different value encoding than siid 2.6/4.23. Plausible — the repo already documents three different sub-mode encodings (CleaningMode 0..3, CleanGeniusSubMode 2/3, ScheduleCleaningMode 1..4). If so, just drop the "subset of CleaningMode" sentence.
  • (b) CleanGeniusSubMode is itself mis-mapped by the same flaw and needs the same label-before-tap re-check.

Cheapest resolution is (a) + a one-line note on whether CleanGeniusSubMode was re-confirmed in the 2026-05-25 session.

Minor (non-blocking)

  • WATER_VOLUME_FINE has no API path. The JSDoc/CHANGELOG say "new code should write the fine field," but setWaterVolume() / setSettings({ waterVolume }) still write the coarse VACUUM_PROP.WATER_VOLUME (siid 4.5). Spec-first is fine, but the "write here instead" guidance has no setter behind it yet — a follow-up setWaterVolumeFine (or a note that it's intentionally spec-only) would close the gap.
  • Adjacent, out of scope: setCleaningMode() (vacuum.ts:1137) writes the packed CLEANING_MODE bitfield (siid 4.23) directly — exactly the "drops the 0x1400 capability bits" trap the corrected JSDoc warns against, and its docstring is now stale post-feat(r2449a): X40 Ultra Complete compat + decode CLEANING_MODE bitfield #9. Worth a follow-up to redirect it to CLEAN_MODE_SETTING (siid 2.6).
  • Versioning: breaking enum-semantics change → next release should be v0.5.0 (consistent with the (pre-v0.5) rename note already in vacuum-props.ts). CHANGELOG correctly files it under Breaking/Unreleased. ✓

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants