You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Issue #3 (closed) covered the start-refusal case where an empty clean-water tank rejects vacuum.start() and surfaces as MiotError.CleanWaterTankEmpty = 107. That path works.
This issue is about the sibling case the closing comment didn't cover: the tank running empty mid-task, where the device pauses an in-progress clean. In that scenario Vacuum.state doesn't expose anything actionable — neither errorCode nor faults[] populate, and the taskLifecycle event does not fire aborted.
Net effect on a consumer: a scheduled clean silently fails to complete, the robot sits paused for hours, and the lib provides no signal that a user-actionable condition exists.
miotState: 21 is MiotState.PausedCleaning. The pause is real, the cause is real, but neither errorCode nor faults[] carries 107 (or any code) for the duration of this paused-mid-task condition.
No taskLifecycle: aborted event fired either — likely because the task hasn't been aborted from the device's point of view, it's been paused pending user action. From the consumer's point of view it's identical (schedule will not complete until intervention) but the event taxonomy doesn't reflect that.
What the consumer needs
A signal — any of these would suffice — that distinguishes a normal user-initiated pause from a paused-because-needs-attention state:
errorCode populated on the paused-mid-task transition (matching the start-refusal behaviour). Simplest and consistent.
taskLifecycle event with a new phase: \"paused\" (or \"halted\") carrying a reason field, mirroring the aborted envelope.
A separate pauseReason field on Vacuum.state populated when miotState === PausedCleaning and the underlying cause is known.
Investigation hints
The Dreamehome app clearly knows the cause, so the signal is reaching the cloud — question is which MIoT property carries it.
The tank-fill request in Expose clean-water tank fill state (and dirty-water tank, optionally) #3 was deferred for lack of a graded fill code; this case may be the same signal observed from a different angle. If a WATER_TANK_LEVEL (or similar) property turns up in the dock cluster (siid 27/28 was suggested), it may flip 0→1 at this pause boundary.
FAULTS_STR (siid 4 piid 18) co-fires with ERROR for the start-refusal case per the existing docs — worth confirming whether it stays empty for the paused-mid-task case (we observed it empty here).
Reproduction
Start a clean while the clean-water tank has just enough to run partway.
Happy to capture MQTT/HTTP traces from this device if a candidate siid/piid is identified.
Workaround in the consumer
For now, the dunbar-os consumer treats miotState === PausedCleaning lasting longer than N minutes with no progress as a heuristic "needs attention" signal, but that's heuristic — a typed signal from the lib is preferable.
Summary
Issue #3 (closed) covered the start-refusal case where an empty clean-water tank rejects
vacuum.start()and surfaces asMiotError.CleanWaterTankEmpty = 107. That path works.This issue is about the sibling case the closing comment didn't cover: the tank running empty mid-task, where the device pauses an in-progress clean. In that scenario
Vacuum.statedoesn't expose anything actionable — neithererrorCodenorfaults[]populate, and thetaskLifecycleevent does not fireaborted.Net effect on a consumer: a scheduled clean silently fails to complete, the robot sits paused for hours, and the lib provides no signal that a user-actionable condition exists.
Observed live (verified)
Device:
dreame.vacuum.r2532a, fw4.3.9_2199, node-dreame0.4.1.The Dreamehome app shows: paused, low water level in clean water tank.
Vacuum.statefrom the live MQTT subscription (state_age fresh ~8s after a refresh ACK):{ "miotState": 21, "miotStateRaw": 21, "errorCode": null, "faults": [], "stuck": false, "battery": 100, "charging": null, ... (most fields null) ... }miotState: 21isMiotState.PausedCleaning. The pause is real, the cause is real, but neithererrorCodenorfaults[]carries107(or any code) for the duration of this paused-mid-task condition.No
taskLifecycle: abortedevent fired either — likely because the task hasn't been aborted from the device's point of view, it's been paused pending user action. From the consumer's point of view it's identical (schedule will not complete until intervention) but the event taxonomy doesn't reflect that.What the consumer needs
A signal — any of these would suffice — that distinguishes a normal user-initiated pause from a paused-because-needs-attention state:
errorCodepopulated on the paused-mid-task transition (matching the start-refusal behaviour). Simplest and consistent.taskLifecycleevent with a newphase: \"paused\"(or\"halted\") carrying areasonfield, mirroring theabortedenvelope.pauseReasonfield onVacuum.statepopulated whenmiotState === PausedCleaningand the underlying cause is known.Investigation hints
WATER_TANK_LEVEL(or similar) property turns up in the dock cluster (siid 27/28 was suggested), it may flip 0→1 at this pause boundary.FAULTS_STR(siid 4 piid 18) co-fires with ERROR for the start-refusal case per the existing docs — worth confirming whether it stays empty for the paused-mid-task case (we observed it empty here).Reproduction
Vacuum.state—miotState: 21,errorCode: null,faults: [].Happy to capture MQTT/HTTP traces from this device if a candidate siid/piid is identified.
Workaround in the consumer
For now, the dunbar-os consumer treats
miotState === PausedCleaninglasting longer than N minutes with no progress as a heuristic "needs attention" signal, but that's heuristic — a typed signal from the lib is preferable.