Skip to content

fix(sensor): read cleaning area from coveredArea, not a station timer#51

Open
jgus wants to merge 3 commits into
sjmotew:masterfrom
jgus:fix/cleaning-area
Open

fix(sensor): read cleaning area from coveredArea, not a station timer#51
jgus wants to merge 3 commits into
sjmotew:masterfrom
jgus:fix/cleaning-area

Conversation

@jgus

@jgus jgus commented Jun 15, 2026

Copy link
Copy Markdown

Summary

The Cleaning area sensor is permanently stuck at 1.8 m². It was reading
working_status field 13 — which is totalDryStationBagTime, a cumulative
station timer that sits at a constant 18000 (5 h) — and dividing it by 10000,
yielding a fixed 1.8. The real per-session figure is field 2, coveredArea
(float32, m²).

Changes

  • Decode coveredArea (field 2, float32) and drop the ÷10000.
  • Correct the WorkingStatusField names to match the app's compiled protobuf
    (field 13 is the station-bag timer, not area).

How it was found

Field identities were read from the decompiled app's protobuf BuilderInfo
(field tag → name → wire type), then confirmed against live broadcasts.

Testing

tests/test_models.py updated; full suite green.

working_status field 13 is totalDryStationBagTime (a cumulative station
timer, constant 18000 = 5 h), which the sensor divided by 10000 and
surfaced as a fixed "1.8 m²". The real per-session area is field 2
coveredArea (float32, m²). Decode it via _to_float32 and drop the
÷10000; correct the WorkingStatusField names from the decompiled proto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes the Cleaning Area sensor, which was permanently stuck at 1.8 m² because it was reading protobuf field 13 (totalDryStationBagTime, a constant 18 000-second station timer) and dividing by 10 000. The fix switches to field 2 (coveredArea, a float32 already in m²) and removes the division.

  • models.py: update_from_working_status now reads field "2" with _to_float32, drops the old integer cast of field "13", and updates the cleaning_area type annotation from int (cm²) to float (m²).
  • sensor.py: Removes the / 10000 divisor since the decoded value is already in m².
  • const.py / WorkingStatusField: Enum members and docstring updated to match the decompiled protobuf BuilderInfo field names.

Confidence Score: 5/5

Safe to merge — the change is a targeted field-number correction with no impact on unrelated sensors or state.

The fix is well-scoped: a wrong protobuf field number and an erroneous divisor are replaced with the correct field and no divisor. The _to_float32 helper is already used throughout the file for the same wire type, the type annotation and default value are updated consistently across both code copies, and the tests exercise the new encoding end-to-end.

Both copies of models.py share the same minor guard inconsistency between >= 0 and the math.isfinite pattern used on every other float field in the file.

Important Files Changed

Filename Overview
narwal_client/models.py Core fix: reads field "2" (coveredArea, float32) instead of field "13" (station timer). Uses _to_float32 for decoding; guard is >= 0 which passes +inf unlike the isfinite guard used on other float fields.
custom_components/narwal/narwal_client/models.py Mirror of narwal_client/models.py — same fix and same >= 0 guard (same minor concern applies).
custom_components/narwal/sensor.py Removes the erroneous / 10000 divisor now that coveredArea is decoded directly in m²; value_fn lambda is correct.
narwal_client/const.py Renames enum members and updates docstring to match decompiled protobuf field names; AREA = 2 is now correctly mapped.
custom_components/narwal/narwal_client/const.py Mirror of narwal_client/const.py — enum rename and docstring update, kept in sync correctly.
tests/test_models.py Tests updated to supply field "2" as a float32 uint32 bit pattern via _float_to_uint32; assertions correctly reflect the new float m² value. 12.5 is exactly representable in float32, so the round-trip equality check is safe.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Robot as Narwal Robot
    participant MQTT as MQTT Broker
    participant Client as NarwalClient
    participant State as NarwalState
    participant HA as Home Assistant Sensor

    Robot->>MQTT: working_status proto message
    MQTT->>Client: raw bytes
    Client->>Client: blackboxprotobuf decode → dict
    Client->>State: update_from_working_status(decoded)
    State->>State: _to_float32(decoded["2"])
    note over State: field 2 = coveredArea (float32, m²)
    State->>State: "cleaning_area = area (float, m²)"
    State->>HA: "value_fn: round(cleaning_area, 2) if > 0"
    HA-->>HA: Display area in m²
    note over State: Previously: int(decoded["13"]) / 10000
    note over State: field 13 = totalDryStationBagTime (18000s) → always 1.8 m²
Loading
%%{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"}}}%%
sequenceDiagram
    participant Robot as Narwal Robot
    participant MQTT as MQTT Broker
    participant Client as NarwalClient
    participant State as NarwalState
    participant HA as Home Assistant Sensor

    Robot->>MQTT: working_status proto message
    MQTT->>Client: raw bytes
    Client->>Client: blackboxprotobuf decode → dict
    Client->>State: update_from_working_status(decoded)
    State->>State: _to_float32(decoded["2"])
    note over State: field 2 = coveredArea (float32, m²)
    State->>State: "cleaning_area = area (float, m²)"
    State->>HA: "value_fn: round(cleaning_area, 2) if > 0"
    HA-->>HA: Display area in m²
    note over State: Previously: int(decoded["13"]) / 10000
    note over State: field 13 = totalDryStationBagTime (18000s) → always 1.8 m²
Loading

Reviews (3): Last reviewed commit: "docs(working-status): correct workingPro..." | Re-trigger Greptile

Comment thread custom_components/narwal/sensor.py Outdated
Comment thread narwal_client/const.py Outdated
Comment thread custom_components/narwal/narwal_client/const.py Outdated
jgus and others added 2 commits June 15, 2026 19:28
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
The decompiled WorkingStatus BuilderInfo marks workingProgress with
PbFieldType 0x100 (float32), not double; fix the field-map docstring in
both client copies. Also join the cleaning-area sensor comment to one line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant