An idiomatic Rust client for the Mastervolt MasterBus CAN-bus protocol, with a terminal UI, a Signal K sidecar, and a C-ABI wrapper. Talks to the bus over Linux SocketCAN, or over the Mastervolt USB link (a class-compliant HID device — no vendor driver) on Linux, macOS, and Windows.
A Cargo workspace:
| crate | what |
|---|---|
masterbus |
core library (the protocol, discovery, caching, subscriptions, the MasterBus/Device/Group/Field navigator API and a non-blocking channel API) |
masterbus-tools |
four command-line tools — masterbus-tui (terminal UI), masterbus-signalk (Signal K sidecar), masterbus-set-field (one-shot field writer), masterbus-dump (whole-bus JSON snapshot). cargo install masterbus-tools installs all four |
masterbus-ffi |
C ABI cdylib (single-threaded), header generated with cbindgen, plus C demos (not published to crates.io) |
The wire protocol is documented in docs/PROTOCOL.md.
Want your own devices in Signal K? No build needed — see
ENDUSER.md, which walks through connecting to the bus and
mapping your devices with masterbus-tui --mapping. To improve what the
project ships for everyone, CONTRIBUTING.md covers the
toolchain and using an AI assistant to do the typing.
For years I've been struggling with the following. After some struggles with Mastervolt Gel batteries that died way too quickly we did a major refit in 2019 at the boatyard with the electrical system being upgraded. All existing devices were shipped back to Mastervolt and they signed off on the new setup with 4 x 5000 Wh Lithium batteries. Cost an arm and a leg, but it's been worth it. Anyway, the customer success engineer that was helping me acknowledged that programming their MasterBus/NMEA2000 interface was a bit difficult, so he mentioned a new library that they were testing and was I interested in it? Sure! They provided me with a "libmasterbus.so/dll" for Linux and Windows. It turned out to be written in Rust, starting in 2017! Amazing. This library served me well over the years, but I never received an update.
I tried contacting MasterVolt, but I learned that after their merger into what is now Brunswick that everyone I knew no longer works there, and I can't get them to reply to my queries about updates. Since the library was released under an "Unlicense", e.g. fully open and free, and I really needed an Aarch64 version, I decided last week that maybe I should just let Claude help me write a new version. This repository is the fruit of that work.
- Fast init:
connect()returns as soon as the bus is usable and one device is heard — no multi-second enumeration wait, so one-shot tools stay snappy. - Lazy, per-menu discovery with an optional on-disk schema cache (per device, keyed by serial) — only the menus you look at are discovered, and long-running programs discover each device once and load from cache thereafter.
- Live values without polling storms: a passive value cache fed by bus traffic, plus rate-based subscriptions that actively poll only what's needed.
- Two API surfaces over one core: a blocking navigator API for simple one-shots, and a non-blocking channel/event API for the TUI and daemons.
The core ships two transports: Linux SocketCAN (for hosts wired to the bus — e.g. a Raspberry Pi) and the Mastervolt USB link (a class-compliant HID device — no vendor driver — usable on Linux, macOS, and Windows). SocketCAN is compiled in only on Linux; the USB transport is built unconditionally.
cargo build --workspace # build everything (host)
cargo test --workspace # unit tests
cargo build --release --target aarch64-unknown-linux-gnu # cross-compile for a PiOr install the four command-line tools (masterbus-tui,
masterbus-signalk, masterbus-set-field, masterbus-dump) directly:
cargo install masterbus-toolsAll tools share a small INI file describing the transport and the optional "act as bus master" role, so the tools don't need transport arguments on every invocation. The file is read on every start and auto-created on first run with sensible defaults:
| OS | Config path | Default cache dir |
|---|---|---|
| Linux (system) | /etc/default/masterbus/config.ini (if writable) |
/var/lib/masterbus |
| Linux (user) | $XDG_CONFIG_HOME/masterbus/config.ini (default $HOME/.config/masterbus/config.ini) |
$XDG_CACHE_HOME/masterbus (default $HOME/.cache/masterbus) |
| macOS | $HOME/Library/Application Support/masterbus/config.ini |
$HOME/Library/Caches/masterbus |
| Windows | %APPDATA%\masterbus\config.ini |
%LOCALAPPDATA%\masterbus\cache |
Auto-detection at creation time prefers a plugged-in Mastervolt USB link;
otherwise the lone CAN interface (e.g. can0) is selected. Multiple CAN
interfaces with no USB link is an error — edit the file and pick one. The path
and detected values are logged to stderr on creation, so a first run isn't
silent.
# /etc/default/masterbus/config.ini
# 24-bit hex device id we announce as master (class-0x05 heartbeat). Devices
# announce themselves and stay responsive in response; comment out to stay
# passive (a hardware master must drive the bus, e.g. an EasyView panel).
heartbeat_master = 000001
# Transport: "usb" for the Mastervolt USB link, "can" for SocketCAN.
device_type = can
# When device_type = can: interface name. When usb: optional serial number
# (blank = first link found).
device_name = can0
# Where to persist discovered schemas (per device, by serial). Default is
# /var/lib/masterbus for system installs and the OS-native per-user cache
# directory otherwise (see the table above). A non-writable path silently
# falls back to the per-user cache at runtime, so root-owned daemons and
# user-run tools share schemas when possible. Comment out to disable
# on-disk caching.
cache_dir = /var/lib/masterbus
# Address masterbus-signalk listens on. Comment out for its default
# (0.0.0.0:3009). A command-line argument still wins over this.
# listen = 0.0.0.0:3009
# Address masterbus-signalk serves its HTTP control API on (what the
# Signal K plugin talks to). Comment out to leave the API off. Any
# address other than loopback also needs api_token.
# api_listen = 127.0.0.1:3010
# api_token = change-meOn every platform MASTERBUS_CONFIG_DIR (or --config-dir on
masterbus-signalk) overrides the location, which is how the Signal K
plugin keeps a daemon's config, mapping and cache under its own data
directory.
That directory is the only one this project configures per host. The Signal K
sidecar's field mapping lives beside config.ini in the same directory, so
there is nothing else to find or to keep in sync.
To change the heartbeat-master behaviour, swap transports, or relocate the cache, edit the file (or delete it and let auto-detection re-create it).
masterbus-tui
masterbus-tui --mapping # also edit the Signal K mapping fileBrowse devices on the left; the selected device's tabs are on the right —
Summary / Monitoring / Configuration / Service / Settings, each
discovered on demand. Tab / Shift-Tab switch tabs, Enter edits a writable
field (booleans toggle, numbers / lists / text open a centred edit modal),
l opens the access-level (login) modal — higher levels unlock more fields,
q quits.
With --mapping the same screen curates mapping.json: the device list shows
how many of each device's fields publish, the Monitoring tab gains a Signal K
column, + and - map and unmap the selected field, a copies a device's
mapping to every other device with the same article, and w writes the file.
The + prompt pre-fills a suggestion and shows the unit conversion the path
implies.
masterbus-signalk is a long-running service that publishes MasterBus values
as Signal K deltas (newline-delimited JSON) over TCP (default
0.0.0.0:3009), with SI-unit conversion, and serves an HTTP control API
(docs/API.md; off unless api_listen is set) through which the
signalk-masterbus plugin browses devices, edits the mapping and writes
fields. A curated mapping.json beside config.ini says which field publishes
to which Signal K path, keyed on device serial and field id; it is seeded on
first run from a bundled per-model database, falling back to per-class name
heuristics. Ships with a hardened systemd unit for the case where the machine
on the bus is not the one running Signal K.
masterbus-signalk [listen-addr] [--stream ADDR] [--api ADDR] [--api-token-file PATH] [--config-dir DIR]
# e.g. masterbus-signalk --api 127.0.0.1:3010Flags override config.ini; without any it uses the file's listen and
api_listen keys, falling back to 0.0.0.0:3009 and no API. --fake-bus
(with the fake-bus cargo feature) serves a canned three-device bus for
trying things without hardware.
masterbus-dump walks every device and writes one JSON document: identity,
groups, fields (id, name, unit, range, enum options, writability) and, for the
monitoring menu, the live values. It is the fastest way to hand someone without
your hardware everything needed to write a mapping for a device class.
masterbus-dump -o mybus.json # monitoring + configuration + service
masterbus-dump --values all --menus all -o mybus.json
masterbus-dump --device 286CA9 # just one device, to stdoutThe host's Signal K mapping is included when there is one, so a dump shows the bus and the decisions made about it together.
Field ids appear as 0x000..0x1FF — the same encoding the TUI shows and
masterbus-set-field accepts. Names are installer-editable and ids are not, so
mapping tables should key on field.id and group.id.
masterbus-ffi builds a libmasterbus_ffi C library; include/masterbus.h is
generated by cbindgen. The C demos under
crates/masterbus-ffi/c (mb_enumerate,
mb_get_value, mb_set_value) build natively by default, with a make remote
target to cross-compile and deploy to a Pi. See that directory's Makefile.
The library emits via the standard log
facade — no runtime cost when no logger is initialized. The bundled binaries
install env_logger and read
RUST_LOG:
RUST_LOG=info masterbus-signalk # connect / device / discovery
RUST_LOG=masterbus=debug masterbus-set-field … # plus cache, retries, write
RUST_LOG=masterbus::frame=trace … # per-frame candump-style dumpUseful targets: masterbus::frame (candump-style Tx/Rx dump),
masterbus::discovery (per-menu enumeration, retries), masterbus::cache
(schema cache load/save), masterbus::write (do_write calls),
masterbus::settings (config-file creation).
The TUI logs to masterbus-tui.log (or $MASTERBUS_TUI_LOG) instead of
stderr so its alt-screen stays clean. The MASTERBUS_LOG env var that used
to point the engine at a .log file is gone — use RUST_LOG=masterbus::frame=trace
to get the equivalent dump on stderr (or pipe to a file).
Works over SocketCAN on Linux and over the Mastervolt USB link on Linux, macOS, and Windows. The navigator API, FFI demos, TUI, and Signal K sidecar have all been exercised against a live bus.
- Alarms tab / event streams —
Menu::AlarmandMenu::Historyare defined and discoverable, but the TUI tabs for them were removed during the channel-aware redesign and not yet restored. The crate also doesn't surface alarm broadcasts as events to API consumers. -
masterbus-set-fieldDate / Time writes — currently rejected; not a common need but the CLI advertises the omission.
Licensed under the Apache License, Version 2.0. See LICENSE.
The original Mastervolt libmasterbus was released under the Unlicense (public
domain); this is an independent reimplementation.