Skip to content

SharkIQ MQTT: maps, room and spot cleaning, settings, live faults and model capabilities - #34

Open
andrew-from-toronto wants to merge 32 commits into
sharkiqlibs:mainfrom
andrew-from-toronto:feature/map-rooms-and-settings
Open

andrew-from-toronto wants to merge 32 commits into
sharkiqlibs:mainfrom
andrew-from-toronto:feature/map-rooms-and-settings

Conversation

@andrew-from-toronto

@andrew-from-toronto andrew-from-toronto commented Sep 20, 2026 •

Copy link
Copy Markdown

Disclosure: This is tool assisted with Claude. Fully tested against my personal Shark RV2610BFCA

Summary

Brings the library's sharkiq_v1 MQTT support much closer to what the SharkClean app can do, entirely over the local network. Everything here was captured from and checked on an RV2610BFCA (Matrix Plus, firmware V6.6.10-P7.N3308.17.0), including real cleaning runs.

What the library can do now

Maps

  • Decodes the live map a robot publishes during a job: floor plan, the path cleaned so far, and the robot's position and heading.
  • Decodes the saved map published when the robot docks: named rooms, walls and doors, the dock, the area cleaned and time spent cleaning, and the robot's own event log for the job.
  • Serialises the saved map, so a caller can store it and keep room cleaning working after a restart.

Cleaning

  • Cleans chosen rooms, optionally as a two-pass Matrix clean.
  • Spot-cleans a square around any point on the map.
  • Pauses and resumes a job; starts an explore run.
  • Sends room and spot commands identical to the app's.

Settings

  • Suction level (eco / normal / max), Recharge & Resume, Evac & Resume, find robot.
  • Other settings from the robot's settings block: do not disturb, volume, CleanEdge, carpet boost and carpet detection, button sounds, underglow lights.

Status

  • Reports the robot's full set of states, including paused, error, charging and returning.
  • Reports the faults and warnings active right now, plus log entries streamed mid-job.
  • Reports device info: temperature, Wi-Fi state, and live motor speeds.
  • Provides readable names for every state, fault, warning and dock-reason code.

Model capabilities

  • Knows which features each of about 600 Shark models has, using the same rules the app uses.
  • Groups them into seven simple robot types, so an integration can ask a user for a type rather than an exact model string.

Improvements and fixes

Notes

  • Suction and most settings aren't reported back by the robot, so callers need to remember the last value they set.
  • Room and spot commands re-send the map's room definition. Build them from the latest saved map, or a stale one can rename rooms.
  • Pause, explore and the extra settings follow the same message format as the verified commands, but haven't all been exercised on hardware yet. Which settings a model honours varies, which is what the model capabilities are for.

Tests

Captured frames are included as fixtures. pytest --cov=sharklocal: 1056 passed, 99% coverage.

Andrew Hunt added 23 commits September 19, 2026 22:15
Live frames carry the occupancy grid, cleaned path and pose log; the persisted frame published on docking adds rooms, wall/door vectors, dock pose, job stats and the robot's event log. VacuumStatus gains job_active, deep_clean, recharge_resume and evac_resume. Captured RV2610BFCA frames are included as fixtures.
…pping

start_cleaning and stop/go_home now send the bare job command; the previous payloads also set suction (normal on start, eco on dock). Mode 4 maps to idle.
The persisted map only arrives when the robot docks, but room and spot cleaning need its room definition at any time; storing it lets a caller clean rooms after a restart.
A distinct version lets Home Assistant tell this build from the PyPI 0.2.0 it may already have installed.
Creating an SSL context reads the certificate store, which Home Assistant reports as a blocking call when it happens on the first request inside the event loop. Built in the constructor, it runs wherever the caller constructs the client, which Home Assistant does in an executor.
Cell value 0x00 is floor covered by the job, not void: every point of the cleaned path lands on one in every captured frame. The persisted frame's field 11 counters (.3, .6) were read as duration and cleaned cells from a single capture, but a second job showed them almost unchanged (335 -> 306, 3947 -> 3978) while the robot's own log reported 101 s and 70 s of cleaning. VacuumMap.cleaned_area now counts cleaned cells and the new clean_time sums the log's DT_*_CLEAN_TIME entries; job_duration and cleaned_cells are removed.
After a Spot Clean the robot keeps the PinDrop square in its saved map, marked as the room the job cleaned (measured 2026-09-26). VacuumMap.spot returns it and named_rooms leaves it out; room and spot cleaning re-upload only the named rooms, so a stale zone is neither selectable nor sent back. spot_polygon(x, y) exposes the square a spot clean sends.
…state table

- sharklocal.codes: the SysStateT, ErrorCodeT, WarningCodeT and dock-event names from the SharkClean app's protobuf schema - the same names the event log writes as strings.
- VacuumStatus.errors / warnings: status fields 5 and 6, the codes active now (repeated, packed or not).
- The sharkiq_v1 mode table now follows SysStateT: 13 is charging on the dock (docked, was docking - a robot back on its dock read as still docking until the battery was full), 4 is paused (was idle), and the error, waiting, spot, zone, redock and explore states are mapped. VacuumMode gains PAUSED and ERROR.
…nd settings

