feat: support read dc out status.

This commit is contained in:
Ivan Li 2022-05-15 22:31:21 +08:00
parent 10fb4601e9
commit 991e9c9867
3 changed files with 154 additions and 45 deletions

View File

@ -1,8 +1,8 @@
use anyhow::{Context, Ok, Result}; use anyhow::{Ok, Result, anyhow};
use retry; use retry;
use std::{ use std::{
sync::{ sync::{
mpsc::{self}, mpsc,
Arc, Mutex, Arc, Mutex,
}, },
thread, thread,
@ -10,15 +10,37 @@ use std::{
}; };
use embedded_hal::digital::blocking::OutputPin; use embedded_hal::digital::blocking::OutputPin;
use log::{info, trace, warn}; 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> pub struct DcOutController<P>
where where
P: OutputPin + Send, P: OutputPin + Send,
{ {
pub state: bool,
pin: Arc<Mutex<P>>, pin: Arc<Mutex<P>>,
shutdown_tx: Option<mpsc::Sender<bool>>, shutdown_tx: Option<mpsc::Sender<bool>>,
status: DcOutStatus,
} }
impl<P> DcOutController<P> impl<P> DcOutController<P>
@ -29,15 +51,21 @@ where
{ {
pub fn new(pin: P) -> Self { pub fn new(pin: P) -> Self {
return Self { return Self {
state: false, status: DcOutStatus::On,
pin: Arc::new(Mutex::new(pin)), pin: Arc::new(Mutex::new(pin)),
shutdown_tx: None, shutdown_tx: None,
}; };
} }
pub fn start(&mut self) -> Result<()> { 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), || { let mut pin = retry::retry(retry::delay::Fixed::from_millis(100).take(10), || {
self.pin.lock() pin.lock()
}) })
.map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?;
@ -45,6 +73,9 @@ where
pin.set_low() pin.set_low()
}) })
.map_err(|_| anyhow::anyhow!("Failed to lock pin"))?; .map_err(|_| anyhow::anyhow!("Failed to lock pin"))?;
self.status = DcOutStatus::On;
info!("DC OUT ON");
Ok(()) Ok(())
} }
@ -60,6 +91,9 @@ where
let pin = Arc::clone(&self.pin); let pin = Arc::clone(&self.pin);
let (status_tx, status_rx) = mpsc::channel();
self.status = DcOutStatus::TurningOff(status_rx);
thread::spawn(move || { thread::spawn(move || {
// Wait for computer shutdown finished // Wait for computer shutdown finished
let target_at = SystemTime::now() + time::Duration::from_secs(10); let target_at = SystemTime::now() + time::Duration::from_secs(10);
@ -74,14 +108,24 @@ where
} }
info!("Shutdown timeout, force shutdown..."); info!("Shutdown timeout, force shutdown...");
retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || {
let result = || {
retry::retry(retry::delay::Fixed::from_millis(1000).take(10), || {
return pin return pin
.lock() .lock()
.map_err(|_| anyhow::anyhow!("Can not lock pin"))? .map_err(|_| anyhow::anyhow!("Can not lock pin"))?
.set_high() .set_high()
.map_err(|_| anyhow::anyhow!("Can not shutdown")); .map_err(|_| anyhow::anyhow!("Can not shutdown"));
}).expect("Failed to shutdown"); }).map_err(|_| anyhow!("Failed to send shutdown status"))?;
info!("Shutdown finished"); 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; break;
} }
}); });
@ -101,9 +145,37 @@ where
} }
self.shutdown_tx = None; self.shutdown_tx = None;
}; };
self.start()?;
info!("Power restored");
Ok(()) 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,
}
}
} }

View File

@ -1,5 +1,6 @@
#![feature(is_some_with)] #![feature(is_some_with)]
use esp_idf_sys as _; use esp_idf_sys as _;
use log::error;
use std::{thread, time::Duration, sync::mpsc, env}; use std::{thread, time::Duration, sync::mpsc, env};
mod beep; mod beep;
@ -8,7 +9,8 @@ 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::set_var("DEFMT_LOG", "trace");
env::set_var("RUST_LOG", "trace");
env_logger::init(); 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.
@ -69,7 +71,12 @@ fn main() {
tx, tx,
).expect("Failed to create manager"); ).expect("Failed to create manager");
loop { loop {
manager.handling_once().expect("Failed to handle once"); match manager.handling_once() {
Ok(_) => {}
Err(err) => {
error!("Exec manager tick task failed: {}", err);
}
}
thread::sleep(Duration::from_millis(100)); thread::sleep(Duration::from_millis(100));
} }
} }

View File

