From 2a8e7db4b3f2123c9c10de78cd2e19150ab8902a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Tue, 30 Aug 2022 22:36:22 +0800 Subject: [PATCH] feat: dc out controller. --- src/dc_out_controller.rs | 248 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 85 +++++++++----- src/voltage_detection.rs | 4 +- 3 files changed, 307 insertions(+), 30 deletions(-) create mode 100644 src/dc_out_controller.rs diff --git a/src/dc_out_controller.rs b/src/dc_out_controller.rs new file mode 100644 index 0000000..ac7cb69 --- /dev/null +++ b/src/dc_out_controller.rs @@ -0,0 +1,248 @@ +use std::sync::{Arc, Mutex}; + +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::{info, warn}; +use serde_json::json; + +use crate::{voltage_detection::VoltageDetectionWorker, VOLTAGE_EVENTLOOP}; + +const WAITING_OFF_SECONDS: u8 = 60; + +pub static mut DC_OUT_STATE_EVENT_LOOP: Option< + EspEventLoop>, +> = None; +static mut INSTANCE: Option = None; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DcOutStatus { + WaitingOn(u8), + On, + Off, + WaitingOff, + TurningOff(u8), +} + +#[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) { + info!("status: {:?}", self); + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::WaitingOff; + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_off(&mut self) { + match self.status { + DcOutStatus::On => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::TurningOff(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(_) => { + self.status = DcOutStatus::Off; + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + self.status = DcOutStatus::Off; + } + } + _ => {} + }; + } + + fn turn_on(&mut self) { + match self.status { + DcOutStatus::WaitingOff => { + self.status = DcOutStatus::On; + } + DcOutStatus::Off => { + self.status = DcOutStatus::WaitingOn(WAITING_OFF_SECONDS); + } + DcOutStatus::WaitingOn(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::WaitingOn(seconds); + } else { + self.status = DcOutStatus::On; + } + } + DcOutStatus::TurningOff(seconds) => { + let seconds = seconds - 1; + if seconds > 0 { + self.status = DcOutStatus::TurningOff(seconds); + } else { + 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 seconds: i16 = match self.status { + DcOutStatus::WaitingOn(seconds) => seconds.into(), + DcOutStatus::TurningOff(seconds) => seconds.into(), + _ => -1, + }; + json!({ "status": status, "pin_state": pin_state, "seconds": seconds }) + .to_string() + } +} + +impl EspTypedEventSource for DcOutControllerState { + fn source() -> *const c_types::c_char { + b"VOLTAGES\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() }) + } +} + +pub struct DcOutController { + pub state: DcOutControllerState, + voltage_subscription: Option>>, +} + +impl DcOutController { + fn new() -> Self { + Self { + state: DcOutControllerState::new(), + voltage_subscription: None, + } + } + + pub fn get_instance() -> anyhow::Result { + // if let Some(instance) = unsafe { &mut INSTANCE } { + // anyhow::Ok(*instance) + // } else { + let instance = Self::new(); + // unsafe { INSTANCE = Some(instance) }; + 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(instance) + // } + } + + pub fn watch(&mut self) -> anyhow::Result<()> { + let state = Arc::new(Mutex::new(self.state)); + + if let Some(event_loop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + let voltage_subscription = event_loop + .subscribe( + move |obj: &VoltageDetectionWorker| match state.lock().as_mut() { + Ok(state) => { + if obj.adapter_voltage < 1000 { + if obj.battery_voltage < 1000 { + state.turn_off(); + } else { + state.handle_adapter_down(); + } + } else { + state.turn_on(); + } + let ref state = **state; + if let Err(err) = Self::output_ctl(*state) { + warn!("Put Control Pin State Failed. {}", err); + } + + 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"); + } + info!("status: {:?}", state); + } + Err(err) => warn!("Can not lock state. {}", err), + }, + ) + .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(mut state: DcOutControllerState) -> anyhow::Result<()> { + let mut pin = unsafe { Gpio6::::new() } + .into_output() + .map_err(|err| anyhow::anyhow!("Make Gpio6 Into output Failed. {}", err))?; + + if DcOutStatus::Off == state.status && state.pin_state == PinState::Low { + 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 && state.pin_state == PinState::High { + pin.set_low() + .map_err(|err| anyhow::anyhow!("Set DC Output Control Pin Low Failed. {}", err))?; + state.pin_state = PinState::Low; + } + return anyhow::Ok(()); + } +} diff --git a/src/main.rs b/src/main.rs index 047732d..b735c3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use embedded_svc::event_bus::EventBus; use esp_idf_svc::eventloop::{Background, EspBackgroundEventLoop, EspEventLoop}; -use esp_idf_sys::{self as _, EspError}; +use esp_idf_sys::{self as _}; use log::*; use std::{ env, @@ -10,17 +10,22 @@ use std::{ mod beep; mod blink; +mod dc_out_controller; mod message_queue; mod time; mod voltage_detection; mod wifi; -use crate::{beep::{Beep, ringtone}, voltage_detection::VoltageDetectionWorker}; +use crate::{ + beep::{ringtone, Beep}, + dc_out_controller::{DcOutController, DcOutControllerState, DC_OUT_STATE_EVENT_LOOP}, + voltage_detection::VoltageDetectionWorker, +}; use crate::{ message_queue::MessageQueue, time::Time, voltage_detection::VoltageDetection, wifi::Internet, }; -static mut EVENTLOOP: Option>> = None; +static mut VOLTAGE_EVENTLOOP: Option>> = None; fn main() { env::set_var("DEFMT_LOG", "trace"); @@ -47,7 +52,7 @@ fn main() { info!("About to start a background event loop"); match EspBackgroundEventLoop::new(&Default::default()) { - Ok(eventloop) => unsafe { EVENTLOOP = Some(eventloop) }, + Ok(eventloop) => unsafe { VOLTAGE_EVENTLOOP = Some(eventloop) }, Err(err) => error!("Init Event Loop failed. {:?}", err), }; @@ -59,30 +64,6 @@ fn main() { ); blink.play(); }); - let mut beep = Beep::new(); - - let _subscription; - if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { - _subscription = eventloop - .subscribe(move |message: &VoltageDetectionWorker| { - info!("Event Loop Value"); - if let Ok(json_str) = serde_json::to_string(&message) { - if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { - warn!("Can not publish message to MQTT. {}", err); - } - } - - if message.battery_voltage < 1000 { - beep.play(ringtone::BATTERY_LOW).expect("Can not beep."); - } else if message.adapter_voltage < 1000 { - beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep."); - } else { - beep.play(ringtone::SILENCE).expect("Can not beep."); - } - }) - .expect(" Listening Event Loop Failed"); - } - let mut voltage_detection = VoltageDetection::new(); @@ -97,6 +78,54 @@ fn main() { let mut time = Time::new(); time.sync().unwrap(); + + let mut beep = Beep::new(); + + // let _voltage_subscription; + // if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { + // _voltage_subscription = eventloop + // .subscribe(move |message: &VoltageDetectionWorker| { + // if let Ok(json_str) = serde_json::to_string(&message) { + // if let Err(err) = mq.publish("voltage", json_str.as_bytes()) { + // warn!("Can not publish message to MQTT. {}", err); + // } + // } + // }) + // .expect(" Listening Event Loop Failed"); + // } + + let mut dc_out_controller = + DcOutController::get_instance().expect("Can not get DcOutController instance"); + dc_out_controller + .watch() + .expect("Can not watch for dc_out_controller"); + + let _dc_out_state_subscription; + if let Some(eventloop) = unsafe { DC_OUT_STATE_EVENT_LOOP.as_mut() } { + _dc_out_state_subscription = eventloop + .subscribe(move |message: &DcOutControllerState| { + info!("Event Loop Value"); + match message.status { + dc_out_controller::DcOutStatus::WaitingOff => { + beep.play(ringtone::ADAPTER_DOWN).expect("Can not beep.") + } + dc_out_controller::DcOutStatus::WaitingOn(_) => { + beep.play(ringtone::BATTERY_LOW).expect("Can not beep.") + } + dc_out_controller::DcOutStatus::TurningOff(_) => { + beep.play(ringtone::SHUTDOWN).expect("Can not beep.") + } + _ => {beep.play(ringtone::SILENCE).expect("Can not beep.")} + } + if let Err(err) = mq.publish("dcOut", message.to_json().as_bytes()) { + warn!("Can not publish message to MQTT. {}", err); + } + }) + .expect(" Listening Event Loop Failed"); + } else { + error!("DC_OUT_STATE_EVENT_LOOP is None"); + } + loop { sleep(Duration::from_millis(1000)); } diff --git a/src/voltage_detection.rs b/src/voltage_detection.rs index 959fdef..f952377 100644 --- a/src/voltage_detection.rs +++ b/src/voltage_detection.rs @@ -26,7 +26,7 @@ use esp_idf_sys::c_types; use log::{info, warn}; use serde::{Deserialize, Serialize}; -use crate::EVENTLOOP; +use crate::VOLTAGE_EVENTLOOP; const ADAPTER_OFFSET: f32 = 12002f32 / 900f32; const BATTERY_OFFSET: f32 = 12002f32 / 900f32; @@ -64,7 +64,7 @@ impl VoltageDetection { "Adapter: {},\tBattery: {},\t Output: {}", worker.adapter_voltage, worker.battery_voltage, worker.output_voltage ); - if let Some(eventloop) = unsafe { EVENTLOOP.as_mut() } { + if let Some(eventloop) = unsafe { VOLTAGE_EVENTLOOP.as_mut() } { if let Err(err) = eventloop.post( &mut VoltageDetectionWorker { adapter_voltage: worker.adapter_voltage,