Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
117 lines
3.3 KiB
Rust
117 lines
3.3 KiB
Rust
mod device;
|
|
/// Runs tests against a TestClass device running on actual hardware using libusb.
|
|
///
|
|
/// This is implemented as an example as opposed to a test because the Rust test runner system is
|
|
/// not well suited for running tests that must depend on outside resources such as hardware and
|
|
/// cannot be run in parallel.
|
|
mod tests;
|
|
|
|
use crate::device::open_device;
|
|
use crate::tests::{get_tests, TestFn};
|
|
use std::io::prelude::*;
|
|
use std::io::stdout;
|
|
use std::panic;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use usb_device::device::CONFIGURATION_VALUE;
|
|
|
|
fn main() {
|
|
let tests = get_tests();
|
|
run_tests(&tests[..]);
|
|
}
|
|
|
|
fn run_tests(tests: &[(&str, TestFn)]) {
|
|
const INTERFACE: u8 = 0;
|
|
|
|
println!("test_class_host starting");
|
|
println!("looking for device...");
|
|
|
|
let ctx = rusb::Context::new().expect("create libusb context");
|
|
|
|
// Look for the device for about 5 seconds in case it hasn't finished enumerating yet
|
|
let mut dev = Err(rusb::Error::NoDevice);
|
|
for _ in 0..50 {
|
|
dev = open_device(&ctx);
|
|
if dev.is_ok() {
|
|
break;
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(100));
|
|
}
|
|
|
|
let mut dev = match dev {
|
|
Ok(d) => d,
|
|
Err(err) => {
|
|
println!("Did not find a TestClass device. Make sure the device is correctly programmed and plugged in. Last error: {}", err);
|
|
return;
|
|
}
|
|
};
|
|
|
|
println!("\nrunning {} tests", tests.len());
|
|
|
|
let mut success = 0;
|
|
for (name, test) in tests {
|
|
if let Err(err) = dev.reset() {
|
|
println!("Failed to reset the device: {}", err);
|
|
return;
|
|
}
|
|
|
|
if let Err(err) = dev.set_active_configuration(CONFIGURATION_VALUE) {
|
|
println!("Failed to set active configuration: {}", err);
|
|
return;
|
|
}
|
|
|
|
if let Err(err) = dev.claim_interface(INTERFACE) {
|
|
println!("Failed to claim interface: {}", err);
|
|
return;
|
|
}
|
|
|
|
print!("test {} ... ", name);
|
|
let _ = stdout().flush();
|
|
|
|
let mut out = String::new();
|
|
|
|
let res = {
|
|
let hook = panic::take_hook();
|
|
panic::set_hook(Box::new(|_| {}));
|
|
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
|
test(&mut dev, &mut out);
|
|
}));
|
|
panic::set_hook(hook);
|
|
|
|
res
|
|
};
|
|
|
|
dev.release_interface(INTERFACE)
|
|
.expect("failed to release interface");
|
|
|
|
if let Err(err) = res {
|
|
let err = if let Some(err) = err.downcast_ref::<&'static str>() {
|
|
String::from(*err)
|
|
} else if let Some(err) = err.downcast_ref::<String>() {
|
|
err.clone()
|
|
} else {
|
|
String::from("???")
|
|
};
|
|
|
|
println!("FAILED\nerror: {}\n", err);
|
|
} else {
|
|
println!("ok");
|
|
|
|
if !out.is_empty() {
|
|
print!("{}", out);
|
|
}
|
|
|
|
success += 1;
|
|
}
|
|
}
|
|
|
|
println!("{} failed, {} succeeded", tests.len() - success, success);
|
|
|
|
if success == tests.len() {
|
|
println!("\nALL TESTS PASSED!");
|
|
} else {
|
|
std::process::exit(1);
|
|
}
|
|
}
|