@ -1,11 +1,11 @@
use anyhow::{anyhow, Result};
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::{
adc::{Atten11dB, PoweredAdc, ADC1}, adc::{Atten11dB, PoweredAdc, ADC1},
gpio::{Gpio1, Gpio2}, gpio::{Gpio1, Gpio2},
}; };
use esp_idf_sys::EspError; use log::*;
use log::info;
use std::{ use std::{
sync::mpsc, sync::mpsc,
thread, thread,
@ -43,11 +43,12 @@ where
adapter_pin: AdapterGpio, adapter_pin: AdapterGpio,
battery_pin: BatteryGpio, battery_pin: BatteryGpio,
tx: mpsc::Sender<ringtone::Type>, tx: mpsc::Sender<ringtone::Type>,
) -> Result<Self, EspError> { ) -> Result<Self> {
let adc = PoweredAdc::new( let adc = PoweredAdc::new(
adc1, adc1,
esp_idf_hal::adc::config::Config::new().calibration(true), esp_idf_hal::adc::config::Config::new().calibration(true),
)?; )
.map_err(|err| anyhow!("Can not init Adc: {}", err))?;
return Ok(Manager { return Ok(Manager {
dc_out_controller, dc_out_controller,
screen, screen,
@ -59,14 +60,22 @@ where
}); });
} }
pub fn get_adapter_voltage(&mut self) -> Result<f32, EspError> { pub fn get_adapter_voltage(&mut self) -> Result<f32> {
return Ok(self.adc.read(&mut self.adapter_pin).unwrap() as f32); return Ok(self
.adc
.read(&mut self.adapter_pin)
.map_err(|err| anyhow!("Can not read adapter voltage. {:?}", err))?
as f32);
} }
pub fn get_battery_voltage(&mut self) -> Result<f32, EspError> { pub fn get_battery_voltage(&mut self) -> Result<f32> {
return Ok(self.adc.read(&mut self.battery_pin).unwrap() as f32); return Ok(self
.adc
.read(&mut self.battery_pin)
.map_err(|err| anyhow!("Can not read battery voltage. {:?}", err))?
as f32);
} }
pub fn handling_once(&mut self) -> Result<(), EspError> { pub fn handling_once(&mut self) -> Result<()> {
let mut adapter = 0.0_f32; let mut adapter = 0.0_f32;
let mut battery = 0.0_f32; let mut battery = 0.0_f32;
for _ in 0..10 { for _ in 0..10 {
@ -78,40 +87,61 @@ where
adapter /= 10.0_f32; adapter /= 10.0_f32;
battery /= 10.0_f32; battery /= 10.0_f32;
if adapter < 1000.0 { if is_adapter_down(adapter) {
if self.adapter_downed_at.is_none() { if self.dc_out_controller.get_status().is_off() {
info!("Recording adapter downed at: {:?}", SystemTime::now()); self.tx
self.adapter_downed_at = Some(SystemTime::now()); .send(ringtone::SILENCE)
} .map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?;
if battery < 500.0 { } else if is_battery_down(battery) {
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 } else if is_battery_low(battery) {
&& 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.dc_out_controller.shutdown();
self.tx if self.adapter_downed_at.is_none() {
.send(ringtone::SHUTDOWN) info!("Recording adapter downed at: {:?}", SystemTime::now());
.expect("Can not send message"); self.adapter_downed_at = Some(SystemTime::now());
} else if self
.adapter_downed_at
.is_some_and(|at| at.elapsed().is_ok_and(|dur| *dur > Duration::from_secs(5)))
{
self.tx
.send(ringtone::SHUTDOWN)
.map_err(|err| anyhow!("Can not send shutdown to Beep. {:?}", err))?;
}
} else { } else {
self.tx self.tx
.send(ringtone::ADAPTER_DOWN) .send(ringtone::ADAPTER_DOWN)
.expect("Can not send message"); .map_err(|err| anyhow!("Can not send adapter down to Beep. {:?}", err))?;
self.dc_out_controller
.stop_shutdown()
.map_err(|err| anyhow!("Can not stop shutdown. {:?}", err))?;
self.adapter_downed_at = None;
} }
} else { } else {
self.dc_out_controller.stop_shutdown().expect("Can not stop shutdown"); self.dc_out_controller
.stop_shutdown()
.expect("Can not stop shutdown");
self.tx.send(ringtone::SILENCE).map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?;
self.adapter_downed_at = None; self.adapter_downed_at = None;
self.tx self.dc_out_controller.open()?;
.send(ringtone::SILENCE)
.expect("Can not send message");
} }
self.screen self.screen
.draw_voltage(adapter, battery) .draw_voltage(adapter, battery)
.expect("Failed to draw voltage"); .map_err(|err| anyhow!("Can not draw voltage. {:?}", err))?;
Ok({}) Ok(())
} }
} }
fn is_battery_low(battery: f32) -> bool {
battery < 1500.0
}
fn is_battery_down(battery: f32) -> bool {
battery < 500.0
}
fn is_adapter_down(adapter: f32) -> bool {
adapter < 1000.0
}