diff --git a/config.example.toml b/config.example.toml index 019f721483af154424157e191162389ad6354552..76ce98d821857eaaf68568e60293ccabcbeaf66f 100644 --- a/config.example.toml +++ b/config.example.toml @@ -5,10 +5,11 @@ ssid = 0 [felinet.beacon] period_seconds = 120 +icon = 0 max_hops = 3 latitude = 45.0 longitude = -66.0 altitude = 30 # meters -comment = "Check out my cool CATS I-gate!" +comment = "Check out my cool CATS SDR I-gate!" antenna_height = 10 # meters above ground antenna_gain = 2 # dBi diff --git a/src/config.rs b/src/config.rs index e0c3f0bd7caa40bab7e9f5dc164be1cc9cec0dcd..3bceba17d6a93dbe037f5d2655ef855c52dcf2fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,31 @@ -use std::fs; +use std::{fs, time::Duration}; use anyhow::{bail, Context}; use serde::Deserialize; +use serde_with::{serde_as, DurationSeconds}; + +#[serde_as] +#[derive(Deserialize, Clone)] +pub struct BeaconConfig { + #[serde_as(as = "DurationSeconds<u64>")] + pub period_seconds: Duration, + pub icon: u16, + #[serde(default)] + pub max_hops: u8, + pub latitude: Option<f64>, + pub longitude: Option<f64>, + pub altitude: Option<f64>, + pub comment: Option<String>, + pub antenna_height: Option<u8>, + pub antenna_gain: Option<f32>, +} #[derive(Deserialize, Clone)] pub struct FelinetConfig { pub address: String, pub callsign: String, pub ssid: u8, + pub beacon: BeaconConfig, } #[derive(Deserialize, Clone)] diff --git a/src/gate.rs b/src/gate.rs index 5d224a05ea725740aed56d23548a9bcc5bbcd6d7..78d7afc72ef88f4b9d5c4ea14f56e6a5ae8b1d79 100644 --- a/src/gate.rs +++ b/src/gate.rs @@ -1,8 +1,129 @@ -use crate::felinet::{handler_client::HandlerClient, PacketIn as SemiPacketIn}; +use crate::{ + config::FelinetConfig, + felinet::{handler_client::HandlerClient, PacketIn as SemiPacketIn}, + MAX_PACKET_LEN, +}; +use anyhow::{anyhow, Context}; use async_stream::stream; -use std::time::Duration; +use half::f16; +use ham_cats::{ + buffer::Buffer, + packet::Packet, + whisker::{Gps, Identification, NodeInfoBuilder, Route}, +}; +use std::time::{Duration, Instant}; use tokio::sync::broadcast; use tonic::{transport::Channel, Request}; +use uuid::Uuid; + +const HARDWARE_ID: u16 = 0x7c86; +const SOFTWARE_ID: u8 = 0x00; + +pub fn beacon_forever( + c: &FelinetConfig, + felinet_send: broadcast::Sender<SemiPacketIn>, + uuid: Uuid, +) -> anyhow::Result<()> { + let start_time = Instant::now(); + + let mut buf = [0; MAX_PACKET_LEN]; + let mut packet = Packet::new(&mut buf); + packet + .add_identification( + Identification::new(&c.callsign, c.ssid, c.beacon.icon).context( + "Config does not describe a valid identification (callsign, ssid, icon)", + )?, + ) + .map_err(|e| anyhow!("Could not add identification to beacon packet: {e}"))?; + + let period = c.beacon.period_seconds; + + if let (Some(latitude), Some(longitude), Some(altitude)) = + (c.beacon.latitude, c.beacon.longitude, c.beacon.altitude) + { + packet + .add_gps(Gps::new( + latitude, + longitude, + f16::from_f64(altitude), + 0, + 0.0, + f16::ZERO, + )) + .map_err(|e| anyhow!("Could not add gps to beacon packet: {e}"))?; + } + + if let Some(c) = &c.beacon.comment { + packet + .add_comment(c) + .map_err(|e| anyhow!("Could not add gps to beacon packet: {e}"))?; + } + + let mut info_builder = NodeInfoBuilder::default() + .hardware_id(HARDWARE_ID) + .software_id(SOFTWARE_ID); + + if let Some(height) = c.beacon.antenna_height { + info_builder = info_builder.antenna_height(height); + } + + if let Some(gain) = c.beacon.antenna_gain { + info_builder = info_builder.antenna_gain(gain as f64); + } + + // This is going to be overwritten. + // We add it now to ensure there are no errors when we unwrap it later + packet + .add_node_info(info_builder.build()) + .map_err(|e| anyhow!("Could not add info to beacon packet: {e}"))?; + + let mut r = Route::new(c.beacon.max_hops); + r.push_internet().context("Could not create beacon route")?; + packet + .add_route(r) + .map_err(|e| anyhow!("Could not add route to beacon packet: {e}"))?; + + let internet_packet_bytes = packet.encode().to_vec(); + tokio::task::spawn(async move { + let mut internet_packet_buf = [0; MAX_PACKET_LEN]; + let mut internet_packet_buf = Buffer::new_empty(&mut internet_packet_buf); + internet_packet_buf.extend(&internet_packet_bytes); + let mut packet = + Packet::decode(internet_packet_buf).expect("Could not re-create internet packet"); + + let mut tmp = [0; MAX_PACKET_LEN]; + + loop { + tokio::time::sleep(period).await; + + let uptime = (Instant::now() - start_time) + .as_secs() + .try_into() + .expect("Could not calculate uptime"); + + let info = info_builder.uptime(uptime).build(); + + packet.clear_node_info(); + packet + .add_node_info(info) + .expect("Could not add node info to rf packet"); + + let semi = packet + .clone_backing(&mut tmp) + .semi_encode() + .expect("Could not encode beacon packet") + .to_vec(); + let semi = SemiPacketIn { + raw: semi, + uuid: uuid.into(), + }; + + let _ = felinet_send.send(semi.clone()); + } + }); + + Ok(()) +} pub async fn felinet_send_forever( mut client: HandlerClient<Channel>, diff --git a/src/main.rs b/src/main.rs index f2f0b0c4149134d85ae3ca284c21ded91d222154..8cbed7ad90d8b543436ec93d02e85e337931e98d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use crate::felinet::handler_client::HandlerClient; use config::Config; use felinet::PingRequest; -use gate::felinet_send_forever; +use gate::{beacon_forever, felinet_send_forever}; use std::{str::FromStr, time::Duration}; use tokio::sync::broadcast; use tonic::{transport::Endpoint, Request}; @@ -33,29 +33,25 @@ async fn gate_forever(config: &Config) -> anyhow::Result<()> { let tx = broadcast::Sender::new(16); - let client = if let Some(felinet_config) = &config.felinet { + if let Some(felinet_config) = &config.felinet { let endpoint = Endpoint::from_str(&felinet_config.address)? .keep_alive_while_idle(true) .keep_alive_timeout(Duration::from_secs(5)) .http2_keep_alive_interval(Duration::from_secs(5)) .tcp_keepalive(Some(Duration::from_secs(5))); - Some(HandlerClient::connect(endpoint).await?) - } else { - eprintln!( - "No FELINET section specified in configuration. Received packets will not be uploaded!" - ); - None - }; + let client = HandlerClient::connect(endpoint).await?; - if let Some(client) = client { { let client = client.clone(); + let tx = tx.clone(); tokio::task::spawn(async { felinet_send_forever(client, tx).await; }); } + beacon_forever(felinet_config, tx.clone(), uuid)?; + { let mut client = client.clone(); tokio::task::spawn(async move { @@ -69,6 +65,10 @@ async fn gate_forever(config: &Config) -> anyhow::Result<()> { } }); } + } else { + eprintln!( + "No FELINET section specified in configuration. Received packets will not be uploaded!" + ); } decoder::decode_forever();