A Linux build of Mammotion's LUBA 1 RTK base-station firmware updater, which the vendor ships only for Windows and macOS.
There was nothing to reverse-engineer from scratch: the official Windows/macOS
"tools" are the same PyInstaller-frozen Python program (Python 3.10 + PyQt6 +
pyserial + protobuf). This package runs the vendor's own program bytecode
(client/, qttoy/, msgbus/ — copied unmodified from the official Windows
build) on Linux. The launcher only adds three small compatibility shims for
Windows-only calls. Nothing in the flashing protocol, the device handshake, or
the firmware image is altered.
✅ Confirmed working on real hardware. Used on Ubuntu 24.04 to flash a LUBA 1 RTK base station from firmware
1.0.0.54→1.1.0.87— the vendor tool's own post-write verification reported success.
⚠️ Compatible hardware: the RTK base station that shipped with the LUBA 1 only. This tool, and the bundled firmware image, target the LUBA 1 RTK reference station. It is not for the LUBA 2 / LUBA 2 series, Yuka, Luba mini, or any other Mammotion RTK. Do not run it against a different model — wrong firmware can brick the unit.
Flashing an RTK base station is inherently risky — a failed or wrong write can brick it.
- The code path here is the vendor's own, and it has now been used successfully on hardware — but you take the final risk.
- Always run
probe.pyfirst (read-only — it only asks the station for its version and cannot write to flash). Only run the real updater (run.py) once the probe confirms two-way communication.
If you are not comfortable with that, the zero-risk alternative is to run the official Windows/macOS tool in a VM with USB pass-through, or ask Mammotion support (support-eu@mammotion.com) to push the update.
# 1. one-time setup
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
# 2. plug the RTK into the PC with a USB-A -> USB-C *data* cable (see Troubleshooting),
# power it on, then confirm two-way comms (safe, read-only):
python probe.py # expect it to print the station's current version
# 3. flash (writes firmware via the vendor GUI):
python run.py # pick "USB Serial" in the dropdown, click Upgrade| Base-station MCU | STM32 (Cortex-M; firmware is a vector table into 0x08000000 flash) |
| USB-serial bridge | CH341 — driver is built into the Linux kernel (ch341.ko); no install needed |
| Serial settings | 230400 baud, 8N1, no flow control |
| Wire protocol | Mammotion msgbus: A5 5A sync, sender/receiver/cmd, CRC16-CCITT (0x1021), ACK/NAK |
| Commands | 0xC5 = get version (read), 0xC3 = LoRa/E22 bridge, 0xC4 = transmit firmware (write) |
| Firmware | firmware/luba_v087_base.bin — 48,220 bytes, version 1.1.0.87, sha256 b7932fc0…0e12bac (also embedded in the app's Qt resources; the app flashes the embedded copy) |
cd luba-rtk-linux
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txtRequires Python 3.10 (the vendor .pyc are 3.10 bytecode) and a desktop session
(the updater is a PyQt6 GUI).
Follow the same physical steps as the official guide (mark the station's position, power it off, unplug it, connect it to the PC by USB).
1. Confirm communication (safe, read-only):
python probe.py # auto-detects the ttyUSB port
# or: python probe.py /dev/ttyUSB0Expect a reply with the current version string. No reply → work through Troubleshooting below before going further. This step cannot brick the station; it only sends the get-version command.
2. Run the real updater (writes firmware):
python run.pyThis opens the vendor GUI ("Upgrade Tools"). In it: select USB Serial in
the Serial COM dropdown (that entry is your /dev/ttyUSB0; the GUI shows
the port's USB description, not the /dev/tty… path), then click Upgrade.
- You do not need the Driver button — that's the Windows CH341 driver installer; the driver is already in your Linux kernel.
- Let it run to 100%.
Nak, try againlines are normal — the protocol retries per frame. Do not unplug mid-write. - On success it re-reads the MCU and shows
upgrade success — mcu version: ….
Then reinstall the station at its marked position and confirm it pairs with the mower.
These are the real snags hit bringing this up on a fresh Linux box, in the order you're likely to meet them.
Cause: a charge-only / USB-C-to-USB-C cable. The CH341 is USB 2.0; many USB-C↔USB-C cables carry no usable data pair, or hit USB-C host/host role ambiguity, so the device never enumerates at all.
Fix: use a USB-A → USB-C cable into a normal USB-A port. The USB-A end forces the PC to be host and the RTK to be the device. (The vendor GUI says the same thing: "Type-A to Type-C cable… Charging-only cables will not work.") Make sure the RTK is powered on — its indicator light should come on.
Verify enumeration:
lsusb | grep 1a86 # -> QinHeng CH340/CH341 serial converter
ls -l /dev/ttyUSB0Cause: brltty. Ubuntu's Braille-display driver grabs every CH340/CH341
chip (they share a USB ID) and kills the serial node. You'll see this in
dmesg:
usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1
ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0
Fix (you almost certainly don't use a Braille display):
sudo apt-get remove --purge -y brltty
sudo udevadm control --reload-rules
# then unplug and replug the RTKThe node is root:dialout. Add yourself to dialout:
sudo usermod -aG dialout "$USER" # durable; takes effect after you log out/in
# or, just for this session:
sudo chmod a+rw /dev/ttyUSB0ModemManager probes new serial ports and can grab the CH341 mid-flash. Stop it for the duration:
sudo systemctl stop ModemManager
# (re-enable afterwards with: sudo systemctl start ModemManager)On startup the vendor code shells out to the Windows driver installer
(pnputil /add-driver CH341SER.INF /install) and, on Linux, died with
FileNotFoundError: 'pnputil' before the Upgrade button unlocked. run.py now
drops a no-op pnputil on PATH (the kernel already provides the driver, so
"install" is genuinely a no-op). No action needed — just don't be surprised by a
.shim-bin/ folder appearing next to run.py.
dmesg is often root-restricted on Ubuntu. To watch USB plug/unplug while
debugging:
sudo sysctl kernel.dmesg_restrict=0 # then: dmesg -w
# (resets to 1 on reboot; or restore manually)Expected. Once flashed, the new firmware boots into active RTK operation and stops answering the bare get-version handshake the old firmware did. This is not a brick — the GUI verified the new version during the flash. The real confirmation is that the base station now pairs with your mower.
The vendor modules are byte-for-byte identical to the Windows build; only the
launcher (run.py) adds shims:
asyncio.WindowsSelectorEventLoopPolicy→ aliased toDefaultEventLoopPolicy(the vendor sets it unconditionally at import; Linux's default loop is already a selector loop, so this is behaviourally equivalent).APPDATAenv var → defaulted to~/.local/share, so the app's config lands in~/.local/share/LubaBaseStationTools/instead of a Windows path.pnputil(Windows CH341 driver installer) → a no-op stub is placed onPATHso the vendor's startup driver-install step succeeds instead of crashing. The kernel already providesch341.ko.
None of these touch the flashing protocol, the firmware image, or the device handshake.
run.py Linux launcher (runs the vendor entry point + 3 shims)
probe.py Safe read-only comms test (get-version only)
requirements.txt Pinned Linux dependencies
firmware/ Extracted firmware (reference; the app flashes its embedded copy)
client/ qttoy/ Vendor program bytecode (unmodified, Python 3.10 .pyc)
msgbus/ Vendor serial/protobuf message bus (unmodified)
LubaBaseStationUpgrade.pyc Vendor entry point (unmodified)
This is an unofficial, community Linux build for interoperability. It is not affiliated with or endorsed by Mammotion.
The client/, qttoy/, msgbus/, LubaBaseStationUpgrade.pyc bytecode and the
firmware image in firmware/ are Mammotion's property, redistributed here
unmodified so Linux owners can update hardware they already own. All rights
to those files remain with Mammotion. Only the original launcher code
(run.py, probe.py) and this documentation are offered under the project
LICENSE. If Mammotion would like the vendor files removed, open an issue and
they will be taken down.