Added smart sleeping
This commit is contained in:
180
src/main.rs
180
src/main.rs
@@ -1,15 +1,16 @@
|
|||||||
use core::panic;
|
|
||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
use std::{borrow::Cow, num::NonZero};
|
use std::{borrow::Cow};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use chrono::{format::Fixed, DateTime, Datelike, Days, FixedOffset, NaiveTime};
|
use anyhow::Context;
|
||||||
|
use chrono::{DateTime, Datelike, Days, FixedOffset, NaiveTime, TimeDelta, Timelike, Weekday};
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
prelude::{Dimensions, Point},
|
prelude::{Dimensions, Point},
|
||||||
text::{Text},
|
text::{Text},
|
||||||
Drawable,
|
Drawable,
|
||||||
};
|
};
|
||||||
use epd_waveshare::{color::Color, epd7in5_v2::Epd7in5, prelude::WaveshareDisplay};
|
use epd_waveshare::{color::Color, epd7in5_v2::Epd7in5, prelude::WaveshareDisplay};
|
||||||
|
use esp_idf_hal::modem::Modem;
|
||||||
use esp_idf_hal::{
|
use esp_idf_hal::{
|
||||||
delay::{Ets},
|
delay::{Ets},
|
||||||
gpio::{Gpio19, PinDriver},
|
gpio::{Gpio19, PinDriver},
|
||||||
@@ -35,7 +36,7 @@ type Timetable = [[Option<Lesson>;8]; 5];
|
|||||||
static mut WAKEUP_COUNTER: u64 = 0;
|
static mut WAKEUP_COUNTER: u64 = 0;
|
||||||
|
|
||||||
#[link_section = ".rtc.data"]
|
#[link_section = ".rtc.data"]
|
||||||
static mut WIFI_FAIL_COUNTER: Option<NonZeroU16> = None;
|
static mut FETCHING_FAIL_COUNT: Option<NonZeroU16> = None;
|
||||||
|
|
||||||
#[link_section = ".rtc.data"]
|
#[link_section = ".rtc.data"]
|
||||||
static mut ERROR_MSG: Option<[u8; 2048]> = None;
|
static mut ERROR_MSG: Option<[u8; 2048]> = None;
|
||||||
@@ -53,9 +54,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let busy = PinDriver::input(peripherals.pins.gpio17).unwrap();
|
let busy = PinDriver::input(peripherals.pins.gpio17).unwrap();
|
||||||
let rst = PinDriver::output(peripherals.pins.gpio16).unwrap();
|
let rst = PinDriver::output(peripherals.pins.gpio16).unwrap();
|
||||||
let cs = peripherals.pins.gpio5; // y
|
let cs = peripherals.pins.gpio5;
|
||||||
let clk = peripherals.pins.gpio18; // y
|
let clk = peripherals.pins.gpio18;
|
||||||
let mosi = peripherals.pins.gpio23; // y
|
let mosi = peripherals.pins.gpio23;
|
||||||
let miso = peripherals.pins.gpio19; // aka. dc
|
let miso = peripherals.pins.gpio19; // aka. dc
|
||||||
|
|
||||||
let dc = PinDriver::output(miso).unwrap();
|
let dc = PinDriver::output(miso).unwrap();
|
||||||
@@ -145,73 +146,42 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a file called wifi-creds.txt and write SSID and password into it separated by a line break
|
// Create a file called wifi-creds.txt and write SSID and password into it separated by a line break
|
||||||
let mut creds = include_str!("../wifi-creds.txt").split('\n');
|
let (tt, now) = match fetch_data(peripherals.modem){
|
||||||
let ssid: heapless::String<32> =
|
Ok(d) => d,
|
||||||
heapless::String::<32>::from_str(creds.next().unwrap()).unwrap();
|
Err(e) => {
|
||||||
let password: heapless::String<64> =
|
|
||||||
heapless::String::<64>::from_str(creds.next().unwrap()).unwrap();
|
|
||||||
|
|
||||||
// Connecting to wifi
|
|
||||||
let sysloop = EspSystemEventLoop::take().unwrap();
|
|
||||||
let nvs = EspDefaultNvsPartition::take().unwrap();
|
|
||||||
let mut wifi = BlockingWifi::wrap(
|
|
||||||
EspWifi::new(peripherals.modem, sysloop.clone(), Some(nvs)).unwrap(),
|
|
||||||
sysloop.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
wifi.set_configuration(&Configuration::Client(
|
|
||||||
esp_idf_svc::wifi::ClientConfiguration {
|
|
||||||
ssid,
|
|
||||||
password,
|
|
||||||
auth_method: esp_idf_svc::wifi::AuthMethod::WPA2WPA3Personal,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
let connected = wifi.start()
|
|
||||||
.and_then(|_| wifi.connect())
|
|
||||||
.and_then(|_| wifi.wait_netif_up())
|
|
||||||
.and_then(|_| loop {if wifi.is_connected().unwrap() {break Ok(());}}).is_ok();
|
|
||||||
|
|
||||||
if !connected{
|
|
||||||
let fail_count: u16 = unsafe {
|
let fail_count: u16 = unsafe {
|
||||||
let new_val = match WIFI_FAIL_COUNTER{
|
let new_val = match FETCHING_FAIL_COUNT{
|
||||||
Some(c) => c.saturating_add(1),
|
Some(c) => c.saturating_add(1),
|
||||||
None => {
|
None => {
|
||||||
NonZeroU16::new_unchecked(1)
|
NonZeroU16::new_unchecked(1)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
WIFI_FAIL_COUNTER = Some(new_val);
|
FETCHING_FAIL_COUNT = Some(new_val);
|
||||||
new_val
|
new_val
|
||||||
}.into();
|
}.into();
|
||||||
|
|
||||||
println!("Failed to connect to wifi for the {}th time", fail_count);
|
println!("Failed to fetch timetable for the {}th time. Error: {}", fail_count, e);
|
||||||
|
|
||||||
// Quadratic falloff
|
// Quadratic falloff maxing out at 1 hours intervals
|
||||||
let sleep_time = u32::min((fail_count as u32).pow(2u32) * 4, 600);
|
let sleep_time = u32::min((fail_count as u32).pow(2u32) * 4, 3600);
|
||||||
println!("Going to sleep for {} seconds", sleep_time);
|
println!("Going to sleep for {} seconds", sleep_time);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
esp_deep_sleep(sleep_time as u64 * 1000_000)
|
esp_deep_sleep(sleep_time as u64 * 1000_000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
println!("Connected to wifi");
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
WIFI_FAIL_COUNTER = None;
|
FETCHING_FAIL_COUNT = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let httpconnection = EspHttpConnection::new(&HttpConfig {
|
|
||||||
use_global_ca_store: true,
|
|
||||||
crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
|
|
||||||
..Default::default()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (tt, now) = fetch_timetable(httpconnection);
|
|
||||||
|
|
||||||
|
// We wanna panic here if this fails because retrying this in a couple of seconds is stupid
|
||||||
|
let next_wakeup_time = calculate_next_wakeuptime(now).context(format!("Failed to calculate next wakeup time for time {now}")).unwrap();
|
||||||
|
|
||||||
// Render the layout
|
// Render the layout
|
||||||
draw_layout(&mut display, &tt, now);
|
draw_layout(&mut display, &tt, now, next_wakeup_time);
|
||||||
|
|
||||||
// Actually push changes to the display
|
// Actually push changes to the display
|
||||||
let mut epd7in5 = Epd7in5::new(&mut spi, busy, dc, rst, &mut delay, None).unwrap();
|
let mut epd7in5 = Epd7in5::new(&mut spi, busy, dc, rst, &mut delay, None).unwrap();
|
||||||
@@ -219,19 +189,86 @@ fn main() -> anyhow::Result<()> {
|
|||||||
epd7in5.sleep(&mut spi, &mut delay)?;
|
epd7in5.sleep(&mut spi, &mut delay)?;
|
||||||
log::info!("Completed");
|
log::info!("Completed");
|
||||||
|
|
||||||
wifi.disconnect().unwrap();
|
|
||||||
wifi.stop().unwrap();
|
|
||||||
|
|
||||||
drop(epd7in5);
|
drop(epd7in5);
|
||||||
drop(wifi);
|
|
||||||
drop(spi);
|
drop(spi);
|
||||||
|
|
||||||
|
let seconds_until_wakeup = next_wakeup_time.timestamp() - now.timestamp();
|
||||||
|
|
||||||
unsafe{
|
unsafe{
|
||||||
esp_deep_sleep(10 * 60 * 1000_000);
|
esp_deep_sleep(seconds_until_wakeup as u64 * 1000_000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_layout(display: &mut epd_waveshare::epd7in5_v2::Display7in5, timetable: &Timetable, now: DateTime<FixedOffset>){
|
fn calculate_next_wakeuptime(now: DateTime<FixedOffset>) -> Option<DateTime<FixedOffset>>{
|
||||||
|
|
||||||
|
if now.weekday().num_days_from_monday() > 4 || (now.weekday() == Weekday::Fri && now.time() > NaiveTime::from_hms_opt(15, 15, 0)?){
|
||||||
|
// On weekends, wait until 5am on the next day to update
|
||||||
|
|
||||||
|
let d = now.checked_add_signed(TimeDelta::days(1))?;
|
||||||
|
let d = d.with_time(NaiveTime::from_hms_opt(5, 0, 0).unwrap()).single()?;
|
||||||
|
|
||||||
|
Some(d)
|
||||||
|
}else if now.time() > NaiveTime::from_hms_opt(15, 15, 0)? || now.time() < NaiveTime::from_hms_opt(5, 0, 0)?{
|
||||||
|
// If it's a weekday but it's after 15:15 or before 5am, update after an hour
|
||||||
|
Some(now.checked_add_signed(TimeDelta::minutes(60))?)
|
||||||
|
}else{
|
||||||
|
// In any other case, update 5 minutes before the end of a lesson but at least 10 minutes from now
|
||||||
|
let (_, end_time) = get_school_period_and_end_time(now.time());
|
||||||
|
|
||||||
|
let before_lesson_end = now.with_time(end_time.overflowing_sub_signed(TimeDelta::minutes(5)).0).earliest()?;
|
||||||
|
let in_10_minutes = now.checked_add_signed(TimeDelta::minutes(10))?;
|
||||||
|
Some(before_lesson_end.max(in_10_minutes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_data(modem: Modem) -> anyhow::Result<(Box<Timetable>, DateTime<FixedOffset>)>{
|
||||||
|
let mut creds = include_str!("../wifi-creds.txt").split('\n');
|
||||||
|
let ssid: heapless::String<32> =
|
||||||
|
heapless::String::<32>::from_str(creds.next().unwrap()).unwrap();
|
||||||
|
let password: heapless::String<64> =
|
||||||
|
heapless::String::<64>::from_str(creds.next().unwrap()).unwrap();
|
||||||
|
|
||||||
|
// Connecting to wifi
|
||||||
|
let sysloop = EspSystemEventLoop::take()?;
|
||||||
|
let nvs = EspDefaultNvsPartition::take()?;
|
||||||
|
let mut wifi = BlockingWifi::wrap(
|
||||||
|
EspWifi::new(modem, sysloop.clone(), Some(nvs))?,
|
||||||
|
sysloop.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
wifi.set_configuration(&Configuration::Client(
|
||||||
|
esp_idf_svc::wifi::ClientConfiguration {
|
||||||
|
ssid,
|
||||||
|
password,
|
||||||
|
auth_method: esp_idf_svc::wifi::AuthMethod::WPA2WPA3Personal,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))?;
|
||||||
|
|
||||||
|
wifi.start()
|
||||||
|
.and_then(|_| wifi.connect())
|
||||||
|
.and_then(|_| wifi.wait_netif_up())
|
||||||
|
.and_then(|_| loop {if wifi.is_connected().unwrap() {break Ok(());}})?;
|
||||||
|
|
||||||
|
|
||||||
|
println!("Connected to wifi");
|
||||||
|
|
||||||
|
let httpconnection = EspHttpConnection::new(&HttpConfig {
|
||||||
|
use_global_ca_store: true,
|
||||||
|
crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
|
||||||
|
..Default::default()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let res = fetch_timetable(httpconnection);
|
||||||
|
|
||||||
|
wifi.disconnect().unwrap();
|
||||||
|
wifi.stop().unwrap();
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn draw_layout(display: &mut epd_waveshare::epd7in5_v2::Display7in5, timetable: &Timetable, now: DateTime<FixedOffset>, next_wakeup_time: DateTime<FixedOffset>){
|
||||||
let display_area = display.bounding_box();
|
let display_area = display.bounding_box();
|
||||||
|
|
||||||
let margin = FixedMargin(10);
|
let margin = FixedMargin(10);
|
||||||
@@ -277,12 +314,16 @@ fn draw_layout(display: &mut epd_waveshare::epd7in5_v2::Display7in5, timetable:
|
|||||||
let time_str = format!("{} ({})", now.format("%Y-%m-%d %H:%M"), unsafe {WAKEUP_COUNTER});
|
let time_str = format!("{} ({})", now.format("%Y-%m-%d %H:%M"), unsafe {WAKEUP_COUNTER});
|
||||||
let time_text = Text::new(&time_str, Point::zero(), &text_style_small);
|
let time_text = Text::new(&time_str, Point::zero(), &text_style_small);
|
||||||
|
|
||||||
|
let next_wakeup_time_str = format!("Next wakup: {}", next_wakeup_time.naive_local());
|
||||||
|
|
||||||
let time_table_grid = LinearLayout::horizontal(columns)
|
let time_table_grid = LinearLayout::horizontal(columns)
|
||||||
.with_alignment(vertical::Center)
|
.with_alignment(vertical::Center)
|
||||||
.with_spacing(margin)
|
.with_spacing(margin)
|
||||||
.arrange();
|
.arrange();
|
||||||
|
|
||||||
LinearLayout::vertical(Chain::new(time_table_grid).append(time_text))
|
let subtext = LinearLayout::vertical(Chain::new(time_text).append(Text::new(&next_wakeup_time_str, Point::zero(), &text_style_small))).with_alignment(horizontal::Center).with_spacing(FixedMargin(2)).arrange();
|
||||||
|
|
||||||
|
LinearLayout::vertical(Chain::new(time_table_grid).append(subtext))
|
||||||
.with_alignment(horizontal::Center)
|
.with_alignment(horizontal::Center)
|
||||||
.with_spacing(DistributeFill(display_area.size.height-20))
|
.with_spacing(DistributeFill(display_area.size.height-20))
|
||||||
.arrange()
|
.arrange()
|
||||||
@@ -314,6 +355,15 @@ fn get_school_period(time: NaiveTime) -> u8{
|
|||||||
7u8
|
7u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_school_period_and_end_time(time: NaiveTime) -> (u8, NaiveTime){
|
||||||
|
for (index, period_time) in PERIOD_LOOKUP.iter().enumerate(){
|
||||||
|
if time < *period_time{
|
||||||
|
return (index as u8, *period_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(7u8, PERIOD_LOOKUP[7])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn transform_lessons(lessons: Vec<Lesson>) -> Box<Timetable>{
|
fn transform_lessons(lessons: Vec<Lesson>) -> Box<Timetable>{
|
||||||
|
|
||||||
@@ -333,8 +383,8 @@ fn transform_lessons(lessons: Vec<Lesson>) -> Box<Timetable>{
|
|||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Make this return result
|
||||||
fn fetch_timetable(client: EspHttpConnection) -> (Box<Timetable>, DateTime<FixedOffset>) {
|
fn fetch_timetable(client: EspHttpConnection) -> anyhow::Result<(Box<Timetable>, DateTime<FixedOffset>)> {
|
||||||
let mut creds = include_str!("../untis-creds.txt").split('\n');
|
let mut creds = include_str!("../untis-creds.txt").split('\n');
|
||||||
|
|
||||||
let server = creds.next().unwrap();
|
let server = creds.next().unwrap();
|
||||||
@@ -348,12 +398,12 @@ fn fetch_timetable(client: EspHttpConnection) -> (Box<Timetable>, DateTime<Fixed
|
|||||||
usr,
|
usr,
|
||||||
password,
|
password,
|
||||||
client
|
client
|
||||||
).unwrap();
|
).or_else(|e| None.context(format!("{:?}", e)))?;
|
||||||
let cest = chrono::FixedOffset::east_opt(2 * 3600).unwrap();
|
let cest = chrono::FixedOffset::east_opt(2 * 3600).unwrap();
|
||||||
let now = s.date().unwrap().with_timezone(&cest);
|
let now = s.date().context("Untis client provided no time but time is necessary for the requesting the timetable")?.with_timezone(&cest);
|
||||||
println!("Current time: {}", now);
|
println!("Current time: {}", now);
|
||||||
let date = now.date_naive().checked_add_days(Days::new(2)).unwrap();
|
let date = now.date_naive().checked_add_days(Days::new(2)).unwrap();
|
||||||
let lessons = s.own_timetable_for_week(&Date(date)).unwrap();
|
let lessons = s.own_timetable_for_week(&Date(date)).or_else(|e| None.context(format!("{:?}", e)))?;
|
||||||
|
|
||||||
(transform_lessons(lessons), now)
|
Ok((transform_lessons(lessons), now))
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user