feat: 支持电源的延迟断开和立即闭合。
This commit is contained in:
parent
65028bd7f6
commit
10fb4601e9
@ -19,9 +19,12 @@ pio = ["esp-idf-sys/pio"]
|
|||||||
anyhow = {version = "1.0.57", features = ["backtrace"]}
|
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]
|
||||||
|
@ -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,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -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>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P> DcOutController<P>
|
||||||
|
where
|
||||||
|
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 on(&mut self) -> Result<(), T::Error> {
|
pub fn start(&mut self) -> Result<()> {
|
||||||
self.state = true;
|
let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || {
|
||||||
return self.pin.set_low();
|
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 off(&mut self) -> Result<(), T::Error> {
|
|
||||||
self.state = false;
|
pub fn shutdown(&mut self) {
|
||||||
return self.pin.set_high();
|
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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))?;
|
||||||
|
Loading…
Reference in New Issue
Block a user