Formatting
This commit is contained in:
5
Makefile
5
Makefile
@@ -12,7 +12,7 @@ help: ## This info
|
|||||||
@cat Makefile | grep -E '^[a-zA-Z\/_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@cat Makefile | grep -E '^[a-zA-Z\/_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
@echo
|
@echo
|
||||||
|
|
||||||
format: format-api format-ui ## Format code
|
format: format-api format-ui format-adsb ## Format code
|
||||||
|
|
||||||
psql: ## Connect to the PSQL DB
|
psql: ## Connect to the PSQL DB
|
||||||
@docker exec -it aviation-postgres psql -U ${POSTGRES_USER} -P pager=off
|
@docker exec -it aviation-postgres psql -U ${POSTGRES_USER} -P pager=off
|
||||||
@@ -34,6 +34,9 @@ run-api: ## Run the API project
|
|||||||
# ADS-B Commands #
|
# ADS-B Commands #
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
format-adsb: ## Format code
|
||||||
|
@cd adsb && cargo fmt
|
||||||
|
|
||||||
build-adsb: ## Build the ADS-B project
|
build-adsb: ## Build the ADS-B project
|
||||||
@cd adsb && cargo build
|
@cd adsb && cargo build
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::hex_to_bytes;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::{Error, ErrorKind, Result};
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
|
||||||
@@ -24,7 +25,8 @@ impl ADSBFrame {
|
|||||||
if frame.len() != 14 {
|
if frame.len() != 14 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
format!("expected 14 bytes, received {}", frame.len())));
|
format!("expected 14 bytes, received {}", frame.len()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut raw_frame = "".to_string();
|
let mut raw_frame = "".to_string();
|
||||||
@@ -37,7 +39,10 @@ impl ADSBFrame {
|
|||||||
if downlink_format != 17 {
|
if downlink_format != 17 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Unsupported,
|
ErrorKind::Unsupported,
|
||||||
format!("downlink format {} is not currently supported", downlink_format)
|
format!(
|
||||||
|
"downlink format {} is not currently supported",
|
||||||
|
downlink_format
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,17 +60,23 @@ impl ADSBFrame {
|
|||||||
capability,
|
capability,
|
||||||
icao,
|
icao,
|
||||||
message,
|
message,
|
||||||
parity
|
parity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn encode(&self) -> Result<Vec<u8>> {
|
||||||
|
Ok(hex_to_bytes(&self.raw_frame)?)
|
||||||
|
}
|
||||||
|
|
||||||
fn decode_icao(data: &[u8]) -> Result<String> {
|
fn decode_icao(data: &[u8]) -> Result<String> {
|
||||||
if data.len() != 3 {
|
if data.len() != 3 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
format!("ICAO must be 3 bytes, received {}", data.len())));
|
format!("ICAO must be 3 bytes, received {}", data.len()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let s = data.iter()
|
let s = data
|
||||||
|
.iter()
|
||||||
.map(|b| format!("{:02X}", b))
|
.map(|b| format!("{:02X}", b))
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
Ok(s)
|
Ok(s)
|
||||||
@@ -75,29 +86,26 @@ impl ADSBFrame {
|
|||||||
if data.len() != 3 {
|
if data.len() != 3 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
format!("parity must be 3 bytes, received {}", data.len())));
|
format!("parity must be 3 bytes, received {}", data.len()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let p = ((data[0] as u32) << 16)
|
let p = ((data[0] as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
|
||||||
| ((data[1] as u32) << 8)
|
|
||||||
| (data[2] as u32);
|
|
||||||
Ok(p)
|
Ok(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ADSBFrame {
|
impl Display for ADSBFrame {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "Frame: {}\
|
write!(
|
||||||
|
f,
|
||||||
|
"Frame: {}\
|
||||||
\nDF: {}\
|
\nDF: {}\
|
||||||
\nCA: {:?}\
|
\nCA: {:?}\
|
||||||
\nICAO: {}\
|
\nICAO: {}\
|
||||||
\nME: {:?}\
|
\nME: {:?}\
|
||||||
\nPI: {}",
|
\nPI: {}",
|
||||||
self.raw_frame,
|
self.raw_frame, self.downlink_format, &self.capability, self.icao, &self.message, self.parity
|
||||||
self.downlink_format,
|
)
|
||||||
&self.capability,
|
|
||||||
self.icao,
|
|
||||||
&self.message,
|
|
||||||
self.parity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +184,9 @@ impl ADSBMessage {
|
|||||||
// First 5 bits is the type code
|
// First 5 bits is the type code
|
||||||
let type_code = data[0] >> 3;
|
let type_code = data[0] >> 3;
|
||||||
let message = match type_code {
|
let message = match type_code {
|
||||||
1..=4 => ADSBMessage::AircraftIdentification(AircraftIdentification::decode(type_code, data)?),
|
1..=4 => {
|
||||||
|
ADSBMessage::AircraftIdentification(AircraftIdentification::decode(type_code, data)?)
|
||||||
|
}
|
||||||
5..=8 => ADSBMessage::SurfacePosition(SurfacePosition::decode(data)?),
|
5..=8 => ADSBMessage::SurfacePosition(SurfacePosition::decode(data)?),
|
||||||
9..=18 => ADSBMessage::AirbornePositionBaro(AirbornePositionBaro::decode(data)?),
|
9..=18 => ADSBMessage::AirbornePositionBaro(AirbornePositionBaro::decode(data)?),
|
||||||
19 => ADSBMessage::AirborneVelocities(AirborneVelocities::decode(data)?),
|
19 => ADSBMessage::AirborneVelocities(AirborneVelocities::decode(data)?),
|
||||||
@@ -185,10 +195,12 @@ impl ADSBMessage {
|
|||||||
28 => ADSBMessage::AircraftStatus(AircraftStatus::decode(data)?),
|
28 => ADSBMessage::AircraftStatus(AircraftStatus::decode(data)?),
|
||||||
29 => ADSBMessage::TargetState(TargetState::decode(data)?),
|
29 => ADSBMessage::TargetState(TargetState::decode(data)?),
|
||||||
31 => ADSBMessage::AircraftOperationStatus(AircraftOperationStatus::decode(data)?),
|
31 => ADSBMessage::AircraftOperationStatus(AircraftOperationStatus::decode(data)?),
|
||||||
_ => 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),
|
||||||
))
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(message)
|
Ok(message)
|
||||||
@@ -289,78 +301,64 @@ impl WakeVortexCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SurfacePosition {
|
pub struct SurfacePosition {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SurfacePosition {
|
impl SurfacePosition {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AirbornePositionBaro {
|
pub struct AirbornePositionBaro {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AirbornePositionBaro {
|
impl AirbornePositionBaro {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AirborneVelocities {
|
pub struct AirborneVelocities {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AirborneVelocities {
|
impl AirborneVelocities {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AirbornePositionGNSS {
|
pub struct AirbornePositionGNSS {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AirbornePositionGNSS {
|
impl AirbornePositionGNSS {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AircraftStatus {
|
pub struct AircraftStatus {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AircraftStatus {
|
impl AircraftStatus {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct TargetState {
|
pub struct TargetState {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TargetState {
|
impl TargetState {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AircraftOperationStatus {
|
pub struct AircraftOperationStatus {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AircraftOperationStatus {
|
impl AircraftOperationStatus {
|
||||||
pub fn decode(data: &[u8]) -> Result<Self> {
|
pub fn decode(_data: &[u8]) -> Result<Self> {
|
||||||
Ok(Self {})
|
Ok(Self {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,8 +370,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_decode_df_17_aircraft_information() {
|
fn test_decode_df_17_aircraft_information() {
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71,
|
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x1C, 0x32, 0xCE, 0x05, 0x76,
|
||||||
0xC3, 0x1C, 0x32, 0xCE, 0x05, 0x76,
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -391,8 +388,7 @@ mod tests {
|
|||||||
assert_eq!(frame.parity, 13501814);
|
assert_eq!(frame.parity, 13501814);
|
||||||
|
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71,
|
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98,
|
||||||
0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -410,8 +406,7 @@ mod tests {
|
|||||||
assert_eq!(frame.parity, 5726360);
|
assert_eq!(frame.parity, 5726360);
|
||||||
|
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x7C, 0x71, 0x81, 0x21, 0x5D, 0x01, 0xA0,
|
0x8D, 0x7C, 0x71, 0x81, 0x21, 0x5D, 0x01, 0xA0, 0x82, 0x08, 0x20, 0x4D, 0x8B, 0xF1,
|
||||||
0x82, 0x08, 0x20, 0x4D, 0x8B, 0xF1
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -429,8 +424,7 @@ mod tests {
|
|||||||
assert_eq!(frame.parity, 5082097);
|
assert_eq!(frame.parity, 5082097);
|
||||||
|
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x7C, 0x77, 0x45, 0x22, 0x61, 0x51, 0xA0,
|
0x8D, 0x7C, 0x77, 0x45, 0x22, 0x61, 0x51, 0xA0, 0x82, 0x08, 0x20, 0x5C, 0xE9, 0xC2,
|
||||||
0x82, 0x08, 0x20, 0x5C, 0xE9, 0xC2
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -448,8 +442,7 @@ mod tests {
|
|||||||
assert_eq!(frame.parity, 6089154);
|
assert_eq!(frame.parity, 6089154);
|
||||||
|
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x7C, 0x80, 0xAD, 0x23, 0x58, 0xF6, 0xB1,
|
0x8D, 0x7C, 0x80, 0xAD, 0x23, 0x58, 0xF6, 0xB1, 0xE3, 0x5C, 0x60, 0xFF, 0x19, 0x25,
|
||||||
0xE3, 0x5C, 0x60, 0xFF, 0x19, 0x25
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -467,8 +460,7 @@ mod tests {
|
|||||||
assert_eq!(frame.parity, 16718117);
|
assert_eq!(frame.parity, 16718117);
|
||||||
|
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x7C, 0x14, 0x65, 0x25, 0x44, 0x60, 0x74,
|
0x8D, 0x7C, 0x14, 0x65, 0x25, 0x44, 0x60, 0x74, 0xDF, 0x58, 0x20, 0x73, 0x8E, 0x90,
|
||||||
0xDF, 0x58, 0x20, 0x73, 0x8E, 0x90
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
assert_eq!(frame.downlink_format, 17);
|
assert_eq!(frame.downlink_format, 17);
|
||||||
@@ -489,8 +481,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_decode_df_17_operation_status() {
|
fn test_decode_df_17_operation_status() {
|
||||||
let input = [
|
let input = [
|
||||||
0x8D, 0x89, 0x65, 0xD2, 0xF8, 0x21, 0x00, 0x02,
|
0x8D, 0x89, 0x65, 0xD2, 0xF8, 0x21, 0x00, 0x02, 0x00, 0x49, 0xB8, 0x94, 0xA4, 0x5F,
|
||||||
0x00, 0x49, 0xB8, 0x94, 0xA4, 0x5F,
|
|
||||||
];
|
];
|
||||||
let frame = ADSBFrame::decode(&input).unwrap();
|
let frame = ADSBFrame::decode(&input).unwrap();
|
||||||
dbg!(frame);
|
dbg!(frame);
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::io::Result;
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
|
||||||
|
pub mod adsb;
|
||||||
|
|
||||||
pub trait RtlDevice {
|
pub trait RtlDevice {
|
||||||
/// Send a control message to the device
|
/// Send a control message to the device
|
||||||
@@ -26,10 +28,7 @@ pub fn run<S: RtlDevice>(device: &mut S) -> Result<()> {
|
|||||||
device.control_send(0x04, &[1])?;
|
device.control_send(0x04, &[1])?;
|
||||||
|
|
||||||
// Precompute the preamble pattern in “half‐bit” units (16 samples)
|
// Precompute the preamble pattern in “half‐bit” units (16 samples)
|
||||||
let preamble_halfbit_pattern = [
|
let preamble_halfbit_pattern = [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0];
|
||||||
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
|
// Create a big buffer to hold raw I/Q bytes
|
||||||
let mut iq_buffer = [0u8; 16_384];
|
let mut iq_buffer = [0u8; 16_384];
|
||||||
@@ -50,11 +49,7 @@ pub fn run<S: RtlDevice>(device: &mut S) -> Result<()> {
|
|||||||
for pair in raw.chunks_exact(2) {
|
for pair in raw.chunks_exact(2) {
|
||||||
let i_sample = pair[0] as u16;
|
let i_sample = pair[0] as u16;
|
||||||
// Threshold at 200
|
// Threshold at 200
|
||||||
halfbit_samples.push(if i_sample > 200 {
|
halfbit_samples.push(if i_sample > 200 { 1 } else { 0 });
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan for the 16-sample preamble
|
// Scan for the 16-sample preamble
|
||||||
@@ -68,7 +63,7 @@ pub fn run<S: RtlDevice>(device: &mut S) -> Result<()> {
|
|||||||
|
|
||||||
let data_start = match data_start_index {
|
let data_start = match data_start_index {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => continue // No preamble found in this chunk
|
None => continue, // No preamble found in this chunk
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect 112 ADS-B bits, each manchester-encoded into 2 half-bits
|
// Collect 112 ADS-B bits, each manchester-encoded into 2 half-bits
|
||||||
@@ -117,3 +112,46 @@ pub fn run<S: RtlDevice>(device: &mut S) -> Result<()> {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hex_to_bytes(s: &str) -> 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()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = Vec::with_capacity(bytes.len() / 2);
|
||||||
|
for chunk in bytes.chunks(2) {
|
||||||
|
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),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
out.push((hi << 4) | lo);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_val(b: u8) -> Option<u8> {
|
||||||
|
match b {
|
||||||
|
b'0'..=b'9' => Some(b - b'0'),
|
||||||
|
b'a'..=b'f' => Some(b - b'a' + 10),
|
||||||
|
b'A'..=b'F' => Some(b - b'A' + 10),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
mod tcp_rtl;
|
|
||||||
mod rusb_rtl;
|
mod rusb_rtl;
|
||||||
mod adsb;
|
mod tcp_rtl;
|
||||||
|
|
||||||
use std::io::{Error, ErrorKind, Result};
|
|
||||||
use clap::Parser;
|
|
||||||
use adsb_lib::run;
|
|
||||||
use crate::adsb::ADSBFrame;
|
|
||||||
use crate::rusb_rtl::RusbRtl;
|
use crate::rusb_rtl::RusbRtl;
|
||||||
use crate::tcp_rtl::TcpRtl;
|
use crate::tcp_rtl::TcpRtl;
|
||||||
|
use adsb_lib::adsb::ADSBFrame;
|
||||||
|
use adsb_lib::{hex_to_bytes, run};
|
||||||
|
use clap::Parser;
|
||||||
|
use std::io::Result;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
@@ -23,11 +22,11 @@ struct ReceiverArgs {
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = ReceiverArgs::parse();
|
let args = ReceiverArgs::parse();
|
||||||
|
|
||||||
if let Some(mut hexString) = args.decode {
|
if let Some(mut hex_string) = args.decode {
|
||||||
if let Some(stripped) = hexString.strip_prefix("0x") {
|
if let Some(stripped) = hex_string.strip_prefix("0x") {
|
||||||
hexString = stripped.to_string();
|
hex_string = stripped.to_string();
|
||||||
}
|
}
|
||||||
let buf = hex_to_bytes(&hexString)?;
|
let buf = hex_to_bytes(&hex_string)?;
|
||||||
let frame = ADSBFrame::decode(&buf)?;
|
let frame = ADSBFrame::decode(&buf)?;
|
||||||
|
|
||||||
println!("{}", frame);
|
println!("{}", frame);
|
||||||
@@ -44,41 +43,3 @@ fn main() -> Result<()> {
|
|||||||
run(&mut device)
|
run(&mut device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex_to_bytes(s: &str) -> 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())));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = Vec::with_capacity(bytes.len() / 2);
|
|
||||||
for chunk in bytes.chunks(2) {
|
|
||||||
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)
|
|
||||||
))
|
|
||||||
};
|
|
||||||
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)
|
|
||||||
))
|
|
||||||
};
|
|
||||||
out.push((hi << 4) | lo);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hex_val(b: u8) -> Option<u8> {
|
|
||||||
match b {
|
|
||||||
b'0'..=b'9' => Some(b - b'0'),
|
|
||||||
b'a'..=b'f' => Some(b - b'a' + 10),
|
|
||||||
b'A'..=b'F' => Some(b - b'A' + 10),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
use adsb_lib::RtlDevice;
|
||||||
|
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;
|
||||||
use rusb::{request_type, Context, DeviceHandle, Direction, Recipient, RequestType, UsbContext};
|
|
||||||
use adsb_lib::RtlDevice;
|
|
||||||
|
|
||||||
// USB identifiers for RTL-SDR
|
// USB identifiers for RTL-SDR
|
||||||
const VENDOR_ID: u16 = 0x0BDA;
|
const VENDOR_ID: u16 = 0x0BDA;
|
||||||
@@ -19,15 +19,17 @@ impl RusbRtl {
|
|||||||
/// 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
|
||||||
let mut ctx = Context::new().map_err(|err| Error::new(ErrorKind::Other, err))?;
|
let ctx = Context::new().map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||||
|
|
||||||
// Find and open the RTL-SDR by VID/PID
|
// Find and open the RTL-SDR by VID/PID
|
||||||
let mut handle = ctx
|
let handle = ctx
|
||||||
.open_device_with_vid_pid(VENDOR_ID, PRODUCT_ID)
|
.open_device_with_vid_pid(VENDOR_ID, PRODUCT_ID)
|
||||||
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Device not found"))?;
|
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Device not found"))?;
|
||||||
|
|
||||||
// Claim interface 0
|
// Claim interface 0
|
||||||
handle.claim_interface(0).map_err(|err| Error::new(ErrorKind::Other, err))?;
|
handle
|
||||||
|
.claim_interface(0)
|
||||||
|
.map_err(|err| Error::new(ErrorKind::Other, err))?;
|
||||||
|
|
||||||
Ok(Self { handle })
|
Ok(Self { handle })
|
||||||
}
|
}
|
||||||
@@ -42,14 +44,16 @@ impl RusbRtl {
|
|||||||
let bm_request_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Device);
|
let bm_request_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Device);
|
||||||
|
|
||||||
// Perform the control transfer.
|
// Perform the control transfer.
|
||||||
self.handle
|
self
|
||||||
|
.handle
|
||||||
.write_control(
|
.write_control(
|
||||||
bm_request_type,
|
bm_request_type,
|
||||||
request,
|
request,
|
||||||
0, // wValue
|
0, // wValue
|
||||||
0, // wIndex
|
0, // wIndex
|
||||||
data,
|
data,
|
||||||
Duration::from_secs(1))
|
USB_TRANSFER_TIMEOUT,
|
||||||
|
)
|
||||||
.map(|_bytes_written| ())
|
.map(|_bytes_written| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +70,14 @@ impl RusbRtl {
|
|||||||
let mut buffer = vec![0u8; response_length];
|
let mut buffer = vec![0u8; response_length];
|
||||||
|
|
||||||
// Read the control response into the buffer
|
// Read the control response into the buffer
|
||||||
let n = self.handle
|
let n = self.handle.read_control(
|
||||||
.read_control(
|
|
||||||
bm_request_type,
|
bm_request_type,
|
||||||
request,
|
request,
|
||||||
0, // wValue
|
0, // wValue
|
||||||
0, // wIndex
|
0, // wIndex
|
||||||
&mut buffer,
|
&mut buffer,
|
||||||
Duration::from_secs(1))?;
|
USB_TRANSFER_TIMEOUT,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Truncate to the actual length returned by the device
|
// Truncate to the actual length returned by the device
|
||||||
buffer.truncate(n);
|
buffer.truncate(n);
|
||||||
@@ -83,18 +87,21 @@ impl RusbRtl {
|
|||||||
|
|
||||||
impl RtlDevice for RusbRtl {
|
impl RtlDevice for RusbRtl {
|
||||||
fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> {
|
fn control_send(&mut self, b_request: u8, data: &[u8]) -> Result<()> {
|
||||||
self.control_out(b_request, data)
|
self
|
||||||
|
.control_out(b_request, data)
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_recv(&mut self, b_request: u8, length: usize) -> Result<Vec<u8>> {
|
fn control_recv(&mut self, b_request: u8, length: usize) -> Result<Vec<u8>> {
|
||||||
self.control_in(b_request, length)
|
self
|
||||||
|
.control_in(b_request, length)
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_bulk(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
fn read_bulk(&mut self, buffer: &mut [u8]) -> Result<usize> {
|
||||||
self.handle
|
self
|
||||||
.read_bulk(DATA_ENDPOINT_ADDRESS, buffer, Duration::from_secs(1))
|
.handle
|
||||||
|
.read_bulk(DATA_ENDPOINT_ADDRESS, buffer, USB_TRANSFER_TIMEOUT)
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use adsb_lib::RtlDevice;
|
||||||
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::RtlDevice;
|
|
||||||
|
|
||||||
// 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;
|
||||||
@@ -112,7 +112,9 @@ impl RtlDevice for TcpRtl {
|
|||||||
let actual_payload_length = u32::from_le_bytes(length_bytes) as usize;
|
let actual_payload_length = u32::from_le_bytes(length_bytes) as usize;
|
||||||
|
|
||||||
// Read the payload
|
// Read the payload
|
||||||
self.socket.read_exact(&mut buffer[..actual_payload_length])?;
|
self
|
||||||
|
.socket
|
||||||
|
.read_exact(&mut buffer[..actual_payload_length])?;
|
||||||
Ok(actual_payload_length)
|
Ok(actual_payload_length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
use clap::Parser;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
// Framing tags
|
// Framing tags
|
||||||
const TAG_CONTROL_OUT: u8 = 0x10;
|
const TAG_CONTROL_OUT: u8 = 0x10;
|
||||||
@@ -10,8 +10,7 @@ const TAG_CONTROL_IN: u8 = 0x11;
|
|||||||
const TAG_BULK: u8 = 0x20;
|
const TAG_BULK: u8 = 0x20;
|
||||||
|
|
||||||
const ADSB_MESSAGE: [u8; 14] = [
|
const ADSB_MESSAGE: [u8; 14] = [
|
||||||
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3,
|
0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98,
|
||||||
0x71, 0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -84,13 +83,13 @@ fn handle_client_connection(mut connection: TcpStream) {
|
|||||||
payload_buffer[0],
|
payload_buffer[0],
|
||||||
payload_buffer[1],
|
payload_buffer[1],
|
||||||
payload_buffer[2],
|
payload_buffer[2],
|
||||||
payload_buffer[3]
|
payload_buffer[3],
|
||||||
]);
|
]);
|
||||||
println!("SET_FREQ -> {} Hz", current_frequency_hz);
|
println!("SET_FREQ -> {} Hz", current_frequency_hz);
|
||||||
}
|
}
|
||||||
// Acknowledge with a single byte = 0 (OK)
|
// Acknowledge with a single byte = 0 (OK)
|
||||||
connection.write_all(&[0u8]).ok();
|
connection.write_all(&[0u8]).ok();
|
||||||
},
|
}
|
||||||
TAG_CONTROL_IN => {
|
TAG_CONTROL_IN => {
|
||||||
dbg!(message_tag);
|
dbg!(message_tag);
|
||||||
// Simulate a CONTROL_IN reply with a fixed pattern
|
// Simulate a CONTROL_IN reply with a fixed pattern
|
||||||
@@ -105,7 +104,7 @@ fn handle_client_connection(mut connection: TcpStream) {
|
|||||||
// Payload (0x42 repeated)
|
// Payload (0x42 repeated)
|
||||||
let reply = vec![0x42; payload_length];
|
let reply = vec![0x42; payload_length];
|
||||||
let _ = connection.write_all(&reply).ok();
|
let _ = connection.write_all(&reply).ok();
|
||||||
},
|
}
|
||||||
TAG_BULK => {
|
TAG_BULK => {
|
||||||
dbg!(message_tag);
|
dbg!(message_tag);
|
||||||
// Generate a ADS-B IQ burst
|
// Generate a ADS-B IQ burst
|
||||||
@@ -123,11 +122,11 @@ fn handle_client_connection(mut connection: TcpStream) {
|
|||||||
|
|
||||||
// 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
|
// On any unrecognized tag, break out
|
||||||
break
|
break;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +157,7 @@ fn generate_adsb_iq_samples() -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Concatenate preamble + data
|
// Concatenate preamble + data
|
||||||
let mut full_bitstream = Vec::with_capacity(
|
let mut full_bitstream = Vec::with_capacity(preamble_bits.len() * 2 + manchester_bits.len());
|
||||||
preamble_bits.len() * 2 + manchester_bits.len());
|
|
||||||
|
|
||||||
// Preamble: each '1' or '0' is one microsecond = 2 samples
|
// Preamble: each '1' or '0' is one microsecond = 2 samples
|
||||||
for &pb in preamble_bits.iter() {
|
for &pb in preamble_bits.iter() {
|
||||||
@@ -175,11 +173,7 @@ fn generate_adsb_iq_samples() -> Vec<u8> {
|
|||||||
// I = 128 + 127*(bit), Q = 128
|
// I = 128 + 127*(bit), Q = 128
|
||||||
let mut iq = Vec::with_capacity(full_bitstream.len() * 2);
|
let mut iq = Vec::with_capacity(full_bitstream.len() * 2);
|
||||||
for &level in full_bitstream.iter() {
|
for &level in full_bitstream.iter() {
|
||||||
let i_sample = if level == 1 {
|
let i_sample = if level == 1 { 255u8 } else { 128u8 };
|
||||||
255u8
|
|
||||||
} else {
|
|
||||||
128u8
|
|
||||||
};
|
|
||||||
let q_sample = 128u8;
|
let q_sample = 128u8;
|
||||||
iq.push(i_sample);
|
iq.push(i_sample);
|
||||||
iq.push(q_sample);
|
iq.push(q_sample);
|
||||||
|
|||||||
3
adsb/rust-toolchain.toml
Normal file
3
adsb/rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
components = ["rustfmt", "clippy"]
|
||||||
3
adsb/rustfmt.toml
Normal file
3
adsb/rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
indent_style = "Block"
|
||||||
|
reorder_imports = false
|
||||||
|
tab_spaces = 2
|
||||||
Reference in New Issue
Block a user