From 4781570f8e1e8148a0a76682fcb1e303d06140eb Mon Sep 17 00:00:00 2001 From: Djeeberjr Date: Mon, 2 Jun 2025 15:12:59 +0200 Subject: [PATCH] use anyhow for errors --- Cargo.lock | 7 +++++ Cargo.toml | 1 + src/feedback.rs | 15 ++++------ src/gpio_buzzer.rs | 9 ++---- src/hardware.rs | 21 +++++-------- src/hotspot.rs | 75 +++++++++++----------------------------------- src/id_store.rs | 19 +++++------- src/main.rs | 11 +++---- src/mock.rs | 18 +++++------ src/pm3.rs | 10 +++---- src/spi_led.rs | 6 ++-- 11 files changed, 72 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69fb7af..73ff820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "async-stream" version = "0.3.6" @@ -419,6 +425,7 @@ dependencies = [ name = "fw-anwesenheit" version = "0.1.0" dependencies = [ + "anyhow", "chrono", "gpio", "log", diff --git a/Cargo.toml b/Cargo.toml index 3ec72f6..2bd6631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ rppal = { version = "0.22.1", features = ["hal"] } smart-leds = "0.3" ws2812-spi = "0.3" rgb = "0.8.50" +anyhow = "1.0.98" diff --git a/src/feedback.rs b/src/feedback.rs index bb87bf3..9f48c6a 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -1,7 +1,8 @@ +use anyhow::Result; use log::error; use rgb::RGB8; use smart_leds::colors::{GREEN, RED}; -use std::{error::Error, time::Duration}; +use std::time::Duration; use tokio::{join, time::sleep}; use crate::hardware::{Buzzer, StatusLed}; @@ -41,18 +42,14 @@ impl Feedback { }); } - async fn blink_led_for_duration( - led: &mut L, - color: RGB8, - duration: Duration, - ) -> Result<(), Box> { + async fn blink_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> { led.turn_on(color)?; sleep(duration).await; led.turn_off()?; Ok(()) } - async fn beep_ack(buzzer: &mut B) -> Result<(), Box> { + async fn beep_ack(buzzer: &mut B) -> Result<()> { buzzer .modulated_tone(1200.0, Duration::from_millis(100)) .await?; @@ -63,7 +60,7 @@ impl Feedback { Ok(()) } - async fn beep_nak(buzzer: &mut B) -> Result<(), Box> { + async fn beep_nak(buzzer: &mut B) -> Result<()> { buzzer .modulated_tone(600.0, Duration::from_millis(150)) .await?; @@ -81,7 +78,7 @@ pub type FeedbackImpl = Feedback; pub type FeedbackImpl = Feedback; impl FeedbackImpl { - pub fn new() -> Result> { + pub fn new() -> Result { #[cfg(feature = "mock_pi")] { Ok(Feedback { diff --git a/src/gpio_buzzer.rs b/src/gpio_buzzer.rs index 1c82bb2..7752f31 100644 --- a/src/gpio_buzzer.rs +++ b/src/gpio_buzzer.rs @@ -1,5 +1,6 @@ +use anyhow::Result; use rppal::pwm::{Channel, Polarity, Pwm}; -use std::{error::Error, time::Duration}; +use std::time::Duration; use tokio::time::sleep; use crate::hardware::Buzzer; @@ -26,11 +27,7 @@ impl GPIOBuzzer { } impl Buzzer for GPIOBuzzer { - async fn modulated_tone( - &mut self, - frequency_hz: f64, - duration: Duration, - ) -> Result<(), Box> { + async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> { self.pwm.set_frequency(frequency_hz, 0.5)?; // 50% duty cycle (square wave) self.pwm.enable()?; sleep(duration).await; diff --git a/src/hardware.rs b/src/hardware.rs index d96fde1..21db1d7 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -1,6 +1,5 @@ -use std::{error::Error, time::Duration}; - -use crate::hotspot::{HotspotError}; +use anyhow::Result; +use std::time::Duration; #[cfg(feature = "mock_pi")] use crate::mock::MockHotspot; @@ -9,9 +8,9 @@ use crate::mock::MockHotspot; use crate::hotspot::NMHotspot; pub trait StatusLed { - fn turn_off(&mut self) -> Result<(), Box>; + fn turn_off(&mut self) -> Result<()>; - fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box>; + fn turn_on(&mut self, color: rgb::RGB8) -> Result<()>; } pub trait Buzzer { @@ -19,22 +18,18 @@ pub trait Buzzer { &mut self, frequency_hz: f64, duration: Duration, - ) -> impl Future>> + std::marker::Send; + ) -> impl Future> + std::marker::Send; } pub trait Hotspot { - fn enable_hotspot( - &self, - ) -> impl std::future::Future> + std::marker::Send; + fn enable_hotspot(&self) -> impl std::future::Future> + std::marker::Send; - fn disable_hotspot( - &self, - ) -> impl std::future::Future> + std::marker::Send; + fn disable_hotspot(&self) -> impl std::future::Future> + std::marker::Send; } /// Create a struct to manage the hotspot /// Respects the `mock_pi` flag. -pub fn create_hotspot() -> Result { +pub fn create_hotspot() -> Result { #[cfg(feature = "mock_pi")] { Ok(MockHotspot {}) diff --git a/src/hotspot.rs b/src/hotspot.rs index 4a1847f..ec9cfc3 100644 --- a/src/hotspot.rs +++ b/src/hotspot.rs @@ -1,9 +1,6 @@ -use log::{error, trace, warn}; -use std::{ - env, - fmt::{self}, - process::Output, -}; +use anyhow::{Result, anyhow}; +use log::{trace, warn}; +use std::env; use tokio::process::Command; use crate::hardware::Hotspot; @@ -13,36 +10,6 @@ const CON_NAME: &str = "fwa-hotspot"; const PASSWORD: &str = "a9LG2kUVrsRRVUo1"; const IPV4_ADDRES: &str = "192.168.4.1/24"; -#[derive(Debug)] -pub enum HotspotError { - IoError(std::io::Error), - NonZeroExit(Output), - PasswordToShort, -} - -impl fmt::Display for HotspotError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - HotspotError::IoError(err) => { - write!(f, "Failed to run hotspot command. I/O error: {err}") - } - HotspotError::NonZeroExit(output) => { - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - write!( - f, - "Failed to run hotspot command.\nStdout: {stdout}\nStderr: {stderr}", - ) - } - HotspotError::PasswordToShort => { - write!(f, "The password must be at leat 8 characters long") - } - } - } -} - -impl std::error::Error for HotspotError {} - /// NetworkManager Hotspot pub struct NMHotspot { ssid: String, @@ -52,7 +19,7 @@ pub struct NMHotspot { } impl NMHotspot { - pub fn new_from_env() -> Result { + pub fn new_from_env() -> Result { let ssid = env::var("HOTSPOT_SSID").unwrap_or(SSID.to_owned()); let password = env::var("HOTSPOT_PW").unwrap_or_else(|_| { warn!("HOTSPOT_PW not set. Using default password"); @@ -60,8 +27,7 @@ impl NMHotspot { }); if password.len() < 8 { - error!("Hotspot PW is to short"); - return Err(HotspotError::PasswordToShort); + return Err(anyhow!("Hotspot password to short")); } Ok(NMHotspot { @@ -72,7 +38,7 @@ impl NMHotspot { }) } - async fn create_hotspot(&self) -> Result<(), HotspotError> { + async fn create_hotspot(&self) -> Result<()> { let cmd = Command::new("nmcli") .args(["device", "wifi", "hotspot"]) .arg("con-name") @@ -82,14 +48,13 @@ impl NMHotspot { .arg("password") .arg(&self.password) .output() - .await - .map_err(HotspotError::IoError)?; + .await?; trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout)); trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr)); if !cmd.status.success() { - return Err(HotspotError::NonZeroExit(cmd)); + return Err(anyhow!("nmcli command had non-zero exit code")); } let cmd = Command::new("nmcli") @@ -101,24 +66,22 @@ impl NMHotspot { .arg("ipv4.addresses") .arg(&self.ipv4) .output() - .await - .map_err(HotspotError::IoError)?; + .await?; if !cmd.status.success() { - return Err(HotspotError::NonZeroExit(cmd)); + return Err(anyhow!("nmcli command had non-zero exit code")); } Ok(()) } /// Checks if the connection already exists - async fn exists(&self) -> Result { + async fn exists(&self) -> Result { let cmd = Command::new("nmcli") .args(["connection", "show"]) .arg(&self.con_name) .output() - .await - .map_err(HotspotError::IoError)?; + .await?; trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout)); trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr)); @@ -128,7 +91,7 @@ impl NMHotspot { } impl Hotspot for NMHotspot { - async fn enable_hotspot(&self) -> Result<(), HotspotError> { + async fn enable_hotspot(&self) -> Result<()> { if !self.exists().await? { self.create_hotspot().await?; } @@ -137,32 +100,30 @@ impl Hotspot for NMHotspot { .args(["connection", "up"]) .arg(&self.con_name) .output() - .await - .map_err(HotspotError::IoError)?; + .await?; trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout)); trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr)); if !cmd.status.success() { - return Err(HotspotError::NonZeroExit(cmd)); + return Err(anyhow!("nmcli command had non-zero exit code")); } Ok(()) } - async fn disable_hotspot(&self) -> Result<(), HotspotError> { + async fn disable_hotspot(&self) -> Result<()> { let cmd = Command::new("nmcli") .args(["connection", "down"]) .arg(&self.con_name) .output() - .await - .map_err(HotspotError::IoError)?; + .await?; trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout)); trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr)); if !cmd.status.success() { - return Err(HotspotError::NonZeroExit(cmd)); + return Err(anyhow!("nmcli command had non-zero exit code")); } Ok(()) diff --git a/src/id_store.rs b/src/id_store.rs index e2e4914..62ac331 100644 --- a/src/id_store.rs +++ b/src/id_store.rs @@ -1,11 +1,8 @@ -use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - error::Error, -}; -use tokio::fs; - use crate::{id_mapping::IDMapping, tally_id::TallyID}; +use anyhow::{Result, anyhow}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use tokio::fs; /// Represents a single day that IDs can attend #[derive(Deserialize, Serialize)] @@ -30,7 +27,7 @@ impl IDStore { } /// Creats a new `IDStore` from a json file - pub async fn new_from_json(filepath: &str) -> Result> { + pub async fn new_from_json(filepath: &str) -> Result { let read_string = fs::read_to_string(filepath).await?; Ok(serde_json::from_str(&read_string)?) } @@ -59,14 +56,14 @@ impl IDStore { } /// Writes the store to a json file - pub async fn export_json(&self, filepath: &str) -> Result<(), Box> { + pub async fn export_json(&self, filepath: &str) -> Result<()> { fs::write(filepath, serde_json::to_string(&self)?).await?; Ok(()) } /// Export the store to a csv file. /// With days in the rows and IDs in the collum. - pub fn export_csv(&self) -> Result> { + pub fn export_csv(&self) -> Result { let mut csv = String::new(); let seperator = ";"; let mut user_ids: HashSet = HashSet::new(); @@ -100,7 +97,7 @@ impl IDStore { let was_there: bool = self .days .get(day) - .ok_or("Failed to access day")? + .ok_or(anyhow!("Failed to access day"))? .ids .contains(user_id); diff --git a/src/main.rs b/src/main.rs index a96fe23..cdf9298 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use hardware::{Hotspot, create_hotspot}; use id_store::IDStore; use log::{error, info, warn}; use pm3::run_pm3; -use std::{env, error::Error, sync::Arc, time::Duration}; +use std::{env, sync::Arc, time::Duration}; use tally_id::TallyID; use tokio::{ fs, @@ -17,6 +17,7 @@ use tokio::{ try_join, }; use webserver::start_webserver; +use anyhow::Result; mod activity_fairing; mod feedback; @@ -39,7 +40,7 @@ async fn run_webserver( store: Arc>, id_channel: Sender, hotspot: Arc>, -) -> Result<(), Box> +) -> Result<()> where H: Hotspot + Send + Sync + 'static, { @@ -59,7 +60,7 @@ where Ok(()) } -async fn load_or_create_store() -> Result> { +async fn load_or_create_store() -> Result { if fs::try_exists(STORE_PATH).await? { info!("Loading data from file"); IDStore::new_from_json(STORE_PATH).await @@ -89,7 +90,7 @@ async fn handle_ids_loop( id_store: Arc>, hotspot: Arc>, mut user_feedback: FeedbackImpl, -) -> Result<(), Box> { +) -> Result<()> { while let Ok(tally_id_string) = id_channel.recv().await { let tally_id = TallyID(tally_id_string); @@ -123,7 +124,7 @@ async fn handle_ids_loop( } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<()> { logger::setup_logger(); info!("Starting application"); diff --git a/src/mock.rs b/src/mock.rs index b2ed20b..bffe022 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -1,6 +1,6 @@ -use std::{error::Error, time::Duration}; - +use anyhow::Result; use log::debug; +use std::time::Duration; use tokio::time::sleep; use crate::hardware::{Buzzer, Hotspot, StatusLed}; @@ -8,11 +8,7 @@ use crate::hardware::{Buzzer, Hotspot, StatusLed}; pub struct MockBuzzer {} impl Buzzer for MockBuzzer { - async fn modulated_tone( - &mut self, - frequency_hz: f64, - duration: Duration, - ) -> Result<(), Box> { + async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> { debug!("MockBuzzer: modulte tone: {frequency_hz} Hz"); sleep(duration).await; Ok(()) @@ -22,12 +18,12 @@ impl Buzzer for MockBuzzer { pub struct MockLed {} impl StatusLed for MockLed { - fn turn_off(&mut self) -> Result<(), Box> { + fn turn_off(&mut self) -> Result<()> { debug!("Turn mock LED off"); Ok(()) } - fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box> { + fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> { debug!("Turn mock LED on to: {color}"); Ok(()) } @@ -36,12 +32,12 @@ impl StatusLed for MockLed { pub struct MockHotspot {} impl Hotspot for MockHotspot { - async fn enable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> { + async fn enable_hotspot(&self) -> Result<()> { debug!("Mockhotspot: Enable hotspot"); Ok(()) } - async fn disable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> { + async fn disable_hotspot(&self) -> Result<()> { debug!("Mockhotspot: Disable hotspot"); Ok(()) } diff --git a/src/pm3.rs b/src/pm3.rs index ec775ed..fa1a135 100644 --- a/src/pm3.rs +++ b/src/pm3.rs @@ -1,6 +1,6 @@ +use anyhow::{Result, anyhow}; use log::{debug, info, trace, warn}; use std::env; -use std::error::Error; use std::process::Stdio; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::Command; @@ -11,7 +11,7 @@ use tokio::sync::broadcast; /// Runs the pm3 binary and monitors it's output /// The pm3 binary is ether set in the env var PM3_BIN or found in the path /// The ouput is parsed and send via the `tx` channel -pub async fn run_pm3(tx: broadcast::Sender) -> Result<(), Box> { +pub async fn run_pm3(tx: broadcast::Sender) -> Result<()> { kill_orphans().await; let pm3_path = match env::var("PM3_BIN") { @@ -32,8 +32,8 @@ pub async fn run_pm3(tx: broadcast::Sender) -> Result<(), Box .stdin(Stdio::piped()) .spawn()?; - let stdout = cmd.stdout.take().ok_or("Failed to get stdout")?; - let mut stdin = cmd.stdin.take().ok_or("Failed to get stdin")?; + let stdout = cmd.stdout.take().ok_or(anyhow!("Failed to get stdout"))?; + let mut stdin = cmd.stdin.take().ok_or(anyhow!("Failed to get stdin"))?; let mut reader = BufReader::new(stdout).lines(); @@ -65,7 +65,7 @@ pub async fn run_pm3(tx: broadcast::Sender) -> Result<(), Box if status.success() { Ok(()) } else { - Err("PM3 exited with a non-zero exit code".into()) + Err(anyhow!("PM3 exited with a non-zero exit code")) } } diff --git a/src/spi_led.rs b/src/spi_led.rs index 936ddfd..4094690 100644 --- a/src/spi_led.rs +++ b/src/spi_led.rs @@ -1,6 +1,6 @@ +use anyhow::Result; use rppal::spi::{Bus, Mode, SlaveSelect, Spi}; use smart_leds::SmartLedsWrite; -use std::error::Error; use ws2812_spi::Ws2812; use crate::hardware::StatusLed; @@ -20,13 +20,13 @@ impl SpiLed { } impl StatusLed for SpiLed { - fn turn_off(&mut self) -> Result<(), Box> { + fn turn_off(&mut self) -> Result<()> { self.controller .write(vec![rgb::RGB8::new(0, 0, 0)].into_iter())?; Ok(()) } - fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box> { + fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> { self.controller.write(vec![color].into_iter())?; Ok(()) }