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