Repository navigation
Dev Adding A Provider
A mower vendor joins MowDeck as one package, libs/mowdeck-control-<vendor>, that plugs in through entry points (ADR 0066). This guide goes from an empty directory to a registered provider: generate the package from the template, keep the provider conformance kit green while the adapter grows, then register it. The design behind both tools is ADR 0069; how providers fit into the gateway, Fleet and the web app is Cloud providers: adding a vendor.
The rule throughout: MowDeck shows exactly what the mower can do. Declare a feature in MowerCapabilities only together with its implementation; everything you leave out is refused by the controller and hidden by the web app. A vendor with few features is a small package, not an unfinished one.
Read the LICENSE file inside the SDK's sdist, not only its PyPI metadata (two of the existing adapters wrap SDKs whose metadata says MIT while the file says GPL-3.0). A package that imports a GPL library must be GPL-3.0 itself and stays in its own package (ADR 0005, ADR 0011); a permissive SDK (MIT, BSD, Apache-2.0) keeps the package Apache-2.0. The template asks for this and has no default.
uv run poe new-provider acmeCopier asks for the vendor's display name, the SDK's import name (vendor-modules, which the core may never import), its PyPI name, its licence (permissive or gpl) and further brand words. The questions are listed in templates/README.md. You get:
| File | What it is |
|---|---|
pyproject.toml |
entry points mowdeck.cloud_providers and mowdeck.error_lexicons, [tool.mowdeck.adapter], the licence you chose |
src/.../plugin.py |
SPEC, importable without the SDK (the gateway loads disabled specs too) |
src/.../provider.py |
the CloudProviderPlugin: descriptor, discovery, connections, the fake or the real client |
src/.../descriptor.py |
credential fields (e-mail and password to start with), guidance, every text in English and German |
src/.../connection.py |
the MowerConnection: capabilities, state mapping, commands |
src/.../client.py |
the port to the vendor cloud; no SDK types above it |
src/.../sdk_client.py |
the real client, a stub until you wire in the SDK |
src/.../fake_client.py |
the fake cloud: accounts *@fake.mowdeck.test, password fake-cloud-password, one online and one offline mower |
src/.../lexicon.py |
the error lexicon, empty |
tests/test_acme_conformance.py |
the conformance kit on the fake cloud |
tests/test_acme_connection.py |
the rules the kit cannot know |
The generated mower can do almost nothing: it reports its state and takes start (the start plan now, because the fake shares no map), pause, resume, return to the dock and the emergency stop. That is a complete MowDeck provider already.
Add "mowdeck-control-acme" to the dev group and to [tool.uv.sources] of the root pyproject.toml, and libs/mowdeck-control-acme to the mypy files; then:
uv sync
uv run pytest libs/mowdeck-control-acme
uv run poe lintThe kit (mowdeck-provider-testkit) runs the same suite every adapter runs: entry points and descriptor texts, discovery with the fake credentials and a refused secret, the connection's lifecycle and states, the commands the capabilities offer and the ones they do not, the settings round trip, the emergency stop rules and the lexicon. Its full list with the source of each check is in the kit's README. Skipped tests say why (no settings declared, no start plans, ...); they turn on by themselves when you declare the feature.
-
The real client. Implement
sdk_client.loginwith the SDK, mapping its models ontoVendorMowerand its errors ontoLoginRefused,VendorUnavailableandMowerOffline; uncomment and pin the SDK inpyproject.toml. Share one logged-in session per vendor account between that account's mowers (the existing adapters keep an account pool), listen to the vendor's push channel instead of polling, never log a credential. -
The fake. Grow
fake_client.pywith every behaviour you rely on, from recorded or handwritten payloads: no test may need a network or a vendor account. -
Credentials. Change the descriptor's fields to what the vendor really needs (an application key and secret, a brand choice, ...). Exactly one field is the identity, at least one is secret, every text has
enandde. -
Capabilities. For each feature the vendor offers, declare it in
capabilities()and implement it in the same change: map areas (or more start plans: zones, edge cut, schedule), blade height range, light, settings fromSETTING_KEYSwithSettingsCapable(ADR 0046),dock_keeps_job, manual driving with aDriveSession, firmware, maintenance. The kit fails when a declared feature is refused or misses its protocol. -
State. Map the vendor's status onto
MowerActivity, setraw.liftedfor lift codes anddrive_recoverableonly for a stuck mower, never for a sensor stop (ADR 0027); leaveheartbeat_at,cloud_providerandconnectedto the gateway. -
Lexicon. Fill
ERROR_TITLESfrom the SDK's code table (Adding an error lexicon for a vendor) and takeerror_categoryfrom it, so lexicon and state agree. Hand the kit the table inemitted_error_codes; it then checks that every code is explained. -
Safety. The emergency stop acts at once (even while another command runs) and is sent even when the vendor reports the mower offline; a start or resume during an error raises
SensorStop. Everything else (expiry, de-duplication, dead-man switch, capability checks) is the controller's, not yours (ADR 0012).
A contract you cannot meet yet goes into ProviderUnderTest.known_violations with the reason; it runs as a strict xfail, so the list cannot outlive the fix.
- Add the package to
services/device-gateway/pyproject.toml(the only service that installs adapters) and runuv lock. - Register the vendor library for hardware-tested updates:
VENDOR_LIBSintools/repo-checks/scripts/vendor_lib_guard.py,exclude-dependenciesin.github/workflows/dependabot-auto-merge.yml, and a checklist in Vendor library updates. - Document it: the package README (what the user provides, capabilities, limits, rate limits, the fake accounts, "untested on real hardware"), a row in Cloud providers and in Libraries, and
LICENSING.mdfor a GPL package. - The repository checks then cover it too:
test_vendor_isolation.pykeeps the vendor out of the core andtest_provider_texts.pychecks the descriptor's languages. - It stays off by default (
enabled_by_default=False,tested_on_hardware=False) until someone tested it on a real mower; operators enable it withMOWDECK_CLOUD_PROVIDERS_ENABLE=acme(Compose:GATEWAY_CLOUD_PROVIDERS_ENABLE).
The kit only needs the package installed (its entry points) and pytest-asyncio in auto mode:
from mowdeck_provider_testkit import ProviderConformance, ProviderUnderTest
class TestAcmeConformance(ProviderConformance):
subject = ProviderUnderTest(
distribution="mowdeck-control-acme",
fake_credentials={"email": "garden@fake.mowdeck.test", "password": "fake-cloud-password"},
)A package without a cloud provider (only mowdeck.connections) passes connection_factory instead of fake_credentials. Single checks can be called directly from mowdeck_provider_testkit.checks with open_mower(subject, installed(name)).
Mirrored from docs/dev/adding-a-provider.md at commit 0031f1857a87c89bad7356661ca60578f18fedaf. Edit the file in the repository, not this wiki page.
Mirrored from the docs/ directory at commit 0031f1857a87c89bad7356661ca60578f18fedaf. Edit the files in the repository, not this wiki.
MowDeck documentation
- Home
- Phases
- Architecture
-
Decisions (ADR)
- Architecture decisions
- ADR 0001: OpenFGA instead of self-built authorization
- ADR 0002: Self-built Identity service in FastAPI instead of Keycloak or Zitadel
- ADR 0003: SvelteKit for the web app
- ADR 0004: Monorepo with a uv workspace
- ADR 0005: AGPL-3.0 for services and apps, Apache-2.0 for libraries
- ADR 0006: PostgreSQL for all persistence, no SQLite
- ADR 0007: NATS JetStream as event bus
- ADR 0008: Traefik as gateway
- ADR 0009: Command and event envelopes, subject naming and de-duplication
- ADR 0010: Python 3.13 for all services and libraries
- ADR 0011: Separate GPL-3.0 package for the PyMammotion adapter
- ADR 0012: Mower control interface and safety rules
- ADR 0013: Device-Gateway registry, sharding and per-mower concurrency
- ADR 0014: Authorization model, tuple ownership and the mowdeck-authz client
- ADR 0015: Tokens, sessions and key rotation
- ADR 0016: Fleet commands and authorization per command kind
- ADR 0017: Telemetry storage and retention
- ADR 0018: Realtime WebSocket protocol
- ADR 0019: Mammotion adapter transports, manual drive and secrets
- ADR 0020: Web app architecture
- ADR 0021: Browser authentication flow
- ADR 0022: Notifications: rules, recipients and delivery
- ADR 0023: Map rendering and projection
- ADR 0024: Scheduler: recurrence, rain pause and command issuing
- ADR 0025: Home Assistant integration as a pure API client
- ADR 0026: Manual drive channel over the Realtime WebSocket
- ADR 0027: Health history and theft alarm
- ADR 0028: Entitlements: plans as data and enforcement
- ADR 0029: Helm chart and release pipeline
- ADR 0030: Mowing patterns as plug-ins and the Planner
- ADR 0031: Billing with a Stripe adapter
- ADR 0032: Live camera over Agora, joined directly by the browser
- ADR 0033: Vision-assisted recovery with human approval
- ADR 0034: Linked cloud accounts and device discovery
- ADR 0035: Server-side camera frames and automatic AI analysis on errors
- ADR 0036: Native app shells with Capacitor, loading the web app from the server
- ADR 0037: Camera panorama calibration in the browser, with the AI supplying points only
- ADR 0038: Dual link per mower and firmware updates
- ADR 0039: Spot mowing by MowDeck-driven paths
- ADR 0040: Link heartbeat for online status
- ADR 0041: Spot runs from the dock: undock, transit and return
- ADR 0042: Drive calibration
- ADR 0043: Turn styles
- ADR 0044: Drive view with camera
- ADR 0045: Camera relay
- ADR 0046: Device settings as capabilities
- ADR 0047: The RTK base station as a Fleet device
- ADR 0048: Maintenance commands
- ADR 0049: Manual driving over the Mammotion cloud
- ADR 0050: Passkeys and the app login hand-off
- ADR 0051: Sign in with GitHub
- ADR 0052: Sign in with Apple
- ADR 0053: Retention and downsampling of the position track
- ADR 0054: Aerial photo under the mower map
- ADR 0055: Deploy maintenance window
- ADR 0056: Camera photo in push notifications for errors and theft alarms
- ADR 0057: Step-up re-authentication for critical actions
- ADR 0058: Theft tracking mode
- ADR 0059: iOS widgets and the mowing Live Activity
- ADR 0060: Apple Watch app
- ADR 0061: Mowing angle rotation
- ADR 0062: Setup tour for new users
- ADR 0063: Matter server (mowers in Apple Home and Google Home)
- ADR 0064: Personal access tokens and the public API documentation
- ADR 0065: Security center and new-login alerts
- ADR 0066: Cloud provider plugins and adapters for more vendors
- ADR 0067: Error code lexicon, provider independent
- ADR 0068: Public demo mode
- ADR 0069: Provider conformance kit and provider package template
- ADR 0070: Virtual zones for mowers without a vendor map
- ADR 0071: Capability matrix generated from the adapters
- ADR 0072: API explorer in the web app, for the public demo
-
Development
- Getting started
- Service template
- Shared libraries
- Cloud providers: adding a vendor
- Adding a provider: template and conformance kit
- Adding an error lexicon for a vendor
- Design: identity and authorization
- Design: fleet, telemetry, realtime
- Design: web app
- Web app
- Home Assistant integration
- Camera source bridge protocol
- Fuzzing and property-based tests
- GitHub wiki mirror
-
Operations
- Operating the Compose stack
- Operating MowDeck on Kubernetes (Helm)
- Building the Bluetooth bridge (ESPHome on a XIAO ESP32-S3)
- Releases
- Vendor library updates
- Billing operations
- Telemetry: retention and position downsampling
- iPhone and Android apps
- Passkeys
- Sign in with GitHub
- Aerial photo under the map
- Sign in with Apple
- Theft tracking mode
- Matter server
- Source code offer (AGPL-3.0 section 13)
- Public demo
- Spikes
-
Guides
- Guides
- ESP32 bridge: purchase and setup
- Signing in with a passkey
- Signing in with GitHub
- Error codes in plain words
- Aerial photo under the map
- Signing in with Apple
- Confirming critical actions
- Your security page
- iPhone widgets and the mowing Live Activity
- Apple Watch
- The setup tour
- Apple Home and Google Home (Matter)
- Worx Landroid, Kress and LandXcape mowers
- Scripting MowDeck
- Capability matrix
- API reference
- Original plan (German)