use std::{ sync::{Arc, Mutex, MutexGuard}, time::Duration, }; use embedded_hal::digital::v2::{OutputPin, PinState}; use embedded_svc::event_bus::{EventBus, Postbox}; use esp_idf_hal::gpio::{Gpio6, Output}; use esp_idf_svc::eventloop::{ Background, EspBackgroundEventLoop, EspEventFetchData, EspEventLoop, EspEventPostData, EspSubscription, EspTypedEventDeserializer, EspTypedEventSerializer, EspTypedEventSource, User, }; use esp_idf_sys::c_types; use log::warn; use serde_json::json; use crate::{ time::Time, voltage_detection::{VoltageDetectionWorker, VOLTAGE_EVENTLOOP}, }; const WAITING_OFF_DURATION: u64 = 60; const WAITING_ON_DURATION: u64 = 60; pub static mut DC_OUT_STATE_EVENT_LOOP: Option< EspEventLoop>, > = None; #[derive(Debug, Clone, Copy, PartialEq)] pub enum DcOutStatus { WaitingOn(Duration), On, Off, WaitingOff, TurningOff(Duration), } #[derive(Debug, Clone, Copy)] pub struct DcOutControllerState { pub status: DcOutStatus, pub pin_state: PinState, } impl DcOutControllerState { pub fn new() -> Self { Self { status: DcOutStatus::On, pin_state: PinState::Low, } } fn handle_adapter_down(&mut self) { match self.status { DcOutStatus::On => { self.status = DcOutStatus::WaitingOff; } DcOutStatus::WaitingOn(_) => { self.status = DcOutStatus::Off; } DcOutStatus::TurningOff(target) => { let now = Time::new().get_time(); if now > target { self.status = DcOutStatus::Off; } } _ => {} }; } fn turn_off(&mut self) { let now = Time::new().get_time(); match self.status { DcOutStatus::On => { self.status = DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } DcOutStatus::WaitingOff => { self.status = DcOutStatus::TurningOff(now + Duration::from_secs(WAITING_OFF_DURATION)); } DcOutStatus::WaitingOn(_) => { self.status = DcOutStatus::Off; } DcOutStatus::TurningOff(target) => { if target < now { self.status = DcOutStatus::Off; } } _ => {} }; } fn turn_on(&mut self) { let now = Time::new().get_time(); match self.status { DcOutStatus::WaitingOff => { self.status = DcOutStatus::On; } DcOutStatus::Off => { self.status = DcOutStatus::WaitingOn(now + Duration::from_secs(WAITING_ON_DURATION)); } DcOutStatus::WaitingOn(target) => { if target <= now { self.status = DcOutStatus::On; } } DcOutStatus::TurningOff(target) => { if target <= now { self.status = DcOutStatus::On; } } _ => {} }; } pub fn to_json(&self) -> String { let status = match self.status { DcOutStatus::WaitingOn(_) => "WaitingOn", DcOutStatus::On => "On", DcOutStatus::Off => "Off", DcOutStatus::WaitingOff => "WaitingOff", DcOutStatus::TurningOff(_) => "TurningOff", }; let pin_state = match self.pin_state { PinState::Low => "Low", PinState::High => "High", }; let now = Time::new().get_time(); let target = match self.status { DcOutStatus::WaitingOn(target) => target, DcOutStatus::TurningOff(target) => target, _ => Duration::ZERO, }; let seconds = if now < target { target - now } else { Duration::ZERO } .as_secs(); json!({ "status": status, "pin_state": pin_state, "seconds": seconds }).to_string() } } impl EspTypedEventSource for DcOutControllerState { fn source() -> *const c_types::c_char { b"DcOut\0".as_ptr() as *const _ } } impl EspTypedEventSerializer for DcOutControllerState { fn serialize( event: &DcOutControllerState, f: impl for<'a> FnOnce(&'a EspEventPostData) -> R, ) -> R { f(&unsafe { EspEventPostData::new(Self::source(), Self::event_id(), event) }) } } impl EspTypedEventDeserializer for DcOutControllerState { fn deserialize( data: &EspEventFetchData, f: &mut impl for<'a> FnMut(&'a DcOutControllerState) -> R, ) -> R { f(unsafe { data.as_payload() }) } } type DcOutPin = Gpio6; pub struct DcOutController { pub state: DcOutControllerState, voltage_subscription: Option>>, pin: Arc>, } impl DcOutController { pub fn new() -> anyhow::Result { let pin = unsafe { Gpio6::::new() } .into_output() .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; match EspBackgroundEventLoop::new(&Default::default()) { Ok(eventloop) => unsafe { DC_OUT_STATE_EVENT_LOOP = Some(eventloop) }, Err(err) => anyhow::bail!("Init Event Loop failed. {:?}", err), } anyhow::Ok(Self { state: DcOutControllerState::new(), voltage_subscription: None, pin: Arc::new(Mutex::new(pin)), }) } pub fn watch(&mut self) -> anyhow::Result<()> { let mut state = self.state.to_owned(); let pin = self.pin.to_owned(); if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { let voltage_subscription = event_loop .subscribe(move |obj: &VoltageDetectionWorker| { if obj.adapter_voltage < 1000 { if obj.battery_voltage < 1000 { state.turn_off(); } else { state.handle_adapter_down(); } } else { state.turn_on(); } match pin.lock() { Ok(pin) => { if let Err(err) = Self::output_ctl(&mut state, pin) { warn!("Put Control Pin State Failed. {}", err); } } Err(_) => todo!(), } if let Some(event_loop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { if let Err(err) = event_loop.post(&state, None) { warn!("Post DC Out Status Failed. {}", err); } } else { warn!("DC_OUT_STATE_EVENT_LOOP is None"); } }) .map_err(|err| anyhow::anyhow!("Subscribe Voltage Failed. {}", err))?; self.voltage_subscription = Some(voltage_subscription); } else { anyhow::bail!("Voltage Event Loop is None"); } anyhow::Ok(()) } fn output_ctl( state: &mut DcOutControllerState, mut pin: MutexGuard, ) -> anyhow::Result<()> { if DcOutStatus::Off == state.status { pin.set_high() .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin High Failed. {}", err))?; state.pin_state = PinState::High; } else if DcOutStatus::On == state.status { pin.set_low() .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?; state.pin_state = PinState::Low; } return anyhow::Ok(()); } }