From dc1a6de6c6de85bb90a1395a1cc91b4fc9d6d9c3 Mon Sep 17 00:00:00 2001 From: Ben Sherriff Date: Sat, 3 May 2025 16:01:29 -0400 Subject: [PATCH] Working on adsb --- Makefile | 5 +- adsb/src/constants.rs | 45 +++++ adsb/src/device.rs | 409 +++++++++++++++++++++++++++--------------- adsb/src/error.rs | 6 + adsb/src/main.rs | 72 +++++--- api/src/main.rs | 1 - 6 files changed, 369 insertions(+), 169 deletions(-) create mode 100644 adsb/src/constants.rs diff --git a/Makefile b/Makefile index b4af527..07dec26 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,7 @@ format-adsb: ## Format code @cd adsb && cargo fmt build-adsb: ## Build the ADS-B project - @cd adsb && cargo build - -run-adsb: ## Run the ADS-B Receiver - @cd adsb && cargo run -- -c -D + @cd adsb && cargo build --release ################# # UI Commands # diff --git a/adsb/src/constants.rs b/adsb/src/constants.rs new file mode 100644 index 0000000..c23623e --- /dev/null +++ b/adsb/src/constants.rs @@ -0,0 +1,45 @@ +use std::fmt::Display; +use std::time::Duration; + +#[derive(Debug)] +pub struct DeviceInfo { + /// Vendor ID + pub vid: u16, + /// Product ID + pub pid: u16, +} + +impl Display for DeviceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "VID: 0x{:04X} PID: 0x{:04X}", self.vid, self.pid) + } +} + +// Devices +pub const DEVICE_RTL2832U: DeviceInfo = DeviceInfo { + vid: 0x0BDA, + pid: 0x2832, +}; + +// Timeout +pub const TIMEOUT: Duration = Duration::from_secs(1); + +// pub const DEFAULT_BUFFER_LENGTH: usize = 4096; +pub const DEFAULT_BUFFER_LENGTH: usize = 64; + +// Request Types +pub const REQ_CTRL_OUT: u8 = + rusb::constants::LIBUSB_ENDPOINT_OUT | rusb::constants::LIBUSB_REQUEST_TYPE_VENDOR; + +// Blocks +pub const BLOCK_USB: u16 = 1; + +// USB +pub const USB_EPA_CTL: u16 = 0x2148; +pub const USB_SYSCTL: u16 = 0x2000; + +/// ADS-B downlink frequency (1090 MHz) +pub const ADSB_FREQUENCY_HZ: u32 = 1_090_000_000; + +/// RTL-SDR sample rate in samples/second. +pub const SAMPLE_RATE_HZ: u32 = 2_048_000; diff --git a/adsb/src/device.rs b/adsb/src/device.rs index e1cf176..8370e67 100644 --- a/adsb/src/device.rs +++ b/adsb/src/device.rs @@ -1,23 +1,27 @@ +use std::borrow::Cow; +use std::fmt::Display; 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); +use crate::constants::{ + ADSB_FREQUENCY_HZ, BLOCK_USB, DEFAULT_BUFFER_LENGTH, REQ_CTRL_OUT, SAMPLE_RATE_HZ, TIMEOUT, + USB_EPA_CTL, USB_SYSCTL, +}; /// rusb/libusb implementation of `RtlSdrDevice` pub struct RtlSdrDevice { /// Device handle handle: DeviceHandle, - device_desc: DeviceDescriptor, - device: Device, + endpoint: Endpoint, + frequency: u32, + rate: u32, } impl RtlSdrDevice { - /// Display dongle information + /// Display RTL SDR information pub fn info(vid: u16, pid: u16) { let device_list = match DeviceList::new() { Ok(d) => d, @@ -29,7 +33,7 @@ impl RtlSdrDevice { for device in device_list.iter() { match device.device_descriptor() { Ok(device_desc) => { - if vid != device_desc.vendor_id() && pid != device_desc.product_id() { + if vid != device_desc.vendor_id() || pid != device_desc.product_id() { continue; } println!( @@ -43,7 +47,7 @@ impl RtlSdrDevice { match device.open() { Ok(handle) => { println!("{}", device_info(&handle, &device_desc, " ", true)); - }, + } Err(err) => { eprintln!(" Unable to open device: {:?}", err); continue; @@ -58,7 +62,7 @@ impl RtlSdrDevice { } } - /// Open the USB device and return a wrapper + /// Open the RTL SDR device and return a wrapper pub fn open(vid: u16, pid: u16) -> Result { // Create a new libusb context let ctx = Context::new().map_err(|_| Error::new("Unable to create libusb context"))?; @@ -71,33 +75,226 @@ impl RtlSdrDevice { if device_desc.vendor_id() == vid && device_desc.product_id() == pid { let handle = device.open()?; - return Ok(Self { - handle, - device_desc, - device, - }); + + log::debug!("{}", device_info(&handle, &device_desc, "", false)); + + // Find the endpoint + let endpoint = match Endpoint::find(&device_desc, &device, TransferType::Bulk) { + Some(e) => e, + None => return Err(Error::new("Unable to find endpoint on device")), + }; + log::debug!("Found readable endpoint: {:?}", endpoint.to_string()); + + let mut sdr = Self::new(handle, endpoint); + + sdr.initialize()?; + + return Ok(sdr); } } Err(Error::new("No valid device found")) } - pub fn read(&mut self, transfer_type: TransferType) -> Result<()> { + /// Close the RTL SDR device + pub fn close(&self) -> Result<()> { + log::debug!("Closing device..."); + self.attach_kernel_driver(self.endpoint.interface); + self.handle.release_interface(self.endpoint.interface)?; + Ok(()) + } + + /// Process the USB data + pub fn process(&mut self, running: Arc) -> Result<()> { log::debug!( - "Reading active configuration: {} ({:?})", - self.handle.active_configuration()?, - transfer_type + "Reading from active configuration: {}", + self.handle.active_configuration()? ); - 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), + let mut buffer = [0u8; DEFAULT_BUFFER_LENGTH]; + while running.load(Ordering::SeqCst) { + let s = self.read(&mut buffer)?; + log::debug!("Read: {}", s); } + self.close() + } + + fn new(handle: DeviceHandle, endpoint: Endpoint) -> Self { + Self { + handle, + endpoint, + frequency: 0, + rate: 0, + } + } + + fn read(&self, buffer: &mut [u8; DEFAULT_BUFFER_LENGTH]) -> Result { + let length = match self.endpoint.transfer_type { + TransferType::Interrupt => self + .handle + .read_interrupt(self.endpoint.address, buffer, TIMEOUT) + .map_err(|err| Error::new(format!("Unable to read interrupt from endpoint: {:?}", err)))?, + TransferType::Bulk => self + .handle + .read_bulk(self.endpoint.address, buffer, TIMEOUT) + .map_err(|err| Error::new(format!("Unable to read bulk from endpoint: {:?}", err)))?, + _ => 0, + }; + log::trace!("Received {} bytes", length); + let s = match String::from_utf8_lossy(&buffer[..length]) { + Cow::Borrowed(s) => s.to_string(), + Cow::Owned(s) => s, + }; + Ok(s.to_string()) + } + + fn initialize(&mut self) -> Result<()> { + // Configure the device for the endpoint + self.set_active_configuration(self.endpoint.config)?; + self.claim_interface(self.endpoint.interface)?; + self.set_alternate_setting(self.endpoint.interface, self.endpoint.setting)?; + self.detach_kernel_driver(self.endpoint.interface); + + self.test_write()?; + + // Reset the internal USB buffer + self.reset_buffer()?; + + // Set the center-frequency in Hz + self.set_center_frequency(ADSB_FREQUENCY_HZ)?; + + // Set the sample rate + self.set_sample_rate(SAMPLE_RATE_HZ)?; + Ok(()) } + + fn set_active_configuration(&self, configuration: u8) -> Result<()> { + self + .handle + .set_active_configuration(configuration) + .map_err(|err| Error::new(format!("Failed to set active configuration: {:?}", err))) + } + + fn claim_interface(&self, interface: u8) -> Result<()> { + self + .handle + .claim_interface(interface) + .map_err(|err| Error::new(format!("Failed to claim interface: {:?}", err))) + } + + fn set_alternate_setting(&self, interface: u8, setting: u8) -> Result<()> { + self + .handle + .set_alternate_setting(interface, setting) + .map_err(|err| Error::new(format!("Failed to set alternate setting: {:?}", err))) + } + + fn test_write(&self) -> Result<()> { + log::trace!("Testing write to device..."); + let length = ctrl_write_register(&self.handle, BLOCK_USB, USB_SYSCTL, 0x89, 1)?; + if length == 0 { + log::info!("Resetting device"); + self + .handle + .reset() + .map_err(|err| Error::new(format!("Failed to reset device: {:?}", err)))?; + } else { + log::trace!("Test write was successful"); + } + Ok(()) + } + + fn reset_buffer(&self) -> Result<()> { + log::trace!("Resetting buffer..."); + ctrl_write_register(&self.handle, BLOCK_USB, USB_EPA_CTL, 0x1002, 2) + .map_err(|err| Error::new(format!("Failed to reset the internal buffer: {:?}", err)))?; + ctrl_write_register(&self.handle, BLOCK_USB, USB_EPA_CTL, 0x0000, 2) + .map_err(|err| Error::new(format!("Failed to reset the internal buffer: {:?}", err)))?; + Ok(()) + } + + fn set_center_frequency(&mut self, frequency: u32) -> Result<()> { + log::trace!("Setting center_frequency to {}Hz", frequency); + self.frequency = frequency; + Ok(()) + } + + fn set_sample_rate(&mut self, rate: u32) -> Result<()> { + log::trace!("Setting sample_rate to {}Hz", rate); + self.rate = rate; + Ok(()) + } + + fn detach_kernel_driver(&self, interface: u8) { + // Detach the kernel driver if applicable + if let Ok(true) = self.handle.kernel_driver_active(interface) { + log::trace!("Detaching active kernel driver"); + self.handle.detach_kernel_driver(interface).ok(); + } + } + + fn attach_kernel_driver(&self, interface: u8) { + // Attach the kernel driver if applicable + if let Ok(true) = self.handle.kernel_driver_active(interface) { + log::trace!("Attaching active kernel driver"); + self.handle.attach_kernel_driver(interface).ok(); + } + } +} + +#[derive(Debug)] +pub struct Endpoint { + config: u8, + interface: u8, + setting: u8, + address: u8, + transfer_type: TransferType, +} + +impl Display for Endpoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Config: {}, Interface: {}, Setting: {}, Address: 0x{:04X}, Transfer Type: {:?}", + self.config, self.interface, self.setting, self.address, self.transfer_type + ) + } +} + +impl Endpoint { + fn find( + device_desc: &DeviceDescriptor, + device: &Device, + transfer_type: TransferType, + ) -> Option { + 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(Endpoint { + config: config_desc.number(), + interface: interface_desc.interface_number(), + setting: interface_desc.setting_number(), + address: endpoint_desc.address(), + transfer_type, + }); + } + } + } + } + } + None + } } fn device_info( @@ -110,20 +307,23 @@ fn device_info( Ok(l) => l, Err(err) => { return format!("{} Unable to get languages: {:?}", offset, err); - }, + } }; let descriptor_type = device_desc.descriptor_type(); let mut output = String::new(); if full { - output = format!("{}Device Descriptor ({})\n", offset, descriptor_type).to_string(); + output = format!("{}Device Descriptor ({})\n", offset, descriptor_type); } if !languages.is_empty() { for language in languages { - let manufacturer = handle.read_manufacturer_string(language, device_desc, TIMEOUT) + let manufacturer = handle + .read_manufacturer_string(language, device_desc, TIMEOUT) .unwrap_or_else(|err| err.to_string()); - let product = handle.read_product_string(language, device_desc, TIMEOUT) + let product = handle + .read_product_string(language, device_desc, TIMEOUT) .unwrap_or_else(|err| err.to_string()); - let serial_number = handle.read_serial_number_string(language, device_desc, TIMEOUT) + let serial_number = handle + .read_serial_number_string(language, device_desc, TIMEOUT) .unwrap_or_else(|err| err.to_string()); output.push_str(&format!( "{}{}Manufacturer: {}, Product: {}, Serial Number: {}", @@ -147,7 +347,7 @@ fn device_info( 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: {}\n", + "{}{}Class: {:#04x}, Subclass: {:#04x}, Protocol: {:#04x}, Max Packet Size: {}", offset, offset, class, sub_class, protocol, max_packet_size )) } @@ -156,119 +356,46 @@ fn device_info( output } -#[derive(Debug)] -struct Endpoint { - config: u8, - interface: u8, - setting: u8, - address: u8, - transfer_type: TransferType, +fn ctrl_write_register( + handle: &DeviceHandle, + block: u16, + address: u16, + value: u16, + length: usize, +) -> rusb::Result { + assert!(length == 1 || length == 2); + + let data: [u8; 2] = value.to_be_bytes(); + let buffer = if length == 1 { &data[1..2] } else { &data }; + let index = (block << 8) | 0x10; + log::trace!( + "Received block {}, address 0x{:04X}, value 0x{:04X}, length {} \ + - writing control register: {} 0x{:04X} 0x{:04X} {:?}", + block, + address, + value, + length, + REQ_CTRL_OUT, + address, + index, + buffer + ); + + handle.write_control(REQ_CTRL_OUT, 0x00, address, index, buffer, TIMEOUT) } -impl Endpoint { - pub fn find_readable( - device: &Device, - device_desc: &DeviceDescriptor, - transfer_type: TransferType, - ) -> Option { - for n in 0..device_desc.num_configurations() { - let config_desc = match device.config_descriptor(n) { - Ok(c) => c, - Err(_) => continue, - }; +fn demod_ctrl_write_register( + handle: &DeviceHandle, + page: u16, + address: u16, + value: u16, + length: usize, +) -> rusb::Result { + assert!(length == 1 || length == 2); - 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(&self, handle: &mut DeviceHandle) -> 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(&self, handle: &mut DeviceHandle) -> 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(()) - } + let data: [u8; 2] = value.to_be_bytes(); + let buffer = if length == 1 { &data[1..2] } else { &data }; + let index = 0x10 | page; + let address = (address << 8) | 0x20; + handle.write_control(REQ_CTRL_OUT, 0x00, address, index, buffer, TIMEOUT) } diff --git a/adsb/src/error.rs b/adsb/src/error.rs index e60ec11..4347969 100644 --- a/adsb/src/error.rs +++ b/adsb/src/error.rs @@ -36,3 +36,9 @@ impl From for Error { Error::Other(err.to_string()) } } + +impl From for Error { + fn from(err: std::str::Utf8Error) -> Self { + Error::Other(err.to_string()) + } +} diff --git a/adsb/src/main.rs b/adsb/src/main.rs index 3e3000d..d90712a 100644 --- a/adsb/src/main.rs +++ b/adsb/src/main.rs @@ -1,12 +1,14 @@ +mod constants; mod device; mod error; mod frame; mod hex; -use error::Result; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use crate::device::RtlSdrDevice; use clap::Parser; -use rusb::TransferType; +use crate::constants::DEVICE_RTL2832U; use crate::frame::ADSBFrame; use crate::hex::hex_to_bytes; @@ -26,47 +28,71 @@ struct ReceiverArgs { info: bool, /// Enable debug logging - #[arg(short = 'D', long, action)] - debug: bool, + #[arg(short = 'D', long, action = clap::ArgAction::Count)] + debug: u8, } -fn main() -> Result<()> { +fn main() { let args = ReceiverArgs::parse(); - let default_filter = if args.debug { - "warn,adsb=debug" - } else { - "warn,adsb=info" + let default_filter = match args.debug { + 0 => "warn,adsb=info", // no -D + 1 => "warn,adsb=debug", // -D + _ => "trace,adsb=trace", // -DD or more }; env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", default_filter)); - let vid = 0x0BDA; - let pid = 0x2832; + let device_info = DEVICE_RTL2832U; // Handle connection if args.connect { - log::info!("Connecting to device"); - let mut device = RtlSdrDevice::open(vid, pid)?; - device.read(TransferType::Bulk) + log::info!("Connecting to {:?}", device_info); + let mut device = match RtlSdrDevice::open(device_info.vid, device_info.pid) { + Ok(d) => d, + Err(err) => { + log::error!("Unable to open RTL SDR device: {:?}", err); + return; + } + }; + log::debug!("Connected to {:?}", device_info.to_string()); + + let running = Arc::new(AtomicBool::new(true)); + if let Err(err) = ctrlc::set_handler({ + let running = running.clone(); + move || running.store(false, Ordering::SeqCst) + }) { + log::error!("Error setting Ctrl-C handler: {}", err); + running.store(false, Ordering::SeqCst); + }; + + if let Err(err) = device.process(running) { + log::error!("Failed to read from device: {}", err); + if let Err(err) = device.close() { + log::error!("Failed to close device: {}", err); + }; + }; } // Display dongle info else if args.info { - RtlSdrDevice::info(vid, pid); - Ok(()) + RtlSdrDevice::info(device_info.vid, device_info.pid); } // 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(()) + let buffer = match hex_to_bytes(&hex_string) { + Ok(buffer) => buffer, + Err(err) => { + eprintln!("Unable to convert hex to bytes: {:?}", err); + return; + } + }; + if let Ok(frame) = ADSBFrame::decode(&buffer) { + println!("{:?}", frame); + }; } else { - log::warn!("No connection specified"); - Ok(()) + eprintln!("No connection specified"); } } diff --git a/api/src/main.rs b/api/src/main.rs index c7d74f5..ba5ce96 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -4,7 +4,6 @@ use actix_cors::Cors; use actix_web::{App, HttpServer, middleware::Logger, web}; use dotenv::from_filename; use reqwest::Certificate; -use uuid::Uuid; use crate::account::hash; use crate::users::{User, ADMIN_ROLE};