From 916abdf8ac70282da85ea7591d0b0c7f7c99111c Mon Sep 17 00:00:00 2001 From: Ben Sherriff Date: Tue, 22 Apr 2025 23:01:07 -0400 Subject: [PATCH] Temp got sim to work with tcp device by stripping out a lot of the logic. --- adsb/README.md | 2 +- adsb/adsb_lib/src/adsb.rs | 10 +- adsb/adsb_lib/src/device.rs | 110 +++++++++++++++++ adsb/adsb_lib/src/lib.rs | 112 +----------------- adsb/adsb_recv/src/main.rs | 37 +++--- .../src/{rusb_rtl.rs => rusb_device.rs} | 8 +- .../src/{tcp_rtl.rs => tcp_device.rs} | 51 +++++++- adsb/adsb_sim/src/main.rs | 106 ++++++++--------- 8 files changed, 237 insertions(+), 199 deletions(-) create mode 100644 adsb/adsb_lib/src/device.rs rename adsb/adsb_recv/src/{rusb_rtl.rs => rusb_device.rs} (96%) rename adsb/adsb_recv/src/{tcp_rtl.rs => tcp_device.rs} (72%) diff --git a/adsb/README.md b/adsb/README.md index 130192f..f748787 100644 --- a/adsb/README.md +++ b/adsb/README.md @@ -1,7 +1,7 @@ ## Simulation Mode `cargo run -p adsb_sim --` -`cargo run -p adsb_recv -- --sim` +`cargo run -p adsb_recv -- --net` ## Decode `cargo run -p adsb_recv -- --decode 8D4840D6202CC371C32CE0576098` diff --git a/adsb/adsb_lib/src/adsb.rs b/adsb/adsb_lib/src/adsb.rs index 572ad3e..96d444a 100644 --- a/adsb/adsb_lib/src/adsb.rs +++ b/adsb/adsb_lib/src/adsb.rs @@ -109,18 +109,18 @@ impl Display for ADSBFrame { } } -/// Transponder Capability (CA) codes from the ADS‑B spec +/// Transponder Capability (CA) codes from the ADS-B spec #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Capability { /// 0: Level 1 transponder Level1, - /// 1–3: Reserved + /// 1-3: Reserved Reserved(u8), - /// 4: Level 2+ transponder, on‑ground (can set CA=7) + /// 4: Level 2+ transponder, ground (can set CA=7) Level2OnGround, /// 5: Level 2+ transponder, airborne (can set CA=7) Level2Airborne, - /// 6: Level 2+ transponder, either on‑ground or airborne (can set CA=7) + /// 6: Level 2+ transponder, either ground or airborne (can set CA=7) Level2Either, /// 7: Downlink Request = 0, or Flight Status = 2,3,4,5 DownlinkRequestOrFlightStatus, @@ -198,7 +198,7 @@ impl ADSBMessage { _ => { return Err(Error::new( ErrorKind::InvalidData, - format!("unsupported ADS‑B type_code {}", type_code), + format!("unsupported ADS-B type_code {}", type_code), )) } }; diff --git a/adsb/adsb_lib/src/device.rs b/adsb/adsb_lib/src/device.rs new file mode 100644 index 0000000..00f9d71 --- /dev/null +++ b/adsb/adsb_lib/src/device.rs @@ -0,0 +1,110 @@ +pub trait Device { + /// Send a control message to the device + fn control_send(&mut self, b_request: u8, data: &[u8]) -> std::io::Result<()>; + /// Receive a control message from a device + fn control_recv(&mut self, b_request: u8, length: usize) -> std::io::Result>; + /// Read a chunk of raw IQ samples from the bulk-in endpoint + /// + /// # Arguments + /// * `buffer` - the slice to fill with received data + /// + /// # Returns + /// Number of bytes actually read + fn read_bulk(&mut self, buffer: &mut [u8]) -> std::io::Result; +} + +pub fn run(device: &mut S) -> std::io::Result<()> { + // RESET + device.control_send(0x00, &[])?; + // SET_FREQ + device.control_send(0x02, &1_090_000_000u32.to_le_bytes())?; + // SET_SR + device.control_send(0x03, &2_400_000u32.to_le_bytes())?; + // AGC on + device.control_send(0x04, &[1])?; + + // Precompute the preamble pattern in “half-bit” units (16 samples) + let preamble_halfbit_pattern = [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]; + + // Create a big buffer to hold raw I/Q bytes + let mut iq_buffer = [0u8; 16_384]; + + loop { + // Read one bulk transfer's worth of I/Q data + let bytes_read = device.read_bulk(&mut iq_buffer)?; + if bytes_read < 32 { + // Must be at least 16 I/Q pairs + continue; + } + + let raw = &iq_buffer[..bytes_read]; + + // Build a vector of "bit-samples" by thresholding I + // raw is [I0,Q0,I1,Q1,...], so step by 2 + let mut halfbit_samples = Vec::with_capacity(raw.len() / 2); + for pair in raw.chunks_exact(2) { + let i_sample = pair[0] as u16; + // Threshold at 200 + halfbit_samples.push(if i_sample > 200 { 1 } else { 0 }); + } + + // Scan for the 16-sample preamble + let mut data_start_index = None; + for idx in 0..halfbit_samples.len().saturating_sub(16) { + if &halfbit_samples[idx..idx + 16] == preamble_halfbit_pattern { + data_start_index = Some(idx + 16); + break; + } + } + + let data_start = match data_start_index { + Some(i) => i, + None => continue, // No preamble found in this chunk + }; + + // Collect 112 ADS-B bits, each manchester-encoded into 2 half-bits + // 224 half-bit samples total + let required_samples = 112 * 2; + if data_start + required_samples > halfbit_samples.len() { + // Not enough in this buffer + continue; + } + let manchester_slice = &halfbit_samples[data_start..data_start + required_samples]; + + // Manchester-decode pairs back into plain bits + let mut adsb_bits = Vec::with_capacity(112); + for window in manchester_slice.chunks_exact(2) { + match window { + [1, 0] => adsb_bits.push(0), + [0, 1] => adsb_bits.push(1), + _ => { + // Failed manchester pattern + adsb_bits.clear(); + break; + } + } + } + + if adsb_bits.len() != 112 { + // Data is malformed + continue; + } + + // Pack 112 bits into 14 bytes (MSB first in each byte) + let mut adsb_payload = [0u8; 14]; + for (bit_index, &bit_value) in adsb_bits.iter().enumerate() { + let byte_index = bit_index / 8; + let bit_in_byte = 7 - (bit_index % 8); + if bit_value == 1 { + adsb_payload[byte_index] |= 1 << bit_in_byte; + } + } + + // Print out the 14-byte payload in hex + print!("ADS-B payload: "); + for byte in &adsb_payload { + print!("{:02X} ", byte); + } + println!(); + } +} diff --git a/adsb/adsb_lib/src/lib.rs b/adsb/adsb_lib/src/lib.rs index 4226144..1adc8dd 100644 --- a/adsb/adsb_lib/src/lib.rs +++ b/adsb/adsb_lib/src/lib.rs @@ -1,117 +1,7 @@ use std::io::{Error, ErrorKind, Result}; pub mod adsb; - -pub trait RtlDevice { - /// Send a control message to the device - fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()>; - /// Receive a control message from a device - fn control_recv(&mut self, b_request: u8, length: usize) -> Result>; - /// Read a chunk of raw IQ samples from the bulk-in endpoint - /// - /// # Arguments - /// * `buffer` - the slice to fill with received data - /// - /// # Returns - /// Number of bytes actually read - fn read_bulk(&mut self, buffer: &mut [u8]) -> Result; -} - -pub fn run(device: &mut S) -> Result<()> { - // RESET - device.control_send(0x00, &[])?; - // SET_FREQ - device.control_send(0x02, &1_090_000_000u32.to_le_bytes())?; - // SET_SR - device.control_send(0x03, &2_400_000u32.to_le_bytes())?; - // AGC on - device.control_send(0x04, &[1])?; - - // Precompute the preamble pattern in “half‐bit” units (16 samples) - let preamble_halfbit_pattern = [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]; - - // Create a big buffer to hold raw I/Q bytes - let mut iq_buffer = [0u8; 16_384]; - - loop { - // Read one bulk transfer's worth of I/Q data - let bytes_read = device.read_bulk(&mut iq_buffer)?; - if bytes_read < 32 { - // Must be at least 16 I/Q pairs - continue; - } - - let raw = &iq_buffer[..bytes_read]; - - // Build a vector of "bit-samples" by thresholding I - // raw is [I0,Q0,I1,Q1,...], so step by 2 - let mut halfbit_samples = Vec::with_capacity(raw.len() / 2); - for pair in raw.chunks_exact(2) { - let i_sample = pair[0] as u16; - // Threshold at 200 - halfbit_samples.push(if i_sample > 200 { 1 } else { 0 }); - } - - // Scan for the 16-sample preamble - let mut data_start_index = None; - for idx in 0..halfbit_samples.len().saturating_sub(16) { - if &halfbit_samples[idx..idx + 16] == preamble_halfbit_pattern { - data_start_index = Some(idx + 16); - break; - } - } - - let data_start = match data_start_index { - Some(i) => i, - None => continue, // No preamble found in this chunk - }; - - // Collect 112 ADS-B bits, each manchester-encoded into 2 half-bits - // 224 half-bit samples total - let required_samples = 112 * 2; - if data_start + required_samples > halfbit_samples.len() { - // Not enough in this buffer - continue; - } - let manchester_slice = &halfbit_samples[data_start..data_start + required_samples]; - - // Manchester-decode pairs back into plain bits - let mut adsb_bits = Vec::with_capacity(112); - for window in manchester_slice.chunks_exact(2) { - match window { - [1, 0] => adsb_bits.push(0), - [0, 1] => adsb_bits.push(1), - _ => { - // Failed manchester pattern - adsb_bits.clear(); - break; - } - } - } - - if adsb_bits.len() != 112 { - // Data is malformed - continue; - } - - // Pack 112 bits into 14 bytes (MSB first in each byte) - let mut adsb_payload = [0u8; 14]; - for (bit_index, &bit_value) in adsb_bits.iter().enumerate() { - let byte_index = bit_index / 8; - let bit_in_byte = 7 - (bit_index % 8); - if bit_value == 1 { - adsb_payload[byte_index] |= 1 << bit_in_byte; - } - } - - // Print out the 14-byte payload in hex - print!("ADS-B payload: "); - for byte in &adsb_payload { - print!("{:02X} ", byte); - } - println!(); - } -} +pub mod device; pub fn hex_to_bytes(s: &str) -> Result> { let bytes = s.as_bytes(); diff --git a/adsb/adsb_recv/src/main.rs b/adsb/adsb_recv/src/main.rs index f6aa490..c822df2 100644 --- a/adsb/adsb_recv/src/main.rs +++ b/adsb/adsb_recv/src/main.rs @@ -1,22 +1,26 @@ -mod rusb_rtl; -mod tcp_rtl; +mod rusb_device; +mod tcp_device; -use crate::rusb_rtl::RusbRtl; -use crate::tcp_rtl::TcpRtl; +use crate::rusb_device::RusbDevice; +use crate::tcp_device::TcpDevice; use adsb_lib::adsb::ADSBFrame; -use adsb_lib::{hex_to_bytes, run}; +use adsb_lib::{hex_to_bytes, device::run}; use clap::Parser; use std::io::Result; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct ReceiverArgs { - #[arg(long)] - sim: bool, - #[arg(long, default_value = "127.0.0.1:9999", requires = "sim")] - addr: String, #[arg(long)] decode: Option, + + #[arg(long)] + net: bool, + #[arg(long, default_value = "127.0.0.1:9999", requires = "net")] + addr: String, + + #[arg(long)] + usb: bool, } fn main() -> Result<()> { @@ -33,13 +37,16 @@ fn main() -> Result<()> { return Ok(()); } - if args.sim { - println!("Starting in SIMULATION mode, connecting to {}", args.addr); - let mut device = TcpRtl::connect(&args.addr)?; + if args.net { + println!("Connecting to network {}", args.addr); + let mut device = TcpDevice::connect(&args.addr)?; + device.run() + } else if args.usb { + println!("Connecting to device"); + let mut device = RusbDevice::open()?; run(&mut device) } else { - println!("Starting in REAL RTL‑SDR mode"); - let mut device = RusbRtl::open()?; - run(&mut device) + println!("No connection specified"); + Ok(()) } } diff --git a/adsb/adsb_recv/src/rusb_rtl.rs b/adsb/adsb_recv/src/rusb_device.rs similarity index 96% rename from adsb/adsb_recv/src/rusb_rtl.rs rename to adsb/adsb_recv/src/rusb_device.rs index d983b2b..dc418f1 100644 --- a/adsb/adsb_recv/src/rusb_rtl.rs +++ b/adsb/adsb_recv/src/rusb_device.rs @@ -1,4 +1,4 @@ -use adsb_lib::RtlDevice; +use adsb_lib::device::Device; use rusb::{request_type, Context, DeviceHandle, Direction, Recipient, RequestType, UsbContext}; use std::io::{Error, ErrorKind, Result}; use std::time::Duration; @@ -11,11 +11,11 @@ const DATA_ENDPOINT_ADDRESS: u8 = 0x81; const USB_TRANSFER_TIMEOUT: Duration = Duration::from_secs(1); /// rusb/libusb implementation of `RtlDevice` -pub struct RusbRtl { +pub struct RusbDevice { handle: DeviceHandle, } -impl RusbRtl { +impl RusbDevice { /// Open the USB device, claim interface 0, and return a wrapper pub fn open() -> Result { // Create a new libusb context @@ -85,7 +85,7 @@ impl RusbRtl { } } -impl RtlDevice for RusbRtl { +impl Device for RusbDevice { fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> { self .control_out(b_request, data) diff --git a/adsb/adsb_recv/src/tcp_rtl.rs b/adsb/adsb_recv/src/tcp_device.rs similarity index 72% rename from adsb/adsb_recv/src/tcp_rtl.rs rename to adsb/adsb_recv/src/tcp_device.rs index 4fa00a2..1297207 100644 --- a/adsb/adsb_recv/src/tcp_rtl.rs +++ b/adsb/adsb_recv/src/tcp_device.rs @@ -1,6 +1,7 @@ -use adsb_lib::RtlDevice; +use adsb_lib::device::Device; use std::io::{Error, ErrorKind, Read, Result, Write}; use std::net::TcpStream; +use adsb_lib::adsb::ADSBFrame; // Tags for framing requests/responses over the TCP socket const TAG_CTRL_OUT: u8 = 0x10; @@ -8,15 +9,55 @@ const TAG_CTRL_IN: u8 = 0x11; const TAG_BULK: u8 = 0x20; /// A TCP-based implementation of `RtlDevice` -pub struct TcpRtl { +pub struct TcpDevice { socket: TcpStream, } -impl TcpRtl { +impl TcpDevice { /// Connect to a remote RTL-SDR server at the given address pub fn connect(addr: &str) -> Result { let socket = TcpStream::connect(addr)?; - Ok(TcpRtl { socket }) + Ok(TcpDevice { socket }) + } + + pub fn run(&mut self) -> Result<()> { + let request_len: u16 = 14; + loop { + // Send header: [tag][bRequest=0][length:2 bytes LE] + let mut hdr = [0u8; 4]; + hdr[0] = TAG_BULK; + hdr[1] = 0; + hdr[2..4].copy_from_slice(&request_len.to_le_bytes()); + self.socket.write_all(&hdr)?; + + // Read status + let mut status = [0u8; 1]; + self.socket.read_exact(&mut status)?; + if status[0] != 0 { + eprintln!("Remote reported error status {}", status[0]); + break; + } + + // Read 4-byte payload length (LE) + let mut len_bytes = [0u8; 4]; + self.socket.read_exact(&mut len_bytes)?; + let actual_len = u32::from_le_bytes(len_bytes) as usize; + + // Read payload (I/Q pairs) + let mut iq = vec![0u8; actual_len]; + self.socket.read_exact(&mut iq)?; + + // Extract I-samples (even indices) and print as ADS‑B hex + let mut adsb = Vec::with_capacity(actual_len / 2); + for chunk in iq.chunks_exact(2) { + adsb.push(chunk[0]); + } + + let frame = ADSBFrame::decode(&adsb)?; + println!("{}", frame); + } + + Ok(()) } /// Send a framed message @@ -75,7 +116,7 @@ impl TcpRtl { } } -impl RtlDevice for TcpRtl { +impl Device for TcpDevice { fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> { self.send_message(TAG_CTRL_OUT, b_request, data)?; self.receive_status_ok() diff --git a/adsb/adsb_sim/src/main.rs b/adsb/adsb_sim/src/main.rs index c62a7aa..5b86e9c 100644 --- a/adsb/adsb_sim/src/main.rs +++ b/adsb/adsb_sim/src/main.rs @@ -1,5 +1,5 @@ use clap::Parser; -use std::io::{Read, Write}; +use std::io::{Read, Write, Result}; use std::net::{TcpListener, TcpStream}; use std::thread; use std::time::Duration; @@ -26,7 +26,6 @@ struct SimulationArgs { } fn main() { - // Parse command‐line arguments let args = SimulationArgs::parse(); // Build the bind address, e.g. "127.0.0.1:9999" @@ -38,24 +37,26 @@ fn main() { .unwrap_or_else(|err| panic!("failed to bind {}: {}", bind_address, err)); // Accept connections in a loop - for incoming in listener.incoming() { - match incoming { + for incoming_connection in listener.incoming() { + match incoming_connection { Ok(client_stream) => { // Spawn a thread per client - thread::spawn(move || handle_client_connection(client_stream)); + thread::spawn(move || { + if let Err(err) = handle_client_connection(client_stream) { + eprintln!("connection error: {}", err); + } + }); } - Err(err) => eprintln!("Error accepting connection: {}", err), + Err(err) => eprintln!("error accepting connection: {}", err), } } } /// Handle a single client connection -fn handle_client_connection(mut connection: TcpStream) { - // Track a "current frequency" - let mut current_frequency_hz: u32 = 0; - +fn handle_client_connection(mut connection: TcpStream) -> Result<()> { + println!("Connection established"); loop { - // Read the 4-byte header: [tag:1][bRequest:1][length:2] + // Read the 4-byte header: [tag:1][bRequest:1][length:2 LE] let mut header_buffer = [0u8; 4]; if connection.read_exact(&mut header_buffer).is_err() { // Client closed on error @@ -63,79 +64,68 @@ fn handle_client_connection(mut connection: TcpStream) { } let message_tag = header_buffer[0]; - let b_request = header_buffer[1]; + let _b_request = header_buffer[1]; let payload_length = u16::from_le_bytes([header_buffer[2], header_buffer[3]]) as usize; + // println!("Received message '{:02x}' with payload length {}", message_tag, payload_length); + // Read the optional payload - let mut payload_buffer = vec![0u8; payload_length]; - if payload_length > 0 { - if connection.read_exact(&mut payload_buffer).is_err() { - break; - } - } + // let mut payload_buffer = vec![0u8; payload_length]; + // if payload_length > 0 { + // if connection.read(&mut payload_buffer).is_err() { + // eprintln!("error reading payload buffer"); + // break; + // } + // } // Dispatch based on the framing tag match message_tag { TAG_CONTROL_OUT => { - // Simulate accepting a CONTROL_OUT (e.g. SET_FREQ) - if b_request == 0x02 && payload_buffer.len() == 4 { - current_frequency_hz = u32::from_le_bytes([ - payload_buffer[0], - payload_buffer[1], - payload_buffer[2], - payload_buffer[3], - ]); - println!("SET_FREQ -> {} Hz", current_frequency_hz); - } - // Acknowledge with a single byte = 0 (OK) - connection.write_all(&[0u8]).ok(); + // println!("Received control out"); + // Acknowledge with a status OK + connection.write_all(&[0u8])?; } TAG_CONTROL_IN => { - dbg!(message_tag); - // Simulate a CONTROL_IN reply with a fixed pattern - - // Status byte - let _ = connection.write_all(&[0u8]); - - // 2-byte little-endian length - let length_u16 = payload_length as u16; - let _ = connection.write_all(&length_u16.to_le_bytes()); - - // Payload (0x42 repeated) - let reply = vec![0x42; payload_length]; - let _ = connection.write_all(&reply).ok(); + // println!("Received control in"); + // STATUS(1) + LENGTH(2) + dummy payload + connection.write_all(&[0x00])?; + connection.write_all(&(payload_length as u16).to_le_bytes())?; + connection.write_all(&vec![0x42; payload_length])?; } TAG_BULK => { - dbg!(message_tag); - // Generate a ADS-B IQ burst - let iq_samples = generate_adsb_iq_samples(); - let length_u32 = (iq_samples.len() as u32).to_le_bytes(); - - // Send status byte = 0 (OK) - let _ = connection.write_all(&[0u8]); - - // Send 4-byte little-endian length (bulk uses u32) - let _ = connection.write_all(&length_u32); - - // Send the IQ payload - let _ = connection.write_all(&iq_samples); + // println!("Received bulk message"); + let iq = generate_adsb_iq(); + // STATUS(1) + LENGTH(4) + IQ data + connection.write_all(&[0x00])?; + connection.write_all(&(iq.len() as u32).to_le_bytes())?; + connection.write_all(&iq)?; // Throttle a bit to simulate real USB/bulk behavior thread::sleep(Duration::from_millis(10)); } _unknown_tag => { - // On any unrecognized tag, break out + eprintln!("Unknown message tag {}", _unknown_tag); break; } } } println!("Connection closed"); + Ok(()) +} + +fn generate_adsb_iq() -> Vec { + let mut v = Vec::with_capacity(ADSB_MESSAGE.len() * 2); + for &b in &ADSB_MESSAGE { + v.push(b); // I + v.push(0x80); // Q fixed + } + v } /// Build one preamble (8 bits) + 112 data bits /// Sampled at 2 Mhz (1 sample per half-bit). Interleaved I/Q bytes -fn generate_adsb_iq_samples() -> Vec { +fn _generate_adsb_iq_samples() -> Vec { // Preamble bits (1us per bit at 2 Mhz -> 2 samples per bit) // Preamble is 8 bits: 1,0,1,0,1,0,0,0 let preamble_bits = [1, 0, 1, 0, 1, 0, 0, 0];