Skip to content

feat: add GOAT A3000 LiDAR support (cr0e4u / 51rcxt)#1641

Open
adc103 wants to merge 1 commit into
DeebotUniverse:devfrom
adc103:feat/goat-a3000-cr0e4u
Open

feat: add GOAT A3000 LiDAR support (cr0e4u / 51rcxt)#1641
adc103 wants to merge 1 commit into
DeebotUniverse:devfrom
adc103:feat/goat-a3000-cr0e4u

Conversation

@adc103

@adc103 adc103 commented Jun 19, 2026

Copy link
Copy Markdown

Summary

Adds support for the Ecovacs GOAT A3000 LiDAR robotic mower (hardware class cr0e4u) and its Pro variant (51rcxt).

All payloads and protocols confirmed via MITM traffic analysis of live device communication.

Note: CI will fail until #1515 / #1624 merges, as CleanMower is imported from deebot_client.commands.json.clean which those PRs add. This PR is intentionally structured to avoid duplicating that work.

Changes

New hardware definitions

  • deebot_client/hardware/cr0e4u.py — Full capability definition for the GOAT A3000 LiDAR:

    • CleanMower for auto-mow start/stop/pause
    • CleanMowerArea for zone-targeted mowing
    • CapabilityMap with GetMI as the startup map-load trigger
    • Blade + lens-brush life-span items
    • Mower-specific settings: border switch, cut direction, safe protect, move-up warning, cross-map border warning, child lock, advanced mode, volume
  • deebot_client/hardware/51rcxt.py — The GOAT A3000 LiDAR Pro shares identical firmware and capabilities; delegates to cr0e4u.

New commands (deebot_client/commands/json/mow.py)

  • CleanMowerArea — Sends cleanMower with a zone type and value for targeted area mowing.
  • GetMI — Sends getMI to trigger the mower to stream its full zone map via chunked onMI/onArI MQTT pushes. This is what the Ecovacs app sends on every connect to load the map.

