Skip to content

feat(mower): handle onChargeInfo and onScheduleTaskInfo state pushes#1647

Open
nord- wants to merge 1 commit into
DeebotUniverse:devfrom
nord-:feat/mower-state-charge-schedule-push
Open

feat(mower): handle onChargeInfo and onScheduleTaskInfo state pushes#1647
nord- wants to merge 1 commit into
DeebotUniverse:devfrom
nord-:feat/mower-state-charge-schedule-push

Conversation

@nord-

@nord- nord- commented Jun 23, 2026

Copy link
Copy Markdown

Summary

GOAT mowers report their activity state via three unsolicited MQTT messages, but only one of them is currently handled:

Message When Handled before?
onCleanInfo manual mow start / pause ✅ via the legacy get* fallback (getCleanInfo)
onScheduleTaskInfo scheduled mow ❌ dropped as Unknown message
onChargeInfo returning to dock / work complete ❌ dropped as Unknown message

getChargeInfo and getScheduleTaskInfo are not in the legacy _LEGACY_USE_GET_COMMAND allow-list and have no dedicated handler, so both messages fall through to Unknown message. As a result the lawn_mower entity:

  • never leaves docked during a scheduled mow (the onScheduleTaskInfo push is dropped), and
  • never returns to returning / docked after a run (the onChargeInfo push is dropped) — state only refreshes via a getChargeState poll on reconnect.

This is the library side of home-assistant/core#168621 (GOAT status stuck on "docked").

Root cause (from device debug logs)

Captured from a GOAT O1200 LiDAR Pro (class 2i0fns, fw 1.9.16) over a full dock → mow → dock cycle:

onChargeInfo       {"trigger":"app","state":"goCharging"}                          -> Unknown message  (should be RETURNING)
onChargeInfo       {"trigger":"workComplete","state":"idle"}                        -> Unknown message  (should be DOCKED)
onScheduleTaskInfo {"trigger":"continue","state":"clean","cleanState":{"motionState":"working"}} -> Unknown message  (should be CLEANING)
onCleanInfo        {"trigger":"app","state":"clean","cleanState":{"motionState":"working"}}       -> StateEvent(CLEANING)  (works)

onScheduleTaskInfo carries the exact same payload shape as onCleanInfo; onChargeInfo carries a top-level state of goCharging / idle.

Changes

  • messages/json/charge_state.py (new) — OnChargeInfo: state goChargingRETURNING, idleDOCKED.
  • messages/json/clean_info.py (new) — OnScheduleTaskInfo, plus a shared handle_clean_info() holding the clean-info → StateEvent parsing.
  • commands/json/clean.pyGetCleanInfo now delegates to handle_clean_info(). The parsing moved from commands into messages, matching the existing commands → messages import direction (a top-level messages → commands import would be circular). Behaviour is unchanged. Note: other commands re-use messages handlers via class inheritance (e.g. GetBattery(OnBattery)); a shared function is used here because GetCleanInfo keeps its command base class and only needs the parsing — happy to restructure to a class-based shape if preferred.
  • messages/json/__init__.py — register the two new handlers.

State is derived from state (what the device is doing), not trigger:

message state motionState
onScheduleTaskInfo / onCleanInfo clean working CLEANING
onScheduleTaskInfo / onCleanInfo clean pause PAUSED
onScheduleTaskInfo / onCleanInfo clean goCharging RETURNING
onChargeInfo goCharging RETURNING
onChargeInfo idle DOCKED

Tests

New tests/messages/json/test_charge_state.py and test_clean_info.py cover every mapping plus the ignored / unknown-state cases. GetCleanInfo's existing test is unchanged and still passes (the refactor is behaviour-preserving).

Relation to other GOAT work

Complementary to, and deliberately non-overlapping with, the in-flight GOAT PRs:

Happy to rebase or consolidate the shared clean-info parsing with #1641 once the merge order is clear.

Copilot AI review requested due to automatic review settings June 23, 2026 12:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds dedicated JSON message handlers for GOAT mower unsolicited MQTT state pushes (onChargeInfo, onScheduleTaskInfo) so mower StateEvent updates are produced without relying on legacy get* fallbacks, addressing stuck state transitions (scheduled mowing and return-to-dock).

Changes:

  • Added OnChargeInfo handler to map mower return/dock transitions (goChargingRETURNING, idleDOCKED).
  • Added OnScheduleTaskInfo handler and refactored clean-info parsing into a shared handle_clean_info() used by both pushes and GetCleanInfo.
  • Registered new handlers in the JSON message dispatch table and added unit tests for the new mappings/ignored cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
deebot_client/messages/json/charge_state.py New onChargeInfo push handler mapping charge-related state to StateEvent.
deebot_client/messages/json/clean_info.py New onScheduleTaskInfo push handler + shared handle_clean_info() parser used by GetCleanInfo.
deebot_client/messages/json/__init__.py Registers OnChargeInfo and OnScheduleTaskInfo in MESSAGES dispatch.
deebot_client/commands/json/clean.py Refactors GetCleanInfo message parsing to delegate to handle_clean_info().
tests/messages/json/test_charge_state.py New tests validating OnChargeInfo mappings and ignored states.
tests/messages/json/test_clean_info.py New tests validating OnScheduleTaskInfo mappings and ignored states.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread deebot_client/messages/json/clean_info.py Outdated
Comment thread tests/messages/json/test_clean_info.py
GOAT mowers report their state via three unsolicited messages, but only
onCleanInfo was handled (through the legacy get-command fallback).
onChargeInfo (return-to-dock / work complete) and onScheduleTaskInfo
(scheduled mow) fell through to "Unknown message" and were dropped, so the
lawn_mower entity stayed on "docked" during scheduled mows and never
returned to docked/returning after a run.

Add dedicated message handlers:
- OnChargeInfo: state "goCharging" -> RETURNING, "idle" -> DOCKED
- OnScheduleTaskInfo: same payload shape as onCleanInfo, reuses the shared
  clean-info parser

The clean-info state parsing is moved out of GetCleanInfo into a shared
handle_clean_info() in messages/json/clean_info.py (commands -> messages,
matching the existing import direction); GetCleanInfo now delegates to it.

Verified against debug logs from a GOAT O1200 LiDAR Pro (class 2i0fns).
@nord- nord- force-pushed the feat/mower-state-charge-schedule-push branch from f8a648f to 7c2c524 Compare June 23, 2026 12:42
@nord-

nord- commented Jul 2, 2026

Copy link
Copy Markdown
Author

Anybody, like @elia-000 or @edenhaus that would like to take a look at this PR, please? 🙏🏻

@elia-000

elia-000 commented Jul 3, 2026

Copy link
Copy Markdown

Chiunque, tipo @elia-000O @edenhausche vorrebbe dare un'occhiata a questo PR, per favore? 🙏🏻

If you explain exactly what needs to be done step by step, I could run some tests and then provide feedback.

In case I need to use MQTT for testing, but I'm sorry, I can't help you. :(

@nord-

nord- commented Jul 4, 2026

Copy link
Copy Markdown
Author

Chiunque, tipo @elia-000O @edenhausche vorrebbe dare un'occhiata a questo PR, per favore? 🙏🏻

If you explain exactly what needs to be done step by step, I could run some tests and then provide feedback.

In case I need to use MQTT for testing, but I'm sorry, I can't help you. :(

I'd like a review so that the PR can be approved. Right now, the integration does not work for GOAT mowers, as stated in the PR description:

…have no dedicated handler, so both messages fall through to Unknown message.

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.

3 participants