diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/fake-radio.rs | 91 | ||||
-rw-r--r-- | src/main.rs | 35 | ||||
-rw-r--r-- | src/radio.rs | 18 | ||||
-rw-r--r-- | src/ui.rs | 58 |
4 files changed, 186 insertions, 16 deletions
diff --git a/src/bin/fake-radio.rs b/src/bin/fake-radio.rs new file mode 100644 index 0000000..fd38149 --- /dev/null +++ b/src/bin/fake-radio.rs @@ -0,0 +1,91 @@ +use anyhow::{anyhow, Context}; +use tokio::net::UdpSocket; +use ham_cats::{ + buffer::Buffer, + whisker::{Arbitrary, Identification, Gps}, +}; + +const MAX_PACKET_LEN : usize = 8191; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + eprintln!("Sending example packet"); + + let packet = build_example_packet().await.unwrap(); + let sock = UdpSocket::bind("127.0.0.1:9074").await.unwrap(); + sock.send_to(&packet, "127.0.0.1:9073").await.unwrap(); + + eprintln!("Receiving packets. Ctrl-C to stop"); + + let mut data = [0; MAX_PACKET_LEN]; + while let Ok((len, _addr)) = sock.recv_from(&mut data).await { + let mut buf = [0; MAX_PACKET_LEN]; + match ham_cats::packet::Packet::fully_decode(&data[2..len], &mut buf) { + Ok(packet) => { + if let Some(ident) = packet.identification() { + eprintln!(" Ident {}-{}", ident.callsign, ident.ssid); + } + + if let Some(gps) = packet.gps() { + eprintln!(" GPS {} {}", gps.latitude(), gps.longitude()); + } + + let mut comment = [0; 1024]; + if let Ok(c) = packet.comment(&mut comment) { + eprintln!(" Comment {}", c); + } + + eprintln!(" With {} Arbitrary whiskers", packet.arbitrary_iter().count()); + }, + Err(e) => { + eprintln!(" Cannot decode packet of length {} {:?}", len, e); + } + } + } + + Ok(()) +} + +async fn build_example_packet() -> anyhow::Result<Vec<u8>> { + let callsign = "EX4MPLE"; + let ssid = 0; + let icon = 0; + + let mut buf = [0; MAX_PACKET_LEN]; + let mut pkt = ham_cats::packet::Packet::new(&mut buf); + pkt.add_identification( + Identification::new(&callsign, ssid, icon) + .context("Invalid identification")?, + ) + .map_err(|e| anyhow!("Could not add identification to packet: {e}"))?; + + pkt.add_comment("Debugging packet") + .map_err(|e| anyhow!("Could not add comment to packet: {e}"))?; + + let latitude = 46.5; + let longitude = -6.2; + let altitude = 200u8.into(); + let max_error = 1; + let heading = 120.0; + let speed = 1u8.into(); + + pkt.add_gps(Gps::new( + latitude, + longitude, + altitude, + max_error, + heading, + speed) + ) + .map_err(|e| anyhow!("Could not add GPS to packet: {e}"))?; + + pkt.add_arbitrary(Arbitrary::new(&[0xA5; 8]).unwrap()) + .map_err(|e| anyhow!("Could not add arbitrary to packet: {e}"))?; + + let mut buf2 = [0; MAX_PACKET_LEN]; + let mut data = Buffer::new_empty(&mut buf2); + pkt.fully_encode(&mut data) + .map_err(|e| anyhow!("Could not encode packet: {e}"))?; + + Ok(data.to_vec()) +} diff --git a/src/main.rs b/src/main.rs index 9a4ae2b..0a427e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Context; use log::{debug, info, warn, error}; use std::sync::{Arc, Mutex}; +use tokio::net::UdpSocket; use tokio::sync::mpsc; use sqlx::{Connection, SqliteConnection}; use radio::{RadioManager, MAX_PACKET_LEN}; @@ -11,7 +12,8 @@ mod ui; struct AppState { conf : config::Config, - db : Mutex<SqliteConnection> + db : Mutex<SqliteConnection>, + transmit_queue : mpsc::Sender<Vec<u8>>, } type SharedState = Arc<Mutex<AppState>>; @@ -28,17 +30,37 @@ async fn main() -> std::io::Result<()> { let conf = config::Config::load().expect("Could not load config"); + let (radio_rx_queue, mut packet_receive) = mpsc::channel(16); + let (packet_send, mut radio_tx_queue) = mpsc::channel::<Vec<u8>>(16); + if conf.freq == 0 { - warn!("Frequency {0} is zero, disabling radio", conf.freq); + warn!("Frequency {0} is zero, disabling radio. Fake receiver udp 127.0.0.1:9073, sending to 9074", conf.freq); + let sock_r = Arc::new(UdpSocket::bind("127.0.0.1:9073").await?); + let sock_s = sock_r.clone(); + + // These two tasks behave like the radio, but use UDP instead of the RF channel. + tokio::spawn(async move { + let mut buf = [0; 1024]; + while let Ok((len, addr)) = sock_r.recv_from(&mut buf).await { + println!("{:?} bytes received from {:?}", len, addr); + let packet = buf[..len].to_vec(); + let rssi = 0f64; + radio_rx_queue.send((packet, rssi)).await.expect("Inject frame"); + } + }); + + tokio::spawn(async move { + while let Some(p) = radio_tx_queue.recv().await { + sock_s.send_to(&p, "127.0.0.1:9074").await.unwrap(); + } + }); } else if !(430000..=436380).contains(&conf.freq) { error!("Frequency {} kHz out of range (430MHz - 436.375MHz), skipping radio setup", conf.freq); } else { info!("Setting up radio"); - let (packet_tx, mut packet_receive) = mpsc::channel(16); - let (packet_send, packet_rx) = mpsc::channel(16); - let mut radio = RadioManager::new(packet_tx, packet_rx).expect("Could not initialize radio"); + let mut radio = RadioManager::new(radio_rx_queue, radio_tx_queue).expect("Could not initialize radio"); let channel = ((conf.freq - 430000) / 25) as u8; radio.set_channel(channel); @@ -82,7 +104,8 @@ async fn main() -> std::io::Result<()> { let shared_state = Arc::new(Mutex::new(AppState { conf, - db: Mutex::new(conn) + db: Mutex::new(conn), + transmit_queue: packet_send.clone(), })); let port = 3000; diff --git a/src/radio.rs b/src/radio.rs index 77f7c0f..239685f 100644 --- a/src/radio.rs +++ b/src/radio.rs @@ -20,14 +20,14 @@ pub const MAX_PACKET_LEN: usize = 8191; pub struct RadioManager { radio: Rf4463<Spi, OutputPin, OutputPin, Delay>, - tx: Sender<(Vec<u8>, f64)>, - rx: Receiver<Vec<u8>>, + receive_queue: Sender<(Vec<u8>, f64)>, + transmit_queue: Receiver<Vec<u8>>, rx_buf: [u8; MAX_PACKET_LEN], temperature: Arc<Mutex<f32>>, } impl RadioManager { - pub fn new(tx: Sender<(Vec<u8>, f64)>, rx: Receiver<Vec<u8>>) -> anyhow::Result<Self> { + pub fn new(receive_queue: Sender<(Vec<u8>, f64)>, transmit_queue: Receiver<Vec<u8>>) -> anyhow::Result<Self> { let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 1_000_000, Mode::Mode0)?; let gpio = Gpio::new()?; let sdn = gpio.get(22)?.into_output(); @@ -44,8 +44,8 @@ impl RadioManager { Ok(Self { radio, - tx, - rx, + receive_queue, + transmit_queue, rx_buf, temperature, }) @@ -65,13 +65,13 @@ impl RadioManager { *self.temperature.lock().await = self.radio.get_temp()?; - match self.rx.try_recv() { + match self.transmit_queue.try_recv() { Ok(pkt) => { self.tx(&pkt).await?; } Err(TryRecvError::Empty) => {} Err(TryRecvError::Disconnected) => { - bail!("RX channel disconnected") + bail!("TX channel disconnected") } } @@ -101,11 +101,11 @@ impl RadioManager { .start_rx(None, false) .map_err(|e| anyhow!("{e}"))?; - self.tx + self.receive_queue .send((data.data().to_vec(), data.rssi())) .await .ok() - .context("TX channel died")?; + .context("RX channel died")?; } Ok(()) @@ -1,9 +1,12 @@ +use anyhow::{anyhow, Context}; use std::str::FromStr; +use axum::Json; +use log::info; use serde::Deserialize; use askama::Template; use axum::{ extract::State, - routing::get, + routing::{get, post}, Router, response::Html, Form, @@ -11,6 +14,11 @@ use axum::{ }; use tower_http::services::ServeDir; +use ham_cats::{ + buffer::Buffer, + whisker::Identification, +}; + use crate::config; use crate::SharedState; @@ -19,6 +27,7 @@ pub async fn serve(port: u16, shared_state: SharedState) { .route("/", get(dashboard)) .route("/incoming", get(incoming)) .route("/send", get(send)) + .route("/api/send_packet", post(post_packet)) .route("/settings", get(show_settings).post(post_settings)) .nest_service("/static", ServeDir::new("static")) /* For an example for timeouts and tracing, have a look at the git history */ @@ -84,6 +93,53 @@ async fn send(State(state): State<SharedState>) -> SendTemplate<'static> { } } +#[derive(Deserialize, Debug)] +struct ApiSendPacket { + comment : Option<String>, +} + +fn build_packet(config: config::Config, comment: Option<String>) -> anyhow::Result<Vec<u8>> { + let mut buf = [0; crate::radio::MAX_PACKET_LEN]; + let mut pkt = ham_cats::packet::Packet::new(&mut buf); + pkt.add_identification( + Identification::new(&config.callsign, config.ssid, config.icon) + .context("Invalid identification")?, + ) + .map_err(|e| anyhow!("Could not add identification to packet: {e}"))?; + + if let Some(c) = comment { + pkt.add_comment(&c) + .map_err(|e| anyhow!("Could not add comment to packet: {e}"))?; + } + + let mut buf2 = [0; crate::radio::MAX_PACKET_LEN]; + let mut data = Buffer::new_empty(&mut buf2); + pkt.fully_encode(&mut data) + .map_err(|e| anyhow!("Could not encode packet: {e}"))?; + + Ok(data.to_vec()) +} + +async fn post_packet(State(state): State<SharedState>, Json(payload): Json<ApiSendPacket>) -> StatusCode { + let (config, transmit_queue) = { + let s = state.lock().unwrap(); + (s.conf.clone(), s.transmit_queue.clone()) + }; + + info!("send_packet {:?}", payload); + + match build_packet(config, payload.comment) { + Ok(p) => { + info!("Built packet of {} bytes", p.len()); + match transmit_queue.send(p).await { + Ok(()) => StatusCode::OK, + Err(_) => StatusCode::BAD_REQUEST, + } + }, + Err(_) =>StatusCode::INTERNAL_SERVER_ERROR, + } +} + #[derive(Template)] #[template(path = "settings.html")] struct SettingsTemplate<'a> { |