mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2026-05-01 02:59:09 +00:00
Compare commits
6 Commits
030a372949
...
5c0ad18b94
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c0ad18b94 | |||
| 75130e2d20 | |||
| 6b2c56f3e5 | |||
| 2980d34394 | |||
| 9b926f7a34 | |||
| f1b471c6d8 |
@@ -9,7 +9,7 @@ use esp_hal::{
|
||||
};
|
||||
use log::{debug, error, info};
|
||||
|
||||
use crate::{FEEDBACK_STATE, drivers, feedback, store::Date};
|
||||
use crate::{FEEDBACK_STATE, drivers, feedback};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
|
||||
|
||||
@@ -45,31 +45,6 @@ impl RTCClock {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_date(&mut self) -> Date {
|
||||
let (year, month, day) = unix_to_ymd_string(self.get_time().await);
|
||||
|
||||
let mut buffer: Date = [0; 10];
|
||||
|
||||
// Write YYYY
|
||||
buffer[0] = b'0' + ((year / 1000) % 10) as u8;
|
||||
buffer[1] = b'0' + ((year / 100) % 10) as u8;
|
||||
buffer[2] = b'0' + ((year / 10) % 10) as u8;
|
||||
buffer[3] = b'0' + (year % 10) as u8;
|
||||
buffer[4] = b'.';
|
||||
|
||||
// Write MM
|
||||
buffer[5] = b'0' + (month / 10) as u8;
|
||||
buffer[6] = b'0' + (month % 10) as u8;
|
||||
|
||||
buffer[7] = b'.';
|
||||
|
||||
// Write DD
|
||||
buffer[8] = b'0' + (day / 10) as u8;
|
||||
buffer[9] = b'0' + (day % 10) as u8;
|
||||
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use alloc::vec::Vec;
|
||||
use embassy_time::Delay;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager};
|
||||
use embedded_sdmmc::{SdCard, ShortFileName, TimeSource, Timestamp, VolumeIdx, VolumeManager};
|
||||
use esp_hal::{Blocking, gpio::Output, spi::master::Spi};
|
||||
|
||||
use crate::store::{AttendanceDay, Date, persistence::Persistence};
|
||||
use crate::store::{AttendanceDay, IDMapping, day::Day, persistence::Persistence};
|
||||
|
||||
pub struct DummyTimesource;
|
||||
|
||||
@@ -38,15 +38,31 @@ pub struct SDCardPersistence {
|
||||
vol_mgr: VolMgr,
|
||||
}
|
||||
|
||||
impl SDCardPersistence {
|
||||
const MAPPING_FILENAME: &'static str = "MAPPING.JS";
|
||||
|
||||
fn generate_filename(day: Day) -> ShortFileName {
|
||||
let basename = day.to_string();
|
||||
let mut filename: heapless::String<11> = heapless::String::new();
|
||||
filename.push_str(&basename).unwrap();
|
||||
filename.push_str(".js").unwrap();
|
||||
|
||||
ShortFileName::create_from_str(&filename).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Persistence for SDCardPersistence {
|
||||
async fn load_day(&mut self, day: crate::store::Date) -> Option<AttendanceDay> {
|
||||
async fn load_day(&mut self, day: Day) -> Option<AttendanceDay> {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
let mut file = root_dir.open_file_in_dir("day.jsn", embedded_sdmmc::Mode::ReadOnly);
|
||||
|
||||
if let Err(e) = file {
|
||||
let filename = Self::generate_filename(day);
|
||||
let file = root_dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly);
|
||||
|
||||
if file.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut open_file = file.unwrap();
|
||||
|
||||
let mut read_buffer: [u8; 1024] = [0; 1024];
|
||||
@@ -58,35 +74,72 @@ impl Persistence for SDCardPersistence {
|
||||
Some(day)
|
||||
}
|
||||
|
||||
async fn save_day(&mut self, day: Date, data: &AttendanceDay) {
|
||||
async fn save_day(&mut self, day: Day, data: &AttendanceDay) {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
|
||||
let filename = Self::generate_filename(day);
|
||||
|
||||
let mut file = root_dir
|
||||
.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.unwrap();
|
||||
|
||||
file.write(&serde_json::to_vec(data).unwrap()).unwrap();
|
||||
|
||||
file.flush().unwrap();
|
||||
file.close().unwrap();
|
||||
}
|
||||
|
||||
async fn load_mapping(&mut self) -> Option<crate::store::IDMapping> {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
|
||||
let file =
|
||||
root_dir.open_file_in_dir(Self::MAPPING_FILENAME, embedded_sdmmc::Mode::ReadOnly);
|
||||
|
||||
if file.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut open_file = file.unwrap();
|
||||
|
||||
let mut read_buffer: [u8; 1024] = [0; 1024];
|
||||
let read = open_file.read(&mut read_buffer).unwrap();
|
||||
open_file.close().unwrap();
|
||||
|
||||
let mapping: IDMapping = serde_json::from_slice(&read_buffer[..read]).unwrap();
|
||||
|
||||
Some(mapping)
|
||||
}
|
||||
|
||||
async fn save_mapping(&mut self, data: &crate::store::IDMapping) {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
|
||||
let mut file = root_dir
|
||||
.open_file_in_dir("day.jsn", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.open_file_in_dir(
|
||||
Self::MAPPING_FILENAME,
|
||||
embedded_sdmmc::Mode::ReadWriteCreateOrTruncate,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
file.write(&serde_json::to_vec(data).unwrap()).unwrap();
|
||||
file.flush();
|
||||
file.close();
|
||||
|
||||
file.flush().unwrap();
|
||||
file.close().unwrap();
|
||||
}
|
||||
|
||||
async fn load_mapping(&mut self) -> Option<crate::store::IDMapping> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn save_mapping(&mut self, data: &crate::store::IDMapping) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn list_days(&mut self) -> Vec<Date> {
|
||||
async fn list_days(&mut self) -> Vec<Day> {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
let mut days_dir = root_dir.open_dir("days").unwrap();
|
||||
|
||||
let mut days: Vec<[u8; 10]> = Vec::new();
|
||||
let mut days: Vec<Day> = Vec::new();
|
||||
days_dir
|
||||
.iterate_dir(|e| {
|
||||
days.push([0; 10]);
|
||||
let filename = e.name.clone();
|
||||
let day: Day = filename.try_into().unwrap();
|
||||
days.push(day);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ extern crate alloc;
|
||||
|
||||
use crate::{
|
||||
init::sd_card::SDCardPersistence,
|
||||
store::{Date, IDStore, tally_id::TallyID},
|
||||
store::{IDStore, day::Day, tally_id::TallyID},
|
||||
webserver::start_webserver,
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ async fn main(mut spawner: Spawner) {
|
||||
Message(msg) => {
|
||||
debug!("Got message: {msg:?}");
|
||||
|
||||
let day: Date = rtc.get_date().await;
|
||||
let day: Day = rtc.get_time().await.into();
|
||||
let added = shared_store.lock().await.add_id(msg, day).await;
|
||||
|
||||
if added {
|
||||
|
||||
63
src/store/day.rs
Normal file
63
src/store/day.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use core::fmt::Write;
|
||||
|
||||
use embedded_sdmmc::ShortFileName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Day(u32);
|
||||
|
||||
impl Day {
|
||||
const SECONDS_PER_DAY: u64 = 86_400;
|
||||
|
||||
pub fn new(daystamp: u32) -> Self {
|
||||
Day(daystamp)
|
||||
}
|
||||
|
||||
pub fn new_from_timestamp(time: u64) -> Self {
|
||||
let day = time / Self::SECONDS_PER_DAY;
|
||||
|
||||
if day > u32::MAX as u64 {
|
||||
// TBH this would only happen if about 11 million years have passed
|
||||
// I sure hope i don't have to work on this project any more then
|
||||
// So we just cap it at this
|
||||
Day(u32::MAX)
|
||||
} else {
|
||||
Day(day as u32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_timestamp(self) -> u64 {
|
||||
(self.0 as u64) * Self::SECONDS_PER_DAY
|
||||
}
|
||||
|
||||
pub fn to_string(self) -> heapless::String<8> {
|
||||
let mut s: heapless::String<8> = heapless::String::new();
|
||||
write!(s, "{:08X}", self.0).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
pub fn from_hex_str(s: &str) -> Result<Self, &'static str> {
|
||||
if s.len() > 8 {
|
||||
return Err("hex string too long");
|
||||
}
|
||||
|
||||
u32::from_str_radix(s, 16)
|
||||
.map_err(|_| "invalid hex string")
|
||||
.map(Day)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Day {
|
||||
fn from(value: u64) -> Self {
|
||||
Self::new_from_timestamp(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ShortFileName> for Day {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: ShortFileName) -> Result<Self, Self::Error> {
|
||||
let name = core::str::from_utf8(value.base_name()).map_err(|_| ())?;
|
||||
Self::from_hex_str(name).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,9 @@ pub struct Name {
|
||||
pub last: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct IDMapping {
|
||||
#[serde(flatten)]
|
||||
id_map: BTreeMap<TallyID, Name>,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,19 @@ use alloc::vec::Vec;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Date;
|
||||
use super::IDMapping;
|
||||
use crate::store::day::Day;
|
||||
use crate::store::persistence::Persistence;
|
||||
use crate::store::tally_id::TallyID;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct AttendanceDay {
|
||||
date: Date,
|
||||
date: Day,
|
||||
ids: Vec<TallyID>,
|
||||
}
|
||||
|
||||
impl AttendanceDay {
|
||||
pub fn new(date: Date) -> Self {
|
||||
pub fn new(date: Day) -> Self {
|
||||
Self {
|
||||
date,
|
||||
ids: Vec::new(),
|
||||
@@ -41,12 +41,12 @@ pub struct IDStore<T: Persistence> {
|
||||
|
||||
impl<T: Persistence> IDStore<T> {
|
||||
pub async fn new_from_storage(mut persistence_layer: T) -> Self {
|
||||
// let mapping = match persistence_layer.load_mapping().await {
|
||||
// Some(map) => map,
|
||||
// None => IDMapping::new(),
|
||||
// };
|
||||
let mapping = match persistence_layer.load_mapping().await {
|
||||
Some(map) => map,
|
||||
None => IDMapping::new(),
|
||||
};
|
||||
|
||||
let current_date: Date = [0; 10];
|
||||
let current_date: Day = Day::new(1);
|
||||
|
||||
let day = persistence_layer
|
||||
.load_day(current_date)
|
||||
@@ -55,7 +55,7 @@ impl<T: Persistence> IDStore<T> {
|
||||
|
||||
Self {
|
||||
current_day: day,
|
||||
mapping: IDMapping::new(),
|
||||
mapping,
|
||||
persistence_layer,
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ impl<T: Persistence> IDStore<T> {
|
||||
|
||||
/// Add a new id for the current day
|
||||
/// Returns false if ID is already present at the current day.
|
||||
pub async fn add_id(&mut self, id: TallyID, current_date: Date) -> bool {
|
||||
pub async fn add_id(&mut self, id: TallyID, current_date: Day) -> bool {
|
||||
if self.current_day.date == current_date {
|
||||
let changed = self.current_day.add_id(id);
|
||||
if changed {
|
||||
|
||||
@@ -5,6 +5,5 @@ mod id_mapping;
|
||||
pub mod persistence;
|
||||
mod id_store;
|
||||
pub mod tally_id;
|
||||
|
||||
pub type Date = [u8; 10];
|
||||
pub mod day;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::store::{Date, IDMapping, id_store::AttendanceDay};
|
||||
use crate::store::{IDMapping, day::Day, id_store::AttendanceDay};
|
||||
|
||||
pub trait Persistence {
|
||||
async fn load_day(&mut self, day: Date) -> Option<AttendanceDay>;
|
||||
async fn save_day(&mut self, day: Date, data: &AttendanceDay);
|
||||
async fn list_days(&mut self) -> Vec<Date>;
|
||||
async fn load_day(&mut self, day: Day) -> Option<AttendanceDay>;
|
||||
async fn save_day(&mut self, day: Day, data: &AttendanceDay);
|
||||
async fn list_days(&mut self) -> Vec<Day>;
|
||||
|
||||
async fn load_mapping(&mut self) -> Option<IDMapping>;
|
||||
async fn save_mapping(&mut self, data: &IDMapping);
|
||||
|
||||
Reference in New Issue
Block a user