diff --git a/src/buzzer.rs b/src/buzzer.rs index 5ebe8e6..0ac517e 100644 --- a/src/buzzer.rs +++ b/src/buzzer.rs @@ -2,10 +2,14 @@ use rppal::pwm::{Channel, Error, Polarity, Pwm}; use std::{future::Future, time::Duration}; use tokio::time::sleep; -pub trait Buzzer { - fn beep_ack(&mut self) -> impl Future> + std::marker::Send; +const DEFAULT_PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12 - fn beep_nak(&mut self) -> impl Future> + std::marker::Send; +pub trait Buzzer { + fn modulated_tone( + &mut self, + frequency_hz: f64, + duration: Duration, + ) -> impl Future> + std::marker::Send; } pub struct GPIOBuzzer { @@ -13,49 +17,26 @@ pub struct GPIOBuzzer { } impl GPIOBuzzer { - /// Create a new GPIOBuzzer instance. - /// 0.5 duty cyle - /// # Arguments - /// * "channel" - PWM channel for buzzer PWM0 = GPIO 12 / PWM1 = GPIO 13 - pub fn new(channel: Channel) -> Result { + pub fn new_from_channel(channel: Channel) -> Result { // Enable with dummy values; we'll set frequency/duty in the tone method let duty_cycle: f64 = 0.5; let pwm = Pwm::with_frequency(channel, 1000.0, duty_cycle, Polarity::Normal, true)?; - pwm.disable()?; // Start disabled + pwm.disable()?; Ok(GPIOBuzzer { pwm }) } - /// Play a tone using hardware PWM on supported GPIO pins. - /// - /// # Arguments - /// * `frequency` - Frequency in Hz. - /// * `duration_ms` - Duration in milliseconds. - async fn modulated_tone(&mut self, frequency: f64, duration_ms: u64) -> Result<(), Error> { - self.pwm.set_frequency(frequency, 0.5)?; // 50% duty cycle (square wave) - self.pwm.enable()?; - sleep(Duration::from_millis(duration_ms)).await; - self.pwm.disable()?; - Ok(()) + pub fn new_default() -> Result { + Self::new_from_channel(DEFAULT_PWM_CHANNEL_BUZZER) } } impl Buzzer for GPIOBuzzer { - async fn beep_ack(&mut self) -> Result<(), Error> { - let sleep_ms: u64 = 10; - - self.modulated_tone(1200.0, 100).await?; - sleep(Duration::from_millis(sleep_ms)).await; - self.modulated_tone(2000.0, 50).await?; - Ok(()) - } - - async fn beep_nak(&mut self) -> Result<(), Error> { - let sleep_ms: u64 = 100; - - self.modulated_tone(600.0, 150).await?; - sleep(Duration::from_millis(sleep_ms)).await; - self.modulated_tone(600.0, 150).await?; + async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<(), Error> { + self.pwm.set_frequency(frequency_hz, 0.5)?; // 50% duty cycle (square wave) + self.pwm.enable()?; + sleep(duration).await; + self.pwm.disable()?; Ok(()) } } diff --git a/src/color.rs b/src/color.rs deleted file mode 100644 index 66970d1..0000000 --- a/src/color.rs +++ /dev/null @@ -1,56 +0,0 @@ -use rgb::Rgb; - -#[derive(Debug)] -pub enum NamedColor { - Red, - Green, - Blue, - White, - Off, - Yellow, - Cyan, - Magenta, -} - -impl Into> for NamedColor { - fn into(self) -> Rgb { - match self { - NamedColor::Red => Rgb { r: 150, g: 0, b: 0 }, - NamedColor::Green => Rgb { r: 0, g: 150, b: 0 }, - NamedColor::Blue => Rgb { r: 0, g: 0, b: 150 }, - NamedColor::White => Rgb { - r: 255, - g: 255, - b: 255, - }, - NamedColor::Off => Rgb { r: 0, g: 0, b: 0 }, - NamedColor::Yellow => Rgb { - r: 255, - g: 255, - b: 0, - }, - NamedColor::Cyan => Rgb { - r: 0, - g: 255, - b: 255, - }, - NamedColor::Magenta => Rgb { - r: 255, - g: 0, - b: 255, - }, - } - } -} - - -impl IntoIterator for NamedColor { - type Item = Self; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![self].into_iter() - } -} - diff --git a/src/feedback.rs b/src/feedback.rs index 65d5b42..6701584 100644 --- a/src/feedback.rs +++ b/src/feedback.rs @@ -1,7 +1,8 @@ use log::error; -use rppal::pwm::Channel; -use std::error::Error; -use tokio::join; +use rgb::RGB8; +use smart_leds::colors::{GREEN, RED}; +use std::{error::Error, time::Duration}; +use tokio::{join, time::sleep}; use crate::{ buzzer::{Buzzer, GPIOBuzzer}, @@ -11,7 +12,7 @@ use crate::{ #[cfg(feature = "mock_pi")] use crate::mock::{MockBuzzer, MockLed}; -const PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12 +const LED_BLINK_DURATION: Duration = Duration::from_secs(1); pub struct Feedback { buzzer: B, @@ -20,28 +21,57 @@ pub struct Feedback { impl Feedback { pub async fn success(&mut self) { - let (buzzer_result, led_result) = - join!(self.buzzer.beep_ack(), self.led.turn_green_on_1s()); + let buzzer_handle = Self::beep_ack(&mut self.buzzer); + let led_handle = Self::blink_led_for_duration(&mut self.led, GREEN, LED_BLINK_DURATION); + let (buzzer_result, _) = join!(buzzer_handle, led_handle); buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}"); }); - - led_result.unwrap_or_else(|err| { - error!("Failed to set LED: {err}"); - }); } pub async fn failure(&mut self) { - let (buzzer_result, led_result) = join!(self.buzzer.beep_nak(), self.led.turn_red_on_1s()); + let buzzer_handle = Self::beep_nak(&mut self.buzzer); + let led_handle = Self::blink_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION); + + let (buzzer_result, _) = join!(buzzer_handle, led_handle); buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}"); }); + } - led_result.unwrap_or_else(|err| { - error!("Failed to set LED: {err}"); - }); + async fn blink_led_for_duration( + led: &mut L, + color: RGB8, + duration: Duration, + ) -> Result<(), Box> { + led.turn_on(color)?; + sleep(duration).await; + led.turn_off()?; + Ok(()) + } + + async fn beep_ack(buzzer: &mut B) -> Result<(), Box> { + buzzer + .modulated_tone(1200.0, Duration::from_millis(100)) + .await?; + sleep(Duration::from_millis(10)).await; + buzzer + .modulated_tone(2000.0, Duration::from_millis(50)) + .await?; + Ok(()) + } + + async fn beep_nak(buzzer: &mut B) -> Result<(), Box> { + buzzer + .modulated_tone(600.0, Duration::from_millis(150)) + .await?; + sleep(Duration::from_millis(100)).await; + buzzer + .modulated_tone(600.0, Duration::from_millis(150)) + .await?; + Ok(()) } } @@ -62,7 +92,7 @@ impl FeedbackImpl { #[cfg(not(feature = "mock_pi"))] { Ok(Feedback { - buzzer: GPIOBuzzer::new(PWM_CHANNEL_BUZZER)?, + buzzer: GPIOBuzzer::new_default()?, led: SpiLed::new()?, }) } diff --git a/src/led.rs b/src/led.rs index 302d83b..0602fea 100644 --- a/src/led.rs +++ b/src/led.rs @@ -1,22 +1,14 @@ -use std::time::Duration; - -use rppal::spi::{Bus, Error, Mode, SlaveSelect, Spi}; +use rppal::spi::{Bus, Mode, SlaveSelect, Spi}; use smart_leds::SmartLedsWrite; -use tokio::time::sleep; +use std::error::Error; use ws2812_spi::Ws2812; -use crate::color::NamedColor; - -const STATUS_DURATION: Duration = Duration::from_secs(1); // 1s sleep for all status led signals +const SPI_CLOCK_SPEED: u32 = 3_800_000; pub trait StatusLed { - fn turn_green_on_1s( - &mut self, - ) -> impl std::future::Future> + std::marker::Send; + fn turn_off(&mut self) -> Result<(), Box>; - fn turn_red_on_1s( - &mut self, - ) -> impl std::future::Future> + std::marker::Send; + fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box>; } pub struct SpiLed { @@ -24,30 +16,22 @@ pub struct SpiLed { } impl SpiLed { - pub fn new() -> Result { - let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 3_800_000, Mode::Mode0)?; + pub fn new() -> Result { + let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, SPI_CLOCK_SPEED, Mode::Mode0)?; let controller = Ws2812::new(spi); Ok(SpiLed { controller }) } - - fn turn_off(&mut self) -> Result<(), Error> { - self.controller.write(NamedColor::Off.into_iter())?; - Ok(()) - } } impl StatusLed for SpiLed { - async fn turn_green_on_1s(&mut self) -> Result<(), Error> { - self.controller.write(NamedColor::Green.into_iter())?; - sleep(STATUS_DURATION).await; - self.turn_off()?; + fn turn_off(&mut self) -> Result<(), Box> { + self.controller + .write(vec![rgb::RGB8::new(0, 0, 0)].into_iter())?; Ok(()) } - async fn turn_red_on_1s(&mut self) -> Result<(), Error> { - self.controller.write(NamedColor::Red.into_iter())?; - sleep(STATUS_DURATION).await; - self.turn_off()?; + fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box> { + self.controller.write(vec![color].into_iter())?; Ok(()) } } diff --git a/src/main.rs b/src/main.rs index f449d4d..f62cbf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,6 @@ use mock::MockHotspot; mod activity_fairing; mod buzzer; -mod color; mod feedback; mod hotspot; mod id_mapping; diff --git a/src/mock.rs b/src/mock.rs index 3846330..552e9f6 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -1,17 +1,20 @@ +use std::time::Duration; + use log::debug; +use tokio::time::sleep; use crate::{buzzer::Buzzer, hotspot::Hotspot, led::StatusLed}; pub struct MockBuzzer {} impl Buzzer for MockBuzzer { - async fn beep_ack(&mut self) -> Result<(), rppal::pwm::Error> { - debug!("Mockbuzzer: ACK"); - Ok(()) - } - - async fn beep_nak(&mut self) -> Result<(), rppal::pwm::Error> { - debug!("Mockbuzzer: NAK"); + async fn modulated_tone( + &mut self, + frequency_hz: f64, + duration: Duration, + ) -> Result<(), rppal::pwm::Error> { + debug!("MockBuzzer: modulte tone: {frequency_hz} Hz"); + sleep(duration).await; Ok(()) } } @@ -19,13 +22,13 @@ impl Buzzer for MockBuzzer { pub struct MockLed {} impl StatusLed for MockLed { - async fn turn_green_on_1s(&mut self) -> Result<(), rppal::spi::Error> { - debug!("Mockled: Turn LED green for 1 sec"); + fn turn_off(&mut self) -> Result<(), Box> { + debug!("Turn mock LED off"); Ok(()) } - async fn turn_red_on_1s(&mut self) -> Result<(), rppal::spi::Error> { - debug!("Mockled: Turn LED red for 1 sec"); + fn turn_on(&mut self, color: rgb::RGB8) -> Result<(), Box> { + debug!("Turn mock LED on to: {color}"); Ok(()) } }