These 56 commits are when the Protocol Buffers files have changed:
| Commit: | 222cf70 | |
|---|---|---|
| Author: | Michael Arthur | |
feat: sync protobufs and send the app's route and schedule settings Auto-reverse rides on field 21 (reserved2, 32 bytes, setting in byte 0); field 20 is the settings-screen mode. Routes and schedules now carry ride_boundary_distance and toward_mode, a Luba 1 keeps toward_mode in reserved[4], and the rain_tactics byte is dropped: it is the plan enable flag.
The documentation is generated from this commit.
| Commit: | c2ed654 | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | GitHub | |
Large scale tidying refactoring (#196) * tidy up tests, fix issues around auth handling * refactor the account registry, add new sleep states and api, deal with loose ble accounts better * chore(release): sync the package version and fix the bumpver targets `pymammotion/__init__.py` declared `__version__ = "0.0.5"` while pyproject said 0.8.14, and the README badge carried two further values (0.8.5 in the label, 0.7.31 in the URL). The cause was `[tool.bumpver.file_patterns]` pointing at `src/pymammotion/__init__.py` and `setup.cfg` — neither of which exists in this layout — plus a `version = "{version}"` pattern whose spacing never matched `[project] version`. bumpver had therefore been updating `current_version` alone, silently, while every other version string drifted from it. All four targets now resolve; verified with `bumpver update --patch --dry`, which rewrites README (both strings), `__init__.py`, `[project] version` and `current_version` together. uv.lock goes with it — CI runs `uv sync --frozen`, which fails when the lock and pyproject disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(layout): move data-model helpers out of utility/, split device_constant `utility/` is the bottom layer, but four modules in it operated on `data.model` types and imported upward: `map.py`, `svg.py`, `device_config.py` and a 450-line PIL renderer that fetches OSM tiles and caches them on disk. They move to `data/model/{coordinates,svg,device_capabilities}.py` and a new `render/` package. The old paths survive as re-export shims because the Home Assistant integration imports from them; deleting one needs a coordinated change on that side, so each shim says so. `tests/unit/utility/test_layering.py` holds the line: nothing under `utility/` may import `data`, `device`, `transport`, `aliyun`, `http`, `messaging`, `state` or `bluetooth` except those four, and nothing under `data/` may import `device`. Two import cycles go with it, and with them the function-body imports that existed only to dodge them: - `data/model/device.py` drops its local `utility.device_type` and `device.readiness` imports. The second went with `MowerDevice.report_missing_data()`, a public method with no in-tree callers whose job `device.readiness.get_readiness_checker` does directly. Nothing replaces it; call the checker. - `hash_list` no longer imports `generate_geojson`, so the dependency runs one way. Its four generator calls each needed a function-body import before. `utility/constant/device_constant.py` was a 514-line grab-bag; it splits into `ble_order`, `buffer_index`, `device_enums`, `display` and `poll_policy`, with the old module re-exporting so existing imports keep working. `UnknownTolerantIntEnum`'s dedupe set also gains a cap: the key holds a value straight off the wire, so a device emitting a stream of distinct unmodelled ints would otherwise grow it without bound in a long-lived process. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(rtk): decode the full NMEA GGA fix-quality range `RapidState.from_raw` mapped `raw[0]` through a local three-state enum — FINE on 4, BAD on 1 or 5, NONE otherwise. That field is the NMEA GGA fix-quality indicator (0 invalid, 1 SPS, 2 DGPS, 4 RTK fixed, 5 RTK float), which `enums.RTKStatus.from_value` already decodes and which `ReportData.fix_status` already used. The local enum was lossy: it reported RTK float and a plain single fix as the same value, and DGPS as no fix at all. Codes whose decoded meaning changes: 2 (NONE -> SINGLE), 5 (BAD -> FLOAT), and anything unmodelled (NONE -> UNKNOWN). Documented on `from_raw`. Also fixes the re-export: `data/model/__init__.py` published `RTKStatus` through `rapid_state`, which no longer defines it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(core): decompose the client, split CloudTransport, add a direct send path Three changes that share `client.py`, `handle.py` and the transport layer, so they land together. Client decomposition. `MammotionClient` sheds four collaborators — `CloudAuthMixin` (`client_auth.py`, a mixin because the dependency is genuinely bidirectional: the authCode chain that mints an Aliyun session *is* part of logging in), `BleInventory`, `InboundRouter` and `AutoFetchWatchers`. The per-transport poll loops now take a `LoopHost` Protocol instead of reaching into ~22 private attributes, so a change to `DeviceHandle` that breaks a loop is a type error rather than an `AttributeError` at runtime. CloudTransport. `transport/base.py` carried a 12-hour send quota, terminal auth flags and four `thing/*` callbacks that `BLETransport` inherited and used none of. The leak was visible in `BLETransport.send`, which still takes the `iot_id` and `firmware_version` it ignores, and in `Transport.is_usable`, which BLE had to override because the default was computed from cloud auth flags. Those move to `transport/cloud.py`; base drops from 597 to 404 lines and now holds only what a link of any kind has. `ty` immediately caught three call sites that had been passing only because the members sat on the base. Deliberately not solved by giving BLE no-op stubs. Priority.USER. `Priority.EMERGENCY` had zero callers and its own comment explained why it never could work: the queue processor is strictly sequential, so an item behind an in-flight saga waits for it however it is ranked. EMERGENCY and USER are now direct-send levels dispatched on the caller's task, and `DeviceCommandQueue.enqueue` raises on them so the old shape cannot return. Error control is shared rather than copied — the gateway-timeout retry and the DEBUG/WARNING/exception buckets move into `execute_command()`, which both paths call, differing only in `reraise`. `is_rate_limited` splits into `is_cloud_banned` / `is_quota_exhausted` so a user command can spend past our own budget without pushing through a cloud 429; `CloudTransport.send_user` carries it, and BLE keeps the plain `send()` because it has no quota. Fixed on the way through: - `send_raw` swallowed `TooManyRequestsException` and `TransportRateLimitedError`, arming the ban and returning normally — so a command the cloud had refused reported success. A saga hitting a 429 continued with an unsent frame and then timed out waiting for a reply that could never arrive. - `AliyunMQTTTransport.send` gated on bare `is_rate_limited` while every other send path used `is_send_blocked`, so a device on quota-free firmware passed the handle's pre-flight and was then refused by the transport. - `send_command_with_args` and `send_command_and_wait` defaulted `prefer_ble` to `False` and `True`. Neither can mean "no opinion", so `set_prefer_ble()` was overridden on every call; both now default to `None` and defer to the handle the way `send_raw` always has. - `setup_device_watchers` dropped the `Subscription` its delegate returns. - MQTT built its TLS context two different ways, so whichever construction site ran first decided whether TLS 1.3 could be negotiated. One builder now, on the non-deprecated `PROTOCOL_TLS_CLIENT`. Certificate verification stays off, matching what both previous contexts did — turning it on is a separate decision, and that link carries the JWT. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: record the architecture, the decisions behind it, and the open findings `docs/architecture.md` is the structural map CLAUDE.md's summary points at: the layer inventory, the `(account_id, device_id)` ownership model, the four flows traced end to end (inbound message, outbound command, saga, credentials), a table of the single home for each recurring question, and extension recipes. `docs/decisions.md` records decisions made against a real alternative, because the reasoning is not recoverable from the code and someone will otherwise re-propose the rejected option — why saga mechanics are free functions rather than base-class helpers (adoption was 1 saga in 7), why there is no `MQTTBaseTransport`, why there are no auth rate limiters, why `CloudTransport` exists, and why `Priority.USER` bypasses the queue but not a cloud 429. `docs/review-2026-08.md` is a review pass with the findings still open, ranked, and a note of what was verified rather than assumed. It replaces `ARCH_ISSUES.md`, `TODO.md`, `dead_code.md` and `dynamics_line.md`, which were untracked at the repo root and substantially stale — every "Critical" entry in TODO.md was already fixed in the code. Their genuine design rationale is now in `decisions.md`; the items that really were open are carried forward. CLAUDE.md gains the send-path invariants: user commands do not queue, the send quota paces polling rather than people, and background traffic must not fire MQTT at a device the cloud has reported offline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(geojson): make the mow-progress test hermetic `test_mow_progress_geojson_matches_example_script_output` read its reference output from `examples/dev_output/`, which is gitignored. It therefore passed only on a machine where `examples/generate_mow_progress_geojson.py` had been run by hand, and raised `FileNotFoundError` on a fresh clone — so it was failing in CI. Only one of the three reference files carried anything: `now_index=1`, with 14 features and 1180 coordinates. That is now committed as `tests/fixtures/mow_progress_1.geojson`, alongside the `yuka_fixture.json` it is derived from, and the coordinate-exact comparison is unchanged. The other two files were 77 bytes each — an empty FeatureCollection. The fixture's longest section is 182 points, so `now_index` 300 and 550 are both past the end and nothing remains to draw. That is the whole assertion, so it is written directly as a parametrised test rather than pinned by a file. The argument setup both tests share moves into `_yuka_progress_inputs()`, which documents why `ub_path_hash=0` and the decoded `path_pos` are needed for the match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: 0.9.0b1 Cut the first beta of 0.9.0 and make the release tooling able to carry one. bumpver's version_pattern could not express a pre-release, so widen it to MAJOR.MINOR.PATCH[PYTAGNUM]: --tag-num walks the beta series, --tag final promotes it to 0.9.0. release.yml compared importlib.metadata.version() to the tag as strings. Metadata always reports the PEP 440 normalised form, so a v0.9.0-beta1 tag could never match a 0.9.0b1 package. Compare parsed versions instead, and mark the GitHub release as a pre-release when the version is one. __init__.py and the README badge had drifted to 0.8.14 while pyproject said 0.9.0; realign them. uv.lock records the workspace version, so re-lock or `uv sync --frozen` fails in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(devices): route thing/event notifications, map the Spino product keys, carry OTA progress Three strands of work that had accumulated in the tree. Notifications. Non-protobuf thing/event posts now reach subscribers through a DeviceNotification bus instead of being dropped after the state update. On the Aliyun side ThingEventMessage.params falls back to a bare dict for any variant mashumaro cannot match — a real device_information_event does exactly that — so the identifier is read both ways or those events never notify. Aliyun routes through on_device_notification like the Mammotion cloud does, so the report-cfg refresh fires on both. A value that is neither a dict nor mashumaro-convertible is logged with its type before being dropped, so "carried nothing" stays distinguishable from "we dropped it". Product keys. Device-family classification was name-only for every pool path, so a Spino whose deviceName does not start with "Spino-" was classified as a mower — and HA drops those entirely. is_swimming_pool now takes the optional product_key that every sibling classifier already took, backed by tables for the E1 (a15Cq8FbCh1, from a live device), the PC210 and its charging pile (from the APK's DeviceProductKey), and the app's unattributed SWIMMING_POOL_PRODUCT_KEY. The key is threaded to every site that decides a device class and has it, which means DeviceHandle now carries its own product_key rather than each caller re-deriving from the name. a1FbaU4Bqk5 (LubaAWD5000743) was missing from LubaProductKey while device_capabilities._DEFAULT_LIST already carried it. Unlisted keys default to Mammotion-IoT, so that Aliyun Luba 1 was being pointed at the wrong broker. The two tables disagreeing is what hid it, so a test now asserts they agree. OTA progress. otaProgress arrives on both clouds and only the Aliyun path unpacked it. Both now feed the same OTAProgress model, and the five hand-written nested-object serialization strategies collapse into one helper that also accepts the object form the device sometimes sends instead of a JSON string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: 0.9.0b2 Second beta of 0.9.0. uv.lock records the workspace version, so it is re-locked in the same commit the tag points at — otherwise `uv sync --frozen` fails in CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: 0.9.0b3 Mammotion MQTT thing/event notifications now carry their payload: the broker puts the fields directly under params (the app reads params.data), while the extractor only looked for an Aliyun-style params.value and delivered None. BLE: a characteristic missing right after connect means bleak's service cache handed back a stale GATT table (seen on Yuka reconnects through ESPHome proxies). The transport now clears the cache and reconnects once instead of arming a two-minute cooldown on every attempt until the process restarts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(devices): cover every published product key, bundle the error-code table Device families - Add the newer-generation product keys the cloud publishes alongside the a1 ones. /device-server/v1/product/product/list ships most families twice with an identical model set, and holding only the a1 half left 15 keys — including Luba 2 Pro's ATyVu9QkAdX and both SolarRtkV2 keys — falling through to defaults: the wrong broker for an Aliyun device, guessed capabilities otherwise. The newer keys deliberately do NOT join AliyunProductKey. - Complete the HM43x Luba mini block (LUBA_MB, LUBA_MD) and add is_support_fill_light / is_support_blade_speed, the capability predicates the app's own settings screen gates on. - Resolve a Spino-S1 name to SWIMMINGPOOL_SP, as the APK does, which needed comma-separated name prefixes in the resolution table. Error codes - Bundle the 469-code table so a device fault renders without a network call, with the negative-code normalisation the mower's reports need. Add the app's paged device-server endpoints and the product list. Fixes - Bound the send in DeviceMessageBroker.send_and_wait: it sat outside the timeout, so a stalled cloud invoke waited forever and could consume a host's whole setup budget (Mammotion-HA#859). - Accept a lean thing/property envelope: id/version/sys were required here but optional in the app, so an OTA progress push was dropped at DEBUG and a firmware install reported 0% for its whole run. - send_svg now rejects a pre-chunked list by name instead of failing deep in the chunker (Mammotion-HA#868), and Saga.retry_backoff is configurable. Testing - Add docs/testing.md, the testing constitution, and tests/meta enforcing its mechanical rules against a ratcheting baseline. Pay down the debt it found: 127 redundant asyncio markers, 62 wall-clock sleeps, 11 duplicated builders, 632 divider comments and 73 unused imports, all now pinned at zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: 0.9.0b4 * test(device): freeze the clock in the sleeping-poll backoff test _loop_handle derived staleness from real time.monotonic() minus 3660s; on a low-uptime process/container that goes negative, and max(negative, last_poll_sent_at=0.0) then picks the 0.0 sentinel, making the loop see the activity as just now instead of long ago and computing the wrong backoff. Monkeypatch time.monotonic to a fixed anchor so the test no longer depends on real process uptime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(device): stop dynamics-line loop from collapsing its poll interval sleep_or_rearm's shared _rearm_event is set by on_saga_end after this loop's own poll saga finishes, so it returned immediately every tick and back-to-back retriggered. Plain sleep restores the intended cadence; BLE-disconnect still exits via task cancellation and the is_connected gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(devices): pin every published product key to a device family Fixture is a capture of /device-server/v1/product/product/list. An unclassified key is not a loud failure at runtime -- it falls through to defaults, guessing the wrong broker and capabilities -- so it is pinned here instead of left to be discovered live. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Bump version 0.9.0b4 -> 0.9.0b5 * fix(ble): recover from a GATT table the device moved under us A Yuka re-registers its service at a fresh handle block, so the cached table still lists ff02 but at a handle the mower dropped. The CCCD write then gets error=1 Invalid handle and the link dies, every five minutes, for hours (issue #193). The stale-cache retry added in 0.9.0b3 could not catch it: it matched BleakCharacteristicNotFoundError, and nothing is missing here. Read the GATT status off the cause chain instead, duck-typed so BLE still works without aioesphomeapi installed. Clearing the cache from the failing link was also not enough. An ESPHome proxy keeps its copy in flash and refuses the purge on a dead link, and the proxy usually drops the link just before we see the error -- which is why that copy survived restarts and reinstalls. When the purge cannot reach the adapter, spend one connection on the purge alone, then drop it so the next pass rediscovers. Two disconnect-callback races, both able to clear a healthy client: - a callback from a link connect() already replaced. Scope it: bump a generation per client and drop a stale one. - a callback from an attempt establish_connection abandoned, which shares a generation with the one that succeeded. Drop disconnects observed while connect() holds the lock, checked at the observation itself because anything scheduled runs a turn later. Also: - announce DISCONNECTED from a finally, so an unanticipated exception or a cancellation cannot strand availability at CONNECTING forever - name the failing step in the error, rather than one message for four unrelated causes - drop timeout=2 and ble_device_callback. Neither did anything: establish_connection connects with its own BLEAK_TIMEOUT and has not invoked that callback since 2.13.0. Re-read the device pointer per pass instead, which is what actually follows a proxy handover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(nav): accept field 21 in either wire form, gate what we send Some firmware sends NavReqCoverPath.auto_change_direction (field 21) length-delimited. betterproto2 does not enforce wire type, so a packed payload on an int32 field parsed as a list, mashumaro rejected it, and every report frame carrying it was dropped -- continuously, taking the task settings and live position with it (issue #192). Declare the field repeated: that accepts both encodings, where int32 rejects the packed one. CurrentTaskSettings normalises back to a scalar, and off still stays off the wire, so nothing changes for devices that already worked. The field number is still inferred, but corroborated: the app's job model lists autoChangeDirection beside rideBoundaryDistance and appDisplayMode, matching 19-21, and reads it as `value != 0`, so a reported 10 is simply on. A frida capture would settle it. Also: - add ride_boundary_distance and app_display_mode to CurrentTaskSettings, which the proto reported and the model silently dropped - gate the outgoing write on DeviceType.supports_auto_change_direction; the app hides the row below 2.3.28.1 and on unlisted models - set the product key on the command builder again. Its only consumer is get_msg_device, which routes NAV by device type, so without it every device fell back to name-only detection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bump version 0.9.0b5 -> 0.9.0b6 * fix(client): detach cloud per device instead of disconnecting it Cloud transports are one object per account, shared by every handle on it, so turning cloud off for a single device was taking cloud down for every other device on that account, with nothing to bring it back. set_cloud_attached unwires the handle instead: the transports stay connected and the account keeps working, while the detached device neither sends nor receives over cloud. set_scheduled_updates now routes through it rather than connecting and disconnecting shared objects. Also demote the repeat "reported offline" warning to debug. A powered-off device fails every queued send, so only the transition is worth a warning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bump version 0.9.0b6 -> 0.9.0b7 * build: make a version bump a single step bumpver's commit could never succeed here, and its tag never triggered a release -- so every bump needed hand repair afterwards. The commit fails because bumpver patches the version in pyproject.toml and then git commit runs the ty and pytest pre-commit hooks, which shell out to uv run. That re-resolves on the version change and rewrites uv.lock mid-commit, so pre-commit aborts with "files were modified by this hook" and leaves the bump staged. bumpver_relock.sh re-locks from pre_commit_hook instead, before the commit, so the lock is consistent when the hooks run and lands in the tagged commit -- which CI needs for uv sync --frozen. The tag is wrong because bumpver tags the bare version and has no setting for a prefix, while release.yml triggers on 'v*'. Turn its tagging off and let bumpver_tag.sh make the v-prefixed tag from BUMPVER_NEW_VERSION. Verified end to end: bumpver update --tag-num now exits 0, commits with uv.lock included, tags v0.9.0b8, and uv sync --frozen passes at the tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix for initial connect fail on Yuka BLE (#194) * fix(aliyun): drop an unbound device from the cached device listing A 29004 detached the transport and migrated or removed the device, but left it in CloudIOTGateway._devices_by_account_response. to_cache persists that listing and _restore_aliyun re-registers an Aliyun binding for every device in it, so each restart rebound the unbound device and it 29004'd again -- with no way out short of deleting the credential cache by hand. forget_device drops the entry; _forget_aliyun_binding calls it and, only on a real change, asks the host to rewrite the cache. It runs before migration is attempted, not after: the retry loop spans several minutes, and a restart during them would otherwise restore the stale binding. The device leaves the Aliyun listing whatever happens next -- migrating to Mammotion MQTT means it is unbound from Aliyun too, and leaving it listed would rebuild an Aliyun binding beside the Mammotion one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bump version 0.9.0b7 -> 0.9.0b8 * Bump version 0.9.0b8 -> 0.9.0b9 * feature: Add support for Yuka grass collector DUMP type (#191) * Add support for Yuka DUMP type - Introduced DUMP_STYLE for grass-collection points in generate_geojson.py. - Updated GeojsonGenerator to handle DUMP type (12) in feature generation. - Enhanced MapFetchSaga to fetch and process dump hash lists alongside boundaries. - Added tests for DUMP type handling in GeoJSON and saga functionality. * Refactor MapFetchSaga to simplify hash retrieval for boundaries and dump spots --------- Co-authored-by: Jaano Rosin <jaano@rosin.space> Co-authored-by: Michael Arthur <michael.arthur@gameglass.gg> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: decode the grass-collector and dump bits of sensor_status CollectorState (bits 24-26) and DumpState (bits 27-29) join the bumper, blade and ultrasonic accessors already on DeviceData, plus collector_installed for the attachment's presence. Sourced from the APK's MACarDataManager, which splits the same packed field, and MapManualActivityNew, which hides every sweep/dump control while the collector reads absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * fix(ble): carry the advertisement RSSI into add_ble_to_device BLETransport.is_usable fails closed below min_rssi and only a stronger reading reopens it, but the host-facing call a host makes on every advertisement had no rssi parameter at all — so set_ble_device was always handed None and the last known RSSI never moved. A mower that faded out of range then stayed unusable however strongly it came back. Reported against Mammotion-HA: BLE not reconnecting after the mower returned to range, with prefer_ble on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * fix: make stop_polling reversible, and stop the BLE loops with it Two halves of the same gap. restart_keep_alive deliberately respects _polling_stopped so a reconnect cannot override a host's "polling off", and only start() cleared the flag — behind an is_started guard that stays True. A host that turned polling off and back on therefore never got its loops back; resume_polling is the explicit way out. stop_polling also cancelled only the MQTT loop. The BLE loops exit on disconnect or handle stop and never consulted the flag, so a BLE-connected device went on streaming RPT_START count=0 at full cadence while its host believed polling was off. They are cancelled now, and the three _start_* helpers honour the stop so _on_ble_connected cannot resurrect them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * fix(schedules): normalise Plan.reserved before sending The device adds +10 to the settings bytes when it stores a plan, so sending the buffer back exactly as read compounded the offset: every enable/disable or rename shifted the schedule's path order, no-go laps, start progress, Yuka config and collect frequency another 10 until they wrapped. The app subtracts the offset on every write (JobScheduleActivity:848-866, shared by rename and toggle): decrement bytes 0,1,3,4,5,6, write the enable flag raw, send byte 7 as 0. reserved_for_send does the same, applied by send_plan/send_schedule so it happens once per transmission and cannot compound — with_enabled now only sets byte 2. The byte meanings were "not fully decoded"; they come from the APK's encoder HomeStateViewModule.getReserved and decoder MACarDataManager.setJobPlanDB, and the docs are corrected accordingly — § 1.3 previously recommended the verbatim round-trip that caused this. Already-drifted schedules are not repaired: the intended values are unknowable, so they hold at their current offset until redone in the app. Reported against Mammotion-HA #891. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * fix(obstacle detection): send the app's off value, and label by device The app's off position on the new Off/Standard/Sensitive list is value 1, not 0 (WorkingSettingManage.aytomatiTypecRange = {"1","10","11"}); we sent 0. The firmware treats them alike, which is exactly why the public API docs describing 0/1 as "slowtouch" led to Mammotion-HA #887 — the app labels both "Off" (TaskAssignmentManager:237), and 0 is bump-to-detect, not slow touch. Labels also depend on which list the device has, not on the value alone: SettingOptionsView.initBypassingStrategy renders 1 as "Slow touch" only beside a 0, and as the off position otherwise. option_key/from_option_key expose that so hosts present the app's own names — off, slow_touch, less_touch, standard, sensitive — rather than the protocol member names. One deviation: the app falls back to "Off" for value 1 on any list shorter than four, which would label two of Luba 1's three tabs identically. Keying off the presence of 0 keeps every option distinct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * feat(spino): cleaning modes per pool-cleaner model, and the MACs A user was offered Waterline and Custom on a Spino E1, which that hardware does not have. The app carries two mode enums — SwimmingWorkModule for the touch models and SwimmingSPWorkModule for the PC210 SP, each backing its own fragment — so Custom is SP-only; and Waterline is narrower still, skipped unless SwimmingPoolHomePop's isPC100 flag is false, which DeviceItemFragment sets as type != SWIMMINGPOOL_S1. A plain Spino or an E1 has four modes. SWIMMINGPOOL_S1 stays in for_device but is unreachable today: the SP entry claims the "Spino-S1" prefix and is matched first, and S1 has no product-key matcher, so a real PC200 lands on the four-mode list and loses Waterline. A test fails the moment a key is added, as a prompt to revisit. PoolCleanerDevice also gains wifi_mac/bt_mac, the pair RTKBaseStationDevice already carries, so a host can register the cleaner's network identity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b9 -> 0.9.0b10 * fix(schedules): drop plans the device no longer has on a completed fetch ``update_plan`` only ever adds, and the one branch that removes a plan — ``all_plan_task`` in the reducer — is never requested by any command builder, so a schedule deleted on the mower stayed in ``map.plan`` for good. Being persisted, it survived restarts: the reporter had four schedule buttons for a device reporting ``total_plan_num = 3``. Not only cosmetic. ``set_task_enabled`` goes out as ``edit_plan``, and sent against an id the device no longer has, the device re-creates the schedule. ``PlanFetchSaga.result`` already holds exactly what the device returned, and ``indexed_fetch`` either yields every frame or raises — so a saga that reaches ``on_complete`` has the whole set, and anything else we hold has been deleted. ``HashList.replace_plans`` makes it the stored set. Reported as Mammotion-HA #892; also the other half of #869, whose entity pruning was already correct but keyed off a ``map.plan`` that never shrank. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * feat(ble): let a pool cleaner be registered over BLE alone ``add_ble_only_device`` passes ``initial_device`` straight to the handle, which has always taken the base ``Device`` and picks its reducer from the device *name* — so a ``Spino-*`` name already resolves to PoolStateReducer. Only these two annotations said ``MowingDevice``, which is what stopped a host offering Bluetooth setup for a Spino: it could be added through a cloud account or not at all. Typing only; no behaviour changes here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b10 -> 0.9.0b11 * fix(rtk): guard the a1Nc68bGZzX longitude at pi, not pi/2 The 436° correction added for Mammotion-HA #563 used the latitude limit as its out-of-range guard on both axes. A longitude beyond 90°E or 90°W is a perfectly valid position, so the quirk fired on correct values and added 436° to them: a New Zealand base station at 174.5°E rendered as 610.5°. #563 was itself a longitude report ("RTK latitude is correct / RTK longitude is smaller than -360°"), so the guard was wrong on the very field the quirk exists for; the docstring naming latitude was wrong too. Each axis now uses its own valid range. The two cases cannot overlap: a shifted longitude is always at least 4.47 rad and a real one never exceeds pi, so #563 is still corrected and no real position can be touched. Fixes #188 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b11 -> 0.9.0b12 * fix(rtk): apply the a1Nc68bGZzX quirk on the HTTP poll path too There are two ways a coordinate reaches an RTK base station. The MQTT property push goes through the reducer and its 436° correction; the HTTP property poll in MammotionClient.fetch_rtk_properties wrote lat/lon straight onto the device and skipped it entirely. On an affected station the poll is what lands, so the correction never ran at all: a reporter's latitude read -474.669°, exactly the raw value, while the longitude happened to look right because it arrives correct to begin with. Fixing the guard in 0.9.0b12 could not help — nothing was being guarded. The helper is now public and used by both paths, so there is one place a coordinate is corrected rather than two places that can disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b12 -> 0.9.0b13 * fix(nav): auto_change_direction is field 20, not 21 Observed directly: toggling "Auto-reverse Mowing Direction" in the app sets field 20 to 1. Field 20 was named app_display_mode, taken from the 2.3.8.201 unpacked APK, and 21 was guessed as "the next free number after it" -- so we were writing the setting into the wrong field and reading someone else's. Field 21 is real but unidentified: 11 has been seen, and a Luba Mini AWD sends it length-delimited with a single 0x0A payload byte. Decode it as repeated int32 under a neutral name so both wire forms parse and the values surface in state dumps while we work out what it is. That also settles issue #192 structurally rather than by tolerating a list: field 21 is no longer read as an int, so there is no type mismatch to drop the frame on, and auto_change_direction is a plain scalar again. The .pyi stubs are regenerated too. protoc's --pyi_out is not part of the documented regeneration command, so they had drifted: luba_msg was missing MSG_CMD_TYPE_SPINO_CTRL and spino_ctrl had jobid typed str. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bump version 0.9.0b13 -> 0.9.0b14 * refactor(errors): one error-code table per process, not one per device DeviceErrors carried the account-fetched table as a field, so each device persisted and re-serialised its own copy of the same ~470 rows in 26 languages. On a three-device account that was 2.9 MB of a 3.0 MB store, 96% of it, repeated verbatim in every diagnostics download. The table is a property of the account, not of a mower, and the library already bundles the same data as a fallback. It now lives once per process: set_fetched_error_codes() installs it, get_error_info() consults it between the caller's own `extra` and the bundle, and DeviceErrors keeps only what the device actually reported. Stored blobs carrying the old field are ignored on load rather than failing. Also adds MammotionHTTP.get_product_params and scripts/dump_product_params.py for the per-model work-setting capability list (product/param/version/search). Nothing calls the endpoint: it is there so the schema can be dumped and folded into the static helpers by hand, which is what the app itself falls back to when it has never fetched one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b14 -> 0.9.0b15 * feat(nav): map-free mowing ("DropMow") The app's noAreaWork(): the mower works from where it stands, in the direction it is facing, with no map and no boundary — for spot mowing outside a mapped area. It is not a route command, just an action on the existing task-control message: MctlNav{ todev_taskctrl: NavTaskCtrl{ type=1, action=16, result=0 } } The app keeps it behind its Beta Features screen, offers it only on the X5 platform (isX5DeviceTyp), and accepts it only while the mower is idle, refusing otherwise with "Robot is mowing. Please retry when the robot is idle". is_x5_series() mirrors that model list so a host can gate on it; the idle check belongs to the caller, which sees the device state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b15 -> 0.9.0b16 * fix(http): correct the product-param endpoint, and dump it per internal model The path taken from the APK was relative to its retrofit base, so it 404'd; the real one is /device-server/v1/product/param/version/search, found by matching code/page-lan, which resolves the same way. The server also rejects a blank deviceVersion or intMod, so neither has a useful default any more. The answer turns out to be keyed by intMod — the internal model id — rather than by product key or firmware. Against a real account, 19 of the 28 models under one product key returned a schema and all 19 returned the same 22 rows at one firmware; the other nine returned none. A device does not report which of its product's models it is over HTTP, so the script sweeps them all. It no longer goes through MammotionClient: this is an account-level lookup and bringing up MQTT for it only added a noisy teardown. Nothing calls the endpoint at runtime. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * chore: ruff 0.16.8, and the betterproto2 bump it needed The ruff pin was mirroring a transitive cap: betterproto2-compiler 0.9.0 requires ruff<0.10, so ruff could not move until the compiler did. betterproto2-compiler 0.10.1 drops the cap and pairs with betterproto2 0.10.0. That pairing is enforced at runtime — betterproto2 refuses code whose recorded compiler major.minor does not match — so pymammotion/proto had to be regenerated. Regenerating against 0.10.1 changes exactly one line, the recorded _COMPILER_VERSION; message_pool.py is byte-identical and the other 5,483 lines of __init__.py are unchanged. The upgrade is a no-op beyond the guard. ruff 0.9.10 -> 0.16.8 in both the hook and the dev dependency, so the hook and a local run finally agree. The resulting format pass is cosmetic: the PEP 758 `except (A, B):` rewrite, which is valid on the Python this package requires, plus some lambda wrapping. ruff check is 233 before and after, measured with the same version and the repo's own config, so none of this adds a finding. Also moves the product-params test to mirror its source module, which tests/meta/test_conventions.py requires. Note for hosts: betterproto2 is a runtime dependency, so this raises their floor to 0.10.0 as well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAyvhFn6mTk1wLwbLhmeYB * Bump version 0.9.0b16 -> 0.9.0b17 * add the capabilities output and missing test * update docs * catch bad data in properties * remove path --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Jason Pelzer <github.com@pelzer.com> Co-authored-by: Jaano Rosin <Jaano@users.noreply.github.com> Co-authored-by: Jaano Rosin <jaano@rosin.space>
| Commit: | 1fe670c | |
|---|---|---|
| Author: | Michael Arthur | |
fix(nav): auto_change_direction is field 20, not 21 Observed directly: toggling "Auto-reverse Mowing Direction" in the app sets field 20 to 1. Field 20 was named app_display_mode, taken from the 2.3.8.201 unpacked APK, and 21 was guessed as "the next free number after it" -- so we were writing the setting into the wrong field and reading someone else's. Field 21 is real but unidentified: 11 has been seen, and a Luba Mini AWD sends it length-delimited with a single 0x0A payload byte. Decode it as repeated int32 under a neutral name so both wire forms parse and the values surface in state dumps while we work out what it is. That also settles issue #192 structurally rather than by tolerating a list: field 21 is no longer read as an int, so there is no type mismatch to drop the frame on, and auto_change_direction is a plain scalar again. The .pyi stubs are regenerated too. protoc's --pyi_out is not part of the documented regeneration command, so they had drifted: luba_msg was missing MSG_CMD_TYPE_SPINO_CTRL and spino_ctrl had jobid typed str. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 8fde0f0 | |
|---|---|---|
| Author: | Michael Arthur | |
fix(nav): accept field 21 in either wire form, gate what we send Some firmware sends NavReqCoverPath.auto_change_direction (field 21) length-delimited. betterproto2 does not enforce wire type, so a packed payload on an int32 field parsed as a list, mashumaro rejected it, and every report frame carrying it was dropped -- continuously, taking the task settings and live position with it (issue #192). Declare the field repeated: that accepts both encodings, where int32 rejects the packed one. CurrentTaskSettings normalises back to a scalar, and off still stays off the wire, so nothing changes for devices that already worked. The field number is still inferred, but corroborated: the app's job model lists autoChangeDirection beside rideBoundaryDistance and appDisplayMode, matching 19-21, and reads it as `value != 0`, so a reported 10 is simply on. A frida capture would settle it. Also: - add ride_boundary_distance and app_display_mode to CurrentTaskSettings, which the proto reported and the model silently dropped - gate the outgoing write on DeviceType.supports_auto_change_direction; the app hides the row below 2.3.28.1 and on unlisted models - set the product key on the command builder again. Its only consumer is get_msg_device, which routes NAV by device type, so without it every device fell back to name-only detection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | 7c037f0 | |
|---|---|---|
| Author: | Michael Arthur | |
feat(devices): cover every published product key, bundle the error-code table Device families - Add the newer-generation product keys the cloud publishes alongside the a1 ones. /device-server/v1/product/product/list ships most families twice with an identical model set, and holding only the a1 half left 15 keys — including Luba 2 Pro's ATyVu9QkAdX and both SolarRtkV2 keys — falling through to defaults: the wrong broker for an Aliyun device, guessed capabilities otherwise. The newer keys deliberately do NOT join AliyunProductKey. - Complete the HM43x Luba mini block (LUBA_MB, LUBA_MD) and add is_support_fill_light / is_support_blade_speed, the capability predicates the app's own settings screen gates on. - Resolve a Spino-S1 name to SWIMMINGPOOL_SP, as the APK does, which needed comma-separated name prefixes in the resolution table. Error codes - Bundle the 469-code table so a device fault renders without a network call, with the negative-code normalisation the mower's reports need. Add the app's paged device-server endpoints and the product list. Fixes - Bound the send in DeviceMessageBroker.send_and_wait: it sat outside the timeout, so a stalled cloud invoke waited forever and could consume a host's whole setup budget (Mammotion-HA#859). - Accept a lean thing/property envelope: id/version/sys were required here but optional in the app, so an OTA progress push was dropped at DEBUG and a firmware install reported 0% for its whole run. - send_svg now rejects a pre-chunked list by name instead of failing deep in the chunker (Mammotion-HA#868), and Saga.retry_backoff is configurable. Testing - Add docs/testing.md, the testing constitution, and tests/meta enforcing its mechanical rules against a ratcheting baseline. Pay down the debt it found: 127 redundant asyncio markers, 62 wall-clock sleeps, 11 duplicated builders, 632 divider comments and 73 unused imports, all now pinned at zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Commit: | c614c43 | |
|---|---|---|
| Author: | Michael Arthur | |
fix some general weirdness with auth again, hard backoff added, as well as backoff for mqtt reconnecting, tasks/schedule work
| Commit: | 5eb8123 | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | Michael Arthur | |
fix adding device after connect for mammotion mqtt, update protobuf
| Commit: | 787f174 | |
|---|---|---|
| Author: | Michael Arthur | |
fix adding device after connect for mammotion mqtt, update protobuf
| Commit: | 006a1f7 | |
|---|---|---|
| Author: | Michael Arthur | |
massive refactoring
| Commit: | f37a028 | |
|---|---|---|
| Author: | Michael Arthur | |
add missing commands
| Commit: | 3b6411e | |
|---|---|---|
| Author: | Michael Arthur | |
large scale improvements, add support for mammotion mqtt, fix a lot of types, rename classes, fix some bugs
| Commit: | 37ad2e5 | |
|---|---|---|
| Author: | Michael Arthur | |
re-fix common data again, more fixes for gateway timeout and authorization expiry for camera
| Commit: | ca313d4 | |
|---|---|---|
| Author: | Michael Arthur | |
fix more issues with migration
| Commit: | 5b3ac79 | |
|---|---|---|
| Author: | Michael Arthur | |
better defensive checking for if a device exists first
| Commit: | 81fcf9d | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with missing enum, hash being wrong type, small improvements to logic
| Commit: | 66657fc | |
|---|---|---|
| Author: | Michael Arthur | |
update proto files and bump version
| Commit: | 0c1f6bd | |
|---|---|---|
| Author: | Michael Arthur | |
enable sending http requests without MQTT connection, and update protobufs
| Commit: | 4c26b3e | |
|---|---|---|
| Author: | Michael Arthur | |
fix stream command to ignore yuka mini, add updates to proto files
| Commit: | c92296a | |
|---|---|---|
| Author: | Michael Arthur | |
add no connection exception wrapper around the retry exception, fix a bug in proto
| Commit: | 663ea55 | |
|---|---|---|
| Author: | Michael Arthur | |
make some tweaks for new mammotion series mowers
| Commit: | efef51c | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | Michael Arthur | |
new release with more fixes
| Commit: | 4a36e1e | |
|---|---|---|
| Author: | Michael Arthur | |
new release with more fixes
| Commit: | f6636ca | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | GitHub | |
Dev (#71) * changes towards revamp of mammotion integration * update poetry lock * migrate to encrypted login * add device limits code * fix login oops * add and update linkkit layer on top of mqtt (paho) * add status message that will now set the state to online if offline * remove cloud service * remove cloud service * tidy up linter mypy and ruff, run some auto fixes and add basic release.yml for now. * fix get stream subscription * adjust the state manager to split the callbacks for now. * tweaks and fixes * fix connection status * fix missing proto value * fix small mistake on logic * store sub model id and try update mypy to not look at tests and so on * tweak the device state manager and allow removal of cloud and ble device in the mixed mode manager * bump version beta8 * Update proto files * change over to betterproto 2+ quality changes
| Commit: | 5e51d88 | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | Michael Arthur | |
fix missing proto value
| Commit: | 4375b4e | |
|---|---|---|
| Author: | Michael Arthur | |
fix missing proto value
| Commit: | 48c2732 | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with jobId and set bytes to empty if there is an exception in ble
| Commit: | 281ca60 | |
|---|---|---|
| Author: | Michael Arthur | |
add svg message and attempt to fix a few issues around ble queuing and mqtt being logged out prematurely
| Commit: | e140da6 | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with svg msg not being parsed properly and start adding mower state
| Commit: | de88582 | |
|---|---|---|
| Author: | Michael Arthur | |
add missing property and defaults to root hash list, update protobufs
| Commit: | 01cc286 | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with area hash name being around the wrong way and turn datamodels into serializable objects for saving
| Commit: | 594eba8 | |
|---|---|---|
| Author: | Michael Arthur | |
fix bug in loracfg proto
| Commit: | 453c1da | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | GitHub | |
Make em mow (#54) * start work on mowing * update mtrl nav * fix nav commands for none luba 1s * ruff format and so on * add product key to commands for checking device * fix blade on off * Try to implement MQTT response - Need helo on Lock * update protobufs with basestation * adding report info model and map conversions for lla enu * release 0.1.7 * try to keep last data if its missing in report cfg * fix report info * Add missing calls for ble sync as well as hook up callbacks, remove dead code and general clean up, added test call to verify things work * format and run ruff * add cloud as part of base MammotionDevice * rename http class * fix up the client sesion for http * update login to login_info * bump version to 0.2.1 * add start sync and sync maps calls to MammotionDevice * refactor mammotion to handle multiple devices and match to a name * update test examples * fix oops * fix quite a few errors and issues * stability improvements * fix more issues * adding movement commands * fix issue with device identification messing up sync maps * remove operation lock for mqtt * publish 0.2.7 * Add check and refresh token into send_cloud_command (#56) Co-authored-by: Andrea Cagnola <info@andreacagnola.it> Co-authored-by: Michael Arthur <mikey0000@users.noreply.github.com> * add methods for getting credentials for storing in HA when reloading or restarting without having to re-login * fix queuing of requests for mqtt, sync maps now works bump version to 0.2.9 * don't create cloud or initiate cloud connection if there isn't credentials * bump version to 0.2.11 and catch small issue with futures queue being empty --------- Co-authored-by: Andrea Cagnola <support@radiotsn.tv> Co-authored-by: d3dfantasy99 <34962618+d3dfantasy99@users.noreply.github.com> Co-authored-by: Andrea Cagnola <info@andreacagnola.it>
| Commit: | cb82be6 | |
|---|---|---|
| Author: | jLynx | |
| Committer: | GitHub | |
Updated to use PyMammotion (#45) * Updated to use PyMammotion * Removed old file * Bumpped version * Ran ruff * Disabled ruff * enabled again
| Commit: | 925f86f | |
|---|---|---|
| Author: | Michael Arthur | |
rename folders and project
| Commit: | 7b5b577 | |
|---|---|---|
| Author: | Michael Arthur | |
fix up better proto models and also fix spelling of height
| Commit: | 7575018 | |
|---|---|---|
| Author: | jLynx | |
| Committer: | GitHub | |
Added more protobufs (#16) * WIP * Updated luba_msg * Added missing path and files * Fixed typo in import * Updated last file and added generated py * updated dev_net * Updated mctrl_nav * Updated refrence * Updated ble_message to new proto * Updating ble message * Missed a few others
| Commit: | d1613c3 | |
|---|---|---|
| Author: | Michael Arthur | |
update more protobufs and add more commands
| Commit: | 0964851 | |
|---|---|---|
| Author: | Michael Arthur | |
update protobufs some more and finally worked out how to get battery and device state back from one of the calls
| Commit: | 7660841 | |
|---|---|---|
| Author: | Michael Arthur | |
add generated pydantic models and start testing them out, start rewriting the base classes for luba so it works with HA based off switchbot
| Commit: | f5420fb | |
|---|---|---|
| Author: | Michael Arthur | |
add in missing mctrlsys protobuf messages
| Commit: | 436d6e5 | |
|---|---|---|
| Author: | Michael Arthur | |
Start updating protobuf files to new ones
| Commit: | 713917a | |
|---|---|---|
| Author: | Michael Arthur | |
move project to poetry and fix various dependency issues
| Commit: | c6876b2 | |
|---|---|---|
| Author: | Michael Arthur | |
start of re-organisation to provide the three interfaces and add in Jans code for MQTT and cloud
| Commit: | cc7e5aa | |
|---|---|---|
| Author: | Michael Arthur | |
further refactoring and adding wifi nodes
| Commit: | 8e0294a | |
|---|---|---|
| Author: | Michael Arthur | |
update code to add in obstacle laps and mow order
| Commit: | b772461 | |
|---|---|---|
| Author: | Michael Arthur | |
fix up protobufs and fix the notification class for joining partial messages
| Commit: | ad520bc | |
|---|---|---|
| Author: | Michael Arthur | |
large update, can read most messages for gathering the map data
| Commit: | 0210bb8 | |
|---|---|---|
| Author: | Michael Arthur | |
protobufs mostly complete, fixed issues with reading data back from Luba, mostly porting calls now and working out how to pull map data
| Commit: | 5ba7796 | |
|---|---|---|
| Author: | Michael Arthur | |
add undock and dock commands and map to buttons on controller
| Commit: | 00d0d20 | |
|---|---|---|
| Author: | Michael Arthur | |
move everything into a proper folder structure and create requirements and so forth
| Commit: | 7737efd | |
|---|---|---|
| Author: | Michael Arthur | |
| Committer: | Michael Arthur | |
enable hand mowing
| Commit: | 3334f64 | |
|---|---|---|
| Author: | Michael Arthur | |
fixed issue with byte length of blufi start bytes works now, can control luba from laptop
| Commit: | c62f88a | |
|---|---|---|
| Author: | Michael Arthur | |
add movement code for remotely moving Luba, work more on the proto files
| Commit: | 66ae63c | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with esp driver very close now
| Commit: | 1811c21 | |
|---|---|---|
| Author: | Michael Arthur | |
fix issue with commESP not being correct
| Commit: | 5326b9a | |
|---|---|---|
| Author: | Michael Arthur | |
partial work on talking to Luba via pc