mirror of
				https://github.com/Djeeberjr/fw-anwesenheit.git
				synced 2025-11-04 07:34:10 +00:00 
			
		
		
		
	redesigned dir structure for rust 2018 style guide. made (untested) rtc funtion
This commit is contained in:
		
							parent
							
								
									4dda9548d3
								
							
						
					
					
						commit
						49027fed99
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -285,6 +285,7 @@ dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "embedded-hal 1.0.0",
 | 
			
		||||
 "embedded-hal-async",
 | 
			
		||||
 "maybe-async-cfg",
 | 
			
		||||
 "paste",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ edition = "2024"
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "fw-anwesenheit"
 | 
			
		||||
path = "./src/bin/main.rs"
 | 
			
		||||
path = "./src/main.rs"
 | 
			
		||||
test = false
 | 
			
		||||
doctest = false
 | 
			
		||||
bench = false
 | 
			
		||||
@ -60,7 +60,7 @@ edge-nal-embassy = { version = "0.6.0", features = ["log"] }
 | 
			
		||||
picoserve = { version = "0.16.0", features = ["embassy", "log"] }
 | 
			
		||||
embassy-sync = { version = "0.7.0", features = ["log"] }
 | 
			
		||||
 | 
			
		||||
ds3231 = "0.3.0"
 | 
			
		||||
ds3231 = { version = "0.3.0", features = ["async"] }
 | 
			
		||||
ws2812-spi = "0.5.1"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use super::TallyID;
 | 
			
		||||
use alloc::collections::BTreeMap;
 | 
			
		||||
use alloc::string::String;
 | 
			
		||||
 | 
			
		||||
pub struct Name {
 | 
			
		||||
    pub first: String,
 | 
			