- Status: charging now follows the system state (13 charging); field 9.1 is the Wi-Fi state, which reads 3 while the robot cleans and made charging read true mid-job. Device info (temperature, Wi-Fi state/link/signal, water and dust level, IP), live suction/brushroll/side-brush speeds, CleanEdge and carpet-detect status and the relocation state are decoded. Checked against the captured frames: the end-of-job frame's live warning (WARN_MM_LOWLIGHT) matches its own event log.
- Commands from the app's schema: pause (USR_CTR_PAUSE; start resumes), edge clean (USR_CTR_ALONG_WALL) and explore over MQTT.
- set_config writes one field of the settings block; set_toggle names the on/off settings (DnD, child lock, silent mode, CleanEdge, carpet boost, button sounds, underglow lights, continuous crosshatch, room by room), and reproduces the app's captured Recharge & Resume / Evac & Resume commands byte for byte. set_carpet_detect and set_volume write theirs.
- codes gains the Wi-Fi, carpet-detect and relocation state names.
A port of the app's capability logic and its 611-model table (SharkClean 6.27.0): model string -> family -> classification, the prefix-letter and hex-bitmask hardware rules, and the exact RV3000 / OPP / spot-lidar / 360EZ / ODM / MPP lists. Features are three-valued, None when the model is missing from the table, because the robot does not report its cloud model locally and retail SKUs are not cloud model strings.
Every model in the app's table falls into one of seven types (lidar, lidar_carpet, rv3000, spot_lidar, map, air, basic); Capabilities(type, self_empty_dock, clean_edge) answers the gated features as booleans, and a test checks that for all 611 models the type plus its two extras matches the per-model rules exactly - so a user can configure a robot without knowing its cloud model string.
Status frames can carry the robot's event log mid-job (field 34, repeated PbDiagnoseInfo, JSON in its field 5), not only at the end with the persisted map. VacuumStatus.log_entries holds them; vacuum_map.parse_log_json is the shared parser. Fixture: a real frame from a spot clean.
@andrew-from-toronto andrew-from-toronto changed the title Decode MQTT map frames; add suction, settings and room cleaning SharkIQ MQTT: maps, room and spot cleaning, settings, live faults and model capabilities Sep 27, 2026
Charging comes from the system state (the old footnote read the Wi-Fi state), state 13 is charging on the dock and 4 is paused, and the report now covers what was verified since: warnings, temperature, motor speeds, firmware versions, mid-job log entries, room/Matrix/spot cleaning runs and the saved PinDrop zone. Commands sent but not yet run on the robot are marked as untested.
@andrew-from-toronto

Copy link
Copy Markdown
Author

I have some forked changes for the sharkiq_local_unofficial repo that I will open a PR for once this hopefully merges. Here's an example of what this PR enables!

image

Andrew Hunt added 4 commits September 27, 2026 10:41
Only a refused connection became ConnectError, so a robot that dropped packets instead (aiohttp ConnectionTimeoutError, seen at the nightly network restart) escaped raw: the MQTT fallback never ran and Home Assistant failed the config entry for good instead of retrying it. Every aiohttp connection error and a bare timeout now count as unreachable.
PbInput field 39 = 1 (FloorControlT FL_CTR_LOAD_ALL) makes a docked robot publish its persisted map within a second: floor type 4, carrying the last job's path and summary but not the event log or the top-level report flag. That recovers a job whose end-of-job frame was missed, e.g. while Home Assistant was restarting. VacuumMap.persisted now also covers floor type 4, VacuumMap.report marks the end-of-job frame, job_minutes comes from the summary (PbMapSummary.currentCleanTime) and backs clean_time when there is no log, and a requested map never replaces the report of the same job. Fixture: the real reply after a whole-home job. SIG_EXT_REQ_ALL_MAP (field 29 = 33) was tried too and draws no reply.
@andrew-from-toronto
andrew-from-toronto force-pushed the feature/map-rooms-and-settings branch from 2b9f5e2 to b14e4b7 Compare September 27, 2026 15:17
Andrew Hunt added 4 commits September 27, 2026 11:47
The four call sites imported aiomqtt on first use, and inside Home Assistant that first use is the monitor starting in the event loop, which HA flags as a blocking call (the import reads the package from disk). It is imported once when the module loads, still optional: without it the MQTT calls raise the same ConnectError. Also asyncio.iscoroutinefunction -> inspect.iscoroutinefunction, deprecated for removal in Python 3.16.
Each job opens with a 78-byte live frame whose grid has no resolution, size or cells (captured on a Matrix clean, 2026-09-27). It decoded as a 0x0 map at resolution 0, and Home Assistant's renderer divided by it: every picture request failed with a 500 until the next real frame. has_map and decode_map now need a grid with cells, size and resolution. Fixture: that frame.

This branch has not been deployed

No deployments
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