Compare commits
13 Commits
4fc93079c2
...
v0.1
Author | SHA1 | Date | |
---|---|---|---|
12f3509304 | |||
7ef35692c9 | |||
5234fe20b5 | |||
696673ba01 | |||
a383d511dd | |||
991e9c9867 | |||
10fb4601e9 | |||
65028bd7f6 | |||
c5d382fafe | |||
de84c85190 | |||
3eb680f1ce | |||
d944fc1de3 | |||
1c20412933 |
@@ -31,4 +31,5 @@ build-std = ["std", "panic_abort"]
|
||||
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF stable (v4.4)
|
||||
#ESP_IDF_VERSION = { value = "branch:release/v4.4" }
|
||||
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master (mainline)
|
||||
ESP_IDF_VERSION = { value = "master" }
|
||||
ESP_IDF_VERSION = { value = "branch:release/v4.4" }
|
||||
|
||||
|
22
Cargo.toml
22
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "ups-esp32c3-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["Ivan Li <ivanli2048@gmail.com>"]
|
||||
edition = "2018"
|
||||
name = "ups-esp32c3-rust"
|
||||
resolver = "2"
|
||||
version = "0.1.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
@@ -16,11 +16,19 @@ opt-level = "z"
|
||||
pio = ["esp-idf-sys/pio"]
|
||||
|
||||
[dependencies]
|
||||
esp-idf-sys = { version = "0.31", features = ["binstart"] }
|
||||
esp-idf-hal = "0.36.0"
|
||||
anyhow = "1"
|
||||
embedded-graphics = "0.7.1"
|
||||
embedded-hal = "1.0.0-alpha.8"
|
||||
|
||||
embedded-hal-0-2 = {package = "embedded-hal", version = "0.2.7", features = ["unproven"]}
|
||||
embedded-svc = "0.22.0"
|
||||
env_logger = "0.9.0"
|
||||
esp-idf-hal = "0.38.0"
|
||||
esp-idf-svc = "0.42.1"
|
||||
esp-idf-sys = { version = "0.31.6", features = ["binstart"] }
|
||||
log = "0.4.17"
|
||||
retry = "1.3.1"
|
||||
ssd1306 = "0.7.0"
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.28"
|
||||
anyhow = "1"
|
||||
anyhow = "1.0.57"
|
||||
embuild = "0.29.1"
|
||||
|
@@ -1,3 +1,2 @@
|
||||
[toolchain]
|
||||
|
||||
channel = "nightly-2022-04-01"
|
||||
channel = "nightly"
|
72
src/beep.rs
72
src/beep.rs
@@ -9,48 +9,70 @@ use std::time::Duration;
|
||||
type LedcChannel<P, T, C> = Channel<C, T, Timer<T>, P>;
|
||||
|
||||
pub struct Beep<P: OutputPin, T: ledc::HwTimer, C: ledc::HwChannel> {
|
||||
state: bool,
|
||||
stopped: bool,
|
||||
beat: u8,
|
||||
ringtone: ringtone::Type,
|
||||
channel: LedcChannel<P, T, C>,
|
||||
duty: u32,
|
||||
}
|
||||
|
||||
impl<P: OutputPin, T: ledc::HwTimer, C: ledc::HwChannel> Beep<P, T, C> {
|
||||
pub fn new(pin: P, timer: T, channel: C) -> Result<Self, EspError> {
|
||||
let channel = Self::init_channel(pin, timer, channel)?;
|
||||
|
||||
let max_duty = channel.get_max_duty();
|
||||
return Ok(Beep {
|
||||
state: false,
|
||||
stopped: false,
|
||||
channel: Self::init_channel(pin, timer, channel)?,
|
||||
channel,
|
||||
beat: 0,
|
||||
duty: max_duty * 3 / 4,
|
||||
ringtone: ringtone::SILENCE,
|
||||
});
|
||||
}
|
||||
|
||||
fn init_channel(pin: P, timer: T, channel: C) -> Result<LedcChannel<P, T, C>, EspError> {
|
||||
let config = TimerConfig::default().frequency(1.kHz().into());
|
||||
let config = TimerConfig::default().frequency(2.kHz().into());
|
||||
let timer = Timer::new(timer, &config)?;
|
||||
let mut channel = Channel::new(channel, timer, pin)?;
|
||||
let max_duty = channel.get_max_duty();
|
||||
channel.set_duty(max_duty * 3 / 4)?;
|
||||
let channel: Channel<C, T, Timer<T>, P> = Channel::new(channel, timer, pin)?;
|
||||
return Ok(channel);
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self) -> Result<(), EspError> {
|
||||
self.state = !self.state;
|
||||
if self.state {
|
||||
self.channel.set_duty(50)
|
||||
} else {
|
||||
self.channel.set_duty(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play(&mut self) {
|
||||
pub fn play(&mut self, rx: &mut std::sync::mpsc::Receiver<ringtone::Type>) {
|
||||
loop {
|
||||
if self.stopped {
|
||||
break;
|
||||
let curr_ringtone = rx.try_recv().unwrap_or_else(|_| self.ringtone);
|
||||
if !curr_ringtone.eq(&mut self.ringtone) {
|
||||
self.beat = 0;
|
||||
self.ringtone = curr_ringtone;
|
||||
}
|
||||
self.toggle().unwrap();
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
self.toggle().unwrap();
|
||||
thread::sleep(Duration::from_millis(1500));
|
||||
let curr = curr_ringtone[self.beat as usize];
|
||||
if curr {
|
||||
self.channel.set_duty(self.duty).expect("Failed to set duty");
|
||||
} else {
|
||||
self.channel.set_duty(0).expect("Failed to set duty");
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
self.beat += 1;
|
||||
if self.beat == 16 {
|
||||
self.beat = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod ringtone {
|
||||
|
||||
pub type Type = [bool; 16];
|
||||
|
||||
pub const ADAPTER_DOWN: Type = [
|
||||
true, true, true, true, false, false, false, false, false, false, false, false, false,
|
||||
false, false, false,
|
||||
];
|
||||
pub const BATTERY_LOW: Type = [
|
||||
true, true, false, false, true, true, false, false, true, true, false, false, true, true,
|
||||
false, false,
|
||||
];
|
||||
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,181 @@
|
||||
use anyhow::{Ok, Result, anyhow};
|
||||
use retry;
|
||||
use std::{
|
||||
sync::{
|
||||
mpsc,
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
time::{self, SystemTime},
|
||||
};
|
||||
|
||||
use embedded_hal::digital::blocking::OutputPin;
|
||||
use log::*;
|
||||
|
||||
pub struct DcOutController<T: OutputPin> {
|
||||
pub state: bool,
|
||||
pin: T,
|
||||
pub enum DcOutStatus {
|
||||
On,
|
||||
Off,
|
||||
TurningOn(mpsc::Receiver<bool>),
|
||||
TurningOff(mpsc::Receiver<bool>),
|
||||
}
|
||||
|
||||
impl<T> DcOutController<T>
|
||||
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>
|
||||
where
|
||||
T: OutputPin,
|
||||
P: OutputPin + Send,
|
||||
{
|
||||
pub fn new(pin: T) -> DcOutController<T> {
|
||||
return DcOutController { state: false, pin };
|
||||
pin: Arc<Mutex<P>>,
|
||||
shutdown_tx: Option<mpsc::Sender<bool>>,
|
||||
status: DcOutStatus,
|
||||
}
|
||||
|
||||
impl<P> DcOutController<P>
|
||||
where
|
||||
P: OutputPin + Send,
|
||||
P: 'static,
|
||||
P: std::marker::Sync,
|
||||
{
|
||||
pub fn new(pin: P) -> Self {
|
||||
return Self {
|
||||
status: DcOutStatus::On,
|
||||
pin: Arc::new(Mutex::new(pin)),
|
||||
shutdown_tx: None,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn on(&mut self) -> Result<(), T::Error> {
|
||||
self.state = true;
|
||||
return self.pin.set_low();
|
||||
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), || {
|
||||
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"))?;
|
||||
|
||||
self.status = DcOutStatus::On;
|
||||
info!("DC OUT ON");
|
||||
Ok(())
|
||||
}
|
||||
pub fn off(&mut self) -> Result<(), T::Error> {
|
||||
self.state = false;
|
||||
return self.pin.set_high();
|
||||
|
||||
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);
|
||||
|
||||
let (status_tx, status_rx) = mpsc::channel();
|
||||
self.status = DcOutStatus::TurningOff(status_rx);
|
||||
|
||||
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...");
|
||||
|
||||
let result = || {
|
||||
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"));
|
||||
}).map_err(|_| anyhow!("Failed to send shutdown status"))?;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
81
src/main.rs
81
src/main.rs
@@ -1,52 +1,89 @@
|
||||
#![feature(is_some_with)]
|
||||
use esp_idf_sys as _;
|
||||
use std::{thread, time::Duration};
|
||||
use log::{error, info};
|
||||
use std::{thread, time::Duration, sync::mpsc, env};
|
||||
|
||||
use crate::wifi::WiFi;
|
||||
|
||||
mod beep;
|
||||
mod blink;
|
||||
mod dc_out_controller;
|
||||
mod manager;
|
||||
mod screen;
|
||||
mod wifi;
|
||||
fn main() {
|
||||
env::set_var("DEFMT_LOG", "trace");
|
||||
env::set_var("RUST_LOG", "trace");
|
||||
env_logger::init();
|
||||
// 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.
|
||||
esp_idf_sys::link_patches();
|
||||
|
||||
|
||||
let peripherals = esp_idf_hal::peripherals::Peripherals::take().unwrap();
|
||||
|
||||
let gpio5 = peripherals.pins.gpio5;
|
||||
let gpio6 = peripherals.pins.gpio6;
|
||||
let blink_pin = peripherals.pins.gpio5;
|
||||
let beep_pin = peripherals.pins.gpio6;
|
||||
let ledc_timer0 = peripherals.ledc.timer0;
|
||||
let ledc_channel0 = peripherals.ledc.channel0;
|
||||
let dc_out_ctl_pin = peripherals.pins.gpio2;
|
||||
let dc_out_ctl_pin = peripherals.pins.gpio3;
|
||||
let i2c0 = peripherals.i2c0;
|
||||
let sda_pin = peripherals.pins.gpio4;
|
||||
let scl_pin = peripherals.pins.gpio10;
|
||||
|
||||
let adc1 = peripherals.adc1;
|
||||
let adapter_pin = peripherals.pins.gpio1;
|
||||
let battery_pin = peripherals.pins.gpio2;
|
||||
|
||||
let (tx, mut rx) = mpsc::channel();
|
||||
|
||||
info!("Starting");
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut blink =
|
||||
blink::Blink::new(gpio5.into_output().expect("Failed to set GPIO5 as output"));
|
||||
blink::Blink::new(blink_pin.into_output().expect("Failed to set GPIO5 as output"));
|
||||
blink.play();
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_millis(5000));
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut beep = beep::Beep::new(
|
||||
gpio6.into_output().expect("Failed to set GPIO6 as output"),
|
||||
beep_pin.into_output().expect("Failed to set GPIO6 as output"),
|
||||
ledc_timer0,
|
||||
ledc_channel0,
|
||||
)
|
||||
.expect("Failed to create beep");
|
||||
beep.play();
|
||||
beep.play(&mut rx);
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut dc_out_ctl = dc_out_controller::DcOutController::new(
|
||||
dc_out_ctl_pin
|
||||
.into_output()
|
||||
.expect("Failed to set GPIO2 as output"),
|
||||
);
|
||||
while true {
|
||||
if dc_out_ctl.state {
|
||||
dc_out_ctl.off().expect("Failed to turn DC_OUT_CTL off");
|
||||
} else {
|
||||
dc_out_ctl.on().expect("Failed to turn DC_OUT_CTL on");
|
||||
let display = screen::Screen::new(i2c0, sda_pin, scl_pin).expect("Failed to create screen");
|
||||
|
||||
let dc_out_ctl = dc_out_controller::DcOutController::new(
|
||||
dc_out_ctl_pin
|
||||
.into_output()
|
||||
.expect("Failed to set GPIO3 as output"),
|
||||
);
|
||||
let mut manager = manager::Manager::new(
|
||||
dc_out_ctl,
|
||||
display,
|
||||
adc1,
|
||||
adapter_pin.into_analog_atten_11db().expect("Failed to set GPIO1 as analog input"),
|
||||
battery_pin.into_analog_atten_11db().expect("Failed to set GPIO2 as analog input"),
|
||||
tx,
|
||||
).expect("Failed to create manager");
|
||||
|
||||
let wifi = WiFi::new().expect("Failed to connect wifi");
|
||||
|
||||
loop {
|
||||
match manager.handling_once() {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!("Exec manager tick task failed: {}", err);
|
||||
}
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
});
|
||||
|
||||
println!("Hello, world!");
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
147
src/manager.rs
Normal file
147
src/manager.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use embedded_hal::digital::blocking::OutputPin;
|
||||
use embedded_hal_0_2::adc::OneShot;
|
||||
use esp_idf_hal::{
|
||||
adc::{Atten11dB, PoweredAdc, ADC1},
|
||||
gpio::{Gpio1, Gpio2},
|
||||
};
|
||||
use log::*;
|
||||
use std::{
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use crate::{beep::ringtone, dc_out_controller::DcOutController, screen::Screen};
|
||||
|
||||
type AdapterGpio = Gpio1<Atten11dB<ADC1>>;
|
||||
type BatteryGpio = Gpio2<Atten11dB<ADC1>>;
|
||||
|
||||
pub struct Manager<C>
|
||||
where
|
||||
C: OutputPin + Send,
|
||||
{
|
||||
dc_out_controller: DcOutController<C>,
|
||||
screen: Screen,
|
||||
adc: PoweredAdc<ADC1>,
|
||||
adapter_pin: AdapterGpio,
|
||||
battery_pin: BatteryGpio,
|
||||
tx: mpsc::Sender<ringtone::Type>,
|
||||
adapter_downed_at: Option<SystemTime>,
|
||||
}
|
||||
|
||||
impl<C> Manager<C>
|
||||
where
|
||||
C: OutputPin + Send,
|
||||
C: 'static,
|
||||
C: std::marker::Sync,
|
||||
{
|
||||
pub fn new(
|
||||
dc_out_controller: DcOutController<C>,
|
||||
screen: Screen,
|
||||
adc1: ADC1,
|
||||
adapter_pin: AdapterGpio,
|
||||
battery_pin: BatteryGpio,
|
||||
tx: mpsc::Sender<ringtone::Type>,
|
||||
) -> Result<Self> {
|
||||
let adc = PoweredAdc::new(
|
||||
adc1,
|
||||
esp_idf_hal::adc::config::Config::new().calibration(true),
|
||||
)
|
||||
.map_err(|err| anyhow!("Can not init Adc: {}", err))?;
|
||||
return Ok(Manager {
|
||||
dc_out_controller,
|
||||
screen,
|
||||
adc,
|
||||
adapter_pin,
|
||||
battery_pin,
|
||||
tx,
|
||||
adapter_downed_at: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_adapter_voltage(&mut self) -> Result<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> {
|
||||
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<()> {
|
||||
let mut adapter = 0.0_f32;
|
||||
let mut battery = 0.0_f32;
|
||||
for _ in 0..10 {
|
||||
adapter += self.get_adapter_voltage()?;
|
||||
battery += self.get_battery_voltage()?;
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
adapter /= 10.0_f32;
|
||||
battery /= 10.0_f32;
|
||||
|
||||
if is_adapter_down(adapter) {
|
||||
if self.dc_out_controller.get_status().is_off() {
|
||||
self.tx
|
||||
.send(ringtone::SILENCE)
|
||||
.map_err(|err| anyhow!("Can not send silence to Beep. {:?}", err))?;
|
||||
} else if is_battery_down(battery) {
|
||||
self.tx
|
||||
.send(ringtone::BATTERY_LOW)
|
||||
.expect("Can not send message");
|
||||
} else if is_battery_low(battery) {
|
||||
self.dc_out_controller.shutdown();
|
||||
if self.adapter_downed_at.is_none() {
|
||||
info!("Recording adapter downed at: {:?}", SystemTime::now());
|
||||
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 {
|
||||
self.tx
|
||||
.send(ringtone::ADAPTER_DOWN)
|
||||
.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 {
|
||||
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.dc_out_controller.open()?;
|
||||
}
|
||||
self.screen
|
||||
.draw_voltage(adapter, battery)
|
||||
.map_err(|err| anyhow!("Can not draw voltage. {:?}", err))?;
|
||||
|
||||
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
|
||||
}
|
142
src/screen.rs
Normal file
142
src/screen.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use anyhow::{anyhow, Result, Ok};
|
||||
use embedded_graphics::{
|
||||
mono_font::{ascii::FONT_10X20, iso_8859_10::FONT_6X10, MonoTextStyle},
|
||||
pixelcolor::Rgb565,
|
||||
prelude::{Dimensions, Drawable, Point, Primitive, RgbColor},
|
||||
primitives::{PrimitiveStyleBuilder, Rectangle},
|
||||
text::Text,
|
||||
};
|
||||
use embedded_hal::{delay::blocking::DelayUs, i2c::blocking::{I2c, Operation}};
|
||||
use esp_idf_hal::{
|
||||
delay,
|
||||
gpio::{self},
|
||||
i2c::{self, Master, I2C0},
|
||||
prelude::*,
|
||||
};
|
||||
use log::warn;
|
||||
use ssd1306::{
|
||||
mode::{BufferedGraphicsMode, DisplayConfig},
|
||||
prelude::I2CInterface,
|
||||
size::DisplaySize128x64,
|
||||
Ssd1306,
|
||||
};
|
||||
|
||||
type Display = Ssd1306<
|
||||
I2CInterface<Master<I2C0, gpio::Gpio4<gpio::Unknown>, gpio::Gpio10<gpio::Unknown>>>,
|
||||
DisplaySize128x64,
|
||||
BufferedGraphicsMode<DisplaySize128x64>,
|
||||
>;
|
||||
|
||||
pub struct Screen {
|
||||
pub display: Option<Display>,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new(
|
||||
i2c: i2c::I2C0,
|
||||
sda: gpio::Gpio4<gpio::Unknown>,
|
||||
scl: gpio::Gpio10<gpio::Unknown>,
|
||||
) -> Result<Self> {
|
||||
let config = <i2c::config::MasterConfig as Default>::default().baudrate(400.kHz().into());
|
||||
let mut i2c = i2c::Master::<i2c::I2C0, _, _>::new(i2c, i2c::MasterPins { sda, scl }, config)?;
|
||||
|
||||
let mut buff = [0u8; 10];
|
||||
if let Err(err) = i2c.transaction(0x3C, &mut [Operation::Read(&mut buff)]) {
|
||||
warn!("Failed to initialize display: {}", err);
|
||||
warn!("Failed to initialize display: {}", err);
|
||||
warn!("Failed to initialize display: {}", err);
|
||||
warn!("Failed to initialize display: {}", err);
|
||||
warn!("Failed to initialize display: {}", err);
|
||||
return Ok(Self { display: None });
|
||||
}
|
||||
|
||||
let di = ssd1306::I2CDisplayInterface::new(i2c);
|
||||
|
||||
|
||||
let mut delay = delay::Ets;
|
||||
delay.delay_ms(10_u32)?;
|
||||
|
||||
let mut display = ssd1306::Ssd1306::new(
|
||||
di,
|
||||
ssd1306::size::DisplaySize128x64,
|
||||
ssd1306::rotation::DisplayRotation::Rotate0,
|
||||
)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
display
|
||||
.init()
|
||||
.map_err(|err| anyhow!("Can not init display: {:?}", err))?;
|
||||
|
||||
let mut instance = Screen { display: Some(display) };
|
||||
|
||||
instance.draw_boot()?;
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
pub fn draw_boot(&mut self) -> Result<()> {
|
||||
if self.display.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let display = self.display.as_mut().unwrap();
|
||||
|
||||
display.clear();
|
||||
|
||||
Rectangle::new(
|
||||
display.bounding_box().top_left,
|
||||
display.bounding_box().size,
|
||||
)
|
||||
.into_styled(
|
||||
PrimitiveStyleBuilder::new()
|
||||
.fill_color(Rgb565::BLUE.into())
|
||||
.stroke_color(Rgb565::YELLOW.into())
|
||||
.stroke_width(1)
|
||||
.build(),
|
||||
)
|
||||
.draw(display)
|
||||
.expect("Failed to draw rectangle");
|
||||
|
||||
Text::new(
|
||||
"Ivan's UPS",
|
||||
Point::new(
|
||||
12,
|
||||
(display.bounding_box().size.height - 10) as i32 / 2 + 1,
|
||||
),
|
||||
MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE.into()),
|
||||
)
|
||||
.draw(display)
|
||||
.expect("Failed to draw text");
|
||||
|
||||
display
|
||||
.flush()
|
||||
.map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_voltage(&mut self, adapter: f32, battery: f32) -> Result<()> {
|
||||
if let Some(display) = self.display.as_mut() {
|
||||
display.clear();
|
||||
|
||||
Text::new(
|
||||
format!("Adp. {:.2} mV", adapter).as_str(),
|
||||
Point::new(12, 24),
|
||||
MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE.into()),
|
||||
)
|
||||
.draw(display)
|
||||
.expect("Failed to draw text");
|
||||
Text::new(
|
||||
format!("Bat. {:.2} mV", battery).as_str(),
|
||||
Point::new(12, 36),
|
||||
MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE.into()),
|
||||
)
|
||||
.draw(display)
|
||||
.expect("Failed to draw text");
|
||||
display
|
||||
.flush()
|
||||
.map_err(|e| anyhow::anyhow!("Display error: {:?}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
120
src/wifi.rs
Normal file
120
src/wifi.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::{
|
||||
bail,
|
||||
Result, Ok,
|
||||
};
|
||||
use esp_idf_svc::ping::EspPing;
|
||||
use esp_idf_svc::{
|
||||
netif::EspNetifStack, nvs::EspDefaultNvs, sysloop::EspSysLoopStack, wifi::EspWifi
|
||||
};
|
||||
use embedded_svc::{wifi::*, ipv4};
|
||||
use log::info;
|
||||
|
||||
use embedded_svc::ping::Ping;
|
||||
|
||||
pub struct WiFi {
|
||||
wifi: Box<EspWifi>,
|
||||
}
|
||||
|
||||
impl WiFi {
|
||||
pub fn new() -> Result<Self> {
|
||||
let netif_stack = Arc::new(EspNetifStack::new()?);
|
||||
let sys_loop_stack = Arc::new(EspSysLoopStack::new()?);
|
||||
let default_nvs = Arc::new(EspDefaultNvs::new()?);
|
||||
|
||||
let wifi = Self::wifi(
|
||||
netif_stack.clone(),
|
||||
sys_loop_stack.clone(),
|
||||
default_nvs.clone(),
|
||||
)?;
|
||||
|
||||
Ok(Self { wifi })
|
||||
}
|
||||
|
||||
fn wifi(
|
||||
netif_stack: Arc<EspNetifStack>,
|
||||
sys_loop_stack: Arc<EspSysLoopStack>,
|
||||
default_nvs: Arc<EspDefaultNvs>,
|
||||
) -> Result<Box<EspWifi>> {
|
||||
const SSID: &str = "Ivan Li";
|
||||
const PASSWORD: &str = "ivanli.cc";
|
||||
let mut wifi = Box::new(EspWifi::new(netif_stack, sys_loop_stack, default_nvs)?);
|
||||
|
||||
info!("Wifi created, about to scan");
|
||||
|
||||
let ap_infos = wifi.scan()?;
|
||||
|
||||
|
||||
info!("Wifi AP Count {}", ap_infos.len());
|
||||
|
||||
let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
|
||||
|
||||
let channel = if let Some(ours) = ours {
|
||||
info!(
|
||||
"Found configured access point {} on channel {}",
|
||||
SSID, ours.channel
|
||||
);
|
||||
Some(ours.channel)
|
||||
} else {
|
||||
info!(
|
||||
"Configured access point {} not found during scanning, will go with unknown channel",
|
||||
SSID
|
||||
);
|
||||
None
|
||||
};
|
||||
|
||||
wifi.set_configuration(&Configuration::Mixed(
|
||||
ClientConfiguration {
|
||||
ssid: SSID.into(),
|
||||
password: PASSWORD.into(),
|
||||
channel,
|
||||
..Default::default()
|
||||
},
|
||||
AccessPointConfiguration {
|
||||
ssid: "aptest".into(),
|
||||
channel: channel.unwrap_or(1),
|
||||
..Default::default()
|
||||
},
|
||||
))?;
|
||||
|
||||
info!("Wifi configuration set, about to get status");
|
||||
|
||||
wifi.wait_status_with_timeout(Duration::from_secs(20), |status| !status.is_transitional())
|
||||
.map_err(|e| anyhow::anyhow!("Unexpected Wifi status: {:?}", e))?;
|
||||
|
||||
let status = wifi.get_status();
|
||||
|
||||
if let Status(
|
||||
ClientStatus::Started(ClientConnectionStatus::Connected(ClientIpStatus::Done(
|
||||
ip_settings,
|
||||
))),
|
||||
ApStatus::Started(ApIpStatus::Done),
|
||||
) = status
|
||||
{
|
||||
info!("Wifi connected");
|
||||
|
||||
Self::ping(&ip_settings)?;
|
||||
} else {
|
||||
bail!("Unexpected Wifi status: {:?}", status);
|
||||
}
|
||||
|
||||
Ok(wifi)
|
||||
}
|
||||
|
||||
fn ping(ip_settings: &ipv4::ClientSettings) -> Result<()> {
|
||||
info!("About to do some pings for {:?}", ip_settings);
|
||||
let ping_summary =
|
||||
EspPing::default().ping(ip_settings.subnet.gateway, &Default::default())?;
|
||||
if ping_summary.transmitted != ping_summary.received {
|
||||
bail!(
|
||||
"Pinging gateway {} resulted in timeouts",
|
||||
ip_settings.subnet.gateway
|
||||
);
|
||||
}
|
||||
|
||||
info!("Pinging done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user