Read your Gen2 Zero motorcycle's own diagnostics over the service port — battery health, motor controller, error logs, settings — back them up, and make sense of them offline. A safe, read-first desktop app for the MBB serial console (OBD-II / C3 port); any writes are whitelist-only and gated. Try the whole thing with the built-in simulator — no bike needed. Windows-11-native look (Sun Valley theme).
⚠️ USE AT YOUR OWN RISK. OpenMBB is an unofficial, independent hobby tool for diagnosing your own vehicle. It is not affiliated with, authorized, endorsed by, or sponsored by Zero Motorcycles, Inc. or Sevcon/BorgWarner; those names are used only to describe the hardware it talks to. Writing settings or sending console commands can damage your motorcycle, brick components, void your warranty, or create an unsafe riding condition, and may be irreversible. It has been verified on a single 2017 Zero FXS (MBB rev 41) only — behavior on any other model or firmware is untested. No warranty; the authors accept no liability for any damage, injury, or loss. See LICENSE.
OpenMBB is a general read / diagnostics tool for the Gen2 MBB console: connect, read everything the bike will tell you (identity, BMS, motor controller, stats, error log), and analyze it offline. Writing is optional and off by default — the Writes tab doubles as a reference of what's adjustable (each whitelisted setting shown with its effect and risk), and any change is gated behind login + a master unlock + a per-write confirm. The gearing calculator is just one of the optional analysis helpers, not the focus.
OpenMBB talks to the MBB (Main Bike Board) console at 38400 baud with the
Gen2 command set (bms, set, spfront, maxcustspmph, …). That pins the scope:
| Verified | 2017 Zero FXS (MBB firmware rev 41) |
| Should work (same console, UNVERIFIED) | Other Gen2 MBB Zeros of the ~2013–2019 era — S, SR, DS, DSR, FX (and the later FXE, a Gen2-platform rebadge) — if they run the same 38400-baud MBB console. Command set assumed compatible but untested; treat writes with extra caution. The write panel auto-adapts — it only shows whitelisted settings that appear in that bike's live dump. |
| Not compatible as-is | Pre-2013 Gen1 (9600 baud) and 2020+ Cypher III / SR-F generation (SR/F, SR/S, DSR/X, newer S/DS) — different baud and a different console language. |
The console baud is fixed at 38400 (the Gen2 rate). Gen1/Gen3 would need both a different baud and a different command set, so OpenMBB is a Gen2 tool by design.
Built and tested on Windows and Linux (CI covers both). Runs from source on macOS too, but that isn't exercised by CI.
Download openmbb-setup-windows-x64.exe from the repo's Releases page and run
it. It's a per-user install — no admin rights: Start-menu entry, optional
desktop icon, and a clean uninstaller. Launch it from the Start menu or the
desktop icon. To update, download the newer setup exe and run it over the old
install.
One self-contained file that bundles Python, Tk, and every dependency:
- Windows (portable):
openmbb-windows-x64.exe— double-click to run from anywhere; nothing is installed. - Linux:
openmbb-linux-x64—chmod +x openmbb-linux-x64 && ./openmbb-linux-x64.
Release binaries are built and verified by CI (.github/workflows/build.yml) on
each tagged release.
git clone https://github.com/rodu4835/openmbb
cd openmbb
python -m venv .venv
.venv\Scripts\activate # Linux/macOS: source .venv/bin/activate
pip install -e .
Then:
openmbb --sim # explore the whole tool with the simulator — no bike needed
openmbb # real serial (auto-lists COM ports; needs an FTDI cable)
openmbb --port COM4 # preselect a port
openmbb --selftest # headless transport/safety tests
openmbb --smoketest # build the GUI once, sim-connect, and exit
Requires Python 3.12+ (older 3.9–3.11 will likely work but are untested; CI
runs 3.12); pyserial and sv-ttk install automatically (if
sv-ttk is ever unavailable the app falls back to a built-in dark theme). Fonts
and the file-manager "open folder" action work cross-platform. To update:
git pull, then re-run pip install -e . if dependencies changed.
If the
openmbbcommand isn't found after install, your PythonScriptsdir isn't on PATH — usepython -m openmbb.cli(same flags).
pip install .[dev] # adds PyInstaller + Pillow
python packaging/build.py # -> dist/openmbb(.exe) — portable, any OS
On Windows, to also build the installer (requires Inno Setup 6):
powershell -File packaging\build_and_install.ps1 -Force # or: build-and-install.bat
This builds dist\openmbb.exe, then packaging\Output\openmbb-setup-windows-x64.exe,
installs it per-user, and verifies with --selftest / --smoketest (-Force
closes a running OpenMBB first). PyInstaller can't cross-compile — build a Windows
binary on Windows and a Linux binary on Linux, which is what the CI matrix does.
| FTDI wire | OBD pin | Pigtail wire | Role |
|---|---|---|---|
| Black (GND) | 5 | Teal | Diagnostic ground |
| Yellow (RXD) | 8 | Black/White | bike MBB Tx |
| Orange (TXD) | 9 | Red/White | bike MBB Rx |
| Red (+5 V) | — | — | NEVER connected |
38400 baud, 8-N-1, no flow control, newline CR-LF. Bike parked, kill switch OFF. Never stream the console while riding. (Port location and pin-12 wiring vary by year; on 2017+ FX/FXS the port is under the seat.)
Powering the console: you do not need to key the bike on — plugging in
the AC charger wakes the MBB and the console is fully live for reads (the bike
shows Mode: Charging). That's the easy way to do a read session. The one
caveat: isolation-resistance readings are only valid off the charger (the
onboard charger ties the HV bus to mains earth and makes the isolation monitor
read falsely low), so OpenMBB flags any isolation reading taken while charging.
OpenMBB opens on a Home screen: a short blurb and two buttons — Analyze (open a saved session, no bike needed) and Connect. A Simulator mode toggle lives there too (its state shows in the status bar), so you can click through the whole tool with no bike or cable. The staged flow below unlocks one step at a time — you can stop after any stage, and closing the window never loses data.
- Connect — optionally run Test your cable first: it opens the port and
only listens (never transmits in software), so it proves RX wiring + baud with
zero risk on any fixed cable — power the bike during the listen window to catch
the boot banner. Then Connect wakes the
ZERO MBB>prompt (retried), rejects wrong-baud/Tx-Rx garbage instead of trusting a stray>, and requires theversionbanner to parse (rev checked against the verified rev 41 — a different rev connects with a warning). - Read — per-command buttons; Pull full database captures the command
reads + the settings dump (your backup) + the small
errorlogdump. The heavy log reads (eventlogdump/dumpall, ~1 MB, minutes at 38400) are not in the default pull — they sit behind their own buttons with a confirm dialog (you can opt in to foldeventlogdumpinto a pull with a checkbox), because on a keyed-on bike a long dump can starve the MBB's CAN servicing enough that the BMS briefly opens the drivetrain contactor (a click + a flashing dash that recovers when the read finishes; observed live) — and each contactor open leaves a permanent "Line Contactor o/c — VERY SEVERE" error-log entry the app can't clear, so only read the event log when you need it. During a heavy read the app shows a blocking "Reading — please wait" window that locks the rest of the UI until it finishes, then offers Continue (the console is single-threaded — nothing else can run mid-dump). One failed command no longer discards the pass, and Login unlocks only once the essential reads + a parsedsetsucceed. The session's power state is stamped (session_meta.txt); an on-charger baseline is flagged because isolation/SOC context isn't valid off the charger. You can stop here — reads are the whole point of day one. - Login — explicit. On rev 41 the tunables (
spfront/sprear/regen/…) are login-gated:setat level 0 shows identity only. The password box is pre-filled with the last one that worked (or a community-known one —tpsreport,wideopenthrottle); press Login, or type a different one (masked in the logs, never saved to disk). Success is confirmed by the read-onlyloginlevel query (Login Level: N), not by guessing at the reply wording. On success the post-loginsetis saved as the authoritative pre-change backup and the tunables appear. - Writes — locked behind login + master unlock toggle + per-write confirm. Rows appear only for settings that are BOTH on the whitelist AND present in the live dump. Click a row's New value cell to type a value, then Write. Every write: journal the intent before it hits the wire → re-read current → confirm old→new with effect/risk → auto-backup full settings → send → read-back verify → journal to disk. A changed row shows ↺ Reset to restore the last-read value; a read-back mismatch points you to it.
The Analyze tab is always available (no bike needed) and interprets a saved session folder — or the current one:
- Health — SOC vs pack voltage, cell balance, capacity, temps, charge cycles, and the effective gearing ratio, each flagged ok / watch / alert.
- Rides — per-ride distance, SOC%/km, and temps, read straight off the
bike:
eventlogdumpprints the full event log as decoded text (including per-sample riding entries — SOC, pack/motor temp, voltage, rpm, odometer), which OpenMBB parses directly. No Zero app, no.binfiles, no external decoder. Pull it with Pull ride log from bike, or Load ride log (.txt) for a log you already have. - Charts — dependency-free plots of the ride-log series, plus
Trend:metrics across your saved real-hardware pulls (pack capacity, charge cycles, temps, effective gearing) on a dated timeline. Pick a date Range, or drag to zoom any x-window (double-click resets). - Compare — pick 2+ sessions to see exactly which settings changed between them; the over-time trends live on the Charts tab.
The analysis parsers are deliberately tolerant: fields the capture doesn't
include show n/a rather than failing.
It's your own bike, so OpenMBB does not put a hard wall between you and the console — but it makes the dangerous things deliberate, loud, and hard to do by accident:
- Destructive commands are gated behind an informed-consent dialog, not
hard-blocked. For the known-destructive set —
format/erase eeprom,settingsrst,statsrst, log clears/adds,reset,exit_to_bl,test,wdt,timing,can,charger,sevcon preop, and the ones the real rev-41 menu revealed (dtc_clear,force_all_storage_mode,blcmds,burn) — the raw Console will not send them casually: it shows a dialog spelling out what the command does, what could happen, and how (if at all) to recover, and makes you typeconfirmfirst. There is no such dialog on the read commands. Bareeeprom(the read-only "EEPROM usage" summary) andobdare ordinary reads;eepromwith any argument is treated as destructive. - What IS refused outright (no override): any command containing a control
character (so a pasted
status⏎settingsrstcan't smuggle a second line onto the wire), and everyset <name> <value>write from the raw box — writes go only through the gated Writes-tab flow (Transport.write_setting), which re-validates against the whitelist. The two-tokenset <name>single-setting view is refused too (its no-value behavior is unverified on rev 41 — it could prompt-to-write), so read values from the fullsetdump instead. - Verified on ONE bike. The blocklist, whitelist, parsers and per-command recovery notes were checked against a single 2017 Zero FXS at MBB rev 41. On any other model or firmware they are untested — a "confirm" you type there is trusting guidance that may not apply to your hardware. The app warns when it can't confirm your bike is a verified FXS rev 41.
- A typed password is masked in everything written to disk for the whole session (not just the one command), so a late console echo can't leak it.
- Regen/thermal guards are shown read-only in the Writes tab.
- Validators: coast regen of exactly 0 is refused (fishtail risk);
noregenstopped Nowarns; range limits on every numeric. - A mid-session reboot (boot banner) or a silent console is detected and surfaced — login state is re-locked rather than trusted.
src/openmbb/
__init__.py name, version, module docstring
safety.py blocklist, write whitelist, validators
transport.py serial transport, session logging, settings parser
sim.py fake serial port (synthetic fixtures for hardware-free use)
theme.py Sun Valley (Win11) theme with dark fallback
gui.py phase-gated panels + Analyze tab + menu bar
cli.py entry point + --selftest / --smoketest
parsers.py tolerant console-output parsers (bms/stats/status/ride log)
sessions.py load saved session folders for analysis
health.py health snapshot (metrics + ok/watch/alert status)
rides.py ride-log summaries + effective gearing from the odometer
gearing.py gearing math (teeth -> ratio -> speedo settings)
compare.py settings diff + capacity / gearing trends across sessions
tests/ pytest suite (safety/transport/config/analysis + GUI flow)
Everything lands in <save-base>/openmbb-sessions/<timestamp>_<port>/:
session_raw.log (every byte, timestamped), one file per command,
session_meta.txt (power mode, firmware rev, timestamp),
settings_baseline_*.txt, settings_backup_*.txt (auto, pre-write),
writes_journal.txt.
Where it saves: by default the <save-base> is ~/Documents/OpenMBB (a
fixed, user-visible location — not the launch directory, so sessions never get
buried inside the install folder when you start from the desktop shortcut).
Change it via Session → Set save location… (remembered across runs in
~/.openmbb/config.json) or per-launch with --logdir <path>; if the configured
folder ever goes missing (deleted, unplugged drive) the app falls back to the
default instead of failing to connect. The current save target is shown in the
top status strip and is clickable.
- Meter check: FTDI Orange idles ~3.3 V vs Black; Red taped off.
- Connect (Phase 0). Garbage at 38400 ⇒ suspect Tx/Rx swap — stop.
- Pull full database before anything else. Recent firmware may be newer than the
community docs — the captured
help/setoutput is the ground truth; the simulator's menus are synthetic samples, not a live capture. - Login attempt only after the backup is on disk.
- No writes on day one. Bring the session folder home first.
pip install ".[dev]" # editable install + test/build tools
python -m pytest -q # full suite (GUI tests need a display; skip cleanly without one)
openmbb --selftest # headless transport/safety self-test
openmbb --smoketest # frozen-GUI/pyserial smoke testEverything runs against the built-in simulator — you can develop and test the
whole app with no bike attached. Issues and pull requests are welcome; please run
pytest + --selftest before opening a PR. If you have a captured set/help
dump from a different Gen2 model or firmware, that's especially valuable — the
safety lists are only ground-truthed against a 2017 FXS rev 41 today.
Found a way to get the tool to send something dangerous it shouldn't, or a way it could damage a bike? Please report it privately — see SECURITY.md.
MIT — Copyright © 2026 rodu4835. See LICENSE. Bundled third-party components (pyserial, sv-ttk, Tcl/Tk, the CPython runtime) keep their own licenses — see THIRD_PARTY_LICENSES.txt.
OpenMBB is an independent community project and is not affiliated with, endorsed by, or sponsored by Zero Motorcycles, Inc. or Sevcon/BorgWarner. "Zero Motorcycles", "Sevcon", and related marks belong to their respective owners and are used here only to describe the hardware this tool communicates with.