mirror of
https://github.com/ruvnet/RuView
synced 2026-08-06 19:51:43 +00:00
4d0521ca08
The ESP32 firmware multiplexes several wire packet types onto the same
UDP port as ADR-018 raw CSI frames (magic 0xC5110001):
0xC5110002 ADR-039 edge vitals (32 B)
0xC5110003 ADR-069 feature vector
0xC5110004 ADR-063 fused vitals
0xC5110005 ADR-039 compressed CSI
0xC5110006 ADR-081 feature state
0xC5110007 ADR-095/#513 temporal classification
Esp32CsiParser only knew 0xC5110001, so the standalone `aggregator`
binary printed "parse error: Invalid magic: expected 0xc5110001, got
0xc5110002" for every vitals packet. No CSI data was lost — just noise.
Add the sibling-magic constants + ruview_sibling_packet_name(), classify
recognized siblings before the CSI-frame length gate, and return a new
ParseError::NonCsiPacket { magic, kind } instead of InvalidMagic. The
`aggregator` CLI now skips them quietly (logs "[skipped ADR-039 edge
vitals packet — not a CSI frame]" only with --verbose); the library-level
CsiAggregator already dropped them silently. New regression tests cover
all seven magics.
Closes #517
Co-Authored-By: claude-flow <ruv@ruv.net>
85 lines
2.5 KiB
Rust
85 lines
2.5 KiB
Rust
//! UDP aggregator CLI for receiving ESP32 CSI frames (ADR-018).
|
|
//!
|
|
//! Listens for ADR-018 binary CSI frames on a UDP socket, parses each
|
|
//! packet, and prints a one-line summary to stdout.
|
|
//!
|
|
//! Usage:
|
|
//! cargo run -p wifi-densepose-hardware --bin aggregator -- --bind 0.0.0.0:5005
|
|
|
|
use std::net::UdpSocket;
|
|
use std::process;
|
|
|
|
use clap::Parser;
|
|
use wifi_densepose_hardware::{Esp32CsiParser, ParseError};
|
|
|
|
/// UDP aggregator for ESP32 CSI nodes (ADR-018).
|
|
#[derive(Parser)]
|
|
#[command(name = "aggregator", about = "Receive and display live CSI frames from ESP32 nodes")]
|
|
struct Cli {
|
|
/// Address:port to bind the UDP listener to.
|
|
#[arg(long, default_value = "0.0.0.0:5005")]
|
|
bind: String,
|
|
|
|
/// Print raw hex dump alongside parsed output.
|
|
#[arg(long, short)]
|
|
verbose: bool,
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
let socket = match UdpSocket::bind(&cli.bind) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
eprintln!("Error: cannot bind to {}: {}", cli.bind, e);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
eprintln!("Listening on {}...", cli.bind);
|
|
|
|
let mut buf = [0u8; 2048];
|
|
|
|
loop {
|
|
let (n, src) = match socket.recv_from(&mut buf) {
|
|
Ok(r) => r,
|
|
Err(e) => {
|
|
eprintln!("recv error: {}", e);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if cli.verbose {
|
|
eprintln!(" [{} bytes from {}]", n, src);
|
|
}
|
|
|
|
match Esp32CsiParser::parse_frame(&buf[..n]) {
|
|
Ok((frame, _consumed)) => {
|
|
let mean_amp = frame.mean_amplitude();
|
|
println!(
|
|
"[node:{} seq:{}] sc={} rssi={} amp={:.1}",
|
|
frame.metadata.node_id,
|
|
frame.metadata.sequence,
|
|
frame.subcarrier_count(),
|
|
frame.metadata.rssi_dbm,
|
|
mean_amp,
|
|
);
|
|
}
|
|
// The firmware sends several packet types on this UDP port
|
|
// (ADR-039 vitals, ADR-081 feature state, ADR-095 temporal, …)
|
|
// alongside ADR-018 CSI frames. Those are expected, not errors —
|
|
// this CSI-only aggregator just skips them. (RuView#517)
|
|
Err(ParseError::NonCsiPacket { kind, .. }) => {
|
|
if cli.verbose {
|
|
eprintln!(" [skipped {} packet — not a CSI frame]", kind);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
if cli.verbose {
|
|
eprintln!(" parse error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|