TL;DR
Reading the PIN or settings currently makes the mower look like fresh status data was received. Old battery, mode, and station values can therefore appear current even though no status packet was read.
Problem
state_from_message() always updates last_seen, last_response_cmd, and raw for every parsed message. It does this regardless of whether the message contains mower status fields.
Relevant code:
The method documentation says the raw helper updates state “when status fields exist,” but the implementation updates temporal state for every response.
Why it happens
The model uses one timestamp for two different concepts:
- the last time any BLE response was received;
- the last time actual mower status was received.
An auth response (0x8c), mower-settings response (0x89), multi-area response (0x8d), schedule response (0x84/0x85), or BlueKey debug response proves that the mower communicated. It does not provide a current battery level, work mode, or station state.
Because replace(base, **updates) preserves old fields, a non-status response combines a new timestamp with old status values.
Current behavior
Example:
- A status poll reports battery 50%, returning mode, and
last_seen=12:00.
- The mower moves and its state changes.
- At 12:30, a settings query returns only settings data.
state_from_message() keeps battery 50% and returning mode but changes last_seen to 12:30.
- Consumers can now display the old status as fresh and available.
A first-ever non-status raw response can also make MowerState.available become True without any status packet.
Expected behavior
Track communication freshness and status freshness separately.
For example:
last_communication: any valid response;
last_status_seen: only a valid status response containing confirmed status fields;
available: based on current transport/status policy, not any arbitrary parsed packet.
Alternatively, only call state_from_message() for messages that contain status fields and store raw/settings responses outside MowerState.
Impact
Home Assistant and other consumers can:
- show stale battery and activity values as current;
- mark status entities available after a settings or debug call;
- hide the fact that the mower is no longer reachable for status polling;
- make proxy-handoff troubleshooting misleading because “last seen” reflects the wrong packet type.
Suggested direction
Make the state update explicit:
state_from_status_message(...)
record_communication(...)
If preserving one function, update last_seen only when the message has validated status semantics, such as a valid DYM 0x80 packet with at least one confirmed status field.
Acceptance criteria
- Auth, PIN, settings, multi-area, schedule, and unrelated raw responses do not update the status timestamp.
- A non-status response cannot make a never-polled mower state available.
- Old battery/mode/station values are not paired with a new status timestamp.
- A separate communication timestamp may still be exposed for diagnostics.
- Existing valid
0x80 status and command-follow-up responses continue to refresh status state.
TL;DR
Reading the PIN or settings currently makes the mower look like fresh status data was received. Old battery, mode, and station values can therefore appear current even though no status packet was read.
Problem
state_from_message()always updateslast_seen,last_response_cmd, andrawfor every parsed message. It does this regardless of whether the message contains mower status fields.Relevant code:
state_from_message()always setslast_seenMowerState.availableis based only onlast_seenGrouwMower.async_send_raw_json()always passes the response throughstate_from_message()The method documentation says the raw helper updates state “when status fields exist,” but the implementation updates temporal state for every response.
Why it happens
The model uses one timestamp for two different concepts:
An auth response (
0x8c), mower-settings response (0x89), multi-area response (0x8d), schedule response (0x84/0x85), or BlueKey debug response proves that the mower communicated. It does not provide a current battery level, work mode, or station state.Because
replace(base, **updates)preserves old fields, a non-status response combines a new timestamp with old status values.Current behavior
Example:
last_seen=12:00.state_from_message()keeps battery 50% and returning mode but changeslast_seento 12:30.A first-ever non-status raw response can also make
MowerState.availablebecomeTruewithout any status packet.Expected behavior
Track communication freshness and status freshness separately.
For example:
last_communication: any valid response;last_status_seen: only a valid status response containing confirmed status fields;available: based on current transport/status policy, not any arbitrary parsed packet.Alternatively, only call
state_from_message()for messages that contain status fields and store raw/settings responses outsideMowerState.Impact
Home Assistant and other consumers can:
Suggested direction
Make the state update explicit:
If preserving one function, update
last_seenonly when the message has validated status semantics, such as a valid DYM0x80packet with at least one confirmed status field.Acceptance criteria
0x80status and command-follow-up responses continue to refresh status state.