use anyhow::{Ok, Result, anyhow}; use retry; use std::{ sync::{ mpsc, Arc, Mutex, }, thread, time::{self, SystemTime}, }; use embedded_hal::digital::blocking::OutputPin; 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, { pin: Arc>, shutdown_tx: Option>, status: DcOutStatus, } impl

DcOutController

where P: OutputPin + Send, P: 'static, P: std::marker::Sync, { pub fn new(pin: P) -> Self { return Self { status: DcOutStatus::On, pin: Arc::new(Mutex::new(pin)), shutdown_tx: None, }; } 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), || { pin.lock() }) .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; retry::retry(retry::delay::Fixed::from_millis(100).take(10), || { pin.set_low() }) .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; self.status = DcOutStatus::On; info!("DC OUT ON"); Ok(()) } pub fn shutdown(&mut self) { if self.shutdown_tx.is_some() { trace!("Shutdown task already running, skipping..."); return; } info!("Shutdown task started..."); let (tx, rx) = mpsc::channel(); self.shutdown_tx = Some(tx); 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); loop { if rx.try_recv().is_ok_and(|state| !*state) { break; } if target_at > SystemTime::now() { thread::sleep(time::Duration::from_millis(1000)); continue; } info!("Shutdown timeout, force shutdown..."); 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")); }).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; } }); } pub fn stop_shutdown(&mut self) -> Result<()> { if let Some(tx) = self.shutdown_tx.as_mut() { info!("Shutdown task stopped..."); if retry::retry(retry::delay::Fixed::from_millis(100).take(5), || { tx.send(false) }) .map_err(|_| anyhow::anyhow!("Failed to stop shutdown")) .is_err() { warn!("Message to shutdown task was not sent"); } else { info!("Shutdown task stopped"); } self.shutdown_tx = None; }; 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, } } }