		||||
    pub last: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct IDMapping {
 | 
			
		||||
    id_map: BTreeMap<TallyID, Name>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDMapping {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDMapping {
 | 
			
		||||
            id_map: BTreeMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn map(&self, id: &TallyID) -> Option<&Name> {
 | 
			
		||||
        self.id_map.get(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_mapping(&mut self, id: TallyID, name: Name) {
 | 
			
		||||
        self.id_map.insert(id, name);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,71 +0,0 @@
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use super::Date;
 | 
			
		||||
use super::IDMapping;
 | 
			
		||||
use super::TallyID;
 | 
			
		||||
use alloc::collections::BTreeMap;
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
 | 
			
		||||
pub struct AttendanceDay {
 | 
			
		||||
    date: Date,
 | 
			
		||||
    ids: Vec<TallyID>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AttendanceDay {
 | 
			
		||||
    fn new(date: Date) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            date,
 | 
			
		||||
            ids: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add an ID to the day.
 | 
			
		||||
    // Returns false if ID was already present
 | 
			
		||||
    fn add_id(&mut self, id: TallyID) -> bool {
 | 
			
		||||
        if self.ids.contains(&id) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        self.ids.push(id);
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct IDStore {
 | 
			
		||||
    days: BTreeMap<Date, AttendanceDay>,
 | 
			
		||||
    mapping: IDMapping,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDStore {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDStore {
 | 
			
		||||
            days: BTreeMap::new(),
 | 
			
		||||
            mapping: IDMapping::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_from_storage() -> Self {
 | 
			
		||||
        // TODO: implement
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new id for the current day
 | 
			
		||||
    /// Returns false if ID is already present at the current day.
 | 
			
		||||
    pub fn add_id(&mut self, id: TallyID) -> bool {
 | 
			
		||||
        self.get_current_day().add_id(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the `AttendanceDay` of the current day
 | 
			
		||||
    /// Creates a new if not exists
 | 
			
		||||
    pub fn get_current_day(&mut self) -> &mut AttendanceDay {
 | 
			
		||||
        let current_day: Date = 1;
 | 
			
		||||
 | 
			
		||||
        if self.days.contains_key(¤t_day) {
 | 
			
		||||
            return self.days.get_mut(¤t_day).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.days
 | 
			
		||||
            .insert(current_day, AttendanceDay::new(current_day));
 | 
			
		||||
 | 
			
		||||
        self.days.get_mut(¤t_day.clone()).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +0,0 @@
 | 
			
		||||
mod id_mapping;
 | 
			
		||||
mod id_store;
 | 
			
		||||
 | 
			
		||||
pub use id_mapping::{IDMapping, Name};
 | 
			
		||||
pub use id_store::IDStore;
 | 
			
		||||
 | 
			
		||||
pub type TallyID = [u8; 8];
 | 
			
		||||
pub type Date = u64;
 | 
			
		||||
							
								
								
									
										2
									
								
								src/drivers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/drivers.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
pub mod nfc_reader;
 | 
			
		||||
pub mod rtc;
 | 
			
		||||
							
								
								
									
										29
									
								
								src/drivers/nfc_reader.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/drivers/nfc_reader.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
use embassy_time::{Duration, Timer};
 | 
			
		||||
use esp_hal::{uart::Uart, Async};
 | 
			
		||||
use log::{ debug, info };
 | 
			
		||||
 | 
			
		||||
use crate::TallyPublisher;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[embassy_executor::task]
 | 
			
		||||
pub async fn rfid_reader_task(mut uart_device: Uart<'static, Async>, chan: TallyPublisher) {
 | 
			
		||||
    let mut uart_buffer = [0u8; 64];
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        debug!("Looking for NFC...");
 | 
			
		||||
        match uart_device.read_async(&mut uart_buffer).await {
 | 
			
		||||
            Ok(n) => {
 | 
			
		||||
                let mut hex_str = heapless::String::<64>::new();
 | 
			
		||||
                for byte in &uart_buffer[..n] {
 | 
			
		||||
                    core::fmt::Write::write_fmt(&mut hex_str, format_args!("{:02X} ", byte)).ok();
 | 
			
		||||
                }
 | 
			
		||||
                info!("Read {n} bytes from UART: {hex_str}");
 | 
			
		||||
                chan.publish([1, 0, 2, 5, 0, 8, 12, 15]).await;
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Error reading from UART: {e}");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Timer::after(Duration::from_millis(200)).await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								src/drivers/rtc.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/drivers/rtc.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
 | 
			
		||||
@ -55,8 +55,7 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
 | 
			
		||||
 | 
			
		||||
        let (buzzer_result, _) = join!(buzzer_handle, led_handle);
 | 
			
		||||
 | 
			
		||||
        buzzer_result.unwrap_or_else(|err| {
 | 
			
		||||
            error!("Failed to buzz: {err}");
 | 
			
		||||
        buzzer_result.unwrap_or_else(|err| {            error!("Failed to buzz: {err}");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let _ = self.led_to_status();
 | 
			
		||||
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
mod gpio_buzzer;
 | 
			
		||||
mod hotspot;
 | 
			
		||||
mod mock;
 | 
			
		||||
mod spi_led;
 | 
			
		||||
 | 
			
		||||
pub use gpio_buzzer::GPIOBuzzer;
 | 
			
		||||
pub use mock::{MockBuzzer, MockHotspot, MockLed};
 | 
			
		||||
pub use spi_led::SpiLed;
 | 
			
		||||
 | 
			
		||||
pub trait StatusLed {
 | 
			
		||||
    fn turn_off(&mut self) -> Result<()>;
 | 
			
		||||
 | 
			
		||||
    fn turn_on(&mut self, color: rgb::RGB8) -> Result<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Buzzer {
 | 
			
		||||
    fn modulated_tone(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        frequency_hz: f64,
 | 
			
		||||
        duration: Duration,
 | 
			
		||||
    ) -> impl Future<Output = Result<()>> + std::marker::Send;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								src/init.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/init.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
pub mod hardware;
 | 
			
		||||
pub mod network;
 | 
			
		||||
pub mod wifi;
 | 
			
		||||
@ -1,6 +1,13 @@
 | 
			
		||||
use core::slice::RChunks;
 | 
			
		||||
 | 
			
		||||
use ds3231::{DS3231, Alarm1Config};
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
use embassy_net::Stack;
 | 
			
		||||
use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO22, GPIO23, I2C0, UART1};
 | 
			
		||||
use embassy_net::{driver, Stack};
 | 
			
		||||
use embassy_sync::mutex::Mutex;
 | 
			
		||||
use esp_hal::config;
 | 
			
		||||
use esp_hal::gpio::{Input, Pull};
 | 
			
		||||
use esp_hal::i2c::master::Config;
 | 
			
		||||
use esp_hal::peripherals::{self, GPIO0, GPIO1, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO21, GPIO22, GPIO23, I2C0, UART1};
 | 
			
		||||
use esp_hal::time::Rate;
 | 
			
		||||
use esp_hal::{
 | 
			
		||||
    Async,
 | 
			
		||||
@ -13,8 +20,10 @@ use esp_hal::{
 | 
			
		||||
use esp_println::logger::init_logger;
 | 
			
		||||
use log::error;
 | 
			
		||||
 | 
			
		||||
mod network;
 | 
			
		||||
mod wifi;
 | 
			
		||||
use crate::init::wifi;
 | 
			
		||||
use crate::init::network;
 | 
			
		||||
 | 
			
		||||
const RTC_ADDRESS: u8 =  0x57;
 | 
			
		||||
 | 
			
		||||
#[panic_handler]
 | 
			
		||||
fn panic(_: &core::panic::PanicInfo) -> ! {
 | 
			
		||||
@ -23,7 +32,7 @@ fn panic(_: &core::panic::PanicInfo) -> ! {
 | 
			
		||||
 | 
			
		||||
esp_bootloader_esp_idf::esp_app_desc!();
 | 
			
		||||
 | 
			
		||||
pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>) {
 | 
			
		||||
pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stack<'static>, I2c<'static, Async>, GPIO21<'static>) {
 | 
			
		||||
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
 | 
			
		||||
    let peripherals = esp_hal::init(config);
 | 
			
		||||
 | 
			
		||||
@ -48,9 +57,12 @@ pub async fn hardware_init(spawner: &mut Spawner) -> (Uart<'static, Async>, Stac
 | 
			
		||||
 | 
			
		||||
    let i2c_device = setup_i2c(peripherals.I2C0, peripherals.GPIO22, peripherals.GPIO23);
 | 
			
		||||
 | 
			
		||||
    //RTC Interrupt pin
 | 
			
		||||
    let sqw_pin = peripherals.GPIO21;
 | 
			
		||||
 | 
			
		||||
    //TODO change to get I2C device back / maybe init for each protocol
 | 
			
		||||
 | 
			
		||||
    (uart_device, stack)
 | 
			
		||||
    (uart_device, stack, i2c_device, sqw_pin)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initialize the level shifter for the NFC reader and LED (output-enable (OE) input is low, all outputs are placed in the high-impedance (Hi-Z) state)
 | 
			
		||||
@ -80,17 +92,40 @@ fn setup_i2c(
 | 
			
		||||
    sda: GPIO22<'static>,
 | 
			
		||||
    scl: GPIO23<'static>,
 | 
			
		||||
) -> I2c<'static, Async> {
 | 
			
		||||
    let config = esp_hal::i2c::master::Config::default().with_frequency(Rate::from_khz(400));
 | 
			
		||||
    let i2c_device = I2c::new(i2c0, config);
 | 
			
		||||
    match i2c_device {
 | 
			
		||||
        Ok(block) => block.with_sda(sda).with_scl(scl).into_async(),
 | 
			
		||||
 | 
			
		||||
    let config = Config::default().with_frequency(Rate::from_khz(400));
 | 
			
		||||
    let i2c = match I2c::new(i2c0, config) {
 | 
			
		||||
        Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            error!("Failed to initialize I2C: {e}");
 | 
			
		||||
            error!("Failed to initialize I2C: {:?}", e);
 | 
			
		||||
            panic!();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
    i2c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn rtc_init_iterrupt(sqw_pin: GPIO21<'static>) -> Input<'static> {
 | 
			
		||||
    let config = esp_hal::gpio::InputConfig::default().with_pull(Pull::Up);
 | 
			
		||||
    let mut sqw_interrupt = Input::new(sqw_pin, config);
 | 
			
		||||
    sqw_interrupt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
 | 
			
		||||
    let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
 | 
			
		||||
    let daily_alarm = Alarm1Config::AtTime {
 | 
			
		||||
        hours: 9,
 | 
			
		||||
        minutes: 30,
 | 
			
		||||
        seconds: 0,
 | 
			
		||||
        is_pm: None, // 24-hour mode
 | 
			
		||||
    };
 | 
			
		||||
    if let Err(e) = rtc.set_alarm1(&daily_alarm).await {
 | 
			
		||||
        error!("Failed to configure RTC: {:?}", e);
 | 
			
		||||
        panic!();
 | 
			
		||||
    }
 | 
			
		||||
    rtc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn setup_spi_led() {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -6,15 +6,14 @@
 | 
			
		||||
use embassy_executor::Spawner;
 | 
			
		||||
use embassy_net::Stack;
 | 
			
		||||
use embassy_sync::{
 | 
			
		||||
    blocking_mutex::raw::NoopRawMutex,
 | 
			
		||||
    pubsub::{
 | 
			
		||||
    blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}, mutex::Mutex, pubsub::{
 | 
			
		||||
        PubSubChannel, Publisher,
 | 
			
		||||
        WaitResult::{Lagged, Message},
 | 
			
		||||
    },
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
use embassy_time::{Duration, Timer};
 | 
			
		||||
use esp_alloc::psram_allocator;
 | 
			
		||||
use esp_hal::Async;
 | 
			
		||||
use esp_hal::{i2c, peripherals, Async};
 | 
			
		||||
use esp_hal::uart::Uart;
 | 
			
		||||
use log::{debug, info};
 | 
			
		||||
use static_cell::make_static;
 | 
			
		||||
@ -22,17 +21,18 @@ use static_cell::make_static;
 | 
			
		||||
use crate::{store::TallyID, webserver::start_webserver};
 | 
			
		||||
 | 
			
		||||
mod init;
 | 
			
		||||
mod drivers;
 | 
			
		||||
mod store;
 | 
			
		||||
mod webserver;
 | 
			
		||||
 | 
			
		||||
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
 | 
			
		||||
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
 | 
			
		||||
 | 
			
		||||
static mut UTC_TIME: u64 = 0;
 | 
			
		||||
static UTC_TIME: Mutex<CriticalSectionRawMutex, u64> = Mutex::new(0);
 | 
			
		||||
 | 
			
		||||
#[esp_hal_embassy::main]
 | 
			
		||||
async fn main(mut spawner: Spawner) {
 | 
			
		||||
    let (uart_device, stack) = init::hardware_init(&mut spawner).await;
 | 
			
		||||
    let (uart_device, stack, i2c, sqw_pin) = init::hardware::hardware_init(&mut spawner).await;
 | 
			
		||||
 | 
			
		||||
    wait_for_stack_up(stack).await;
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,8 @@ async fn main(mut spawner: Spawner) {
 | 
			
		||||
 | 
			
		||||
    let publisher = chan.publisher().unwrap();
 | 
			
		||||
 | 
			
		||||
    spawner.must_spawn(rfid_reader_task(uart_device, publisher));
 | 
			
		||||
    spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(uart_device, publisher));
 | 
			
		||||
    spawner.must_spawn(rtc_task(i2c, sqw_pin));
 | 
			
		||||
 | 
			
		||||
    let mut sub = chan.subscriber().unwrap();
 | 
			
		||||
    loop {
 | 
			
		||||
@ -68,24 +69,22 @@ async fn wait_for_stack_up(stack: Stack<'static>) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[embassy_executor::task]
 | 
			
		||||
async fn rfid_reader_task(mut uart_device: Uart<'static, Async>, chan: TallyPublisher) {
 | 
			
		||||
    let mut uart_buffer = [0u8; 64];
 | 
			
		||||
async fn rtc_task(
 | 
			
		||||
    mut i2c: i2c::master::I2c<'static, Async>,
 | 
			
		||||
    sqw_pin: peripherals::GPIO21<'static>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut rtc_interrupt = init::hardware::rtc_init_iterrupt(sqw_pin).await;
 | 
			
		||||
    let mut rtc = init::hardware::rtc_config(i2c).await;
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        debug!("Looking for NFC...");
 | 
			
		||||
        match uart_device.read_async(&mut uart_buffer).await {
 | 
			
		||||
            Ok(n) => {
 | 
			
		||||
                let mut hex_str = heapless::String::<64>::new();
 | 
			
		||||
                for byte in &uart_buffer[..n] {
 | 
			
		||||
                    core::fmt::Write::write_fmt(&mut hex_str, format_args!("{:02X} ", byte)).ok();
 | 
			
		||||
                }
 | 
			
		||||
                info!("Read {n} bytes from UART: {hex_str}");
 | 
			
		||||
                chan.publish([1, 0, 2, 5, 0, 8, 12, 15]).await;
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Error reading from UART: {e}");
 | 
			
		||||
            }
 | 
			
		||||
        rtc_interrupt.wait_for_falling_edge().await;
 | 
			
		||||
        debug!("RTC interrupt triggered");
 | 
			
		||||
        if let Ok(datetime) = rtc.datetime().await {
 | 
			
		||||
            let mut utc_time = UTC_TIME.lock().await;
 | 
			
		||||
            *utc_time = datetime.and_utc().timestamp() as u64;
 | 
			
		||||
            info!("RTC updated UTC_TIME: {}", *utc_time);
 | 
			
		||||
        } else {
 | 
			
		||||
            info!("Failed to read RTC datetime");
 | 
			
		||||
        }
 | 
			
		||||
        Timer::after(Duration::from_millis(200)).await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@ -1,22 +1,22 @@
 | 
			
		||||
use crate::tally_id::TallyID;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use super::TallyID;
 | 
			
		||||
use alloc::collections::BTreeMap;
 | 
			
		||||
use alloc::string::String;
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
 | 
			
		||||
pub struct Name {
 | 
			
		||||
    pub first: String,
 | 
			
		||||
    pub last: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize, Clone)]
 | 
			
		||||
pub struct IDMapping {
 | 
			
		||||
    id_map: HashMap<TallyID, Name>,
 | 
			
		||||
    id_map: BTreeMap<TallyID, Name>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDMapping {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDMapping {
 | 
			
		||||
            id_map: HashMap::new(),
 | 
			
		||||
            id_map: BTreeMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -29,48 +29,3 @@ impl IDMapping {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn basic() {
 | 
			
		||||
        let mut map = IDMapping::new();
 | 
			
		||||
        let id1 = TallyID("A2Fb44".to_owned());
 | 
			
		||||
        let name1 = Name {
 | 
			
		||||
            first: "Max".to_owned(),
 | 
			
		||||
            last: "Mustermann".to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        map.add_mapping(id1.clone(), name1.clone());
 | 
			
		||||
 | 
			
		||||
        let res = map.map(&id1);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(res, Some(&name1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn multiple() {
 | 
			
		||||
        let mut map = IDMapping::new();
 | 
			
		||||
        let id1 = TallyID("A2Fb44".to_owned());
 | 
			
		||||
        let name1 = Name {
 | 
			
		||||
            first: "Max".to_owned(),
 | 
			
		||||
            last: "Mustermann".to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let id2 = TallyID("7D3DF5B5".to_owned());
 | 
			
		||||
        let name2 = Name {
 | 
			
		||||
            first: "First".to_owned(),
 | 
			
		||||
            last: "Last".to_owned(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        map.add_mapping(id1.clone(), name1.clone());
 | 
			
		||||
        map.add_mapping(id2.clone(), name2.clone());
 | 
			
		||||
 | 
			
		||||
        let res = map.map(&id1);
 | 
			
		||||
        assert_eq!(res, Some(&name1));
 | 
			
		||||
 | 
			
		||||
        let res = map.map(&id2);
 | 
			
		||||
        assert_eq!(res, Some(&name2));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,123 +1,20 @@
 | 
			
		||||
use anyhow::{Result, anyhow};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::collections::{HashMap, HashSet};
 | 
			
		||||
use tokio::fs;
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
use crate::{store::IDMapping, tally_id::TallyID};
 | 
			
		||||
use super::Date;
 | 
			
		||||
use super::IDMapping;
 | 
			
		||||
use super::TallyID;
 | 
			
		||||
use alloc::collections::BTreeMap;
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
 | 
			
		||||
/// Represents a single day that IDs can attend
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub struct AttendanceDay {
 | 
			
		||||
    date: String,
 | 
			
		||||
    date: Date,
 | 
			
		||||
    ids: Vec<TallyID>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Stores all the days
 | 
			
		||||
#[derive(Deserialize, Serialize)]
 | 
			
		||||
pub struct IDStore {
 | 
			
		||||
    days: HashMap<String, AttendanceDay>,
 | 
			
		||||
    pub mapping: IDMapping,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDStore {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDStore {
 | 
			
		||||
            days: HashMap::new(),
 | 
			
		||||
            mapping: IDMapping::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creats a new `IDStore` from a json file
 | 
			
		||||
    pub async fn new_from_json(filepath: &str) -> Result<Self> {
 | 
			
		||||
        let read_string = fs::read_to_string(filepath).await?;
 | 
			
		||||
        Ok(serde_json::from_str(&read_string)?)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new id for the current day
 | 
			
		||||
    /// Returns false if ID is already present at the current day.
 | 
			
		||||
    pub fn add_id(&mut self, id: TallyID) -> bool {
 | 
			
		||||
        self.get_current_day().add_id(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the `AttendanceDay` of the current day
 | 
			
		||||
    /// Creates a new if not exists
 | 
			
		||||
    pub fn get_current_day(&mut self) -> &mut AttendanceDay {
 | 
			
		||||
        let current_day = get_day_str();
 | 
			
		||||
 | 
			
		||||
        if self.days.contains_key(¤t_day) {
 | 
			
		||||
            return self.days.get_mut(¤t_day).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.days.insert(
 | 
			
		||||
            current_day.clone(),
 | 
			
		||||
            AttendanceDay::new(¤t_day.clone()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self.days.get_mut(¤t_day.clone()).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Writes the store to a json file
 | 
			
		||||
    pub async fn export_json(&self, filepath: &str) -> Result<()> {
 | 
			
		||||
        fs::write(filepath, serde_json::to_string(&self)?).await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Export the store to a csv file.
 | 
			
		||||
    /// With days in the rows and IDs in the collum.
 | 
			
		||||
    pub fn export_csv(&self) -> Result<String> {
 | 
			
		||||
        let mut csv = String::new();
 | 
			
		||||
        let seperator = ";";
 | 
			
		||||
        let mut user_ids: HashSet<TallyID> = HashSet::new();
 | 
			
		||||
 | 
			
		||||
        for day in self.days.values() {
 | 
			
		||||
            for id in day.ids.iter() {
 | 
			
		||||
                user_ids.insert(id.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut user_ids: Vec<TallyID> = user_ids.into_iter().collect();
 | 
			
		||||
        user_ids.sort();
 | 
			
		||||
 | 
			
		||||
        let mut days: Vec<String> = self.days.keys().cloned().collect();
 | 
			
		||||
        days.sort();
 | 
			
		||||
 | 
			
		||||
        let header = days.join(seperator);
 | 
			
		||||
        csv.push_str(&format!(
 | 
			
		||||
            "ID{seperator}Nachname{seperator}Vorname{seperator}{header}\n"
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        for user_id in user_ids.iter() {
 | 
			
		||||
            let id = &user_id.0.to_string();
 | 
			
		||||
            let name = self.mapping.map(user_id);
 | 
			
		||||
 | 
			
		||||
            let firstname = name.map(|e| e.first.clone()).unwrap_or("".to_owned());
 | 
			
		||||
            let lastname = name.map(|e| e.last.clone()).unwrap_or("".to_owned());
 | 
			
		||||
 | 
			
		||||
            csv.push_str(&format!("{id}{seperator}{lastname}{seperator}{firstname}"));
 | 
			
		||||
            for day in days.iter() {
 | 
			
		||||
                let was_there: bool = self
 | 
			
		||||
                    .days
 | 
			
		||||
                    .get(day)
 | 
			
		||||
                    .ok_or(anyhow!("Failed to access day"))?
 | 
			
		||||
                    .ids
 | 
			
		||||
                    .contains(user_id);
 | 
			
		||||
 | 
			
		||||
                if was_there {
 | 
			
		||||
                    csv.push_str(&format!("{seperator}x"));
 | 
			
		||||
                } else {
 | 
			
		||||
                    csv.push_str(seperator);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            csv.push('\n');
 | 
			
		||||
        }
 | 
			
		||||
        Ok(csv)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AttendanceDay {
 | 
			
		||||
    fn new(day: &str) -> Self {
 | 
			
		||||
    fn new(date: Date) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            date: day.to_owned(),
 | 
			
		||||
            date,
 | 
			
		||||
            ids: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -133,7 +30,42 @@ impl AttendanceDay {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_day_str() -> String {
 | 
			
		||||
    let now = chrono::offset::Local::now();
 | 
			
		||||
    now.format("%Y-%m-%d").to_string()
 | 
			
		||||
pub struct IDStore {
 | 
			
		||||
    days: BTreeMap<Date, AttendanceDay>,
 | 
			
		||||
    mapping: IDMapping,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IDStore {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        IDStore {
 | 
			
		||||
            days: BTreeMap::new(),
 | 
			
		||||
            mapping: IDMapping::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_from_storage() -> Self {
 | 
			
		||||
        // TODO: implement
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new id for the current day
 | 
			
		||||
    /// Returns false if ID is already present at the current day.
 | 
			
		||||
    pub fn add_id(&mut self, id: TallyID) -> bool {
 | 
			
		||||
        self.get_current_day().add_id(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the `AttendanceDay` of the current day
 | 
			
		||||
    /// Creates a new if not exists
 | 
			
		||||
    pub fn get_current_day(&mut self) -> &mut AttendanceDay {
 | 
			
		||||
        let current_day: Date = 1;
 | 
			
		||||
 | 
			
		||||
        if self.days.contains_key(¤t_day) {
 | 
			
		||||
            return self.days.get_mut(¤t_day).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.days
 | 
			
		||||
            .insert(current_day, AttendanceDay::new(current_day));
 | 
			
		||||
 | 
			
		||||
        self.days.get_mut(¤t_day.clone()).unwrap()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
mod id_store;
 | 
			
		||||
mod id_mapping;
 | 
			
		||||
mod id_store;
 | 
			
		||||
 | 
			
		||||
pub use id_mapping::{IDMapping, Name};
 | 
			
		||||
pub use id_store::IDStore;
 | 
			
		||||
pub use id_mapping::{IDMapping,Name};
 | 
			
		||||
 | 
			
		||||
pub type TallyID = [u8; 8];
 | 
			
		||||
pub type Date = u64;
 | 
			
		||||
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    cmp::Ordering,
 | 
			
		||||
    fmt::Display,
 | 
			
		||||
    hash::{Hash, Hasher},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
/// Represents the ID that is stored on the Tally
 | 
			
		||||
/// Is case-insensitive.
 | 
			
		||||
/// While any string can be a ID, most IDs are going to be a hex string.
 | 
			
		||||
#[derive(Deserialize, Serialize, Clone)]
 | 
			
		||||
pub struct TallyID(pub String);
 | 
			
		||||
 | 
			
		||||
impl PartialEq for TallyID {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.0.eq_ignore_ascii_case(&other.0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Hash for TallyID {
 | 
			
		||||
    fn hash<H: Hasher>(&self, state: &mut H) {
 | 
			
		||||
        self.0.to_uppercase().hash(state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Eq for TallyID {}
 | 
			
		||||
 | 
			
		||||
impl Ord for TallyID {
 | 
			
		||||
    fn cmp(&self, other: &Self) -> Ordering {
 | 
			
		||||
        self.0.to_uppercase().cmp(&other.0.to_uppercase())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialOrd for TallyID {
 | 
			
		||||
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | 
			
		||||
        Some(self.cmp(other))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Display for TallyID {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        write!(f, "{}", self.0.to_uppercase())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user