diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs index a74e3b8..a7b940a 100644 --- a/src/dc_out_controller.rs +++ b/src/dc_out_controller.rs @@ -1,8 +1,8 @@ -use anyhow::{Context, Ok, Result}; +use anyhow::{Ok, Result, anyhow}; use retry; use std::{ sync::{ - mpsc::{self}, + mpsc, Arc, Mutex, }, thread, @@ -10,15 +10,37 @@ use std::{ }; use embedded_hal::digital::blocking::OutputPin; -use log::{info, trace, warn}; +use log::*; + +pub enum DcOutStatus { + On, + Off, + TurningOn(mpsc::Receiver), + TurningOff(mpsc::Receiver), +} + +impl DcOutStatus { + pub fn is_on(&self) -> bool { + match self { + DcOutStatus::On => true, + _ => false, + } + } + pub fn is_off(&self) -> bool { + match self { + DcOutStatus::Off => true, + _ => false, + } + } +} pub struct DcOutController

where P: OutputPin + Send, { - pub state: bool, pin: Arc>, shutdown_tx: Option>, + status: DcOutStatus, } impl

DcOutController

@@ -29,15 +51,21 @@ where { pub fn new(pin: P) -> Self { return Self { - state: false, + status: DcOutStatus::On, pin: Arc::new(Mutex::new(pin)), shutdown_tx: None, }; } - pub fn start(&mut self) -> Result<()> { + pub fn open(&mut self) -> Result<()> { + if self.status.is_on() { + trace!("DC OUT already on, skipping"); + return Ok(()); + } + + let pin = self.pin.clone(); let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || { - self.pin.lock() + pin.lock() }) .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; @@ -45,6 +73,9 @@ where pin.set_low() }) .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; + + self.status = DcOutStatus::On; + info!("DC OUT ON"); Ok(()) } @@ -60,6 +91,9 @@ where let pin = Arc::clone(&self.pin); + let (status_tx, status_rx) = mpsc::channel(); + self.status = DcOutStatus::TurningOff(status_rx); + thread::spawn(move || { // Wait for computer shutdown finished let target_at = SystemTime::now() + time::Duration::from_secs(10); @@ -74,14 +108,24 @@ where } info!("Shutdown timeout, force shutdown..."); - retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || { + + let result = || { + retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || { return pin .lock() .map_err(|_| anyhow::anyhow!("Can not lock pin"))? .set_high() .map_err(|_| anyhow::anyhow!("Can not shutdown")); - }).expect("Failed to shutdown"); - info!("Shutdown finished"); + }).map_err(|_| anyhow!("Failed to send shutdown status"))?; + status_tx.send(true).map_err(|_| anyhow!("Failed to send shutdown status"))?; + Ok(()) + }; + + if let Err(e) = result() { + warn!("Failed to shutdown: {}", e); + } else { + info!("Shutdown finished"); + } break; } }); @@ -101,9 +145,37 @@ where } self.shutdown_tx = None; }; - - self.start()?; - info!("Power restored"); Ok(()) } + + pub fn get_status(&mut self) -> DcOutStatus { + match &self.status { + DcOutStatus::TurningOn(rx) => { + rx.try_recv().map_or((), |state| { + trace!("DcOutStatus::TurningOn({})", state); + if state { + self.status = DcOutStatus::On; + } else { + self.status = DcOutStatus::Off; + } + }) + }, + DcOutStatus::TurningOff(rx) => { + rx.try_recv().map_or((), |state| { + trace!("DcOutStatus::TurningOff({})", state); + if state { + self.status = DcOutStatus::Off; + } else { + self.status = DcOutStatus::On; + } + }) + }, + _default => {} + } + match &self.status { + DcOutStatus::On => DcOutStatus::On, + DcOutStatus::Off => DcOutStatus::Off, + _ => DcOutStatus::On, + } + } } diff --git a/src/main.rs b/src/main.rs index c259209..553350f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(is_some_with)] use esp_idf_sys as _; +use log::error; use std::{thread, time::Duration, sync::mpsc, env}; mod beep; @@ -8,7 +9,8 @@ mod dc_out_controller; mod manager; mod screen; fn main() { - env::set_var("RUST_LOG", env!("RUST_LOG")); + env::set_var("DEFMT_LOG", "trace"); + env::set_var("RUST_LOG", "trace"); env_logger::init(); // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, // or else some patches to the runtime implemented by esp-idf-sys might not link properly. @@ -69,7 +71,12 @@ fn main() { tx, ).expect("Failed to create manager"); loop { - manager.handling_once().expect("Failed to handle once"); + match manager.handling_once() { + Ok(_) => {} + Err(err) => { + error!("Exec manager tick task failed: {}", err); + } + } thread::sleep(Duration::from_millis(100)); } } diff --git a/src/manager.rs b/src/manager.rs index bf602e3..3561701 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,11 +1,11 @@ +use anyhow::{anyhow, Result}; use embedded_hal::digital::blocking::OutputPin; use embedded_hal_0_2::adc::OneShot; use esp_idf_hal::{ adc::{Atten11dB, PoweredAdc, ADC1}, gpio::{Gpio1, Gpio2}, }; -use esp_idf_sys::EspError; -use log::info; +use log::*; use std::{ sync::mpsc, thread, @@ -43,11 +43,12 @@ where adapter_pin: AdapterGpio, battery_pin: BatteryGpio, tx: mpsc::Sender, - ) -> Result { + ) -> Result { let adc = PoweredAdc::new( adc1, esp_idf_hal::adc::config::Config::new().calibration(true), - )?; + ) + .map_err(|err| anyhow!("Can not init Adc: {}", err))?; return Ok(Manager { dc_out_controller, screen, @@ -59,14 +60,22 @@ where }); } - pub fn get_adapter_voltage(&mut self) -> Result { - return Ok(self.adc.read(&mut self.adapter_pin).unwrap() as f32); + pub fn get_adapter_voltage(&mut self) -> Result { + return Ok(self + .adc + .read(&mut self.adapter_pin) + .map_err(|err| anyhow!("Can not read adapter voltage. {:?}", err))? + as f32); } - pub fn get_battery_voltage(&mut self) -> Result { - return Ok(self.adc.read(&mut self.battery_pin).unwrap() as f32); + pub fn get_battery_voltage(&mut self) -> Result { + return Ok(self + .adc + .read(&mut self.battery_pin) + .map_err(|err| anyhow!("Can not read battery voltage. {:?}", err))? + as f32); } - pub fn handling_once(&mut self) -> Result<(), EspError> { + pub fn handling_once(&mut self) -> Result<()> { let mut adapter = 0.0_f32; let mut battery = 0.0_f32; for _ in 0..10 { @@ -78,40 +87,61 @@ where adapter /= 10.0_f32; battery /= 10.0_f32; - if adapter < 1000.0 { - if self.adapter_downed_at.is_none() { - info!("Recording adapter downed at: {:?}", SystemTime::now()); - self.adapter_downed_at = Some(SystemTime::now()); - } - if battery < 500.0 { + if is_adapter_down(adapter) { + if self.dc_out_controller.get_status().is_off() { + self.tx + .send(ringtone::SILENCE) + .map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?; + } else if is_battery_down(battery) { self.tx .send(ringtone::BATTERY_LOW) .expect("Can not send message"); - } else if battery < 1500.0 - && self - .adapter_downed_at - .is_some_and(|at| at.elapsed().is_ok_and(|dur| dur > &Duration::from_secs(10))) - { + } else if is_battery_low(battery) { self.dc_out_controller.shutdown(); - self.tx - .send(ringtone::SHUTDOWN) - .expect("Can not send message"); + if self.adapter_downed_at.is_none() { + info!("Recording adapter downed at: {:?}", SystemTime::now()); + self.adapter_downed_at = Some(SystemTime::now()); + } else if self + .adapter_downed_at + .is_some_and(|at| at.elapsed().is_ok_and(|dur| *dur > Duration::from_secs(5))) + { + self.tx + .send(ringtone::SHUTDOWN) + .map_err(|err| anyhow!("Can not send shutdown to Beep. {:?}", err))?; + } } else { self.tx .send(ringtone::ADAPTER_DOWN) - .expect("Can not send message"); + .map_err(|err| anyhow!("Can not send adapter down to Beep. {:?}", err))?; + self.dc_out_controller + .stop_shutdown() + .map_err(|err| anyhow!("Can not stop shutdown. {:?}", err))?; + self.adapter_downed_at = None; } } else { - self.dc_out_controller.stop_shutdown().expect("Can not stop shutdown"); + self.dc_out_controller + .stop_shutdown() + .expect("Can not stop shutdown"); + self.tx.send(ringtone::SILENCE).map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?; self.adapter_downed_at = None; - self.tx - .send(ringtone::SILENCE) - .expect("Can not send message"); + self.dc_out_controller.open()?; } self.screen .draw_voltage(adapter, battery) - .expect("Failed to draw voltage"); + .map_err(|err| anyhow!("Can not draw voltage. {:?}", err))?; - Ok({}) + Ok(()) } } + +fn is_battery_low(battery: f32) -> bool { + battery < 1500.0 +} + +fn is_battery_down(battery: f32) -> bool { + battery < 500.0 +} + +fn is_adapter_down(adapter: f32) -> bool { + adapter < 1000.0 +}