diff --git a/src/beep.rs b/src/beep.rs new file mode 100644 index 0000000..e295ccd --- /dev/null +++ b/src/beep.rs @@ -0,0 +1,108 @@ +use embedded_svc::timer::{PeriodicTimer, TimerService}; +use esp_idf_hal::gpio::{Gpio4, Output}; +use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; +use esp_idf_hal::ledc::{CHANNEL0, TIMER0}; +use esp_idf_hal::prelude::*; +use esp_idf_svc::timer::{EspTimer, EspTimerService}; +use esp_idf_sys::EspError; +use log::{info, warn}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +type LedcChannel = Channel, P>; + +#[derive(Clone, Copy, Debug)] +struct BeepState { + beat: u8, + ringtone: ringtone::Type, +} + +impl BeepState { + pub fn new() -> Self { + Self { + beat: 0, + ringtone: ringtone::SILENCE, + } + } + pub fn from_ringtone(ringtone: ringtone::Type) -> Self { + Self { beat: 0, ringtone } + } +} + +pub struct Beep { + watch_timer: Option, + state: BeepState, +} +impl Beep { + pub fn new() -> Self { + return Beep { + watch_timer: None, + state: BeepState::new(), + }; + } + fn init_channel( + pin: Gpio4, + timer: TIMER0, + channel: CHANNEL0, + frequency: Hertz, + ) -> Result, TIMER0, CHANNEL0>, EspError> { + let config = TimerConfig::default().frequency(frequency); + let timer = Timer::new(timer, &config)?; + let channel = Channel::new(channel, timer, pin)?; + return Ok(channel); + } + pub fn play(&mut self, ringtone: ringtone::Type) -> anyhow::Result<()> { + if self.state.ringtone != ringtone { + self.state = BeepState::from_ringtone(ringtone); + } + let state = Arc::new(Mutex::new(self.state)); + let mut timer = EspTimerService::new()?.timer(move || { + info!("One-shot timer triggered"); + match state.lock().as_mut() { + Ok(state) => { + info!("B state.beat: {}", state.beat); + if let Err(err) = Self::play_once(state) { + warn!("{}", err); + } + info!("A state.beat: {}", state.beat); + } + Err(err) => { + warn!("Failed to lock state. {}", err); + } + } + })?; + timer.every(Duration::from_millis(250))?; + + self.watch_timer = Some(timer); + return anyhow::Ok(()); + } + + fn play_once(state: &mut BeepState) -> anyhow::Result<()> { + let pin = unsafe { Gpio4::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Failed to set GPIO 4 as ledc output. {}", err))?; + let hw_timer = unsafe { TIMER0::new() }; + let hw_channel = unsafe { CHANNEL0::new() }; + let curr = state.ringtone[state.beat as usize]; + let mut channel = Self::init_channel(pin, hw_timer, hw_channel, if curr < 1000 { 1000.Hz().into()} else {curr.Hz().into()} ) + .map_err(|err| anyhow::anyhow!("Failed to initialize channel. {}", err))?; + + if curr < 1000 { + channel.set_duty(0).expect("Failed to set duty"); + } else { + channel.set_duty(60).expect("Failed to set duty"); + } + + state.beat += 1; + if state.beat == 16 { + state.beat = 0; + } + return anyhow::Ok(()); + } +} +pub mod ringtone { + pub type Type = [u32; 16]; + pub const ADAPTER_DOWN: Type = [2000, 1950, 1900, 1800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + pub const BATTERY_LOW: Type = [2000, 1950, 0, 0, 1930, 1900, 0, 0, 2000, 1900, 0, 0, 1930, 1900, 0, 0]; + pub const SILENCE: Type = [0; 16]; + pub const SHUTDOWN: Type = [2300, 0, 2300, 2000, 2100, 2000, 0, 2300, 2000, 2100, 2000, 1900, 0, 0, 0, 0]; +} diff --git a/src/main.rs b/src/main.rs index 082a88d..fddf1e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,14 @@ use std::{ time::Duration, }; +mod beep; mod blink; mod message_queue; mod time; mod voltage_detection; mod wifi; -use crate::voltage_detection::VoltageDetectionWorker; +use crate::{beep::{Beep, ringtone}, voltage_detection::VoltageDetectionWorker}; use crate::{ message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; @@ -58,6 +59,8 @@ fn main() { ); blink.play(); }); + let mut beep = Beep::new(); + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); let mut voltage_detection = VoltageDetection::new(); @@ -89,19 +92,5 @@ fn main() { loop { sleep(Duration::from_millis(1000)); - // if let Some(ref mut mq_client) = mq.client { - // let timestamps = time.get_time().as_millis(); - - // info!("timestamps {}", timestamps); - // mq_client - // .publish( - // "ups-0.2/heartbeat", - // QoS::AtMostOnce, - // false, - // timestamps.to_string().as_bytes(), - // ) - // .map_err(|err| warn!("publish heartbeat failed {}", err)) - // .unwrap(); - // } } }