diff --git a/Cargo.lock b/Cargo.lock index 350ec80ab0e9c88c73123a4bdd456aff6bc30993..0e68525849f5d891d6c82dfda16cbabf426e371a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,8 +40,8 @@ dependencies = [ "crc", "image", "kiss-tnc", + "rppal", "serde", - "serial", "sha3", "subprocess", "tokio", @@ -383,15 +383,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "ioctl-rs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -687,6 +678,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rppal" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612e1a22e21f08a246657c6433fe52b773ae43d07c9ef88ccfc433cc8683caba" +dependencies = [ + "libc", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -722,48 +722,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" -dependencies = [ - "serial-core", - "serial-unix", - "serial-windows", -] - -[[package]] -name = "serial-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" -dependencies = [ - "libc", -] - -[[package]] -name = "serial-unix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" -dependencies = [ - "ioctl-rs", - "libc", - "serial-core", - "termios", -] - -[[package]] -name = "serial-windows" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" -dependencies = [ - "libc", - "serial-core", -] - [[package]] name = "sha3" version = "0.10.8" @@ -841,15 +799,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termios" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" -dependencies = [ - "libc", -] - [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index a78d03c43cf29b5fb1bc981810760eef0c20cab2..0aaeac3aa08100814f2572872f2c2e0c9040808d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,9 @@ bitvec = "1.0.1" crc = "3.0.1" image = "0.24.6" kiss-tnc = "0.2.2" +rppal = "0.14.1" serde = { version = "1.0.160", features = ["derive"] } -serial = "0.4.0" sha3 = "0.10.7" subprocess = "0.2.9" tokio = { version = "1.27.0", features = ["full"] } toml = "0.7.3" - diff --git a/config.toml.example b/config.toml.example index afc4001fbfdcbb052b65dd6a1f3b8cb0d6a3af6d..109e6d26fa5e6417fb1d79cb97baf2aa95f5652d 100644 --- a/config.toml.example +++ b/config.toml.example @@ -14,9 +14,6 @@ callsign = "NOCALL" # Max image dimension. Comment this out to send full-sized images max_img_dimension = 1024 -# radio uart -uart = "/dev/ttyAMA0" - # Comment this section out if you're not using # APRS for control [control] diff --git a/src/config.rs b/src/config.rs index b4b8e2666ed490318a9d34fc4bce45574492f62c..f9057d1311a3d90aad91706b3eeb0804e563038b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,7 +14,6 @@ pub struct ControlConfig { pub struct Config { pub paths: Vec<PathBuf>, pub callsign: String, - pub uart: String, pub max_img_dimension: Option<u32>, pub control: Option<ControlConfig>, diff --git a/src/control.rs b/src/control.rs index 761d59f4336d131fcd1bb28ed20362aa76421801..ef709de4536cccaf30ab3f5705c4b2f066c0d371 100644 --- a/src/control.rs +++ b/src/control.rs @@ -11,13 +11,12 @@ use crate::{ config::Config, img::ImgManager, packet::{FecPacket, Packet}, - radio::UartRadio, + radio::McuRadio, ssdv::ssdv_encode, }; const IMAGE_PACKET_QUEUE_LENGTH: usize = 8192; const TEMP_REFRESH_INTERVAL: Duration = Duration::from_secs(5); -const SYNC_REFRESH_INTERVAL: Duration = Duration::from_secs(7); pub struct Controller { config: Config, @@ -35,8 +34,7 @@ impl Controller { { let callsign = self.config.callsign.clone(); - let uart = self.config.uart.clone(); - thread::spawn(|| Self::tx_thread(callsign, uart, img_rx, telem_rx)); + thread::spawn(|| Self::tx_thread(callsign, img_rx, telem_rx)); } { @@ -86,14 +84,9 @@ impl Controller { } // manages our transceiver - fn tx_thread( - callsign: String, - uart: String, - image_rx: Receiver<FecPacket>, - telem_rx: Receiver<Packet>, - ) { + fn tx_thread(callsign: String, image_rx: Receiver<FecPacket>, telem_rx: Receiver<Packet>) { let mut radio = loop { - let r = UartRadio::new(&uart); + let r = McuRadio::new(); match r { Ok(r) => break r, @@ -107,49 +100,53 @@ impl Controller { let mut text_msg_id = 0; let mut last_got_temp = Instant::now(); - let mut last_synced_at = Instant::now(); loop { - while let Ok(tm) = telem_rx.try_recv() { - let tm = tm.into_raw(&mut text_msg_id).into(); - - if let Err(e) = radio.send_packet(&tm) { - eprintln!("Could not send packet: {}", e); - } + if let Err(e) = Self::tx_thread_single_iter( + &callsign, + &image_rx, + &telem_rx, + &mut text_msg_id, + &mut last_got_temp, + &mut radio, + ) { + eprintln!("Radio error: {}", e); + radio.reset(); } + } + } - if let Ok(img) = image_rx.try_recv() { - if let Err(e) = radio.send_packet(&img) { - eprintln!("Could not send packet: {}", e); - } - } else { - thread::sleep(Duration::from_millis(50)); - } + fn tx_thread_single_iter( + callsign: &str, + image_rx: &Receiver<FecPacket>, + telem_rx: &Receiver<Packet>, + text_msg_id: &mut u16, + last_got_temp: &mut Instant, + radio: &mut McuRadio, + ) -> anyhow::Result<()> { + while let Ok(tm) = telem_rx.try_recv() { + let tm = tm.into_raw(text_msg_id).into(); + + radio.send_packet(&tm)?; + } - if Instant::now() - last_got_temp > TEMP_REFRESH_INTERVAL { - last_got_temp = Instant::now(); + if let Ok(img) = image_rx.try_recv() { + radio.send_packet(&img)?; + } else { + radio.flush()?; - let temp = match radio.get_temp() { - Ok(x) => x, - Err(e) => { - eprintln!("Could not get radio temp: {}", e); - continue; - } - }; + thread::sleep(Duration::from_millis(50)); + } - let packet = Packet::new_text_message(&callsign, &format!("Temp: {}", temp)); + if Instant::now() - *last_got_temp > TEMP_REFRESH_INTERVAL { + *last_got_temp = Instant::now(); - if let Err(e) = radio.send_packet(&packet.into_raw(&mut text_msg_id).into()) { - eprintln!("Could not send packet: {}", e); - } - } + let temp = radio.get_temp()?; - if Instant::now() - last_synced_at > SYNC_REFRESH_INTERVAL { - last_synced_at = Instant::now(); + let packet = Packet::new_text_message(callsign, &format!("Temp: {}", temp)); - if let Err(e) = radio.sync() { - eprintln!("Could not sync: {}", e); - } - } + radio.send_packet(&packet.into_raw(text_msg_id).into())?; } + + Ok(()) } } diff --git a/src/packet.rs b/src/packet.rs index 8bd7b63aba2f06b777c338088de06464b3f58038..1d52ce6bf13a224b9cb761911a346e8b367f7178 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -23,16 +23,13 @@ impl Packet { .iter() .chain(b": ".iter()) .chain(msg.as_bytes().iter()); + let mut len = 0; for (i, b) in iter.enumerate().take(TEXT_MESSAGE_LEN) { out[i] = *b; + len += 1; } - let len = msg - .as_bytes() - .len() - .min(TEXT_MESSAGE_LEN) - .try_into() - .unwrap(); + let len = len.min(TEXT_MESSAGE_LEN).try_into().unwrap(); Self::TextMessage(len, out) } diff --git a/src/radio.rs b/src/radio.rs index 8c338422d33c1d2c74cc3df3d45c4c5d6a905f65..6a35abfd577874854d3dac7667cbf3307b7378b7 100644 --- a/src/radio.rs +++ b/src/radio.rs @@ -1,102 +1,100 @@ -use serial::{unix::TTYPort, BaudRate::BaudOther, SerialPort}; -use std::{ - io::{self, Read, Write}, - thread::sleep, - time::Duration, +use anyhow::anyhow; +use rppal::{ + gpio::{Gpio, OutputPin}, + spi::{Bus, Mode, SlaveSelect, Spi}, }; +use std::{thread::sleep, time::Duration}; use crate::packet::FecPacket; -const BUFFER_STATUS_CMD: u8 = 0x00; const SEND_PACKET_CMD: u8 = 0x01; -const GET_TEMP_CMD: u8 = 0x02; -const SYNC_CMD: u8 = 0x03; +const NOP_CMD: u8 = 0x55; -const PACKET_SPACE: u8 = 0x02; +const PREAMBLE: u8 = 0xE5; +const NO_SPACE: u8 = 0x55; +const FREE_SPACE: u8 = 0xAA; // Used for talking to an RF4463 via a microcontroller -// The microcontroller takes in packet data over UART and sends it to the RF4463 -pub struct UartRadio { - uart: TTYPort, +// The microcontroller takes in packet data over SPI and sends it to the RF4463 +pub struct McuRadio { + spi: Spi, + reset_pin: OutputPin, } -impl UartRadio { - pub fn new(uart: &str) -> anyhow::Result<Self> { - let mut uart = serial::open(uart)?; - uart.reconfigure(&|settings| settings.set_baud_rate(BaudOther(921_600)))?; +impl McuRadio { + pub fn new() -> anyhow::Result<Self> { + let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss1, 1_000_000, Mode::Mode0)?; + let reset_pin = Gpio::new()?.get(8)?.into_output_high(); - Ok(Self { uart }) - } + let mut s = Self { reset_pin, spi }; - pub fn send_packet(&mut self, packet: &FecPacket) -> anyhow::Result<()> { - self.uart.write_all(&[SEND_PACKET_CMD])?; - self.uart.write_all(&packet.0)?; + s.reset(); - let mut buf = [0; 1]; - self.uart.read_exact(&mut buf)?; + Ok(s) + } - // wait until we have space for packets - while buf != [PACKET_SPACE] { - sleep(Duration::from_millis(10)); + pub fn reset(&mut self) { + self.reset_pin.set_low(); + sleep(Duration::from_millis(10)); + self.reset_pin.set_high(); + sleep(Duration::from_millis(1000)); + } - // poll to see if the buffer has emptied yet - self.uart.write_all(&[BUFFER_STATUS_CMD])?; + pub fn send_packet(&mut self, packet: &FecPacket) -> anyhow::Result<()> { + // wait until we have packet space - self.uart.read_exact(&mut buf)?; + while !self.get_info()?.0 { + sleep(Duration::from_millis(10)); } + self.spi.write(&[SEND_PACKET_CMD])?; + self.spi.write(&packet.0)?; + Ok(()) } pub fn get_temp(&mut self) -> anyhow::Result<f32> { - self.uart.write_all(&[GET_TEMP_CMD])?; - - let mut buf = [0; 4]; - self.uart.read_exact(&mut buf)?; + Ok(self.get_info()?.1) + } - let temp = f32::from_le_bytes(buf); + // sends a bunch of nops over SPI to clear out its DMA buffer + pub fn flush(&mut self) -> anyhow::Result<()> { + self.spi.write(&[NOP_CMD; 256])?; - Ok(temp) + Ok(()) } - pub fn sync(&mut self) -> anyhow::Result<()> { - // clear any pending packets - let mut tries = 0; - let mut buf = [0; 100]; - loop { - match self.uart.read(&mut buf) { - Ok(0) => break, - Ok(_) => { - tries += 1; - } - Err(e) if matches!(e.kind(), io::ErrorKind::TimedOut) => break, - Err(e) => { - return Err(e.into()); - } + // try 10 times and then give up + fn get_info(&mut self) -> anyhow::Result<(bool, f32)> { + for _ in 0..9 { + if let Ok(x) = self.get_info_once() { + return Ok(x); } } - if tries > 0 { - eprintln!("[WARN] Sync had pending packets(tries={tries})"); - } + self.get_info_once() + } - tries = 0; - let mut buf = [0; 10]; - loop { - self.uart.write_all(&[SYNC_CMD; 10])?; - match self.uart.read_exact(&mut buf) { - Ok(()) => break, - Err(e) if matches!(e.kind(), io::ErrorKind::TimedOut) => { - tries += 1; - } - Err(e) => { - return Err(e.into()); - } - } - } + fn get_info_once(&mut self) -> anyhow::Result<(bool, f32)> { + // get 256 bytes from the mcu + let mut buf = [0; 256]; + self.spi.transfer(&mut buf, &[NOP_CMD; 256])?; - eprintln!("Synced in {tries} tries"); + // look for our data + // it goes [0xE5, 0xE5, 0xAA || 0x55, ?, ?, ?, ?, 0xE5, 0xE5] + for i in 0..(buf.len() - 9) { + let chunk = &buf[i..(i + 9)]; - Ok(()) + if chunk[0..2] == [PREAMBLE, PREAMBLE] + && (chunk[2] == FREE_SPACE || chunk[2] == NO_SPACE) + && chunk[7..9] == [PREAMBLE, PREAMBLE] + { + let free = chunk[2] == FREE_SPACE; + let temp = f32::from_le_bytes(chunk[3..7].try_into().unwrap()); + + return Ok((free, temp)); + } + } + Err(anyhow!("could not find valid data")) } } diff --git a/src/ssdv.rs b/src/ssdv.rs index 63784d3fc4026b94d2a7f54a3175f9a6245fec53..f8e75907b1620032d9e4a5980bbe5e5ddce3de0a 100644 --- a/src/ssdv.rs +++ b/src/ssdv.rs @@ -1,12 +1,14 @@ use anyhow::{bail, Context}; +use subprocess::{Popen, PopenConfig, Redirection}; use crate::packet::RawPacket; // TODO eventually rewrite Ssdv in Rust? // Don't want to use FFI because then segfaults can hurt us pub fn ssdv_encode(callsign: &str, img: &[u8], img_idx: u8) -> anyhow::Result<Vec<RawPacket>> { - let (stdout, stderr) = subprocess::Exec::cmd("ssdv") - .args(&[ + let mut p = Popen::create( + &[ + "ssdv", "-e", "-c", callsign, @@ -15,10 +17,16 @@ pub fn ssdv_encode(callsign: &str, img: &[u8], img_idx: u8) -> anyhow::Result<Ve "6", "-i", &format!("{}", img_idx), - ]) - .stdin(img.to_vec()) - .communicate()? - .read()?; + ], + PopenConfig { + stdin: Redirection::Pipe, + stdout: Redirection::Pipe, + stderr: Redirection::Pipe, + ..Default::default() + }, + )?; + + let (stdout, stderr) = p.communicate_bytes(Some(img))?; if let Some(stderr) = stderr { if !stderr.is_empty() {