Skip to content
Snippets Groups Projects
radio.rs 5 KiB
Newer Older
Stephen D's avatar
Stephen D committed
use ham_cats::{buffer::Buffer, packet::Packet};
use rand::{rngs::SmallRng, Rng, SeedableRng};
Stephen D's avatar
Stephen D committed
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 systick_monotonic::fugit::Duration;
Stephen D's avatar
Stephen D committed

use crate::{app::monotonics::MyMono::now, config::Config, MAX_PACKET_LEN, SYS_TICK_FREQ};
Stephen D's avatar
Stephen D committed

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> {
Stephen D's avatar
Stephen D committed
    radio: Radio,
Stephen D's avatar
Stephen D committed
    buf: &'a mut [u8; MAX_PACKET_LEN],
    enable_digipeating: bool,
    rng: SmallRng,
Stephen D's avatar
Stephen D committed
}

impl<'a> RadioManager<'a> {
    pub fn new(
        spi: Spi<SPI1>,
        sdn: SdnPin,
        cs: CsPin,
        delay: MyDelay,
        buf: &'a mut [u8; MAX_PACKET_LEN],
        config: &Config,
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);

Stephen D's avatar
Stephen D committed
        radio.start_rx(None, false).ok()?;
Stephen D's avatar
Stephen D committed

        let enable_digipeating = config.enable_digipeating;
        let seed = rand_seed_from_str(&config.callsign) ^ u64::from(config.ssid);
        let rng = SmallRng::seed_from_u64(seed);

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

    // call me every 20-ish ms
    // technically needs to be every 100ms, tops
Stephen D's avatar
Stephen D committed
    // digipeats only if ident is Some,
    // otherwise the packet is discarded
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
        if self.radio.is_idle() {
            self.radio
Stephen D's avatar
Stephen D committed
                .start_rx(None, false)
Stephen D's avatar
Stephen D committed
                .map_err(TransferError::SpiError)?;
        }

Stephen D's avatar
Stephen D committed
        self.radio.interrupt(Some(self.buf), None)?;
Stephen D's avatar
Stephen D committed

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

            self.radio
Stephen D's avatar
Stephen D committed
                .start_rx(None, false)
Stephen D's avatar
Stephen D committed
                .map_err(TransferError::SpiError)?;
        }

        Ok(())
    }

Stephen D's avatar
Stephen D committed
    // digipeats only if ident is Some,
    // otherwise the rx'd packet is discarded
    pub fn tx<P: OutputPin, M: Mutex<T = P>>(
        &mut self,
        led: &mut M,
        data: &[u8],
        ident: Option<(&str, u8)>,
    ) -> Option<()> {
        // ensures we don't tx over a packet,
        // and adds some random delay so that every node
        // if offset slightly
        self.tx_delay(led, ident)?;
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

        Some(())
    }

    fn tx_delay<P: OutputPin, M: Mutex<T = P>>(
        &mut self,
        led: &mut M,
        ident: Option<(&str, u8)>,
    ) -> Option<()> {
        loop {
            let delay_ms = self.rng.gen_range(0..50);

            let mut rx = false;
            let thres = now() + Duration::<u64, 1, { SYS_TICK_FREQ }>::millis(delay_ms);
            while now() < thres {
                self.tick(led, ident).ok()?;

                while self.radio.is_busy_rxing().ok()? {
                    rx = true;
                    self.tick(led, ident).ok()?;
                }

                if rx {
                    // if we rx'd a packet, we need to draw a new random time and start again
                    break;
                }
            }

            if !rx {
                // didn't an rx packet, so we're safe to leave
                break Some(());
            }
        }
    }

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 packet.should_digipeat(callsign, ssid).is_ok() {
            if packet.append_to_route(callsign, ssid).is_err() {
Stephen D's avatar
Stephen D committed
                return;
            }

Stephen D's avatar
Stephen D committed
            let mut buf = [0; MAX_PACKET_LEN];
            let mut buf = Buffer::new_empty(&mut buf);
            if packet.fully_encode(&mut buf).is_ok() {
                self.tx(led, &buf, None);
Stephen D's avatar
Stephen D committed
            }
        }
    }
}

fn rand_seed_from_str(s: &str) -> u64 {
    let mut out = 0;

    for (i, &b) in s.as_bytes().iter().enumerate() {
        let i = i % 4;
        let b: u64 = b.into();

        out ^= b << (i * 8);
    }

    out
}