Temp got sim to work with tcp device by stripping out a lot of the logic.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
## Simulation Mode
|
## Simulation Mode
|
||||||
`cargo run -p adsb_sim --`
|
`cargo run -p adsb_sim --`
|
||||||
|
|
||||||
`cargo run -p adsb_recv -- --sim`
|
`cargo run -p adsb_recv -- --net`
|
||||||
|
|
||||||
## Decode
|
## Decode
|
||||||
`cargo run -p adsb_recv -- --decode 8D4840D6202CC371C32CE0576098`
|
`cargo run -p adsb_recv -- --decode 8D4840D6202CC371C32CE0576098`
|
||||||
|
|||||||
@@ -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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
/// 0: Level 1 transponder
|
/// 0: Level 1 transponder
|
||||||
Level1,
|
Level1,
|
||||||
/// 1–3: Reserved
|
/// 1-3: Reserved
|
||||||
Reserved(u8),
|
Reserved(u8),
|
||||||
/// 4: Level 2+ transponder, on‑ground (can set CA=7)
|
/// 4: Level 2+ transponder, ground (can set CA=7)
|
||||||
Level2OnGround,
|
Level2OnGround,
|
||||||
/// 5: Level 2+ transponder, airborne (can set CA=7)
|
/// 5: Level 2+ transponder, airborne (can set CA=7)
|
||||||
Level2Airborne,
|
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,
|
Level2Either,
|
||||||
/// 7: Downlink Request = 0, or Flight Status = 2,3,4,5
|
/// 7: Downlink Request = 0, or Flight Status = 2,3,4,5
|
||||||
DownlinkRequestOrFlightStatus,
|
DownlinkRequestOrFlightStatus,
|
||||||
@@ -198,7 +198,7 @@ impl ADSBMessage {
|
|||||||
_ => {
|
_ => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::InvalidData,
|
ErrorKind::InvalidData,
|
||||||
format!("unsupported ADS‑B type_code {}", type_code),
|
format!("unsupported ADS-B type_code {}", type_code),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
110
adsb/adsb_lib/src/device.rs
Normal file
110
adsb/adsb_lib/src/device.rs
Normal file
@@ -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<Vec<u8>>;
|
||||||
|
/// 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<usize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<S: Device>(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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,117 +1,7 @@
|
|||||||
use std::io::{Error, ErrorKind, Result};
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
|
||||||
pub mod adsb;
|
pub mod adsb;
|
||||||
|
pub mod device;
|
||||||
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<Vec<u8>>;
|
|
||||||
/// 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<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<S: RtlDevice>(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 fn hex_to_bytes(s: &str) -> Result<Vec<u8>> {
|
pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>> {
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
mod rusb_rtl;
|
mod rusb_device;
|
||||||
mod tcp_rtl;
|
mod tcp_device;
|
||||||
|
|
||||||
use crate::rusb_rtl::RusbRtl;
|
use crate::rusb_device::RusbDevice;
|
||||||
use crate::tcp_rtl::TcpRtl;
|
use crate::tcp_device::TcpDevice;
|
||||||
use adsb_lib::adsb::ADSBFrame;
|
use adsb_lib::adsb::ADSBFrame;
|
||||||
use adsb_lib::{hex_to_bytes, run};
|
use adsb_lib::{hex_to_bytes, device::run};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct ReceiverArgs {
|
struct ReceiverArgs {
|
||||||
#[arg(long)]
|
|
||||||
sim: bool,
|
|
||||||
#[arg(long, default_value = "127.0.0.1:9999", requires = "sim")]
|
|
||||||
addr: String,
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
decode: Option<String>,
|
decode: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
net: bool,
|
||||||
|
#[arg(long, default_value = "127.0.0.1:9999", requires = "net")]
|
||||||
|
addr: String,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
usb: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
@@ -33,13 +37,16 @@ fn main() -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.sim {
|
if args.net {
|
||||||
println!("Starting in SIMULATION mode, connecting to {}", args.addr);
|
println!("Connecting to network {}", args.addr);
|
||||||
let mut device = TcpRtl::connect(&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)
|
run(&mut device)
|
||||||
} else {
|
} else {
|
||||||
println!("Starting in REAL RTL‑SDR mode");
|
println!("No connection specified");
|
||||||
let mut device = RusbRtl::open()?;
|
Ok(())
|
||||||
run(&mut device)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use adsb_lib::RtlDevice;
|
use adsb_lib::device::Device;
|
||||||
use rusb::{request_type, Context, DeviceHandle, Direction, Recipient, RequestType, UsbContext};
|
use rusb::{request_type, Context, DeviceHandle, Direction, Recipient, RequestType, UsbContext};
|
||||||
use std::io::{Error, ErrorKind, Result};
|
use std::io::{Error, ErrorKind, Result};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -11,11 +11,11 @@ const DATA_ENDPOINT_ADDRESS: u8 = 0x81;
|
|||||||
const USB_TRANSFER_TIMEOUT: Duration = Duration::from_secs(1);
|
const USB_TRANSFER_TIMEOUT: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
/// rusb/libusb implementation of `RtlDevice`
|
/// rusb/libusb implementation of `RtlDevice`
|
||||||
pub struct RusbRtl {
|
pub struct RusbDevice {
|
||||||
handle: DeviceHandle<Context>,
|
handle: DeviceHandle<Context>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RusbRtl {
|
impl RusbDevice {
|
||||||
/// Open the USB device, claim interface 0, and return a wrapper
|
/// Open the USB device, claim interface 0, and return a wrapper
|
||||||
pub fn open() -> Result<Self> {
|
pub fn open() -> Result<Self> {
|
||||||
// Create a new libusb context
|
// 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<()> {
|
fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> {
|
||||||
self
|
self
|
||||||
.control_out(b_request, data)
|
.control_out(b_request, data)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use adsb_lib::RtlDevice;
|
use adsb_lib::device::Device;
|
||||||
use std::io::{Error, ErrorKind, Read, Result, Write};
|
use std::io::{Error, ErrorKind, Read, Result, Write};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
use adsb_lib::adsb::ADSBFrame;
|
||||||
|
|
||||||
// Tags for framing requests/responses over the TCP socket
|
// Tags for framing requests/responses over the TCP socket
|
||||||
const TAG_CTRL_OUT: u8 = 0x10;
|
const TAG_CTRL_OUT: u8 = 0x10;
|
||||||
@@ -8,15 +9,55 @@ const TAG_CTRL_IN: u8 = 0x11;
|
|||||||
const TAG_BULK: u8 = 0x20;
|
const TAG_BULK: u8 = 0x20;
|
||||||
|
|
||||||
/// A TCP-based implementation of `RtlDevice`
|
/// A TCP-based implementation of `RtlDevice`
|
||||||
pub struct TcpRtl {
|
pub struct TcpDevice {
|
||||||
socket: TcpStream,
|
socket: TcpStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TcpRtl {
|
impl TcpDevice {
|
||||||
/// Connect to a remote RTL-SDR server at the given address
|
/// Connect to a remote RTL-SDR server at the given address
|
||||||
pub fn connect(addr: &str) -> Result<Self> {
|
pub fn connect(addr: &str) -> Result<Self> {
|
||||||
let socket = TcpStream::connect(addr)?;
|
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
|
/// 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<()> {
|
fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> {
|
||||||
self.send_message(TAG_CTRL_OUT, b_request, data)?;
|
self.send_message(TAG_CTRL_OUT, b_request, data)?;
|
||||||
self.receive_status_ok()
|
self.receive_status_ok()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write, Result};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -26,7 +26,6 @@ struct SimulationArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Parse command‐line arguments
|
|
||||||
let args = SimulationArgs::parse();
|
let args = SimulationArgs::parse();
|
||||||
|
|
||||||
// Build the bind address, e.g. "127.0.0.1:9999"
|
// 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));
|
.unwrap_or_else(|err| panic!("failed to bind {}: {}", bind_address, err));
|
||||||
|
|
||||||
// Accept connections in a loop
|
// Accept connections in a loop
|
||||||
for incoming in listener.incoming() {
|
for incoming_connection in listener.incoming() {
|
||||||
match incoming {
|
match incoming_connection {
|
||||||
Ok(client_stream) => {
|
Ok(client_stream) => {
|
||||||
// Spawn a thread per client
|
// 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
|
/// Handle a single client connection
|
||||||
fn handle_client_connection(mut connection: TcpStream) {
|
fn handle_client_connection(mut connection: TcpStream) -> Result<()> {
|
||||||
// Track a "current frequency"
|
println!("Connection established");
|
||||||
let mut current_frequency_hz: u32 = 0;
|
|
||||||
|
|
||||||
loop {
|
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];
|
let mut header_buffer = [0u8; 4];
|
||||||
if connection.read_exact(&mut header_buffer).is_err() {
|
if connection.read_exact(&mut header_buffer).is_err() {
|
||||||
// Client closed on error
|
// Client closed on error
|
||||||
@@ -63,79 +64,68 @@ fn handle_client_connection(mut connection: TcpStream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message_tag = header_buffer[0];
|
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;
|
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
|
// Read the optional payload
|
||||||
let mut payload_buffer = vec![0u8; payload_length];
|
// let mut payload_buffer = vec![0u8; payload_length];
|
||||||
if payload_length > 0 {
|
// if payload_length > 0 {
|
||||||
if connection.read_exact(&mut payload_buffer).is_err() {
|
// if connection.read(&mut payload_buffer).is_err() {
|
||||||
break;
|
// eprintln!("error reading payload buffer");
|
||||||
}
|
// break;
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Dispatch based on the framing tag
|
// Dispatch based on the framing tag
|
||||||
match message_tag {
|
match message_tag {
|
||||||
TAG_CONTROL_OUT => {
|
TAG_CONTROL_OUT => {
|
||||||
// Simulate accepting a CONTROL_OUT (e.g. SET_FREQ)
|
// println!("Received control out");
|
||||||
if b_request == 0x02 && payload_buffer.len() == 4 {
|
// Acknowledge with a status OK
|
||||||
current_frequency_hz = u32::from_le_bytes([
|
connection.write_all(&[0u8])?;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
TAG_CONTROL_IN => {
|
TAG_CONTROL_IN => {
|
||||||
dbg!(message_tag);
|
// println!("Received control in");
|
||||||
// Simulate a CONTROL_IN reply with a fixed pattern
|
// STATUS(1) + LENGTH(2) + dummy payload
|
||||||
|
connection.write_all(&[0x00])?;
|
||||||
// Status byte
|
connection.write_all(&(payload_length as u16).to_le_bytes())?;
|
||||||
let _ = connection.write_all(&[0u8]);
|
connection.write_all(&vec![0x42; payload_length])?;
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
TAG_BULK => {
|
TAG_BULK => {
|
||||||
dbg!(message_tag);
|
// println!("Received bulk message");
|
||||||
// Generate a ADS-B IQ burst
|
let iq = generate_adsb_iq();
|
||||||
let iq_samples = generate_adsb_iq_samples();
|
// STATUS(1) + LENGTH(4) + IQ data
|
||||||
let length_u32 = (iq_samples.len() as u32).to_le_bytes();
|
connection.write_all(&[0x00])?;
|
||||||
|
connection.write_all(&(iq.len() as u32).to_le_bytes())?;
|
||||||
// Send status byte = 0 (OK)
|
connection.write_all(&iq)?;
|
||||||
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);
|
|
||||||
|
|
||||||
// Throttle a bit to simulate real USB/bulk behavior
|
// Throttle a bit to simulate real USB/bulk behavior
|
||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
_unknown_tag => {
|
_unknown_tag => {
|
||||||
// On any unrecognized tag, break out
|
eprintln!("Unknown message tag {}", _unknown_tag);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Connection closed");
|
println!("Connection closed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_adsb_iq() -> Vec<u8> {
|
||||||
|
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
|
/// Build one preamble (8 bits) + 112 data bits
|
||||||
/// Sampled at 2 Mhz (1 sample per half-bit). Interleaved I/Q bytes
|
/// Sampled at 2 Mhz (1 sample per half-bit). Interleaved I/Q bytes
|
||||||
fn generate_adsb_iq_samples() -> Vec<u8> {
|
fn _generate_adsb_iq_samples() -> Vec<u8> {
|
||||||
// Preamble bits (1us per bit at 2 Mhz -> 2 samples per bit)
|
// Preamble bits (1us per bit at 2 Mhz -> 2 samples per bit)
|
||||||
// Preamble is 8 bits: 1,0,1,0,1,0,0,0
|
// Preamble is 8 bits: 1,0,1,0,1,0,0,0
|
||||||
let preamble_bits = [1, 0, 1, 0, 1, 0, 0, 0];
|
let preamble_bits = [1, 0, 1, 0, 1, 0, 0, 0];
|
||||||
|
|||||||
Reference in New Issue
Block a user