Repository navigation
Guide Esp32 Bridge Purchase And Setup
This guide gets you from nothing to a working BLE bridge on the LUBA 2 AWD X, using the ESPHome Bluetooth Proxy as the quickest test path. It feeds steps 4 to 6 of spike 0001. Prices were checked on 2026-10-08 where a link is given; everything else is an approximate street price.
A small ESP32 board rides on the mower, keeps a Bluetooth LE connection to it at close range and talks to your home Wi-Fi. A Python process on your laptop (later the MowDeck Device-Gateway) uses the ESP32 as a remote Bluetooth radio. PyMammotion already supports this path through bleak-esphome (dev_console.py).
- USB port on the mower. The LUBA 2 AWD manual lists on the rear view "SIM card tray" and "USB port for troubleshooting and debugging" (manual PDF). Unverified for your X model: connector type, whether it supplies 5 V, how much current, and whether it stays powered while the mower sleeps or charges. Look at the port (next to the SIM tray, per the manual), note the connector type and whether it has a sealing cap, and plan to measure it with a USB power meter before relying on it. One community project powers its ESP32-S3 from "the mower's USB-C port" (mammotion-rc), but does not name the model.
- Antenna. The mower body and the board's position matter more than the chip. Pick a board with a real external antenna (U.FL) or a proven compact module. Avoid metal enclosures.
- Wi-Fi in the garden. Walk the mowing area with your phone and a Wi-Fi analyser app. If the far end shows worse than about -75 dBm, plan an outdoor access point or mesh node first. A bridge without Wi-Fi is useless.
| # | Item | Recommended | Alternative | Approx. price | Where |
|---|---|---|---|---|---|
| 1 | ESP32 board | Seeed Studio XIAO ESP32-S3 (BLE 5, U.FL antenna connector, USB-C) | M5Stack AtomS3 Lite (ESP32-S3 in a small plastic case, internal antenna) | 9.90 EUR each | Berrybase XIAO ESP32-S3, Berrybase AtomS3 Lite, also reichelt, Amazon.de |
| 2 | Antenna | 2.4 GHz rod or FPC antenna with U.FL plug (the XIAO ESP32-S3 is normally sold with one; check the listing says so) | U.FL to RP-SMA pigtail plus a small 2.4 GHz RP-SMA antenna through the enclosure wall | 0 to 10 EUR | same shops, Amazon.de |
| 3 | USB cable | USB-C to USB-C (or USB-A to USB-C, depending on the mower port), 20 to 30 cm, angled plug | Any short data-capable cable | 5 to 8 EUR | Amazon.de, reichelt |
| 4 | USB power meter | Inline USB-C meter showing V and mA | USB-A meter plus adapter | 10 to 15 EUR | Amazon.de |
| 5 | Enclosure | Small ABS or polycarbonate box, IP65, about 80 x 82 x 55 mm | Any IP65 plastic junction box that fits board and cable bend | 5 to 10 EUR | reichelt, Conrad, RS (example RND 455) |
| 6 | Cable gland | M12 nylon gland, IP68, for 3 to 6.5 mm cable | Grommet plus sealant | 1 to 3 EUR | reichelt, RS, Amazon.de |
| 7 | Mounting | Outdoor double-sided tape (3M VHB type), UV-stable cable ties, a silica gel pack | Velcro straps | 5 to 10 EUR | hardware store, Amazon.de |
| 8 | Fallback power | 5,000 mAh power bank with an always-on or low-current mode | USB battery pack from a cycling light | 15 to 25 EUR | Amazon.de |
Buy two boards: one for the mower, one to keep on the desk for firmware work. Expected total: about 40 to 60 EUR. Mouser and Digi-Key also stock XIAO boards but only make sense when combined with a larger order because of shipping.
Why the XIAO ESP32-S3: tiny, cheap, USB-C, ESP-IDF support in ESPHome, and an external antenna you can route to the best spot. The AtomS3 Lite is the easier choice if you prefer a ready case and accept the internal antenna. Do not buy an ESP32-S2: it has no Bluetooth.
You need Python with uv (see the spike's environment setup). Install the ESPHome CLI:
uv tool install "esphome==2026.9.1"Create a folder mowdeck-bridge/ with two files.
secrets.yaml (never commit it):
wifi_ssid: "YourGardenSSID"
wifi_password: "..."
api_key: "..." # generate: python -c "import base64,secrets;print(base64.b64encode(secrets.token_bytes(32)).decode())"
ota_password: "..."
fallback_password: "..."mowdeck-bridge.yaml:
esphome:
name: mowdeck-bridge
friendly_name: MowDeck Bridge
esp32:
board: seeed_xiao_esp32s3 # AtomS3 Lite: m5stack-atoms3
framework:
type: esp-idf # required for the proxy, uses less memory
logger:
api:
encryption:
key: !secret api_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "MowDeck Bridge Fallback"
password: !secret fallback_password
captive_portal:
esp32_ble_tracker:
scan_parameters:
active: true
bluetooth_proxy:
active: true # proxy GATT connections, not only adverts
connection_slots: 3
sensor:
- platform: wifi_signal
name: "WiFi signal"
update_interval: 15s
- platform: uptime
name: "Uptime"
button:
- platform: restart
name: "Restart"Option notes, from the ESPHome Bluetooth Proxy docs: active: true enables GATT connections, connection_slots defaults to 3 on ESP32 (maximum 9, keep it at 5 or less), and ESP-IDF is recommended. The API password option is deprecated; use the encryption key (ESPHome API docs).
Do not add a ble_client: entry for the mower, even though the Mammotion-HA ESP32 guide shows one. It makes the ESP itself hold the mower's connection, which probably blocks the proxied connection we need (unverified).
Flash over USB:
cd mowdeck-bridge
esphome run mowdeck-bridge.yaml # choose the USB serial portIf the port does not show up or flashing fails, hold the BOOT button while plugging in the board, then try again. Later updates go over the air with the same command (choose the network target). Watch the logs:
esphome logs mowdeck-bridge.yaml --device mowdeck-bridge.localYou should see Wi-Fi connected, the API server listening, and BLE advertisements once the mower is on.
Home Assistant: HA will discover the new ESPHome device. Click "Ignore" for now. If HA adopts it, the old Mammotion integration may grab the mower through it and collide with the spike.
-
Quick API check (in the spike project folder, with
ESPHOME_NOISE_PSKset toapi_key):import asyncio, os from aioesphomeapi import APIClient async def main() -> None: api = APIClient("mowdeck-bridge.local", 6053, None, noise_psk=os.environ["ESPHOME_NOISE_PSK"]) await api.connect(login=True) info = await api.device_info() print( info.name, info.esphome_version, info.bluetooth_proxy_feature_flags_compat(api.api_version) ) await api.disconnect() asyncio.run(main())
Expected: name, ESPHome version and a non-zero feature flag value. If
.localdoes not resolve on Windows, use the IP from your router. -
Mower over the proxy: put the ESP32 next to the mower on a USB charger, close the phone app, then run spike step 2 with
--via proxy --name <Luba name> --address <MAC>. Battery and mode must appear. Note: the scripts usebluetooth-adaptersto build a Bluetooth manager; if that fails on your laptop, run the same command on a Linux machine (unverified on Windows). -
Latency: spike step 5 with
--via proxy, first next to the mower, then mounted.
- Placement: on the top rear area, as high as possible, away from metal parts. Keep clear of the camera, ultrasonic sensors, bumper, lights and the rain sensor. Do not drill or open the mower; that risks the warranty and the IPX6 seal.
- Enclosure: board inside the IP65 box, antenna inside or through the wall, cable through the M12 gland, silica gel pack inside. Fix the box with outdoor tape plus a cable tie as backup.
- Power, option A (mower USB): only after the meter shows stable 5 V with the mower switched on. Record whether power stays on while docked and charging, and while the mower sleeps. The port is a service port: if it has a cap, the cap cannot close with a cable plugged in, so shield it from rain with a drip loop and do not leave this setup out in rain until you know the port is sealed or the cable plug is protected. Unverified whether this is safe for the mower long term.
- Power, option B (power bank): for the first outdoor tests, a power bank in the enclosure keeps the mower's port closed. Many power banks switch off at the low current an ESP32 draws (typically around 50 to 150 mA with Wi-Fi and BLE active, a rough estimate); pick one with an always-on mode and test it overnight on the desk.
-
Check coverage: after mounting, let the mower drive its normal route and watch the
WiFi signalsensor inesphome logs. Note where it drops below about -75 dBm.
The ESPHome proxy is the Phase 1 tool. A later MowDeck firmware (only if the spike shows it is needed) changes:
| ESPHome proxy today | MowDeck firmware later |
|---|---|
| Inbound TCP API on port 6053, client must reach the ESP on the LAN | Outbound WebSocket over TLS to the Device-Gateway, works behind any router |
| Shared Noise key per device, typed in by hand | Per-device secret, provisioned during pairing in the MowDeck app |
| Generic Bluetooth proxy for any device and Home Assistant | Single-purpose GATT tunnel for the mower's ff01 and ff02 characteristics |
| ESPHome OTA from your laptop | Signed OTA delivered by the Device-Gateway |
| No mower-specific safety | Local deadman: sends a stop to the mower if the gateway link drops |
An intermediate option is ESPHome with its wireguard component, which builds an outbound tunnel to one peer and keeps the stock proxy (ESPHome WireGuard). The spike results decide between the two.
Mirrored from docs/guides/esp32-bridge-purchase-and-setup.md at commit a117684672263ba4f0b7e2e3538233456122d24e. Edit the file in the repository, not this wiki page.
Mirrored from the docs/ directory at commit a117684672263ba4f0b7e2e3538233456122d24e. 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
- Development
- Operations
- Spikes
- Guides
- Original plan (German)