feat: 支持电源的延迟断开和立即闭合。

This commit is contained in:
Ivan Li 2022-05-10 23:52:31 +08:00
parent 65028bd7f6
commit 10fb4601e9
6 changed files with 144 additions and 33 deletions

View File

@ -20,8 +20,11 @@ anyhow = {version = "1.0.57", features = ["backtrace"]}
embedded-graphics = "0.7.1" embedded-graphics = "0.7.1"
embedded-hal = "1.0.0-alpha.8" embedded-hal = "1.0.0-alpha.8"
embedded-hal-0-2 = {package = "embedded-hal", version = "0.2.7", features = ["unproven"]} embedded-hal-0-2 = {package = "embedded-hal", version = "0.2.7", features = ["unproven"]}
env_logger = "0.9.0"
esp-idf-hal = "0.37.3" esp-idf-hal = "0.37.3"
esp-idf-sys = {version = "0.31.5", features = ["binstart"]} esp-idf-sys = {version = "0.31.5", features = ["binstart"]}
log = "0.4.17"
retry = "1.3.1"
ssd1306 = "0.7.0" ssd1306 = "0.7.0"
[build-dependencies] [build-dependencies]

View File

@ -59,7 +59,7 @@ pub mod ringtone {
pub type Type = [bool; 16]; pub type Type = [bool; 16];
pub const POWER_DOWN: Type = [ pub const ADAPTER_DOWN: Type = [
true, true, true, true, false, false, false, false, false, false, false, false, false, true, true, true, true, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false,
]; ];
@ -68,4 +68,8 @@ pub mod ringtone {
false, false, false, false,
]; ];
pub const SILENCE: Type = [false; 16]; pub const SILENCE: Type = [false; 16];
pub const SHUTDOWN: Type = [
true, false, true, true, true, true, false, true, true, true, true, true, false, false,
false, false,
];
} }

View File

@ -1,24 +1,109 @@
use anyhow::{Context, Ok, Result};
use retry;
use std::{
sync::{
mpsc::{self},
Arc, Mutex,
},
thread,
time::{self, SystemTime},
};
use embedded_hal::digital::blocking::OutputPin; use embedded_hal::digital::blocking::OutputPin;
use log::{info, trace, warn};
pub struct DcOutController<T: OutputPin> { pub struct DcOutController<P>
pub state: bool,
pin: T,
}
impl<T> DcOutController<T>
where where
T: OutputPin, P: OutputPin + Send,
{ {
pub fn new(pin: T) -> DcOutController<T> { pub state: bool,
return DcOutController { state: false, pin }; pin: Arc<Mutex<P>>,
shutdown_tx: Option<mpsc::Sender<bool>>,
} }
pub fn on(&mut self) -> Result<(), T::Error> { impl<P> DcOutController<P>
self.state = true; where
return self.pin.set_low(); P: OutputPin + Send,
P: 'static,
P: std::marker::Sync,
{
pub fn new(pin: P) -> Self {
return Self {
state: false,
pin: Arc::new(Mutex::new(pin)),
shutdown_tx: None,
};
} }
pub fn off(&mut self) -> Result<(), T::Error> {
self.state = false; pub fn start(&mut self) -> Result<()> {
return self.pin.set_high(); let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || {
self.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"))?;
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);
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...");
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");
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;
};
self.start()?;
info!("Power restored");
Ok(())
} }
} }

View File

@ -1,5 +1,6 @@
#![feature(is_some_with)]
use esp_idf_sys as _; use esp_idf_sys as _;
use std::{thread, time::Duration, sync::mpsc}; use std::{thread, time::Duration, sync::mpsc, env};
mod beep; mod beep;
mod blink; mod blink;
@ -7,10 +8,13 @@ mod dc_out_controller;
mod manager; mod manager;
mod screen; mod screen;
fn main() { fn main() {
env::set_var("RUST_LOG", env!("RUST_LOG"));
env_logger::init();
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, // 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. // or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_sys::link_patches(); esp_idf_sys::link_patches();
let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap(); let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap();
let gpio5 = peripherals.pins.gpio5; let gpio5 = peripherals.pins.gpio5;
@ -66,5 +70,6 @@ fn main() {
).expect("Failed to create manager"); ).expect("Failed to create manager");
loop { loop {
manager.handling_once().expect("Failed to handle once"); manager.handling_once().expect("Failed to handle once");
thread::sleep(Duration::from_millis(100));
} }
} }

