Skip to main content

lager/
lib.rs

1//! First-class Rust access to [Lager](https://lagerdata.com) nets, so
2//! embedded developers can write their entire hardware-in-the-loop test
3//! suite in Rust and run it with `cargo test` — no Python required.
4//!
5//! The crate is a pure HTTP/JSON client of the Lager box's API on port
6//! 9000: power supplies, battery simulators, e-loads, solar simulators,
7//! GPIO, ADC, DAC, thermocouples, watt meters, energy analyzers, SPI, I2C,
8//! USB hub ports, robot arms, webcams, routers, and (behind the `uart`
9//! feature) streaming UART. Box-level capabilities — the box's own BLE adapter
10//! ([`LagerBox::ble`]), WiFi interface ([`LagerBox::wifi`]), and BluFi
11//! ESP32 provisioning ([`LagerBox::blufi`]) — are exposed the same way.
12//! Debug-probe nets ([`nets::debug::DebugNet`]) talk to the box's debug
13//! service on port 8765 for flash/erase/reset/memory-read and RTT log
14//! streaming; behind the `rtt` feature they also open bi-directional
15//! (interactive) RTT sessions against boxes >= 0.35.0.
16//!
17//! # Quickstart
18//!
19//! ```toml
20//! [dev-dependencies]
21//! lager = { package = "lager-net", version = "0.4" }
22//! ```
23//!
24//! ```no_run
25//! # #[cfg(feature = "blocking")]
26//! # mod demo {
27//! use lager::{LagerBox, Level};
28//!
29//! #[test]
30//! fn dut_boots_at_3v3() -> lager::Result<()> {
31//!     let lager = LagerBox::from_env()?; // reads LAGER_BOX_HOST
32//!     let supply = lager.supply("supply1");
33//!     let boot_ok = lager.gpio("boot_ok");
34//!
35//!     supply.set_voltage(3.3)?;
36//!     supply.enable()?;
37//!     // Hardware-timed wait on the box; returns elapsed seconds.
38//!     let t = boot_ok.wait_for_level(Level::High, 5.0)?;
39//!     println!("booted in {t:.3}s");
40//!     supply.disable()
41//! }
42//! # }
43//! # fn main() {}
44//! ```
45//!
46//! # Concurrency
47//!
48//! The box serializes access per physical instrument (a single-owner
49//! hardware service with per-device locks), so parallel cargo tests can
50//! never interleave I/O on one instrument. Tests sharing a *net* still
51//! observe each other's state changes — partition nets across tests or run
52//! `cargo test -- --test-threads=1` when that matters.
53//!
54//! # Boxes behind an authenticating gateway
55//!
56//! Boxes fronted by an authenticating reverse proxy (gateway) reject
57//! unauthenticated traffic with 401 + an `X-Gateway-Auth-Url` header. The
58//! crate handles this transparently: it reuses the session created by
59//! `lager login <auth_url>` (the Lager CLI's token store in
60//! `~/.lager_gateway_auth`), attaches `Authorization: Bearer` to every
61//! request — including debug-service and UART Socket.IO traffic — and
62//! refreshes expired access tokens automatically. For CI or machines
63//! without a CLI login, pin a token with
64//! [`LagerBoxBuilder::bearer_token`] or the `LAGER_GATEWAY_TOKEN`
65//! environment variable. Plain (ungated) boxes are unaffected: no header
66//! is sent and no code path runs. When no usable credential exists, calls
67//! fail with [`Error::AuthRequired`] naming the auth server to log into.
68//!
69//! # Features
70//!
71//! - `blocking` *(default)*: the [`LagerBox`] client on `ureq` — no tokio
72//!   in the dependency tree.
73//! - `async`: the [`AsyncLagerBox`] client on `reqwest`/tokio. Both clients
74//!   share one wire layer ([`wire`]) so they cannot drift.
75//! - `uart`: streaming UART sessions over Socket.IO ([`nets::uart::Uart`]).
76//! - `rtt`: bi-directional RTT sessions over Socket.IO
77//!   ([`nets::rtt::RttSession`], box >= 0.35.0).
78//!
79//! # Not yet on the HTTP API
80//!
81//! Oscilloscope workflows are not yet exposed on any box HTTP API;
82//! [`nets::scope::Scope`] ships as a documented stub returning
83//! [`Error::NotSupportedByBox`]. See `MISSING_ENDPOINTS.md` in the crate
84//! repository.
85
86#![deny(missing_docs)]
87
88mod auth;
89mod error;
90pub mod nets;
91pub mod wire;
92
93#[cfg(feature = "async")]
94mod async_client;
95#[cfg(feature = "blocking")]
96mod client;
97
98pub use error::{Error, Result};
99
100/// Environment variable read by `from_env` constructors (`LAGER_BOX_HOST`).
101pub const BOX_HOST_ENV: &str = "LAGER_BOX_HOST";
102
103/// Environment variable that overrides the debug-service base URL
104/// (`LAGER_DEBUG_SERVICE_URL`), e.g. `http://127.0.0.1:8765` when tunneling.
105pub const DEBUG_SERVICE_URL_ENV: &str = "LAGER_DEBUG_SERVICE_URL";
106
107/// Environment variable holding a bearer token for boxes behind an
108/// authenticating gateway (`LAGER_GATEWAY_TOKEN`). When set, every request
109/// carries `Authorization: Bearer <token>`. Equivalent to
110/// `LagerBoxBuilder::bearer_token`.
111pub const GATEWAY_TOKEN_ENV: &str = "LAGER_GATEWAY_TOKEN";
112
113/// Environment variable that overrides the path of the Lager CLI's gateway
114/// token store (`LAGER_GATEWAY_AUTH_FILE`; default `~/.lager_gateway_auth`).
115/// The crate reads sessions created by `lager login` from this file, so the
116/// same name/semantics as the CLI are honored.
117pub const GATEWAY_AUTH_FILE_ENV: &str = "LAGER_GATEWAY_AUTH_FILE";
118
119#[cfg(feature = "blocking")]
120pub use client::{BoxLockGuard, LagerBox, LagerBoxBuilder};
121
122#[cfg(feature = "async")]
123pub use async_client::{AsyncLagerBox, AsyncLagerBoxBuilder};
124
125// Net handle types and their vocabularies, re-exported at the crate root
126// for ergonomic imports (`use lager::{LagerBox, Level, EloadMode};`).
127pub use nets::battery::BatteryMode;
128pub use nets::debug::{ConnectOptions, FirmwareKind, RttOptions};
129pub use nets::dfu::DfuOptions;
130pub use nets::eload::EloadMode;
131pub use nets::gpio::{Level, WaitForLevelOptions};
132pub use nets::scope::Scope;
133pub use nets::spi::{BitOrder, CsActive, CsMode, SpiConfig, SpiOptions, SpiTransfer};
134
135/// Debug-service response types.
136pub use wire::{DebugConnection, DebugInfo, DebugStatus, GdbServer};
137
138#[cfg(feature = "blocking")]
139pub use nets::{
140    adc::Adc, arm::Arm, battery::Battery, ble::Ble, blufi::Blufi, dac::Dac, debug::DebugNet,
141    debug::RttStream, dfu::Dfu, eload::Eload, energy::EnergyAnalyzer, gpio::Gpio, i2c::I2c,
142    router::Router, solar::Solar, spi::Spi, supply::Supply, thermocouple::Thermocouple,
143    usb::UsbPort, watt::WattMeter, webcam::Webcam, wifi::Wifi,
144};
145
146#[cfg(feature = "async")]
147pub use nets::{
148    adc::AsyncAdc, arm::AsyncArm, battery::AsyncBattery, ble::AsyncBle, blufi::AsyncBlufi,
149    dac::AsyncDac, debug::AsyncDebugNet, dfu::AsyncDfu, eload::AsyncEload,
150    energy::AsyncEnergyAnalyzer, gpio::AsyncGpio, i2c::AsyncI2c, router::AsyncRouter,
151    solar::AsyncSolar, spi::AsyncSpi, supply::AsyncSupply, thermocouple::AsyncThermocouple,
152    usb::AsyncUsbPort, watt::AsyncWattMeter, webcam::AsyncWebcam, wifi::AsyncWifi,
153};
154
155#[cfg(feature = "uart")]
156pub use nets::uart::Uart;
157
158#[cfg(feature = "rtt")]
159pub use nets::rtt::RttSession;
160
161// Structured result types, re-exported from the wire layer.
162pub use wire::{
163    ArmPosition, BatteryState, BleCharacteristic, BleDevice, BleDeviceInfo, BleService,
164    BlufiDeviceInfo, BlufiNetwork, BlufiProvisionResult, BlufiStatus, BoxCapabilities, BoxLock,
165    BoxStatus, DfuDevice, DfuOutput, EloadState, EnergyReading, EnergyStats, Health, NetRecord,
166    NetSummary, RouterSystemInfo, SafetyLimits, StatSummary, SupplyState, UsbDeviceFilter,
167    UsbDeviceInfo, WattReading, WebcamStatus, WebcamStream, WifiAccessPoint, WifiConnection,
168    WifiInterface,
169};
170pub use nets::i2c::I2cEffectiveConfig;
171pub use nets::spi::SpiEffectiveConfig;