Expand description
§ados-display-driver
Linux output for small displays: SSD1306/SH1106 OLED over I2C, RGB565/RGB888/xRGB pixel packers and an off-thread latest-wins framebuffer writer.
Small SPI LCDs behind the Linux fbtft driver and I2C OLEDs are slow devices: a full-frame write can take tens of milliseconds, and doing it on a render loop or an async runtime worker stalls everything else. This crate gives you the output half of such a display: find the right framebuffer in sysfs, pack an RGB888 canvas to the panel’s bit depth, and hand the frame to a dedicated writer thread that keeps only the newest frame, skips duplicates and pushes only the bytes that changed. For OLEDs it drives SSD1306 and SH1106 controllers over /dev/i2c-N with no embedded-hal stack.
use ados_display_driver::fb_writer::{FbWriter, Frame, VirtualSink};
use ados_display_driver::pack::pack_for_bpp;
// A 4x2 RGB888 canvas: one red pixel, the rest black.
let mut rgb = vec![0u8; 4 * 2 * 3];
rgb[..3].copy_from_slice(&[0xFF, 0x00, 0x00]);
// Pack it for a 16 bpp panel: RGB565, little-endian.
let packed = pack_for_bpp(&rgb, 16).expect("16 bpp is supported");
assert_eq!(&packed[..2], &[0x00, 0xF8]);
// Hand it to the writer thread. VirtualSink writes a file; on Linux,
// MmapSink writes the real /dev/fbN mapping instead.
let path = std::env::temp_dir().join("ados-display-driver-readme.fb");
let mut writer = FbWriter::spawn(VirtualSink::open(&path));
writer.present(Frame::new(packed, &rgb));
// cleanup() drains the pending frame, then joins the thread.
writer.cleanup();
assert_eq!(writer.stats().writes, 1);
assert_eq!(std::fs::read(&path).unwrap().len(), 4 * 2 * 2);
std::fs::remove_file(&path).unwrap();§Highlights
- One runtime dependency (
tracing);memmap2andlibcare pulled in on Linux only. - The packers, the OLED page layout, the upload plan, the writer thread model and the sysfs discovery are portable and build on any host; only
MmapSinkandI2cDevare Linux-only. - Single-slot latest-wins writer on a dedicated OS thread:
FbWriter::presentnever blocks on the device. MmapSinkcopies and flushes only the changed byte span of each frame, so fbtft pushes only the touched scanlines over SPI.VirtualSinkreplaces its file atomically (temp file plus rename), so a concurrent reader never sees a torn frame.- 47 unit tests: the writer thread model against a fake sink, the OLED framing against a fake bus, the packers, sysfs discovery against temp trees, and a concurrent torn-read test for
VirtualSink.
§How it works
The crate has four modules.
§fb_geometry: which framebuffer, at what size
match_framebuffer walks the fbN entries under a sysfs root (normally /sys/class/graphics) and returns the first one whose driver name is acceptable and whose bit depth is 16, 24 or 32. With an expected name configured, the driver name must contain it; otherwise it must be one of the known fbtft SPI-LCD drivers in SPI_LCD_DRIVER_NAMES, so the primary HDMI/DRM framebuffer is never picked by accident. Geometry comes from virtual_size plus bits_per_pixel, falling back to the legacy var blob. The sysfs root is a parameter, so the whole module is testable against a temp directory.
§pack: RGB888 to the panel’s bit depth
Input is a tightly-packed RGB888 slice. pack_for_bpp dispatches on bit depth:
| bpp | Output | Layout per pixel |
|---|---|---|
| 16 | RGB565 little-endian | ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3), low byte first |
| 24 | RGB888 passthrough | R, G, B |
| 32 | xRGB32 | B, G, R, 0 |
Any other depth returns None, so the caller can drop the frame.
§fb_writer: the off-thread writer
FbWriter::spawn moves a FrameSink onto a thread named fb-writer that owns it for its whole life.
- Latest wins.
presentstashes the frame in a single slot and returns. A frame still pending when the next one arrives is overwritten and counted as a drop. - Duplicate skip. Each
Framecarries a hash of its raw, pre-pack input. A frame whose hash matches the last successful write is skipped. Callers can askis_last(hash)before packing at all. - Failures do not freeze the panel. A failed write is logged with
tracingand the last-written hash is left unchanged, so the next frame is written even if its input is the same. - Side jobs.
request_snapshotqueues a boxed closure (for example a PNG encode of the canvas) that the writer runs after its pending frame, again latest wins. - Teardown.
cleanup(also run on drop) sets the stop flag, wakes the thread, drains a frame stashed just before stop, and joins for up toWRITER_JOIN_TIMEOUT(1 s) before the sink is released. - Stats.
stats()returns writes, drops, skipped duplicates and the last write time in milliseconds.
Two sinks ship with the crate. MmapSink (Linux) maps /dev/fbN and, after the first full frame, copies and flushes only the span reported by changed_span. VirtualSink writes each frame to a file, which makes a render path runnable in CI or a VM with no framebuffer. Anything else implements FrameSink.
§oled: SSD1306 / SH1106 over I2C
pack_page_buffer turns a one-byte-per-pixel canvas (non-zero is on) into the controllers’ vertical-byte page layout: for each 8-pixel page and each column, bit b is the pixel at row page * 8 + b. init_commands builds the power-on sequence for a 64- or 32-row panel, and page_upload_plan the per-page address commands and data. The two controllers differ only in the start column: SH1106 is a 132-column die windowed to 128, so it starts at column 2. Commands go out behind control byte 0x00 and data behind 0x40.
The transport is the I2cBus trait. On Linux, I2cDev opens /dev/i2c-N, binds the slave address with the I2C_SLAVE ioctl and writes with plain write(2):
use ados_display_driver::oled::{self, Controller, I2cDev, OledGeometry};
let geom = OledGeometry::W128_H64;
let mut dev = I2cDev::open(1, oled::DEFAULT_I2C_ADDR)?; // /dev/i2c-1, address 0x3C
oled::init(&mut dev, geom)?;
// A canvas with a horizontal line across row 10.
let mut canvas = vec![0u8; geom.width as usize * geom.height as usize];
canvas[10 * geom.width as usize..11 * geom.width as usize].fill(1);
let packed = oled::pack_page_buffer(&canvas, geom).expect("canvas matches the geometry");
oled::render_frame(&mut dev, &packed, geom, Controller::Ssd1306)?;§Part of ADOS
Used in production by the ADOS Drone Agent, the onboard software of the ADOS software-defined drone platform.
All ADOS crates · Docs · Discord
§Other ADOS crates
Platform (versioned with the ADOS Drone Agent)
| Crate | What it does | Docs |
|---|---|---|
ados-protocol | Wire contracts of the ADOS Drone Agent: IPC framing, plugin RPC envelope, capability tokens and the vision frame bus. | docs.rs |
ados-sdk | Write Rust plugins for the ADOS Drone Agent: IPC client, plugin context, driver traits and lifecycle runner. | docs.rs |
ados-vision | Single-object visual tracker: Kalman motion model, appearance re-identification and YOLOv8 head decoding. | docs.rs |
ados-gpio | Drive status buzzers and LEDs on Linux GPIO lines, with bounded software-PWM beep patterns. | docs.rs |
ados-hal-probe | Find out what a board’s hardware can really do by probing it, plus bundled board profiles. | docs.rs |
Standalone
| Crate | What it does | Docs |
|---|---|---|
ados-sensorhub | Sensor hub wire protocol: COBS framing, CRC-16 and fixed-layout IMU, baro, mag and GNSS packets. no_std. | docs.rs |
ados-offload | Choose where perception runs (onboard, offloaded or hybrid) and hold the vehicle when results go stale. | docs.rs |
ados-macpin | Stable MAC addresses for Linux network adapters that randomize theirs every boot. | docs.rs |
ados-lora | LoRa transmit governor with cited regional band plans, duty cycle, dwell time and airtime accounting. | docs.rs |
ados-crsf-codec | no_std CRSF / ExpressLRS codec for RC channels, telemetry and parameter frames. | docs.rs |
ados-positioning | Split mixed UBX, NMEA and RTCM3 GNSS streams, configure u-blox receivers and run an RTK base survey-in. | docs.rs |
ados-rtcm | RTCM3 framing, CRC-24Q and MAVLink GPS_RTCM_DATA fragmentation for RTK corrections. | docs.rs |
ados-clock | Map remote clocks onto the host clock: least-squares and envelope fits, TIMESYNC and a chrony sink. | docs.rs |
ados-swarm | Swarm control laws: separation, flocking, formations, CBBA task allocation and a point-mass simulator. | docs.rs |
ados-mavlink-tunnel | Send large messages over MAVLink TUNNEL frames with chunking and bounded reassembly. | docs.rs |
ados-hmi | Front-panel input: touchscreen calibration and gestures, button presses and rotary encoders. | docs.rs |
§ADOS repositories
| Repo | Runs on | What it does | Link |
|---|---|---|---|
| ADOS Mission Control | Browser, desktop (Electron) | Ground control station: FC setup, mission planning, flight, 3D simulation, fleet | ADOSMissionControl |
| ADOS Drone Agent | Companion computer on the drone, or a ground node | Onboard agent: MAVLink routing, HD video, long-range radio link, plugins, remote control | ADOSDroneAgent |
| ADOS Android GCS | Android phones and tablets | Native ground control app for agents on the LAN, direct flight controllers and the cloud | ADOSAndroidGCS |
| ADOS Extensions | Drone Agent and Mission Control | First-party signed plugins, each with an agent half and a GCS half | ADOSExtensions |
| ADOS MCP | Your machine | Model Context Protocol server that lets AI clients operate ADOS drones under scopes and a safety gate | ADOS-MCP |
| Documentation | docs.altnautica.com | Public docs for every ADOS product | Documentation |
§License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Modules§
- fb_
geometry - Framebuffer geometry and driver-name discovery from
/sys/class/graphics. - fb_
writer - Off-thread framebuffer writer.
- oled
- SSD1306 / SH1106 monochrome OLED over I2C.
- pack
- Pixel packers: RGB888 source to the panel’s bit depth.