Format adsb code
This commit is contained in:
7
Makefile
7
Makefile
@@ -40,11 +40,8 @@ format-adsb: ## Format code
|
||||
build-adsb: ## Build the ADS-B project
|
||||
@cd adsb && cargo build
|
||||
|
||||
run-sim: ## Run the ADS-B Simulator
|
||||
@cd adsb/adsb_sim && cargo run -p adsb_sim
|
||||
|
||||
run-recv: ## Run the ADS-B Receiver
|
||||
@cd adsb/adsb_recv && cargo run -p adsb_recv -- --sim
|
||||
run-adsb: ## Run the ADS-B Receiver
|
||||
@cd adsb && cargo run -- -c -D
|
||||
|
||||
#################
|
||||
# UI Commands #
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"adsb_lib",
|
||||
"squawk",
|
||||
"squawk_sim"
|
||||
]
|
||||
resolver = "2"
|
||||
package.version = "0.1.0"
|
||||
package.edition = "2024"
|
||||
[package]
|
||||
name = "adsb"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rusb = "0.9.4"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
ctrlc = "3.4.6"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# ADSB
|
||||
Debug using `export LIBUSB_DEBUG=4`
|
||||
|
||||
`lsusb -v -d 0bda:2832`
|
||||
|
||||
## Simulation Mode
|
||||
`cargo run -p adsb_sim --`
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[package]
|
||||
name = "adsb_lib"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
@@ -1,110 +0,0 @@
|
||||
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,11 +0,0 @@
|
||||
[package]
|
||||
name = "squawk"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
adsb_lib = { path = "../adsb_lib" }
|
||||
rusb = "0.9.4"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
@@ -1,79 +0,0 @@
|
||||
mod rusb_device;
|
||||
mod tcp_device;
|
||||
|
||||
use crate::rusb_device::RusbDevice;
|
||||
use crate::tcp_device::TcpDevice;
|
||||
use adsb_lib::adsb_frame::ADSBFrame;
|
||||
use adsb_lib::{hex_to_bytes, device::run};
|
||||
use clap::Parser;
|
||||
use std::io::Result;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = "An ADS-B Receiver")]
|
||||
struct ReceiverArgs {
|
||||
/// Hex-string to decode
|
||||
#[arg(long)]
|
||||
decode: Option<String>,
|
||||
|
||||
/// Connect to the network
|
||||
#[arg(long)]
|
||||
net: bool,
|
||||
/// Network listen address (requires --net)
|
||||
#[arg(long, requires = "net", hide = true)]
|
||||
addr: Option<String>,
|
||||
/// Network listen port (requires --net)
|
||||
#[arg(long, requires = "net", hide = true)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Connect to the USB device
|
||||
#[arg(long)]
|
||||
usb: bool,
|
||||
|
||||
/// Enable debug logging
|
||||
#[arg(short, long, action)]
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = ReceiverArgs::parse();
|
||||
|
||||
let default_filter = if args.debug {
|
||||
"warn,squawk=debug"
|
||||
} else {
|
||||
"warn,squawk=info"
|
||||
};
|
||||
|
||||
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", default_filter));
|
||||
|
||||
// Handle decode mode
|
||||
if let Some(mut hex_string) = args.decode {
|
||||
if let Some(stripped) = hex_string.strip_prefix("0x") {
|
||||
hex_string = stripped.to_string();
|
||||
}
|
||||
let buf = hex_to_bytes(&hex_string)?;
|
||||
let frame = ADSBFrame::decode(&buf)?;
|
||||
|
||||
log::info!("{}", frame);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Handle net mode
|
||||
if args.net {
|
||||
let host = args.addr.unwrap_or_else(|| "127.0.0.1".into());
|
||||
let port = args.port.unwrap_or(9999);
|
||||
let addr = format!("{host}:{port}");
|
||||
|
||||
log::info!("Connecting to network {}", addr);
|
||||
let mut device = TcpDevice::connect(&addr)?;
|
||||
device.run()
|
||||
}
|
||||
// Handle usb mode
|
||||
else if args.usb {
|
||||
log::info!("Connecting to device");
|
||||
let mut device = RusbDevice::open()?;
|
||||
run(&mut device)
|
||||
} else {
|
||||
log::warn!("No connection specified");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
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;
|
||||
|
||||
// USB identifiers for RTL-SDR
|
||||
const VENDOR_ID: u16 = 0x0BDA;
|
||||
const PRODUCT_ID: u16 = 0x2832;
|
||||
// Bulk-IN endpoint (0x80 | 0x01)
|
||||
const DATA_ENDPOINT_ADDRESS: u8 = 0x81;
|
||||
const USB_TRANSFER_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
/// rusb/libusb implementation of `RtlDevice`
|
||||
pub struct RusbDevice {
|
||||
handle: DeviceHandle<Context>,
|
||||
}
|
||||
|
||||
impl RusbDevice {
|
||||
/// Open the USB device, claim interface 0, and return a wrapper
|
||||
pub fn open() -> Result<Self> {
|
||||
// Create a new libusb context
|
||||
let ctx = Context::new().map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||
|
||||
// Find and open the RTL-SDR by VID/PID
|
||||
let handle = ctx
|
||||
.open_device_with_vid_pid(VENDOR_ID, PRODUCT_ID)
|
||||
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Device not found"))?;
|
||||
|
||||
// Claim interface 0
|
||||
handle
|
||||
.claim_interface(0)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||
|
||||
Ok(Self { handle })
|
||||
}
|
||||
|
||||
/// Send a CONTROL-OUT request to the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request` - the bRequest byte identifying the operation
|
||||
/// * `data_payload` - a slice of bytes to send in the data stage
|
||||
fn control_out(&mut self, request: u8, data: &[u8]) -> rusb::Result<()> {
|
||||
// bmRequestType: OUT | VENDOR | DEVICE
|
||||
let bm_request_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Device);
|
||||
|
||||
// Perform the control transfer.
|
||||
self
|
||||
.handle
|
||||
.write_control(
|
||||
bm_request_type,
|
||||
request,
|
||||
0, // wValue
|
||||
0, // wIndex
|
||||
data,
|
||||
USB_TRANSFER_TIMEOUT,
|
||||
)
|
||||
.map(|_bytes_written| ())
|
||||
}
|
||||
|
||||
/// Perform a CONTROL_IN request and read back up to `response_length` bytes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request` - the bRequest byte identifying the operation
|
||||
/// * `response_length` - the maximum number of bytes to read
|
||||
fn control_in(&mut self, request: u8, response_length: usize) -> rusb::Result<Vec<u8>> {
|
||||
// bmRequestType: IN | VENDOR | DEVICE
|
||||
let bm_request_type = request_type(Direction::In, RequestType::Vendor, Recipient::Device);
|
||||
|
||||
// Allocate a buffer for the incoming data
|
||||
let mut buffer = vec![0u8; response_length];
|
||||
|
||||
// Read the control response into the buffer
|
||||
let n = self.handle.read_control(
|
||||
bm_request_type,
|
||||
request,
|
||||
0, // wValue
|
||||
0, // wIndex
|
||||
&mut buffer,
|
||||
USB_TRANSFER_TIMEOUT,
|
||||
)?;
|
||||
|
||||
// Truncate to the actual length returned by the device
|
||||
buffer.truncate(n);
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for RusbDevice {
|
||||
fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> {
|
||||
self
|
||||
.control_out(b_request, data)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
|
||||
fn control_recv(&mut self, b_request: u8, length: usize) -> Result<Vec<u8>> {
|
||||
self
|
||||
.control_in(b_request, length)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
|
||||
fn read_bulk(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
||||
self
|
||||
.handle
|
||||
.read_bulk(DATA_ENDPOINT_ADDRESS, buffer, USB_TRANSFER_TIMEOUT)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
use adsb_lib::device::Device;
|
||||
use std::io::{Error, ErrorKind, Read, Result, Write};
|
||||
use std::net::TcpStream;
|
||||
use adsb_lib::adsb_frame::ADSBFrame;
|
||||
|
||||
// Tags for framing requests/responses over the TCP socket
|
||||
const TAG_CTRL_OUT: u8 = 0x10;
|
||||
const TAG_CTRL_IN: u8 = 0x11;
|
||||
const TAG_BULK: u8 = 0x20;
|
||||
|
||||
/// A TCP-based implementation of `RtlDevice`
|
||||
pub struct TcpDevice {
|
||||
socket: TcpStream,
|
||||
}
|
||||
|
||||
impl TcpDevice {
|
||||
/// Connect to a remote RTL-SDR server at the given address
|
||||
pub fn connect(addr: &str) -> Result<Self> {
|
||||
let socket = TcpStream::connect(addr)?;
|
||||
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 {
|
||||
log::error!("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)?;
|
||||
log::info!("{}", frame);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a framed message
|
||||
///
|
||||
/// 1 byte: tag
|
||||
/// 1 byte: bRequest
|
||||
/// 2 bytes: payload length (big endian)
|
||||
/// N bytes: optional payload data
|
||||
fn send_message(&mut self, message_tag: u8, b_request: u8, data: &[u8]) -> Result<()> {
|
||||
let payload_length = data.len() as u16;
|
||||
|
||||
// Build the 4-byte header
|
||||
let mut header = [0u8; 4];
|
||||
header[0] = message_tag;
|
||||
header[1] = b_request;
|
||||
header[2..4].copy_from_slice(&payload_length.to_be_bytes());
|
||||
|
||||
// Send header + payload
|
||||
self.socket.write_all(&header)?;
|
||||
if payload_length > 0 {
|
||||
self.socket.write_all(&data)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read one status byte. Expect 0 => OK, non-zero => error
|
||||
fn receive_status_ok(&mut self) -> Result<()> {
|
||||
let mut status_byte = [0u8; 1];
|
||||
self.socket.read_exact(&mut status_byte)?;
|
||||
if status_byte[0] != 0 {
|
||||
Err(Error::new(ErrorKind::Other, "Remote reported error"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a length-prefixed payload
|
||||
///
|
||||
/// 1 byte: tag (ignored)
|
||||
/// 2 bytes: length (little-endian)
|
||||
/// N bytes: payload data
|
||||
fn receive_length_prefixed_payload(&mut self) -> Result<Vec<u8>> {
|
||||
// Discard tag
|
||||
let mut tag_bytes = [0u8; 1];
|
||||
self.socket.read_exact(&mut tag_bytes)?;
|
||||
|
||||
// Read 2-byte little-endian length
|
||||
let mut length_bytes = [0u8; 2];
|
||||
self.socket.read_exact(&mut length_bytes)?;
|
||||
let payload_length = u16::from_le_bytes(length_bytes) as usize;
|
||||
|
||||
// Read exactly payload_length bytes
|
||||
let mut buffer = vec![0u8; payload_length];
|
||||
self.socket.read_exact(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn control_recv(&mut self, b_request: u8, _length: usize) -> Result<Vec<u8>> {
|
||||
self.send_message(TAG_CTRL_IN, b_request, &[])?;
|
||||
self.receive_length_prefixed_payload()
|
||||
}
|
||||
|
||||
fn read_bulk(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
||||
// Number of bytes expected
|
||||
let requested_byte_count = buffer.len() as u16;
|
||||
|
||||
// Prepare a 4-byte header slot
|
||||
let mut header = [0u8; 4];
|
||||
header[0] = TAG_BULK;
|
||||
header[1] = 0; // bRequest=0 for bulk
|
||||
header[2..4].copy_from_slice(&requested_byte_count.to_le_bytes());
|
||||
|
||||
// Write the 4-byte header from the peer
|
||||
let _ = self.socket.write_all(&header)?;
|
||||
|
||||
// Read and check the status byte
|
||||
let mut status_byte = [0u8];
|
||||
self.socket.read_exact(&mut status_byte)?;
|
||||
if status_byte[0] != 0 {
|
||||
return Err(Error::new(ErrorKind::Other, "Remote reported error"));
|
||||
}
|
||||
|
||||
// Read the 4-byte payload length
|
||||
let mut length_bytes = [0u8; 4];
|
||||
self.socket.read_exact(&mut length_bytes)?;
|
||||
let actual_payload_length = u32::from_le_bytes(length_bytes) as usize;
|
||||
|
||||
// Read the payload
|
||||
self
|
||||
.socket
|
||||
.read_exact(&mut buffer[..actual_payload_length])?;
|
||||
Ok(actual_payload_length)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "squawk_sim"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
adsb_lib = { path = "../adsb_lib" }
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
@@ -1,180 +0,0 @@
|
||||
use clap::Parser;
|
||||
use std::io::{Read, Write, Result};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// Framing tags
|
||||
const TAG_CONTROL_OUT: u8 = 0x10;
|
||||
const TAG_CONTROL_IN: u8 = 0x11;
|
||||
const TAG_BULK: u8 = 0x20;
|
||||
|
||||
const ADSB_MESSAGE: [u8; 14] = [
|
||||
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98,
|
||||
];
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct SimulationArgs {
|
||||
/// Host/IP to bind the TCP listener on
|
||||
#[arg(long, default_value = "127.0.0.1")]
|
||||
host: String,
|
||||
|
||||
/// TCP port to bind the listener on
|
||||
#[arg(long, default_value = "9999")]
|
||||
port: u16,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(
|
||||
env_logger::Env::default().filter_or("RUST_LOG", "warn,squawk_sim=info"),
|
||||
);
|
||||
let args = SimulationArgs::parse();
|
||||
|
||||
// Build the bind address, e.g. "127.0.0.1:9999"
|
||||
let bind_address = format!("{}:{}", args.host, args.port);
|
||||
log::info!("Listening on {}", bind_address);
|
||||
|
||||
// Start listening for incoming TCP connections
|
||||
let listener = TcpListener::bind(&bind_address)
|
||||
.unwrap_or_else(|err| panic!("failed to bind {}: {}", bind_address, err));
|
||||
|
||||
// Accept connections in a loop
|
||||
for incoming_connection in listener.incoming() {
|
||||
match incoming_connection {
|
||||
Ok(client_stream) => {
|
||||
// Spawn a thread per client
|
||||
thread::spawn(move || {
|
||||
if let Err(err) = handle_client_connection(client_stream) {
|
||||
log::error!("connection error: {}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => log::error!("error accepting connection: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a single client connection
|
||||
fn handle_client_connection(mut connection: TcpStream) -> Result<()> {
|
||||
log::info!("Connection established");
|
||||
loop {
|
||||
// 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
|
||||
break;
|
||||
}
|
||||
|
||||
let message_tag = header_buffer[0];
|
||||
let _b_request = header_buffer[1];
|
||||
let payload_length = u16::from_le_bytes([header_buffer[2], header_buffer[3]]) as usize;
|
||||
|
||||
log::trace!(
|
||||
"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(&mut payload_buffer).is_err() {
|
||||
// log::error!("error reading payload buffer");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Dispatch based on the framing tag
|
||||
match message_tag {
|
||||
TAG_CONTROL_OUT => {
|
||||
log::trace!("Received control out");
|
||||
// Acknowledge with a status OK
|
||||
connection.write_all(&[0u8])?;
|
||||
}
|
||||
TAG_CONTROL_IN => {
|
||||
log::trace!("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 => {
|
||||
log::trace!("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 => {
|
||||
log::warn!("Unknown message tag {}", _unknown_tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("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
|
||||
/// Sampled at 2 Mhz (1 sample per half-bit). Interleaved I/Q bytes
|
||||
fn _generate_adsb_iq_samples() -> Vec<u8> {
|
||||
// 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];
|
||||
|
||||
// Manchester encode the 112 data bits
|
||||
// bit=0 -> [1,0], bit=1 -> [0,1] (half-bit intervals)
|
||||
let mut manchester_bits = Vec::with_capacity(112 * 2);
|
||||
for &byte in ADSB_MESSAGE.iter() {
|
||||
for bit_idx in (0..8).rev() {
|
||||
let bit = (byte >> bit_idx) & 1;
|
||||
if bit == 0 {
|
||||
manchester_bits.push(1);
|
||||
manchester_bits.push(0);
|
||||
} else {
|
||||
manchester_bits.push(0);
|
||||
manchester_bits.push(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenate preamble + data
|
||||
let mut full_bitstream = Vec::with_capacity(preamble_bits.len() * 2 + manchester_bits.len());
|
||||
|
||||
// Preamble: each '1' or '0' is one microsecond = 2 samples
|
||||
for &pb in preamble_bits.iter() {
|
||||
// Push two identical half-bits = 2 samples
|
||||
full_bitstream.push(pb);
|
||||
full_bitstream.push(pb);
|
||||
}
|
||||
|
||||
// Data: already in half-bit units = 1 sample per element
|
||||
full_bitstream.extend(manchester_bits);
|
||||
|
||||
// Build interleaved I/Q samples
|
||||
// I = 128 + 127*(bit), Q = 128
|
||||
let mut iq = Vec::with_capacity(full_bitstream.len() * 2);
|
||||
for &level in full_bitstream.iter() {
|
||||
let i_sample = if level == 1 { 255u8 } else { 128u8 };
|
||||
let q_sample = 128u8;
|
||||
iq.push(i_sample);
|
||||
iq.push(q_sample);
|
||||
}
|
||||
|
||||
iq
|
||||
}
|
||||
248
adsb/src/device.rs
Normal file
248
adsb/src/device.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use rusb::{
|
||||
Context, Device, DeviceDescriptor, DeviceHandle, DeviceList, Direction, TransferType, UsbContext,
|
||||
};
|
||||
use crate::error::{Error, Result};
|
||||
use std::time::Duration;
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
/// rusb/libusb implementation of `RtlSdrDevice`
|
||||
pub struct RtlSdrDevice {
|
||||
/// Device handle
|
||||
handle: DeviceHandle<Context>,
|
||||
device_desc: DeviceDescriptor,
|
||||
device: Device<Context>,
|
||||
}
|
||||
|
||||
impl RtlSdrDevice {
|
||||
/// List devices
|
||||
pub fn list() -> Result<()> {
|
||||
for device in DeviceList::new()?.iter() {
|
||||
let device_desc = device.device_descriptor()?;
|
||||
|
||||
println!(
|
||||
"Bus: {:03}, Device: {:03} VID: 0x{:04X}, PID: 0x{:04X}",
|
||||
device.bus_number(),
|
||||
device.address(),
|
||||
device_desc.vendor_id(),
|
||||
device_desc.product_id()
|
||||
);
|
||||
|
||||
let handle = device.open()?;
|
||||
println!("{}", device_info(&handle, &device_desc, " ", true)?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Open the USB device and return a wrapper
|
||||
pub fn open(vid: u16, pid: u16) -> Result<Self> {
|
||||
// Create a new libusb context
|
||||
let ctx = Context::new().map_err(|_| Error::new("Unable to create libusb context"))?;
|
||||
|
||||
for device in ctx.devices()?.iter() {
|
||||
let device_desc = match device.device_descriptor() {
|
||||
Ok(d) => d,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
|
||||
let handle = device.open()?;
|
||||
return Ok(Self {
|
||||
handle,
|
||||
device_desc,
|
||||
device,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(Error::new("No valid device found"))
|
||||
}
|
||||
|
||||
pub fn read(&mut self, transfer_type: TransferType) -> Result<()> {
|
||||
log::debug!(
|
||||
"Reading active configuration: {} ({:?})",
|
||||
self.handle.active_configuration()?,
|
||||
transfer_type
|
||||
);
|
||||
|
||||
log::debug!(
|
||||
"{}",
|
||||
device_info(&self.handle, &self.device_desc, "", false)?
|
||||
);
|
||||
|
||||
// Read endpoint
|
||||
match Endpoint::find_readable(&self.device, &self.device_desc, transfer_type) {
|
||||
Some(endpoint) => endpoint.read(&mut self.handle)?,
|
||||
None => log::warn!("No readable {:?} endpoint", transfer_type),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn device_info<T: UsbContext>(
|
||||
handle: &DeviceHandle<T>,
|
||||
device_desc: &DeviceDescriptor,
|
||||
offset: &str,
|
||||
full: bool,
|
||||
) -> Result<String> {
|
||||
let languages = handle.read_languages(TIMEOUT)?;
|
||||
let descriptor_type = device_desc.descriptor_type();
|
||||
let mut output = String::new();
|
||||
if full {
|
||||
output = format!("{}Device Descriptor ({})\n", offset, descriptor_type).to_string();
|
||||
}
|
||||
if !languages.is_empty() {
|
||||
for language in languages {
|
||||
let manufacturer = handle.read_manufacturer_string(language, device_desc, TIMEOUT)?;
|
||||
let product = handle.read_product_string(language, device_desc, TIMEOUT)?;
|
||||
let serial_number = handle.read_serial_number_string(language, device_desc, TIMEOUT)?;
|
||||
output.push_str(&format!(
|
||||
"{}{}Manufacturer: {}, Product: {}, Serial Number: {}",
|
||||
offset, offset, manufacturer, product, serial_number
|
||||
));
|
||||
|
||||
if full {
|
||||
let length = device_desc.length();
|
||||
let version = format!(
|
||||
" v{}.{}.{}",
|
||||
device_desc.usb_version().major(),
|
||||
device_desc.usb_version().minor(),
|
||||
device_desc.usb_version().sub_minor()
|
||||
);
|
||||
output.push_str(&format!(
|
||||
"\n{}{}Length: {}, USB:{}\n",
|
||||
offset, offset, length, version,
|
||||
));
|
||||
let class = device_desc.class_code();
|
||||
let sub_class = device_desc.sub_class_code();
|
||||
let protocol = device_desc.protocol_code();
|
||||
let max_packet_size = device_desc.max_packet_size();
|
||||
output.push_str(&format!(
|
||||
"{}{}Class: {:#04x}, Subclass: {:#04x}, Protocol: {:#04x}, Max Packet Size: {}",
|
||||
offset, offset, class, sub_class, protocol, max_packet_size
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Endpoint {
|
||||
config: u8,
|
||||
interface: u8,
|
||||
setting: u8,
|
||||
address: u8,
|
||||
transfer_type: TransferType,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub fn find_readable<T: UsbContext>(
|
||||
device: &Device<T>,
|
||||
device_desc: &DeviceDescriptor,
|
||||
transfer_type: TransferType,
|
||||
) -> Option<Self> {
|
||||
for n in 0..device_desc.num_configurations() {
|
||||
let config_desc = match device.config_descriptor(n) {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
for interface in config_desc.interfaces() {
|
||||
for interface_desc in interface.descriptors() {
|
||||
for endpoint_desc in interface_desc.endpoint_descriptors() {
|
||||
if endpoint_desc.direction() == Direction::In
|
||||
&& endpoint_desc.transfer_type() == transfer_type
|
||||
{
|
||||
return Some(Self {
|
||||
config: config_desc.number(),
|
||||
interface: interface_desc.interface_number(),
|
||||
setting: interface_desc.setting_number(),
|
||||
address: endpoint_desc.address(),
|
||||
transfer_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn read<T: UsbContext>(&self, handle: &mut DeviceHandle<T>) -> Result<()> {
|
||||
log::debug!("Reading from endpoint: {:?}", self);
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
{
|
||||
let running = running.clone();
|
||||
ctrlc::set_handler(move || {
|
||||
running.store(false, Ordering::SeqCst);
|
||||
})?;
|
||||
}
|
||||
|
||||
// Detach the kernel driver if applicable
|
||||
let has_kernel_driver = match handle.kernel_driver_active(self.interface) {
|
||||
Ok(true) => {
|
||||
log::debug!("Detaching active kernel driver");
|
||||
handle.detach_kernel_driver(self.interface).ok();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
self.configure_endpoint(handle)?;
|
||||
|
||||
let mut buffer = [0u8; 4096];
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let length = match self.transfer_type {
|
||||
TransferType::Interrupt => handle
|
||||
.read_interrupt(self.address, &mut buffer, TIMEOUT)
|
||||
.map_err(|err| {
|
||||
Error::new(format!("Unable to read interrupt from endpoint: {:?}", err))
|
||||
})?,
|
||||
TransferType::Bulk => handle
|
||||
.read_bulk(self.address, &mut buffer, TIMEOUT)
|
||||
.map_err(|err| Error::new(format!("Unable to read bulk from endpoint: {:?}", err)))?,
|
||||
_ => 0,
|
||||
};
|
||||
log::debug!("Received: {:?}", &buffer[..length]);
|
||||
}
|
||||
|
||||
// Attach the kernel driver if applicable
|
||||
if has_kernel_driver {
|
||||
log::debug!("Attaching active kernel driver");
|
||||
handle.attach_kernel_driver(self.interface).ok();
|
||||
}
|
||||
|
||||
log::debug!("Exiting USB read");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_endpoint<T: UsbContext>(&self, handle: &mut DeviceHandle<T>) -> Result<()> {
|
||||
log::debug!("Configuring endpoint: {:?}", self);
|
||||
|
||||
// Switch to ADS-B mode
|
||||
// let request_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Interface);
|
||||
// handle.write_control(
|
||||
// request_type,
|
||||
// 0x42,
|
||||
// 0x0002,
|
||||
// 0,
|
||||
// &[],
|
||||
// TIMEOUT,
|
||||
// )?;
|
||||
|
||||
handle
|
||||
.set_active_configuration(self.config)
|
||||
.map_err(|err| Error::new(format!("Failed to set active configuration: {:?}", err)))?;
|
||||
handle
|
||||
.claim_interface(self.interface)
|
||||
.map_err(|err| Error::new(format!("Failed to claim interface: {:?}", err)))?;
|
||||
handle
|
||||
.set_alternate_setting(self.interface, self.setting)
|
||||
.map_err(|err| Error::new(format!("Failed to set alternate setting: {:?}", err)))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
38
adsb/src/error.rs
Normal file
38
adsb/src/error.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::{fmt, result};
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
RusbError(rusb::Error),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new<S: Into<String>>(msg: S) -> Self {
|
||||
Error::Other(msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
|
||||
match self {
|
||||
Error::RusbError(err) => write!(f, "USB Error: {}", err),
|
||||
Error::Other(err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<rusb::Error> for Error {
|
||||
fn from(err: rusb::Error) -> Self {
|
||||
Error::RusbError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ctrlc::Error> for Error {
|
||||
fn from(err: ctrlc::Error) -> Self {
|
||||
Error::Other(err.to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::hex_to_bytes;
|
||||
use std::fmt::Display;
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use crate::error::{Result, Error};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ADSBFrame {
|
||||
@@ -23,10 +23,10 @@ impl ADSBFrame {
|
||||
/// [ DF:5 ][ CA:3 ][ ICAO:24 ][ ME:56 ][ PI:24 ]
|
||||
pub fn decode(frame: &[u8]) -> Result<ADSBFrame> {
|
||||
if frame.len() != 14 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("expected 14 bytes, received {}", frame.len()),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"expected 14 bytes, received {}",
|
||||
frame.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut raw_frame = "".to_string();
|
||||
@@ -37,13 +37,10 @@ impl ADSBFrame {
|
||||
// Decode the downlink format by discarding the lower 3 bits
|
||||
let downlink_format = &frame[0] >> 3;
|
||||
if downlink_format != 17 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Unsupported,
|
||||
format!(
|
||||
return Err(Error::new(format!(
|
||||
"downlink format {} is not currently supported",
|
||||
downlink_format
|
||||
),
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
// Decode the capability by masking off everything but the lower 3 bits
|
||||
@@ -70,10 +67,10 @@ impl ADSBFrame {
|
||||
|
||||
fn decode_icao(data: &[u8]) -> Result<String> {
|
||||
if data.len() != 3 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("ICAO must be 3 bytes, received {}", data.len()),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"ICAO must be 3 bytes, received {}",
|
||||
data.len()
|
||||
)));
|
||||
}
|
||||
let s = data
|
||||
.iter()
|
||||
@@ -84,10 +81,10 @@ impl ADSBFrame {
|
||||
|
||||
fn decode_parity(data: &[u8]) -> Result<u32> {
|
||||
if data.len() != 3 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("parity must be 3 bytes, received {}", data.len()),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"parity must be 3 bytes, received {}",
|
||||
data.len()
|
||||
)));
|
||||
}
|
||||
let p = ((data[0] as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
|
||||
Ok(p)
|
||||
@@ -138,10 +135,7 @@ impl TryFrom<u8> for Capability {
|
||||
6 => Capability::Level2Either,
|
||||
7 => Capability::DownlinkRequestOrFlightStatus,
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("invalid CA value: {}", value),
|
||||
));
|
||||
return Err(Error::new(format!("invalid CA value: {}", value)));
|
||||
}
|
||||
};
|
||||
Ok(capability)
|
||||
@@ -175,10 +169,10 @@ pub enum ADSBMessage {
|
||||
impl ADSBMessage {
|
||||
pub fn decode(data: &[u8]) -> Result<ADSBMessage> {
|
||||
if data.len() != 7 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("ME field must be 7 bytes, received {}", data.len()),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"ME field must be 7 bytes, received {}",
|
||||
data.len()
|
||||
)));
|
||||
}
|
||||
// First 5 bits is the type code
|
||||
let type_code = data[0] >> 3;
|
||||
@@ -195,10 +189,10 @@ impl ADSBMessage {
|
||||
29 => ADSBMessage::TargetState(TargetState::decode(data)?),
|
||||
31 => ADSBMessage::AircraftOperationStatus(AircraftOperationStatus::decode(data)?),
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("unsupported ADS-B type_code {}", type_code),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"unsupported ADS-B type_code {}",
|
||||
type_code
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use crate::error::Error;
|
||||
|
||||
pub mod adsb_frame;
|
||||
pub mod device;
|
||||
|
||||
pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>> {
|
||||
pub fn hex_to_bytes(s: &str) -> crate::error::Result<Vec<u8>> {
|
||||
let bytes = s.as_bytes();
|
||||
if bytes.len() % 2 != 0 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("hex string must have even length, got {}", bytes.len()),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"hex string must have even length, got {}",
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(bytes.len() / 2);
|
||||
@@ -17,19 +14,19 @@ pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>> {
|
||||
let hi = match hex_val(chunk[0]) {
|
||||
Some(hi) => hi,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("invalid hex char '{}'", chunk[0] as char),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"invalid hex char '{}'",
|
||||
chunk[0] as char
|
||||
)));
|
||||
}
|
||||
};
|
||||
let lo = match hex_val(chunk[1]) {
|
||||
Some(lo) => lo,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!("invalid hex char '{}'", chunk[1] as char),
|
||||
));
|
||||
return Err(Error::new(format!(
|
||||
"invalid hex char '{}'",
|
||||
chunk[1] as char
|
||||
)));
|
||||
}
|
||||
};
|
||||
out.push((hi << 4) | lo);
|
||||
68
adsb/src/main.rs
Normal file
68
adsb/src/main.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
mod device;
|
||||
mod error;
|
||||
mod frame;
|
||||
mod hex;
|
||||
|
||||
use error::Result;
|
||||
use crate::device::RtlSdrDevice;
|
||||
use clap::Parser;
|
||||
use rusb::TransferType;
|
||||
use crate::frame::ADSBFrame;
|
||||
use crate::hex::hex_to_bytes;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about = "An ADS-B Receiver")]
|
||||
struct ReceiverArgs {
|
||||
/// Hex-string to decode
|
||||
#[arg(short = 'd', long)]
|
||||
decode: Option<String>,
|
||||
|
||||
/// Connect to the USB device
|
||||
#[arg(short = 'c', long, action)]
|
||||
connect: bool,
|
||||
|
||||
/// List USB devices
|
||||
#[arg(short = 'l', long, action)]
|
||||
list: bool,
|
||||
|
||||
/// Enable debug logging
|
||||
#[arg(short = 'D', long, action)]
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = ReceiverArgs::parse();
|
||||
|
||||
let default_filter = if args.debug {
|
||||
"warn,adsb=debug"
|
||||
} else {
|
||||
"warn,adsb=info"
|
||||
};
|
||||
|
||||
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", default_filter));
|
||||
|
||||
// Handle connection
|
||||
if args.connect {
|
||||
log::info!("Connecting to device");
|
||||
let mut device = RtlSdrDevice::open(0x0BDA, 0x2832)?;
|
||||
device.read(TransferType::Bulk)
|
||||
}
|
||||
// List devices
|
||||
else if args.list {
|
||||
RtlSdrDevice::list()
|
||||
}
|
||||
// Handle decode mode
|
||||
else if let Some(mut hex_string) = args.decode {
|
||||
if let Some(stripped) = hex_string.strip_prefix("0x") {
|
||||
hex_string = stripped.to_string();
|
||||
}
|
||||
let buf = hex_to_bytes(&hex_string)?;
|
||||
let frame = ADSBFrame::decode(&buf)?;
|
||||
|
||||
log::info!("{}", frame);
|
||||
Ok(())
|
||||
} else {
|
||||
log::warn!("No connection specified");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user