CleanMower is sourced from deebot_client.commands.json.clean (added by #1515 / #1624) — no duplication.

New message handlers

  • deebot_client/messages/json/mow.pyOnMI and OnArI:

    • Handles the GOAT's chunked LZMA zone-polygon protocol (batid/serial/index reassembly)
    • Decompresses using the LZMA1 raw filter with embedded header (no public API equivalent; lzma._decode_filter_properties used with # noqa: SLF001)
    • Fires MapSubsetEvent for each zone polygon (ROOMS) and no-go zone (NO_MOP_ZONES)
    • Fires MapChangedEvent when a full batch is assembled
    • OnArI uses the same handler as OnMI (identical wire protocol)
  • deebot_client/messages/json/mower_telemetry.py — Three push telemetry handlers:

    • OnPos — GPS position/heading push → PositionsEvent
    • OnCleanInfo — Mow state push (working/charging/idle) → StateEvent
    • OnStats — Mow progress push (area m², time s) → StatsEvent

Updated

  • deebot_client/messages/json/__init__.py — Registers OnMI, OnArI, OnPos, OnCleanInfo, and OnStats in the MESSAGES dispatch table.

Tests

  • tests/hardware/test_init.py — Adds cr0e4u to test_get_static_device_info and a full cr0e4u entry to test_capabilities_event_extraction with the complete expected event→command mapping.

Protocol notes

The GOAT A3000 uses a request-triggered push model for zone map data, distinct from both standard vacuums and the device's own auto-pushed telemetry:

Data Protocol
Zone polygons Integration sends getMI → mower streams chunked onMI/onArI LZMA pushes → OnMI/OnArIMapSubsetEvent
Mow trace Firmware auto-pushes onMapTrace during mowing → handled by #1567 (OnMapTraceMapTraceEvent)
Position Firmware auto-pushes onPos on each GPS fix → OnPosPositionsEvent
Mow state Firmware auto-pushes onCleanInfo on state change → OnCleanInfoStateEvent
Progress Firmware auto-pushes onStats periodically → OnStatsStatsEvent

Chunked LZMA format used by onMI/onArI:

  • Chunk 0: bytes 0–4 = LZMA1 filter properties, bytes 5–8 = uint32 LE uncompressed length, bytes 9+ = start of compressed data
  • Chunks 1+: raw continuation of the compressed stream
  • All chunks concatenated in index order before decompression

Related PRs

Copilot AI review requested due to automatic review settings June 19, 2026 08:12

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Copilot reviewed 7 out of 8 changed files in this pull request and generated 11 comments.

Comment thread deebot_client/messages/json/mower_telemetry.py
Comment thread deebot_client/messages/json/mower_telemetry.py
Comment thread deebot_client/messages/json/mower_telemetry.py Outdated
Comment thread deebot_client/messages/json/__init__.py
Comment thread deebot_client/messages/json/mow.py
Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/hardware/51rcxt.py
Comment thread deebot_client/messages/json/mow.py
Comment thread deebot_client/messages/json/mower_telemetry.py

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

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Comment thread tests/hardware/test_init.py
Comment thread deebot_client/messages/json/__init__.py Outdated
Comment thread deebot_client/messages/json/mower_telemetry.py Outdated
Comment thread deebot_client/messages/json/mow.py Outdated
Comment thread deebot_client/messages/json/mow.py Outdated

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

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/messages/json/mow.py Outdated
Comment thread deebot_client/messages/json/mow.py Outdated
Comment thread deebot_client/messages/json/mow.py Outdated
Comment thread deebot_client/messages/json/mower_telemetry.py Outdated

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

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Comment thread deebot_client/commands/json/clean.py
Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/messages/json/mow.py
Comment thread deebot_client/messages/json/mower_telemetry.py

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

Copilot reviewed 10 out of 11 changed files in this pull request and generated 8 comments.

Comment thread deebot_client/messages/json/mower_telemetry.py Outdated
Comment thread deebot_client/commands/json/clean.py
Comment thread deebot_client/commands/json/clean.py
Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/messages/json/mow.py
Comment thread tests/messages/json/test_mow.py
Comment thread tests/messages/json/test_mow.py Outdated
Comment thread tests/messages/json/test_mow.py Outdated
@adc103 adc103 force-pushed the feat/goat-a3000-cr0e4u branch 2 times, most recently from c590334 to cedd3f3 Compare June 19, 2026 21:41
@adc103 adc103 requested a review from Copilot June 19, 2026 21:42

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

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

Comment thread deebot_client/messages/json/mow.py
Comment thread deebot_client/messages/json/mow.py
@adc103 adc103 force-pushed the feat/goat-a3000-cr0e4u branch from cedd3f3 to 5f21556 Compare June 19, 2026 21:49

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

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Comment thread deebot_client/commands/json/mow.py
Comment thread deebot_client/commands/json/clean.py
Comment thread deebot_client/messages/json/mow.py
@shinerblue

Copy link
Copy Markdown
Contributor

Thanks for putting this together — this is the full implementation the cr0e4u/51rcxt devices have needed.

Some context that may help: I opened #1538 (the initial 51rcxt hardware definition that this supersedes) and provided the iOS MQTT captures behind the {"type": "auto"} finding in #1515. I have a live GOAT A3000 LiDAR Pro (51rcxt, TruEdge-trimmer variant) on current firmware wired into Home Assistant, so I can validate this end-to-end against real hardware.

Happy to help:

Is the #1515 merge the main thing holding this back? If a maintainer can advise on the preferred sequencing (land #1515 / #1624 first, or fold the CleanMower dependency in differently), I'm glad to do the legwork to move it along.

Add support for the Ecovacs GOAT A3000 LiDAR mower (cr0e4u) and its
Pro variant (51rcxt).

New hardware definitions:
- deebot_client/hardware/cr0e4u.py: full capability definition for the
  GOAT A3000 LiDAR including CleanMower, CleanMowerArea, CapabilityMap,
  blade + lens-brush life-span, and all mower-specific settings
- deebot_client/hardware/51rcxt.py: delegates to cr0e4u (identical FW)

New command:
- deebot_client/commands/json/mow.py: CleanMowerArea sends the
  cleanMower command with a specific zone_id for targeted area mowing

New message handlers:
- deebot_client/messages/json/mow.py: OnMI + OnArI handlers reassemble
  chunked LZMA zone-polygon payloads and fire MapSubsetEvent /
  MapChangedEvent so the HA integration can render the zone map
- deebot_client/messages/json/mower_telemetry.py: OnPos, OnCleanInfo,
  and OnStats handle mower-specific push telemetry (position, mow
  state, and progress statistics) mapping to PositionsEvent, StateEvent,
  and StatsEvent respectively

Updated:
- deebot_client/messages/json/__init__.py: register OnMI, OnArI,
  OnPos, OnCleanInfo, and OnStats in the MESSAGES dispatch table
@adc103 adc103 force-pushed the feat/goat-a3000-cr0e4u branch from 5f21556 to 5fb408d Compare June 20, 2026 18:34
@adc103

adc103 commented Jun 20, 2026

Copy link
Copy Markdown
Author

Thanks so much for jumping in — your hardware context is exactly what we need. A few things:

Yes, the #1515 dependency is resolved in this PR. CleanMower is now defined directly in commands/json/clean.py and exported from the package, so this PR is fully self-contained and mergeable on its own.

When you get a chance to test on the live 51rcxt, the most useful things to validate would be:

getMI → onMI/onArI chunk flow: does the map actually render in HA after startup?
onPos → does the robot position update in real time during mowing?
Start / pause / resume / stop / dock: do the StateEvent transitions track correctly?
Zone mowing (CleanMowerArea): does selecting a specific zone work?
No-go zones: do they appear in HA if the GOAT has any defined?
If you can capture any onMI chunk payloads from traffic (especially multi-chunk batches) that would help validate the reassembly logic. The LZMA decode formula has been corrected and tested locally but real hardware confirmation would be ideal.

Thanks again! I am excited to get this all supported.

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

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Comment on lines +67 to +72
# deebotPos is a dict; chargePos can be a list or dict
if isinstance(raw, dict):
items: list[Any] = [raw]
else:
items = raw

Comment on lines +133 to +136
serial: int = int(data.get("serial", 0))
index: int = int(data.get("index", 0))
mid: str = str(data.get("mid", "1"))
info: str = str(data.get("info", ""))
Comment on lines +202 to +204
dec = lzma.LZMADecompressor(lzma.FORMAT_RAW, None, [filter_props])
full_bytes = dec.decompress(compressed, uncompressed_len)
except Exception as exc:
Comment on lines +111 to +132
class CleanMower(Clean):
"""Auto-mow command for lawn mower devices (GOAT A3000 LiDAR and similar).

Uses the ``clean`` endpoint with a V2-style ``content`` dict, matching
the protocol confirmed via traffic analysis of the GOAT A3000 (cr0e4u):

.. code-block:: json

{"act": "start", "content": {"type": "auto"}}
{"act": "stop", "content": {"type": ""}}
"""

def _get_args(self, action: CleanAction) -> dict[str, Any]:
content: dict[str, str] = {}
args = {"act": action.value, "content": content}
match action:
case CleanAction.START | CleanAction.RESUME:
content["type"] = CleanMode.AUTO.value
case CleanAction.STOP | CleanAction.PAUSE:
content["type"] = ""
return args

Comment on lines +13 to +16
Note: ``onStats`` pushes are handled by the existing
:class:`~deebot_client.messages.json.stats.OnStats` handler, which
already supports the mower payload shape (``area`` + ``time`` fields).

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