Skip to content
Snippets Groups Projects
radio.rs 6.01 KiB
Newer Older
Stephen D's avatar
Stephen D committed
use ham_cats::{
    packet::Packet,
    whisker::{Route, RouteNode},
};
use rf4463::{config::RADIO_CONFIG_CATS, error::TransferError, Rf4463};
Stephen D's avatar
Stephen D committed
use rtic::Mutex;
Stephen D's avatar
Stephen D committed
use stm32f4xx_hal::{
    gpio,
Stephen D's avatar
Stephen D committed
    hal::digital::v2::OutputPin,
Stephen D's avatar
Stephen D committed
    pac::{SPI1, TIM5},
    spi::{self, Spi},
    timer::Delay,
};

use crate::MAX_PACKET_LEN;

type SdnPin = gpio::Pin<'B', 12, gpio::Output>;
type CsPin = gpio::Pin<'A', 8, gpio::Output>;
type MyDelay = Delay<TIM5, 1000000>;
type Radio = Rf4463<Spi<SPI1>, SdnPin, CsPin, MyDelay>;

pub struct RadioManager<'a> {
    radio: Radio,
    buf: &'a mut [u8; MAX_PACKET_LEN],
    enable_digipeating: bool,
}

impl<'a> RadioManager<'a> {
    pub fn new(
        spi: Spi<SPI1>,
        sdn: SdnPin,
        cs: CsPin,
        delay: MyDelay,
        buf: &'a mut [u8; MAX_PACKET_LEN],
        enable_digipeating: bool,
Stephen D's avatar
Stephen D committed
    ) -> Option<Self> {
        let mut radio = Rf4463::new(spi, sdn, cs, delay, &mut RADIO_CONFIG_CATS.clone()).ok()?;
Stephen D's avatar
Stephen D committed

        // sets us up for the default CATS frequency, 430.500 MHz
        radio.set_channel(20);

        // perpetual mode
Stephen D's avatar
Stephen D committed
        radio.start_rx(None, true).ok()?;
Stephen D's avatar
Stephen D committed

Stephen D's avatar
Stephen D committed
        Some(Self {
Stephen D's avatar
Stephen D committed
            radio,
            buf,
            enable_digipeating,
        })
    }

    // call me every 20-ish ms
    // technically needs to be every 100ms, tops
Stephen D's avatar
Stephen D committed
    pub fn tick<P: OutputPin, M: Mutex<T = P>>(
        &mut self,
        led: &mut M,
        ident: Option<(&str, u8)>,
    ) -> Result<(), TransferError<spi::Error>> {
Stephen D's avatar
Stephen D committed
        self.radio.interrupt(Some(self.buf), None)?;

        if let Some(data) = self
            .radio
            .finish_rx(self.buf)
            .map_err(TransferError::SpiError)?
        {
            if self.enable_digipeating {
                if let Some((callsign, ssid)) = ident {
                    if let Ok(packet) = Packet::fully_decode(data.data()) {
Stephen D's avatar
Stephen D committed
                        self.handle_packet_rx(led, packet, callsign, ssid);
Stephen D's avatar
Stephen D committed
                    };
                }
            }

            // Only needed if the radio gets confused
            self.radio
                .start_rx(None, true)
                .map_err(TransferError::SpiError)?;
        }

        Ok(())
    }

Stephen D's avatar
Stephen D committed
    pub fn tx<P: OutputPin, M: Mutex<T = P>>(&mut self, led: &mut M, data: &[u8]) -> Option<()> {
Stephen D's avatar
Stephen D committed
        // don't want to transmit on top of a packet
        while self.radio.is_busy_rxing().ok()? {
Stephen D's avatar
Stephen D committed
            self.tick(led, None).ok()?;
Stephen D's avatar
Stephen D committed
        }

Stephen D's avatar
Stephen D committed
        led.lock(|l| l.set_high().ok());
Stephen D's avatar
Stephen D committed
        self.radio.start_tx(data).ok()?;

        while !self.radio.is_idle() {
            self.radio.interrupt(None, Some(data)).ok();
        }
Stephen D's avatar
Stephen D committed
        led.lock(|l| l.set_low().ok());
Stephen D's avatar
Stephen D committed

        self.radio
            .start_rx(None, true)
            .map_err(TransferError::SpiError)
            .ok();

        Some(())
    }

