Skip to main content

Crate masterbus

Crate masterbus 

Source
Expand description

Idiomatic Rust client for the Mastervolt MasterBus CAN-bus protocol.

MasterBus is the CAN network used by Mastervolt marine power equipment — inverter/chargers, lithium and lead batteries, alternator regulators, solar/charge controllers, switch panels and displays. This crate speaks the protocol directly: it tracks which devices are present, discovers their schema (the groups and fields each device exposes), reads and writes those fields, and streams live values — without the vendor’s closed library.

§Quick start

The navigator API is a small tree of cheap, clonable, 'static handles: MasterBusDeviceGroupField.

use masterbus::{Config, MasterBus, Menu, Value};

// One-call connect using the per-host config file (auto-created on first
// run; see [`FileConfig`]). Or call [`MasterBus::socketcan`] /
// [`MasterBus::usb`] directly to bypass the file.
let bus = MasterBus::auto(Config::default())?;

// Browse the monitoring tab of every device currently on the bus.
for device in bus.devices() {
    println!("{} (id {})", device.name()?, device.id());
    for group in device.tab(Menu::Monitoring)? {
        for field in group.fields()? {
            println!("  {:<24} {:?}", field.name()?, field.value()?);
        }
    }
}

// Write a value. The supplied `Value` is checked against the field's schema
// type, and the value observed after the write is returned.
let device = bus.device(0x188EE2);
let applied = device.field(23).set(Value::Float(6.0))?;
println!("AC input limit is now {applied:?}");

§Two API surfaces over one engine

  • Navigator (above): blocking and ergonomic, for scripts and one-shot tools. Field::value returns a cached value when it is fresh enough (see Config::max_age) and otherwise polls the bus; Field::set writes and confirms by observing the resulting value, so a rejected write is reported truthfully.

  • Channel / event: MasterBus::device_events streams device presence (DeviceEvent), and MasterBus::subscribe returns a Subscription that delivers ValueUpdates at a chosen rate. Both are crossbeam-channel receivers, so a TUI or daemon can select! bus events alongside its own (terminal input, timers, …).

    use std::time::Duration;
    use masterbus::{Config, DeviceEvent, MasterBus, Menu};
    
    let bus = MasterBus::auto(Config::default())?;
    let events = bus.device_events();
    
    // React to every device that comes online: subscribe to its
    // Monitoring tab and print value updates as they arrive.
    while let Ok(ev) = events.recv() {
        if let DeviceEvent::Alive(id) = ev {
            let device = bus.device(id);
            let mut fields = Vec::new();
            for g in device.tab(Menu::Monitoring)? {
                for f in g.fields()? {
                    fields.push(f.index());
                }
            }
            let sub = bus.subscribe(id, fields, Duration::from_secs(1), true);
            std::thread::spawn(move || {
                while let Some(u) = sub.recv() {
                    println!("0x{:06X} fid 0x{:03X} = {:?}", u.device, u.field, u.value);
                }
            });
        }
    }

    Dropping the Subscription unsubscribes from the engine; see the watch example in the repo for a fuller program that also tears down per-device subscriptions on DeviceEvent::Offline.

§Discovery and caching

Nothing is enumerated until you ask for it:

  • Identity (name / article / serial / firmware) is the cheap half of discovery and is fetched on demand — e.g. Device::name.
  • Schema is discovered lazily per menu (Menu::Monitoring, Menu::Configuration, Menu::Service): Device::tab discovers just that menu, while Device::groups/Device::schema discover all of them.
  • An optional on-disk cache (Config::cache_path) persists each device’s schema across runs (keyed per device, by serial), so a long-running daemon discovers a device once and loads it from cache thereafter. Schemas are cached per device rather than shared by article/firmware, since same-model devices can differ (e.g. one battery in a cluster exposes an extra group).

All bus access is serialized on a single scheduler thread and paced to a bus budget, so the crate coexists with the boat’s real Mastervolt masters.

§Transports

The engine runs over any transport::Transport:

§Values

Field values are a single Value enum (float, boolean, list selection, Date, Time, device reference, …). Writes are expressed as a Value and validated against the field’s VisualizationType before anything is sent.

§Protocol reference

The wire protocol — frame classes, the date/time encodings, the per-field metadata opcodes, writability, and more — is documented in docs/PROTOCOL.md.

Re-exports§

pub use api::Device;
pub use api::Field;
pub use api::Group;
pub use api::MasterBus;
pub use api::Subscription;
pub use api::MAX_EDITABLE_TEXT_BYTES;
pub use error::Error;
pub use error::Result;
pub use model::field_id;
pub use model::AccessLevel;
pub use model::Channel;
pub use model::DeviceId;
pub use model::DeviceIdentity;
pub use model::DeviceSchema;
pub use model::DeviceStatus;
pub use model::FieldId;
pub use model::FieldInfo;
pub use model::GroupInfo;
pub use model::Menu;
pub use protocol::VisualizationType;
pub use settings::DeviceType;
pub use settings::FileConfig;
pub use value::Date;
pub use value::Time;
pub use value::Value;
pub use value::WriteValue;

Modules§

api
Public navigator API: MasterBusDeviceGroupField, plus rate-based subscriptions. Handles are cheap, Clone, 'static (Arc-backed).
error
Error and result types for the public API.
model
Device schema model — the static, per-firmware description of a device that discovery produces and the optional disk cache persists.
protocol
MasterBus wire protocol: frame types, class constants, and codec.
settings
Permanentish per-host configuration: where the bus is and whether we drive it as master. Lives in a small INI file so tools don’t need transport arguments on every invocation.
transport
CAN transport abstraction.
value
Field value types.

Structs§

Config
Tunable, lib-level behaviour.
ValueUpdate
A value update delivered to a subscriber.

Enums§

DeviceEvent
Device presence change.