Compare commits

...

4 Commits

Author SHA1 Message Date
0f5ca88ae4 fixed many warning by removing unused imports
Removed a lot of imports — believe me, so many imports were totally
unnecessary. Nobody’s seen imports like these. Cleaned up the code, made
it faster, smarter, the best code. People are talking about it!
Tremendous work by me. Some say i am the best at it. It may be true.
2025-10-08 01:54:51 +02:00
9dd2f88cbc implemented SSE 2025-10-08 01:44:32 +02:00
aa91d69f0b explicit type of tallyid channels 2025-10-08 01:43:48 +02:00
b13ae76bc5 moved TallyID str function to right module 2025-10-08 01:42:25 +02:00
13 changed files with 112 additions and 102 deletions

View File

@@ -2,7 +2,7 @@ use embassy_time::{Duration, Timer};
use esp_hal::{Async, uart::Uart}; use esp_hal::{Async, uart::Uart};
use log::{debug, info}; use log::{debug, info};
use crate::{TallyPublisher, store::TallyID}; use crate::TallyPublisher;
#[embassy_executor::task] #[embassy_executor::task]
pub async fn rfid_reader_task(mut uart_device: Uart<'static, Async>, chan: TallyPublisher) { pub async fn rfid_reader_task(mut uart_device: Uart<'static, Async>, chan: TallyPublisher) {

View File

@@ -1,3 +1,4 @@
use chrono::{TimeZone, Utc};
use ds3231::{ use ds3231::{
Config, DS3231, DS3231Error, InterruptControl, Oscillator, SquareWaveFrequency, Config, DS3231, DS3231Error, InterruptControl, Oscillator, SquareWaveFrequency,
TimeRepresentation, TimeRepresentation,
@@ -9,7 +10,6 @@ use esp_hal::{
use log::{debug, error, info}; use log::{debug, error, info};
use crate::{FEEDBACK_STATE, drivers, feedback, store::Date}; use crate::{FEEDBACK_STATE, drivers, feedback, store::Date};
use chrono::{TimeZone, Utc};
include!(concat!(env!("OUT_DIR"), "/build_time.rs")); include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
@@ -49,7 +49,7 @@ impl RTCClock {
pub async fn get_date(&mut self) -> Date { pub async fn get_date(&mut self) -> Date {
let (year, month, day) = unix_to_ymd_string(self.get_time().await); let (year, month, day) = unix_to_ymd_string(self.get_time().await);
let mut buffer: Date = [0; 10] ; let mut buffer: Date = [0; 10];
// Write YYYY // Write YYYY
buffer[0] = b'0' + ((year / 1000) % 10) as u8; buffer[0] = b'0' + ((year / 1000) % 10) as u8;
@@ -70,7 +70,6 @@ impl RTCClock {
buffer buffer
} }
} }
fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) { fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) {
@@ -82,11 +81,11 @@ fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) {
// Convert to proleptic Gregorian date // Convert to proleptic Gregorian date
civil_from_days(days_since_epoch as i64 + UNIX_OFFSET_DAYS as i64) civil_from_days(days_since_epoch as i64 + UNIX_OFFSET_DAYS as i64)
} }
// This function returns (year, month, day). // This function returns (year, month, day).
// Based on the algorithm by Howard Hinnant. // Based on the algorithm by Howard Hinnant.
fn civil_from_days(z: i64) -> (u16, u8, u8) { fn civil_from_days(z: i64) -> (u16, u8, u8) {
let mut z = z; let mut z = z;
z -= 60; // shift epoch for algorithm z -= 60; // shift epoch for algorithm
let era = (z >= 0).then_some(z).unwrap_or(z - 146096) / 146097; let era = (z >= 0).then_some(z).unwrap_or(z - 146096) / 146097;
@@ -98,7 +97,7 @@ fn unix_to_ymd_string(timestamp: u64) -> (u16, u8, u8) {
let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
let m = mp + (if mp < 10 { 3 } else { -9 }); // [1, 12] let m = mp + (if mp < 10 { 3 } else { -9 }); // [1, 12]
((y + (m <= 2) as i64) as u16, m as u8, d as u8) ((y + (m <= 2) as i64) as u16, m as u8, d as u8)
} }
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> { 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 mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);

View File

@@ -1,8 +1,7 @@
use embassy_time::{Delay, Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::{delay, gpio::Output, peripherals, rmt::ConstChannelAccess}; use esp_hal::{peripherals, rmt::ConstChannelAccess};
use esp_hal_smartled::SmartLedsAdapterAsync; use esp_hal_smartled::SmartLedsAdapterAsync;
use init::hardware; use log::debug;
use log::{debug, error, info};
use smart_leds::SmartLedsWriteAsync; use smart_leds::SmartLedsWriteAsync;
use smart_leds::colors::{BLACK, GREEN, RED, YELLOW}; use smart_leds::colors::{BLACK, GREEN, RED, YELLOW};
use smart_leds::{brightness, colors::BLUE}; use smart_leds::{brightness, colors::BLUE};

View File

@@ -1,13 +1,10 @@
use core::cell::RefCell; use core::cell::RefCell;
use bleps::att::Att;
use critical_section::Mutex; use critical_section::Mutex;
use ds3231::InterruptControl;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::Stack; use embassy_net::Stack;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use esp_hal::gpio::{Input, InputConfig}; use esp_hal::Blocking;
use esp_hal::gpio::Input;
use esp_hal::i2c::master::Config; use esp_hal::i2c::master::Config;
use esp_hal::peripherals::{ use esp_hal::peripherals::{
GPIO0, GPIO1, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, I2C0, RMT, SPI2, GPIO0, GPIO1, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, I2C0, RMT, SPI2,
@@ -15,8 +12,6 @@ use esp_hal::peripherals::{
}; };
use esp_hal::rmt::{ConstChannelAccess, Rmt}; use esp_hal::rmt::{ConstChannelAccess, Rmt};
use esp_hal::spi::master::{Config as Spi_config, Spi}; use esp_hal::spi::master::{Config as Spi_config, Spi};
use esp_hal::Blocking;
use esp_hal::time::Rate; use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_hal::{ use esp_hal::{
@@ -28,16 +23,12 @@ use esp_hal::{
uart::Uart, uart::Uart,
}; };
use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async}; use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async};
use esp_println::dbg;
use esp_println::logger::init_logger; use esp_println::logger::init_logger;
use log::{debug, error, info}; use log::{debug, error};
use crate::FEEDBACK_STATE;
use crate::init::network; use crate::init::network;
use crate::init::sd_card::{setup_sdcard, SDCardPersistence}; use crate::init::sd_card::{SDCardPersistence, setup_sdcard};
use crate::init::wifi; use crate::init::wifi;
use crate::store::AttendanceDay;
use crate::store::persistence::Persistence;
/************************************************* /*************************************************
* GPIO Pinout Xiao Esp32c6 * GPIO Pinout Xiao Esp32c6

View File

@@ -1,5 +1,4 @@
use core::{net::Ipv4Addr, str::FromStr}; use core::{net::Ipv4Addr, str::FromStr};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};

View File

@@ -10,7 +10,7 @@ use embassy_sync::{
blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}, blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex},
mutex::Mutex, mutex::Mutex,
pubsub::{ pubsub::{
PubSubChannel, Publisher, PubSubChannel, Publisher, Subscriber,
WaitResult::{Lagged, Message}, WaitResult::{Lagged, Message},
}, },
signal::Signal, signal::Signal,
@@ -21,14 +21,14 @@ use esp_hal::{gpio::InputConfig, peripherals};
use log::{debug, info}; use log::{debug, info};
use static_cell::make_static; use static_cell::make_static;
extern crate alloc;
use crate::{ use crate::{
init::sd_card::SDCardPersistence, init::sd_card::SDCardPersistence,
store::{Date, IDStore, TallyID}, store::{Date, IDStore, TallyID},
webserver::start_webserver, webserver::start_webserver,
}; };
extern crate alloc;
mod drivers; mod drivers;
mod feedback; mod feedback;
mod init; mod init;
@@ -39,6 +39,7 @@ static FEEDBACK_STATE: Signal<CriticalSectionRawMutex, feedback::FeedbackState>
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>; type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>; type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
type TallySubscriber = Subscriber<'static, NoopRawMutex, TallyID, 8, 2, 1>;
type UsedStore = IDStore<SDCardPersistence>; type UsedStore = IDStore<SDCardPersistence>;
#[esp_hal_embassy::main] #[esp_hal_embassy::main]
@@ -54,12 +55,12 @@ async fn main(mut spawner: Spawner) {
let shared_store = Rc::new(Mutex::new(store)); let shared_store = Rc::new(Mutex::new(store));
let chan: &'static mut TallyChannel = make_static!(PubSubChannel::new()); let chan: &'static mut TallyChannel = make_static!(PubSubChannel::new());
let publisher = chan.publisher().unwrap(); let publisher: TallyPublisher = chan.publisher().unwrap();
let mut sub = chan.subscriber().unwrap(); let mut sub: TallySubscriber = chan.subscriber().unwrap();
wait_for_stack_up(stack).await; wait_for_stack_up(stack).await;
start_webserver(&mut spawner, stack, shared_store.clone()); start_webserver(&mut spawner, stack, shared_store.clone(), chan);
/****************************** Spawning tasks ***********************************/ /****************************** Spawning tasks ***********************************/
debug!("spawing NFC reader task..."); debug!("spawing NFC reader task...");

View File

@@ -1,8 +1,9 @@
use super::TallyID;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::string::String; use alloc::string::String;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::TallyID;
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Name { pub struct Name {
pub first: String, pub first: String,

View File

@@ -1,13 +1,11 @@
use crate::drivers::rtc; use alloc::vec::Vec;
use crate::drivers::rtc::RTCClock; use serde::Deserialize;
use crate::store::persistence::Persistence; use serde::Serialize;
use super::Date; use super::Date;
use super::IDMapping; use super::IDMapping;
use super::TallyID; use super::TallyID;
use alloc::vec::Vec; use crate::store::persistence::Persistence;
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug)]
pub struct AttendanceDay { pub struct AttendanceDay {
@@ -75,7 +73,6 @@ impl<T: Persistence> IDStore<T> {
/// Add a new id for the current day /// Add a new id for the current day
/// Returns false if ID is already present at 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: Date) -> bool {
if self.current_day.date == current_date { if self.current_day.date == current_date {
let changed = self.current_day.add_id(id); let changed = self.current_day.add_id(id);
if changed { if changed {

View File

@@ -1,9 +1,48 @@
mod id_mapping; use heapless::String;
pub mod persistence;
mod id_store;
pub use id_mapping::{IDMapping, Name}; pub use id_mapping::{IDMapping, Name};
pub use id_store::{IDStore,AttendanceDay}; pub use id_store::{IDStore,AttendanceDay};
mod id_mapping;
pub mod persistence;
mod id_store;
pub type TallyID = [u8; 6]; pub type TallyID = [u8; 6];
pub type Date = [u8; 10]; pub type Date = [u8; 10];
pub fn hex_string_to_tally_id(s: &str) -> Option<TallyID> {
let bytes = s.as_bytes();
if bytes.len() != 12 {
return None;
}
let mut out: TallyID = [0;6];
for i in 0..6 {
let hi = hex_val(bytes[2 * i])?;
let lo = hex_val(bytes[2 * i + 1])?;
out[i] = (hi << 4) | lo;
}
Some(out)
}
pub fn tally_id_to_hex_string(bytes: TallyID) -> Option<String<12>> {
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
let mut s: String<12> = String::new();
for &b in &bytes {
s.push(HEX_CHARS[(b >> 4) as usize] as char).ok()?;
s.push(HEX_CHARS[(b & 0x0F) as usize] as char).ok()?;
}
Some(s)
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}

View File

@@ -6,8 +6,8 @@ use picoserve::{
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
store::{Name, TallyID}, store::{Name, hex_string_to_tally_id},
webserver::app::AppState, webserver::{app::AppState, sse::IDEvents},
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -16,30 +16,6 @@ pub struct NewMapping {
name: Name, name: Name,
} }
pub fn hex_string_to_tally_id(s: &str) -> Option<TallyID> {
let bytes = s.as_bytes();
if bytes.len() != 24 {
return None;
}
let mut out = [0u8; 12];
for i in 0..12 {
let hi = hex_val(bytes[2 * i])?;
let lo = hex_val(bytes[2 * i + 1])?;
out[i] = (hi << 4) | lo;
}
Some(out)
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
/* /*
* #[get("/api/idevent")] * #[get("/api/idevent")]
* #[get("/api/csv")] * #[get("/api/csv")]
@@ -64,3 +40,7 @@ pub async fn add_mapping(
let tally_id = hex_string_to_tally_id(&data.id).unwrap(); let tally_id = hex_string_to_tally_id(&data.id).unwrap();
store.mapping.add_mapping(tally_id, data.name); store.mapping.add_mapping(tally_id, data.name);
} }
pub async fn get_idevent(State(state): State<AppState>) -> impl IntoResponse {
response::EventStream(IDEvents(state.chan.subscriber().unwrap()))
}

View File

@@ -3,15 +3,17 @@ use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
use picoserve::{AppWithStateBuilder, routing::get}; use picoserve::{AppWithStateBuilder, routing::get};
use crate::{ use crate::{
TallyChannel, UsedStore,
webserver::{ webserver::{
api::{add_mapping, get_mapping}, api::{add_mapping, get_idevent, get_mapping},
assets::Assets, assets::Assets,
}, UsedStore, },
}; };
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
pub store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>, pub store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
pub chan: &'static TallyChannel,
} }
pub struct AppProps; pub struct AppProps;
@@ -23,9 +25,6 @@ impl AppWithStateBuilder for AppProps {
fn build_app(self) -> picoserve::Router<Self::PathRouter, AppState> { fn build_app(self) -> picoserve::Router<Self::PathRouter, AppState> {
picoserve::Router::from_service(Assets) picoserve::Router::from_service(Assets)
.route("/api/mapping", get(get_mapping).post(add_mapping)) .route("/api/mapping", get(get_mapping).post(add_mapping))
// .route( .route("/api/idevent", get(get_idevent))
// "/api/idevent",
// get(move || response::EventStream(Events(self.chan))),
// )
} }
} }

View File

@@ -7,14 +7,14 @@ use picoserve::{AppRouter, AppWithStateBuilder};
use static_cell::make_static; use static_cell::make_static;
use crate::{ use crate::{
UsedStore, TallyChannel, UsedStore,
webserver::app::{AppProps, AppState}, webserver::app::{AppProps, AppState},
}; };
mod assets;
// mod sse;
mod api; mod api;
mod app; mod app;
mod assets;
mod sse;
pub const WEB_TAKS_SIZE: usize = 3; // Up this number if request start fail with Timeouts. pub const WEB_TAKS_SIZE: usize = 3; // Up this number if request start fail with Timeouts.
@@ -22,10 +22,11 @@ pub fn start_webserver(
spawner: &mut Spawner, spawner: &mut Spawner,
stack: Stack<'static>, stack: Stack<'static>,
store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>, store: Rc<Mutex<CriticalSectionRawMutex, UsedStore>>,
chan: &'static TallyChannel,
) { ) {
let app = make_static!(AppProps.build_app()); let app = make_static!(AppProps.build_app());
let state = make_static!(AppState { store }); let state = make_static!(AppState { store, chan });
let config = make_static!(picoserve::Config::new(picoserve::Timeouts { let config = make_static!(picoserve::Config::new(picoserve::Timeouts {
start_read_request: Some(Duration::from_secs(5)), start_read_request: Some(Duration::from_secs(5)),

View File

@@ -2,9 +2,11 @@ use embassy_time::{Duration, Timer};
use log::warn; use log::warn;
use picoserve::response; use picoserve::response;
pub struct Events(pub TallySubscriber); use crate::{TallySubscriber, store::tally_id_to_hex_string};
impl response::sse::EventSource for Events { pub struct IDEvents(pub TallySubscriber);
impl response::sse::EventSource for IDEvents {
async fn write_events<W: picoserve::io::Write>( async fn write_events<W: picoserve::io::Write>(
mut self, mut self,
mut writer: response::sse::EventWriter<W>, mut writer: response::sse::EventWriter<W>,
@@ -16,7 +18,9 @@ impl response::sse::EventSource for Events {
match sel.await { match sel.await {
embassy_futures::select::Either::First(msg) => match msg { embassy_futures::select::Either::First(msg) => match msg {
embassy_sync::pubsub::WaitResult::Message(id) => { embassy_sync::pubsub::WaitResult::Message(id) => {
writer.write_event("msg", id.to_string().as_str()).await? writer
.write_event("msg", tally_id_to_hex_string(id).unwrap().as_str())
.await?
} }
embassy_sync::pubsub::WaitResult::Lagged(_) => { embassy_sync::pubsub::WaitResult::Lagged(_) => {
warn!("SSE subscriber got lagged"); warn!("SSE subscriber got lagged");