ups-esp32c3-rust/src/dc_out_controller.rs

182 lines
5.1 KiB
Rust

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<bool>),
TurningOff(mpsc::Receiver<bool>),
}
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<P>
where
P: OutputPin + Send,
{
pin: Arc<Mutex<P>>,
shutdown_tx: Option<mpsc::Sender<bool>>,
status: DcOutStatus,
}
impl<P> DcOutController<P>
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,
}
}
}