182 lines
5.1 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|