device
// ~/projects/magellan/device.rs
let device = Project {
status: Status::Active,
started: "2026-09-20",
tags: &["rust", "tokio", "modbus"],
repo: Some("https://github.com/fabbrito/magellan-device"),
};A single Rust binary on a Raspberry Pi. It reads a solar inverter over Modbus today, holds the readings on flash, and uploads them when the cloud is reachable.
Why it carries the variety
Everything specific to a source, from register maps to scaling and units, stays on the device. It describes what it measures in a manifest, so adding a new kind of source is a device change and never a cloud deploy.
How it got here
The first version was a prototype for one inverter, in Python, on the existing library. The library never read the logger reliably, so I stopped working around it: I mapped the inverter’s registers from a community spreadsheet and checked them against captures from my own installation. With the map in hand, Rust was the language I wanted to build the real device in, and the inverter-specific code became the first driver behind a generic seam.
Decisions
- Outages are normal. Wi-Fi drops and power cuts are expected, not errors. Readings go to a buffer on flash first, and that buffer survives a reboot.
- A bounded buffer. The buffer has a fixed size. In a long outage it drops the oldest batch and leaves a visible gap, rather than filling the disk and dying.
- Catch its own mistakes. The device checks its work against the contract’s limits before queueing it, so a bad reading never waits in the buffer for a rejection.
- Read-only. It never writes to a source. No commands, no settings changed.
- Layers the compiler enforces. Runtime, drivers and platform are separate crates, so a layer reaching where it should not fails to build.
- Alive is apart from data. An hourly heartbeat says the device is running, even at night when the inverter has nothing to report.