Repository navigation
Dev Cloud Providers
MowDeck talks to mowers through adapter packages. A vendor whose mowers are reached through its cloud (account linking, device list, cloud connections) plugs in as a cloud provider (ADR 0066). The core services never import a vendor package and never name a vendor: the Device-Gateway loads the providers through entry points, Fleet learns their credential fields from the gateway, and the web app renders the link form from those fields.
flowchart LR
web["Web app<br/>Settings, Linked accounts"] -->|GET /linked-accounts/providers<br/>POST /linked-accounts| fleet["Fleet"]
fleet -->|gateway.cloud.providers<br/>gateway.cloud.discover| gw["Device-Gateway"]
gw -->|entry points<br/>mowdeck.cloud_providers| plugin["Provider plugin<br/>(adapter package)"]
gw -->|cloud registration<br/>option cloud_provider| plugin
plugin --> sdk["Vendor SDK"]
sdk --> cloud["Vendor cloud"]
fleet -.->|fleet.mowers.credentials<br/>every field of the account| gw
- The gateway loads the specs of the installed packages (
mowdeck.cloud_providers) and creates a plugin for every enabled provider: those withenabled_by_default(tested on real hardware) plusMOWDECK_CLOUD_PROVIDERS_ENABLE, minusMOWDECK_CLOUD_PROVIDERS_DISABLE. Disabled providers are not loaded and not offered. - Fleet asks for the enabled providers on
gateway.cloud.providersand serves their descriptors atGET /api/v1/fleet/linked-accounts/providers. - The web app renders the provider's fields, guidance and warnings ("untested on real hardware", "needs a developer account").
- Linking: Fleet checks the credentials against the descriptor, asks the gateway to log in (
gateway.cloud.discover), seals the secret fields (AES-256-GCM) and stores the rest. Secrets never leave Fleet except in two internal NATS replies and are never logged or returned. - A mower registered from the account carries
cloud_providerin its registration; the gateway hands it to that provider's plugin and merges the account's credential fields into the connection options when it connects.
MowDeck is the same for every provider; a mower shows what its adapter declares and nothing more. Every feature is gated by MowerCapabilities, by capability-declared device settings (ADR 0046) and by the mower options the provider declares, never by the vendor. An adapter that only connects, reports its state and starts, pauses and docks gets a clean MowDeck without dead buttons; declare more, get more.
-
Package
libs/mowdeck-control-<vendor>: Apache-2.0, or GPL-3.0 with its ownLICENSEwhen the SDK is GPL (ADR 0011; read the SDK'sLICENSEfile, not only PyPI). Pin the SDK exactly. -
Connection: a thin
MowerConnection(ADR 0012). Declare only what the mower does inMowerCapabilities(the defaults mean "not supported"; the controller refuses the rest); map the state,raw.liftedandraw.blade_on(ADR 0027), anderror_categoryfrom your error lexicon. -
Plugin: a
CloudProviderPluginwith itsdescriptor(credential fields, setup steps, links,needs_developer_account,tested_on_hardware,mower_optionsfor the settings its mowers take),discover,create_connection,close, and a fake cloud forProviderContext.fake. Write every user-visible text as{"en": ..., "de": ...}(the app's languages;test_provider_texts.pychecks it) and leave generic hints such as "stored encrypted" to the web app. -
Declarations in
pyproject.toml:[project.entry-points."mowdeck.cloud_providers"] acme = "mowdeck_control_acme.plugin:SPEC" [project.entry-points."mowdeck.error_lexicons"] acme = "mowdeck_control_acme.lexicon:LEXICON" [tool.mowdeck.adapter] vendor-modules = ["acme_sdk"] brand-names = ["acme"]
tools/repo-checks/tests/test_vendor_isolation.pythen keeps the vendor out of the core. The lexicon guide: Adding an error lexicon for a vendor. -
Tests against recorded or handwritten payloads and a fake SDK client, no network.
-
Image (the one mechanical core step): add the package to
services/device-gateway/pyproject.tomland to the workspacepyproject.toml(dev group,tool.uv.sources, mypy files), thenuv lock. -
Docs: a package README (what the user provides, capabilities, limits, rate limits, the fake accounts, "untested on real hardware") and a row in the root README.
-
Enable: off by default until tested on real hardware; operators set
MOWDECK_CLOUD_PROVIDERS_ENABLE=<id>(Compose:GATEWAY_CLOUD_PROVIDERS_ENABLE).
| Field kind | Entered as | Stored | Shown on the linked account |
|---|---|---|---|
email |
e-mail input | plain (public_credentials) |
yes |
text |
text input (monospace) | plain | yes |
choice |
select | plain | yes (the choice's label) |
secret |
password input, write-only | sealed with MOWDECK_CREDENTIAL_ENCRYPTION_KEY
|
never |
The identity field is the account's title and is unique per provider and MowDeck account (compared without case). A re-authentication (POST /linked-accounts/{id}/sync with credentials) takes secret fields only; the stored values stay for the others.
Never log a credential, never put one into an exception message, and never answer one in an API response; the gateway reports only the exception type of an unexpected plugin error.
| Provider | Package | Licence | User provides | Tested on hardware | On by default |
|---|---|---|---|---|---|
mammotion |
mowdeck-control-mammotion |
GPL-3.0 | e-mail and password of a second Mammotion account | yes (LUBA 2 AWD X) | yes |
husqvarna |
mowdeck-control-husqvarna |
Apache-2.0 | application key and secret of their own application in the Husqvarna Developer Portal (free developer account) | no | no (MOWDECK_CLOUD_PROVIDERS_ENABLE=husqvarna) |
The Husqvarna adapter's capabilities, state mapping, commands and rate limits are in libs/mowdeck-control-husqvarna/README.md. The research behind further vendors (Husqvarna, Segway Navimow, Worx) is in ADR 0066.
Mirrored from docs/dev/cloud-providers.md at commit 20efce891a6b21e5a427244130b6c0ff457f47e9. Edit the file in the repository, not this wiki page.
Mirrored from the docs/ directory at commit 20efce891a6b21e5a427244130b6c0ff457f47e9. 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
-
Development
- Getting started
- Service template
- Shared libraries
- Cloud providers: adding a vendor
- 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
- 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)
- Scripting MowDeck
- API reference
- Original plan (German)