use std::sync::Arc;
use clap::Parser;
use tokio::sync::RwLock;
use radio_utils_emu::protocol1::SiggenConfig;
use radio_utils_emu::radio::{EchoBuffer, EchoMode, HpsdrHw, HwInfo};
#[derive(Parser)]
#[command(
name = "radio-utils-emu",
about = "OpenHPSDR Protocol 1 radio emulator (Hermes / Hermes Lite 2)"
)]
struct Cli {
#[arg(long, value_parser = parse_radio)]
radio: HpsdrHw,
#[arg(long)]
mac: Option<String>,
#[arg(long, default_value = "1.26e-5")]
noise: f64,
#[arg(long)]
echo: bool,
#[arg(long)]
echo_live: bool,
#[arg(long, default_value = "0.0.0.0")]
bind: String,
#[arg(long, default_value_t = radio_utils_emu::protocol1::DEFAULT_MAX_CLIENTS)]
max_clients: usize,
#[arg(short, long)]
verbose: bool,
}
fn parse_radio(s: &str) -> Result<HpsdrHw, String> {
HpsdrHw::from_name(s).ok_or_else(|| {
format!(
"unknown radio '{}'. Valid: {}",
s,
HpsdrHw::all_names().join(", ")
)
})
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let log_level = if cli.verbose { "debug" } else { "info" };
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level))
.format_timestamp_secs()
.init();
const DEFAULT_MAC: [u8; 6] = [0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE];
let mac = if let Some(ref mac_str) = cli.mac {
let hex: String = mac_str.chars().filter(|c| c.is_ascii_hexdigit()).collect();
if hex.len() != 12 {
eprintln!("MAC address must be 6 bytes (12 hex digits)");
std::process::exit(1);
}
let mut bytes = [0u8; 6];
for i in 0..6 {
bytes[i] = match u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16) {
Ok(b) => b,
Err(_) => {
eprintln!("Invalid hex in MAC address");
std::process::exit(1);
}
};
}
bytes
} else {
DEFAULT_MAC
};
let sample_rate: u32 = 48000;
let hw = Arc::new(HwInfo::new(cli.radio, mac));
let sg_cfg = Arc::new(RwLock::new(SiggenConfig {
sample_rate,
noise_level: cli.noise,
}));
let echo_mode = if cli.echo_live {
Some(EchoMode::Live)
} else if cli.echo {
Some(EchoMode::Loop)
} else {
None
};
let echo = Arc::new(RwLock::new(echo_mode.map(EchoBuffer::new)));
let echo_label = match echo_mode {
Some(EchoMode::Loop) => "loop",
Some(EchoMode::Live) => "live",
None => "off",
};
log::info!(
"Starting HPSDR emulator: radio={}, noise={:.2e}, echo={}, max_clients={}",
cli.radio,
cli.noise,
echo_label,
cli.max_clients,
);
tokio::select! {
result = radio_utils_emu::protocol1::run_protocol1(hw, sg_cfg, echo, &cli.bind, cli.max_clients) => {
if let Err(e) = result {
eprintln!("Fatal: failed to start Protocol 1: {}", e);
std::process::exit(1);
}
}
_ = tokio::signal::ctrl_c() => {
log::info!("Shutting down...");
}
}
}