Updated adsb stuff
This commit is contained in:
11
adsb/squawk/Cargo.toml
Normal file
11
adsb/squawk/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[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"
|
||||
79
adsb/squawk/src/main.rs
Normal file
79
adsb/squawk/src/main.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
107
adsb/squawk/src/rusb_device.rs
Normal file
107
adsb/squawk/src/rusb_device.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
161
adsb/squawk/src/tcp_device.rs
Normal file
161
adsb/squawk/src/tcp_device.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user