From 33396317b2e1c3469fca77f6cb5e15d6764a3170 Mon Sep 17 00:00:00 2001 From: Luke Else Date: Tue, 18 Jul 2023 21:25:34 +0100 Subject: [PATCH] Started moving some of the code out to separate files. From here I can then make everything asynchronous and more performant --- src/display/mod.rs | 48 ++++++++++++++++++++ src/error.rs | 9 ++++ src/gps/gpsdata.rs | 89 +++++++++++++++++++++++++++++++++++++ src/gps/mod.rs | 8 ++++ src/main.rs | 108 +++++---------------------------------------- 5 files changed, 164 insertions(+), 98 deletions(-) create mode 100644 src/display/mod.rs create mode 100644 src/error.rs create mode 100644 src/gps/gpsdata.rs create mode 100644 src/gps/mod.rs diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 0000000..22004c1 --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,48 @@ +use std::sync::{Arc, Mutex}; +use embedded_graphics::{ + prelude::*, + mono_font::{ascii::FONT_7X13, MonoTextStyle}, + pixelcolor::BinaryColor, + text::{Text, Alignment}, +}; +use esp_idf_hal::i2c::I2cDriver; +use ssd1306::{prelude::*, Ssd1306}; +use crate::error::Error; + +pub fn update_display(interface: I2CInterface>, data: Arc>) -> Result<(), Error> +where T: ToString +{ + let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + + display.init() + .map_err(|_| Error::DisplayError(DisplayError::SetupError))?; + + // let raw: ImageRaw = ImageRaw::new(include_bytes!("./../rust.raw"), 64); + // let im: Image<'_, _> = Image::new(&raw, Point::new(32, 0)); + // im.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_7X13, BinaryColor::On); + + display.clear(BinaryColor::Off) + .map_err(|_| Error::DisplayError(DisplayError::DrawingError))?; + + Text::with_alignment( + &data.lock().unwrap().to_string().as_str(), + Point::new(64, 10), + style, + Alignment::Center, + ) + .draw(&mut display) + .map_err(|_| Error::DisplayError(DisplayError::DrawingError))?; + + display.flush().map_err(|_| Error::DisplayError(DisplayError::FlushError))?; + Ok(()) +} + +/// Enum to make clear the stage of failure of the display +pub enum DisplayError { + SetupError, + DrawingError, + FlushError, +} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..15cf98a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,9 @@ +use esp_idf_sys::EspError; +use crate::display::DisplayError; +use crate::gps::GpsError; + +pub enum Error { + EspError(EspError), + DisplayError(DisplayError), + GpsError(GpsError), +} \ No newline at end of file diff --git a/src/gps/gpsdata.rs b/src/gps/gpsdata.rs new file mode 100644 index 0000000..a1370a4 --- /dev/null +++ b/src/gps/gpsdata.rs @@ -0,0 +1,89 @@ +use std::sync::{Mutex, Arc}; +use super::GpsError; + +use nmea_parser; +use nmea_parser::gnss::FaaMode; + +/// Data structure to store all relevant data collected from the gps +#[derive(Default)] +pub struct GpsData { + /// Latitude of reported GPS location + latitude: Option, + /// Longitude of reported GPS location + longitude: Option, + /// Calculated speed from GPS reciever + speed: Option, + /// Altitude reported from GPS location + altitude: Option, +} + +impl GpsData { + pub fn to_string(&self) -> String { + format!("Latitude: {:.4?} \nLongitude: {:.4?} \nSpeed: {:.1?}mph \nAltitude: {:.1?}m", + self.latitude.unwrap_or_default(), + self.longitude.unwrap_or_default(), + self.speed.unwrap_or_default(), + self.altitude.unwrap_or_default() + ) + } + + /// Function to asynchronously go through and update GPS data from a nmea stream + pub async fn update(&mut self, buf: &Vec) -> Result<(), GpsError> { + let mut nmea_parser = nmea_parser::NmeaParser::new(); + + // Format the nmea buffer into a usable string + let nmea_raw = String::from_utf8(buf.to_owned()) + .map_err(|_| GpsError::ReadError())?; + let nmea_vec: Vec<&str> = nmea_raw.split('$').collect(); + + // Loop through each sentence and use the information to update GPS data + for nmea_line in nmea_vec { + // Don't try and process / parse if the string is empty + if nmea_line.is_empty() { + continue; + } + + // Construct string that is in the correct format for parsing + let mut sentence = "$".to_string(); + sentence.push_str(nmea_line); + + let nmea = match nmea_parser.parse_sentence(sentence.as_str()) { + Ok(nmea) => nmea, + // Don't continue processing a sentence if we know that it isn't supported + Err(_) => { continue; } + }; + + // print decoded gps data to serial + match nmea { + nmea_parser::ParsedMessage::Gga(gga) => { + self.latitude = gga.latitude; + self.longitude = gga.longitude; + self.altitude = gga.altitude; + } + nmea_parser::ParsedMessage::Gll(gll) => { + if gll.faa_mode.unwrap_or(FaaMode::NotValid) == FaaMode::Autonomous { + self.latitude = gll.latitude; + self.longitude = gll.longitude; + } + }, + nmea_parser::ParsedMessage::Rmc(rms) => { + self.latitude = rms.latitude; + self.longitude = rms.longitude; + self.speed = Some(knots_to_mph(rms.sog_knots.unwrap_or_default())); + }, + _ => {} + } + } + + Ok(()) + } + + pub fn new() -> Arc> { + return Arc::new(Mutex::new(GpsData::default())) + } +} + +/// Function to simply convert knots to mph +fn knots_to_mph(knots: f64) -> f64{ + knots * 1.150779 +} \ No newline at end of file diff --git a/src/gps/mod.rs b/src/gps/mod.rs new file mode 100644 index 0000000..06309c4 --- /dev/null +++ b/src/gps/mod.rs @@ -0,0 +1,8 @@ +pub mod gpsdata; + +pub enum GpsError { + ParseError(nmea_parser::ParseError), + UpdateError(), + ReadError(), + NoData(), +} diff --git a/src/main.rs b/src/main.rs index eb70f7a..751027b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,18 +8,16 @@ use esp_idf_hal::{ i2c::{I2cConfig, I2cDriver} }; use esp_idf_hal; -use nmea_parser; -use nmea_parser::gnss::FaaMode; +use ssd1306::I2CDisplayInterface; + +mod error; +mod gps; +use gps::gpsdata::GpsData; + +mod display; + -use embedded_graphics::{ - prelude::*, - image::{Image, ImageRaw}, - mono_font::{ascii::FONT_7X13, MonoTextStyle}, - pixelcolor::BinaryColor, - text::{Text, Alignment}, -}; -use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; fn main() -> Result<(), EspError> { // It is necessary to call this function once. Otherwise some patches to the runtime @@ -57,104 +55,18 @@ fn main() -> Result<(), EspError> { scl, &config )?; - let interface = I2CDisplayInterface::new(i2c); - let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) - .into_buffered_graphics_mode(); - display.init().unwrap(); - let raw: ImageRaw = ImageRaw::new(include_bytes!("./rust.raw"), 64); - let im: Image<'_, _> = Image::new(&raw, Point::new(32, 0)); - - im.draw(&mut display).unwrap(); - - - let mut nmea_parser = nmea_parser::NmeaParser::new(); - let mut latest_data = GpsData{latitude: None, longitude: None, speed: None, direction: None}; + let mut latest_data = GpsData::new(); loop { // Read buffer from UART let mut buf: Vec = (0..uart.count()? as u8).collect(); uart.read(&mut buf[..], BLOCK)?; std::thread::sleep(std::time::Duration::from_millis(10)); + - let nmea_raw = String::from_utf8(buf).unwrap_or_default(); - let nmea_vec: Vec<&str> = nmea_raw.split('$').collect(); - for nmea_line in nmea_vec { - // Don't try and process / parse if the string is empty - if nmea_line.is_empty() { - continue; - } - - // Construct string that is in the correct format for parsing - let mut sentence = "$".to_string(); - sentence.push_str(nmea_line); - - let nmea = match nmea_parser.parse_sentence(sentence.as_str()) { - Ok(nmea) => nmea, - // Don't continue processin sentence if we know that it isn't supported - Err(_) => { continue; } - }; - - // print decoded gps data to serial - match nmea { - nmea_parser::ParsedMessage::Gll(gll) => { - println!("Latitude: {:.6?}", gll.latitude.unwrap_or_default()); - println!("Longitude: {:.6?}", gll.longitude.unwrap_or_default()); - println!("FAA Mode: {:?}m", gll.faa_mode.unwrap_or(nmea_parser::gnss::FaaMode::NotValid)); - println!("Data Valid: {:?}", gll.data_valid.unwrap_or(false)); - if gll.faa_mode.unwrap_or(FaaMode::NotValid) == FaaMode::Autonomous { - latest_data.latitude = gll.latitude; - latest_data.longitude = gll.longitude; - } - println!("\n\n"); - }, - nmea_parser::ParsedMessage::Gns(gns) => { - println!("Altitude: {:.2?}", gns.altitude.unwrap_or_default()); - println!("Num Sat: {:?}", gns.satellite_count.unwrap_or_default()); - println!("Time: {:?}", gns.timestamp.unwrap_or_default()); - }, - nmea_parser::ParsedMessage::Rmc(rms) => { - println!("Speed: {:.2?}mph", rms.sog_knots.unwrap_or_default() * 1.150779); - println!("Current Track: {:.2?}deg", rms.bearing.unwrap_or_default()); - latest_data.speed = Some(rms.sog_knots.unwrap_or_default() * 1.150779); - latest_data.direction = rms.bearing; - }, - _ => {} - } - } - - let style = MonoTextStyle::new(&FONT_7X13, BinaryColor::On); - - display.clear(BinaryColor::Off).unwrap(); - - Text::with_alignment( - &latest_data.to_string().as_str(), - Point::new(64, 10), - style, - Alignment::Center, - ) - .draw(&mut display).unwrap(); - - display.flush().unwrap(); } } -/// Data structure to store all relevant data collected from the gps -struct GpsData { - latitude: Option, - longitude: Option, - speed: Option, - direction: Option -} -impl GpsData { - pub fn to_string(&self) -> String { - format!("Latitude: {:.4?} \nLongitude: {:.4?} \nSpeed: {:.1?}mph \nDirection: {:.4?}deg", - self.latitude.unwrap_or_default(), - self.longitude.unwrap_or_default(), - self.speed.unwrap_or_default(), - self.direction.unwrap_or_default() - ) - } -}