View File

@ -1,5 +1,3 @@
use std::{sync::mpsc, thread, time::Duration};
use embedded_hal::digital::blocking::OutputPin; use embedded_hal::digital::blocking::OutputPin;
use embedded_hal_0_2::adc::OneShot; use embedded_hal_0_2::adc::OneShot;
use esp_idf_hal::{ use esp_idf_hal::{
@ -7,6 +5,12 @@ use esp_idf_hal::{
gpio::{Gpio1, Gpio2}, gpio::{Gpio1, Gpio2},
}; };
use esp_idf_sys::EspError; use esp_idf_sys::EspError;
use log::info;
use std::{
sync::mpsc,
thread,
time::{Duration, SystemTime},
};
use crate::{beep::ringtone, dc_out_controller::DcOutController, screen::Screen}; use crate::{beep::ringtone, dc_out_controller::DcOutController, screen::Screen};
@ -15,7 +19,7 @@ type BatteryGpio = Gpio2<Atten11dB<ADC1>>;
pub struct Manager<C> pub struct Manager<C>
where where
C: OutputPin, C: OutputPin + Send,
{ {
dc_out_controller: DcOutController<C>, dc_out_controller: DcOutController<C>,
screen: Screen, screen: Screen,
@ -23,11 +27,14 @@ where
adapter_pin: AdapterGpio, adapter_pin: AdapterGpio,
battery_pin: BatteryGpio, battery_pin: BatteryGpio,
tx: mpsc::Sender<ringtone::Type>, tx: mpsc::Sender<ringtone::Type>,
adapter_downed_at: Option<SystemTime>,
} }
impl<C> Manager<C> impl<C> Manager<C>
where where
C: OutputPin, C: OutputPin + Send,
C: 'static,
C: std::marker::Sync,
{ {
pub fn new( pub fn new(
dc_out_controller: DcOutController<C>, dc_out_controller: DcOutController<C>,
@ -48,6 +55,7 @@ where
adapter_pin, adapter_pin,
battery_pin, battery_pin,
tx, tx,
adapter_downed_at: None,
}); });
} }
@ -71,21 +79,34 @@ where
battery /= 10.0_f32; battery /= 10.0_f32;
if adapter < 1000.0 { if adapter < 1000.0 {
self.dc_out_controller.off().expect("Can not turn off Out"); if self.adapter_downed_at.is_none() {
if battery < 1000.0 { info!("Recording adapter downed at: {:?}", SystemTime::now());
self.adapter_downed_at = Some(SystemTime::now());
}
if battery < 500.0 {
self.tx self.tx
.send(ringtone::BATTERY_LOW) .send(ringtone::BATTERY_LOW)
.expect("Can not send message"); .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)))
{
self.dc_out_controller.shutdown();
self.tx
.send(ringtone::SHUTDOWN)
.expect("Can not send message");
} else { } else {
self.tx self.tx
.send(ringtone::POWER_DOWN) .send(ringtone::ADAPTER_DOWN)
.expect("Can not send message"); .expect("Can not send message");
} }
} else { } else {
self.dc_out_controller.stop_shutdown().expect("Can not stop shutdown");
self.adapter_downed_at = None;
self.tx self.tx
.send(ringtone::SILENCE) .send(ringtone::SILENCE)
.expect("Can not send message"); .expect("Can not send message");
self.dc_out_controller.on().expect("Can not turn on Out");
} }
self.screen self.screen
.draw_voltage(adapter, battery) .draw_voltage(adapter, battery)

View File

@ -58,9 +58,6 @@ impl Screen {
.init() .init()
.unwrap(); .unwrap();
println!("LED rendering done");
let mut instance = Screen { display }; let mut instance = Screen { display };
instance.draw_boot()?; instance.draw_boot()?;
@ -95,8 +92,6 @@ impl Screen {
) )
.draw(&mut self.display).expect("Failed to draw text"); .draw(&mut self.display).expect("Failed to draw text");
println!("LED rendering done");
self.display self.display
.flush() .flush()
.map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;
@ -126,8 +121,6 @@ impl Screen {
) )
.draw(&mut self.display).expect("Failed to draw text"); .draw(&mut self.display).expect("Failed to draw text");
println!("LED rendering done");
self.display self.display
.flush() .flush()
.map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?; .map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;