diff --git a/src/main.rs b/src/main.rs index cc033a9..c39bd30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ extern crate alloc; use crate::{ init::sd_card::SDCardPersistence, - store::{Date, IDStore, TallyID}, + store::{Date, IDStore, tally_id::TallyID}, webserver::start_webserver, }; diff --git a/src/store/id_mapping.rs b/src/store/id_mapping.rs index d531af2..41dc2c6 100644 --- a/src/store/id_mapping.rs +++ b/src/store/id_mapping.rs @@ -2,7 +2,7 @@ use alloc::collections::BTreeMap; use alloc::string::String; use serde::{Deserialize, Serialize}; -use super::TallyID; +use crate::store::tally_id::TallyID; #[derive(Clone, Serialize, Deserialize)] pub struct Name { diff --git a/src/store/id_store.rs b/src/store/id_store.rs index 61ebc4c..e8c675b 100644 --- a/src/store/id_store.rs +++ b/src/store/id_store.rs @@ -4,8 +4,8 @@ use serde::Serialize; use super::Date; use super::IDMapping; -use super::TallyID; use crate::store::persistence::Persistence; +use crate::store::tally_id::TallyID; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct AttendanceDay { diff --git a/src/store/mod.rs b/src/store/mod.rs index 66848f0..ef7402a 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -1,48 +1,10 @@ -use heapless::String; - pub use id_mapping::{IDMapping, Name}; pub use id_store::{IDStore,AttendanceDay}; mod id_mapping; pub mod persistence; mod id_store; +pub mod tally_id; -pub type TallyID = [u8; 6]; pub type Date = [u8; 10]; -pub fn hex_string_to_tally_id(s: &str) -> Option { - 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> { - 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 { - 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, - } -} - diff --git a/src/store/tally_id.rs b/src/store/tally_id.rs new file mode 100644 index 0000000..56bcb83 --- /dev/null +++ b/src/store/tally_id.rs @@ -0,0 +1,93 @@ +use core::str::FromStr; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct TallyID([u8; 6]); + +impl FromStr for TallyID { + type Err = (); + + fn from_str(s: &str) -> Result { + s.as_bytes().try_into() + } +} + +impl TryFrom> for TallyID { + type Error = (); + + fn try_from(value: heapless::String<12>) -> Result { + let bytes = value.as_bytes(); + + let mut out: [u8; 6] = [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; + } + + Ok(TallyID(out)) + } +} + +fn hex_val(b: u8) -> Result { + match b { + b'0'..=b'9' => Ok(b - b'0'), + b'a'..=b'f' => Ok(b - b'a' + 10), + b'A'..=b'F' => Ok(b - b'A' + 10), + _ => Err(()), + } +} + +impl From for heapless::String<12> { + fn from(value: TallyID) -> Self { + const HEX_CHARS: &[u8; 16] = b"0123456789ABCDEF"; + let mut s: Self = Self::new(); + + for &b in &value.0 { + // Should be safe to unwrap since the string is already long enough + s.push(HEX_CHARS[(b >> 4) as usize] as char).unwrap(); + s.push(HEX_CHARS[(b & 0x0F) as usize] as char).unwrap(); + } + s + } +} + +/// From a array of hex chars +impl TryFrom<&[u8]> for TallyID { + type Error = (); + + fn try_from(value: &[u8]) -> Result { + if value.len() != 12 { + return Err(()); + } + + let mut out: [u8; 6] = [0; 6]; + for i in 0..6 { + let hi = hex_val(value[2 * i])?; + let lo = hex_val(value[2 * i + 1])?; + out[i] = (hi << 4) | lo; + } + + Ok(TallyID(out)) + } +} + +impl Serialize for TallyID { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s: heapless::String<12> = (*self).into(); + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for TallyID { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + TallyID::from_str(s).map_err(|_| de::Error::custom("Failed to parse Tally ID")) + } +} diff --git a/src/webserver/api.rs b/src/webserver/api.rs index 8a49616..778a0ff 100644 --- a/src/webserver/api.rs +++ b/src/webserver/api.rs @@ -1,4 +1,3 @@ -use alloc::string::String; use picoserve::{ extract::{Json, State}, response::{self, IntoResponse}, @@ -6,26 +5,31 @@ use picoserve::{ use serde::Deserialize; use crate::{ - store::{Name, hex_string_to_tally_id}, + store::{Name, tally_id::TallyID}, webserver::{app::AppState, sse::IDEvents}, }; #[derive(Deserialize)] pub struct NewMapping { - id: String, + id: TallyID, name: Name, } -/* - * #[get("/api/idevent")] - * #[get("/api/csv")] - * #[get("/api/mapping")] - * #[post("/api/mapping", format = "json", data = "")] - * struct NewMapping { - * id: String, - * name: Name, - * } -*/ +// struct MappingWrapper(IDMapping); +// +// impl Serialize for MappingWrapper { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// use serde::ser::SerializeMap; +// let mut map = serializer.serialize_map(Some(self.0.id_map.len()))?; +// for (k, v) in &self.0.id_map { +// map.serialize_entry(tally_id_to_hex_string(*k).unwrap().as_str(), &v)?; +// } +// map.end() +// } +// } pub async fn get_mapping(State(state): State) -> impl IntoResponse { let store = state.store.lock().await; @@ -37,8 +41,7 @@ pub async fn add_mapping( Json(data): Json, ) -> impl IntoResponse { let mut store = state.store.lock().await; - let tally_id = hex_string_to_tally_id(&data.id).unwrap(); - store.mapping.add_mapping(tally_id, data.name); + store.mapping.add_mapping(data.id, data.name); } pub async fn get_idevent(State(state): State) -> impl IntoResponse { diff --git a/src/webserver/sse.rs b/src/webserver/sse.rs index 1cec6db..0f93dfe 100644 --- a/src/webserver/sse.rs +++ b/src/webserver/sse.rs @@ -2,7 +2,7 @@ use embassy_time::{Duration, Timer}; use log::warn; use picoserve::response; -use crate::{TallySubscriber, store::tally_id_to_hex_string}; +use crate::TallySubscriber; pub struct IDEvents(pub TallySubscriber); @@ -18,9 +18,8 @@ impl response::sse::EventSource for IDEvents { match sel.await { embassy_futures::select::Either::First(msg) => match msg { embassy_sync::pubsub::WaitResult::Message(id) => { - writer - .write_event("msg", tally_id_to_hex_string(id).unwrap().as_str()) - .await? + let id_str: heapless::String<12> = id.into(); + writer.write_event("msg", id_str.as_str()).await? } embassy_sync::pubsub::WaitResult::Lagged(_) => { warn!("SSE subscriber got lagged");