feat(sensor): audit base_status fields, add station/consumable diagnostics#52
feat(sensor): audit base_status fields, add station/consumable diagnostics#52jgus wants to merge 2 commits into
Conversation
…gnostics
Decoded the full RobotBaseStatus proto and corrected mislabeled fields
parsed into state: battery_health→curingAgentConsumptionPercent (f38),
session_id→bindedUuid (f13), timestamp→stationBagHealthResetTime (f36),
upgrade status (read f2, not f4=stage), download state (read f3, not
f1=type); BaseStatusField/UpgradeStatusField enums corrected to match.
Added diagnostics from fields the robot already broadcasts:
- sensors: dust bag health (f35), detergent remaining (f41)
- binary_sensors (problem, field-gated): error/fault (f1, exposing
code/level/detail + a best-effort help-center link), clean-water and
sewage tanks, dust box, dust bag, station dust bag
Binary sensors are now description-driven; ha_stubs gains the
BinarySensorEntityDescription stub for the new tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile SummaryThis PR corrects several mislabeled proto field mappings in
Confidence Score: 5/5Safe to merge; field corrections are backed by decompiled proto evidence, the stale-fault regression case is explicitly tested, and new entities degrade gracefully to unavailable on models that do not report those fields. The field-rename corrections are consistently applied across both the standalone narwal_client tree and the custom_components mirror. The error-clear-on-absent-field fix is the most behaviorally sensitive change and is covered by a dedicated regression test. Both findings are design or style concerns that do not affect correctness under the stated assumption that station-equipped models always include fields 35 and 41 in every base_status response. custom_components/narwal/sensor.py — the dust_bag_health and detergent_remaining value functions gate availability on the most-recent raw_base_status snapshot; worth verifying with a live deep-sleep capture that fields 35 and 41 are present in polled responses. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[base_status received] --> B{Source?}
B -->|Broadcast| C[update_from_base_status]
B -->|Deep-sleep poll| D[update_battery_from_base_status]
C --> E[raw_base_status = decoded]
D --> F[raw_base_status = decoded]
E --> G[field 2 battery_level]
E --> H[_update_consumables]
F --> G2[field 2 battery_level]
F --> H2[_update_consumables]
H --> N[_parse_error_codes field 1]
H --> O[fields 20 21 23 24 39 tank states]
H --> J[field 35 dust_bag_health]
H --> K[field 41 detergent_remaining]
N --> P{field 1 absent?}
P -->|Yes| Q[error_codes empty has_error False]
P -->|No| R[parse codes level detail]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[base_status received] --> B{Source?}
B -->|Broadcast| C[update_from_base_status]
B -->|Deep-sleep poll| D[update_battery_from_base_status]
C --> E[raw_base_status = decoded]
D --> F[raw_base_status = decoded]
E --> G[field 2 battery_level]
E --> H[_update_consumables]
F --> G2[field 2 battery_level]
F --> H2[_update_consumables]
H --> N[_parse_error_codes field 1]
H --> O[fields 20 21 23 24 39 tank states]
H --> J[field 35 dust_bag_health]
H --> K[field 41 detergent_remaining]
N --> P{field 1 absent?}
P -->|Yes| Q[error_codes empty has_error False]
P -->|No| R[parse codes level detail]
Reviews (2): Last reviewed commit: "fix(base-status): clear error state when..." | Re-trigger Greptile |
protobuf omits an empty repeated field, so a recovered robot drops the
errorCode field entirely. The old `if "1" in decoded` guard left has_error
and error_codes latched on the prior fault forever, sticking the Error
sensor on "Problem". Parse unconditionally via decoded.get("1") — the parser
already clears to no-error on None/empty.
Also corrects the update_battery_from_base_status docstring (it refreshes
consumable/station/fault/tank state too, not battery alone) and adds a
regression test for the field-absent recovery path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
A full decode of the
RobotBaseStatusproto turned up several mislabeledfields already being parsed into state, plus a number of useful signals the
robot broadcasts that weren't surfaced. This corrects the former and exposes the
latter as diagnostic entities.
Field corrections (were mapped to the wrong proto field)
battery_health→curingAgentConsumptionPercent(f38)session_id→bindedUuid(f13)timestamp→stationBagHealthResetTime(f36)BaseStatusField/UpgradeStatusFieldenums corrected to matchNew entities (all from fields already broadcast)
problemdevice class, gated on the field being present):error/fault (f1 — exposes code/level/detail + a best-effort help-center link),
clean-water tank, sewage tank, dust box, dust bag, station dust bag
Binary sensors are now description-driven (small refactor) to keep the additions
declarative.
Caveat
The error help-center URL is a best-effort deep link — the app builds it from
a localized base we can't read over LAN, so the template is inferred from the
Flow's help-center family and falls back to showing the raw code. Worth a sanity
check if a real fault opens the wrong page.
How it was derived
Field/enum identities decoded from the app's compiled protobuf
BuilderInfoandconstant pool; live-confirmed where possible.
Testing
New
tests/test_binary_sensor.py;test_models.py/ha_stubsupdated; suite green.Relates to
Dependent PRs, stacked on this one (review after it merges): #53 (last clean result), #54 (consumable alerts).