    // Not great - lots of copies, and therefore stack usage here
Stephen D's avatar
Stephen D committed
    fn handle_packet_rx<P: OutputPin, M: Mutex<T = P>>(
        &mut self,
        led: &mut M,
        mut packet: Packet<MAX_PACKET_LEN>,
        callsign: &str,
        ssid: u8,
    ) {
Stephen D's avatar
Stephen D committed
        if should_digipeat(callsign, ssid, &packet) {
            if append_to_packet_route(callsign, ssid, &mut packet).is_none() {
                return;
            }

            if let Ok(buf) = packet.fully_encode() {
Stephen D's avatar
Stephen D committed
                self.tx(led, &buf);
Stephen D's avatar
Stephen D committed
            }
        }
    }
}

pub fn should_digipeat(callsign: &str, ssid: u8, packet: &Packet<MAX_PACKET_LEN>) -> bool {
    let route = match packet.route() {
        Some(x) => x,
        None => {
            return false;
        }
    };

Stephen D's avatar
Stephen D committed
    if let Some(ident) = packet.identification() {
        if &ident.callsign == callsign && ident.ssid == ssid {
            // this node is the source of the packet
            return false;
        }
    }

Stephen D's avatar
Stephen D committed
    let max_hops: usize = route.max_hops.into();
    let cur_hops = route
        .iter()
        .filter(|r| match r {
            RouteNode::Internet => false,
            RouteNode::Identity(_, _, is_future) => !is_future,
        })
        .count();

    if max_hops <= cur_hops {
        // already hit our max hops
        return false;
    }

    let already_digipeated = route.iter().any(|r| match r {
        RouteNode::Internet => false,
        RouteNode::Identity(rc, rs, is_future) => rc == callsign && rs == ssid && !is_future,
    });

    if already_digipeated {
        // this node has already digipeated this packet
        return false;
    }

    let next_node = route.iter().find_map(|r| match r {
        RouteNode::Identity(c, s, is_future) if is_future => Some((c, s)),
        _ => None,
    });

    match next_node {
        Some((rc, rs)) => rc == callsign && rs == ssid,
        None => true,
    }
}

#[must_use]
pub fn append_to_packet_route(
    callsign: &str,
    ssid: u8,
    packet: &mut Packet<MAX_PACKET_LEN>,
) -> Option<()> {
    let mut route = packet.route().unwrap_or(Route::new(0));
    append_to_route(callsign, ssid, &mut route)?;
    packet.clear_route();
    packet.add_route(route).ok()?;

    Some(())
}

#[must_use]
fn append_to_route(callsign: &str, ssid: u8, r: &mut Route) -> Option<()> {
    let replace_future = r.iter().any(|rn| match rn {
        RouteNode::Identity(rc, rs, is_future) => rc == callsign && rs == ssid && is_future,
        _ => false,
    });

    if replace_future {
        let mut new_route = Route::new(r.max_hops);
        let mut already_replaced = false;

        for rn in r.iter() {
            match rn {
                RouteNode::Identity(rc, rs, is_future)
                    if rc == callsign && rs == ssid && is_future && !already_replaced =>
                {
                    already_replaced = true;
                    new_route.push_callsign(callsign, ssid, false)?;
                }
                RouteNode::Identity(rc, rs, is_future) => {
                    new_route.push_callsign(rc, rs, is_future)?;
                }
                RouteNode::Internet => {
                    new_route.push_internet()?;
                }
            }
        }

        *r = new_route;
    } else {
        r.push_callsign(callsign, ssid, false)?;
    }

    Some(())
}