From 07593e45f3bdb29a65c467d634797732207b6a68 Mon Sep 17 00:00:00 2001 From: rhetenor Date: Sun, 23 Aug 2026 09:45:18 +0200 Subject: [PATCH 01/10] implement simple card storage retreival --- Cargo.lock | 58 +++++++++++++++++++++++++++++++ Cargo.toml | 3 ++ src/bin/main.rs | 6 +++- src/lib.rs | 1 + src/storage.rs | 30 +++++++++++++++-- src/storage/cardstore.rs | 73 ++++++++++++++++++++++++++++++++++++++++ src/storage/dex_db.rs | 1 - src/storage/nvs.rs | 1 - src/storage/settings.rs | 0 src/storage/store.rs | 40 ++++++++++++++++++++++ 10 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/storage/cardstore.rs delete mode 100644 src/storage/dex_db.rs delete mode 100644 src/storage/nvs.rs create mode 100644 src/storage/settings.rs create mode 100644 src/storage/store.rs diff --git a/Cargo.lock b/Cargo.lock index 6b5b672..8038235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,6 +53,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c583acf993cf4245c4acb0a2cc2ab1f9cc097de73411bb6d3647ff6af2b1013d" +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "autocfg" version = "1.5.1" @@ -96,6 +102,29 @@ dependencies = [ "vsimd", ] +[[package]] +name = "binary_serde" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4b2fe974c561ed04fb89673dd221bed36fc4794b71ca6ae96ffe19949045686" +dependencies = [ + "array-init", + "binary_serde_macros", + "recursive_array", + "thiserror-no-std", +] + +[[package]] +name = "binary_serde_macros" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4790b8dbcfa9d3adf2020ebf5fd36c6bc89a77d434131615ea349bc9620e8831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "bitfield" version = "0.17.0" @@ -319,6 +348,7 @@ name = "creaturedex" version = "0.1.0" dependencies = [ "base64 0.23.1", + "binary_serde", "ch1115", "critical-section", "display-interface-spi", @@ -332,12 +362,14 @@ dependencies = [ "embedded-hal-async", "embedded-hal-bus", "embedded-io 0.7.1", + "embedded-storage", "esp-alloc", "esp-bootloader-esp-idf", "esp-hal", "esp-hal-wifimanager", "esp-println", "esp-rtos", + "esp-storage", "i2c-character-display", "ili9341", "log", @@ -2796,6 +2828,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recursive_array" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69bad83913c3b39011ad9d43b7ac0cae139cec5d7e7288fbea5bb4b4e3cc78f6" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3273,6 +3311,26 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", +] + [[package]] name = "thread_local" version = "1.1.10" diff --git a/Cargo.toml b/Cargo.toml index e0485db..d0f6ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,9 @@ embassy-sync = "0.8.0" embedded-hal-async = "1.0.0" i2c-character-display = "0.5.1" ch1115 = { version = "0.1.2", features = ["graphics"] } +esp-storage = "0.9.0" +embedded-storage = "0.3.1" +binary_serde = "1.0.25" [build-dependencies] log = "0.4.27" diff --git a/src/bin/main.rs b/src/bin/main.rs index a1a26b8..2110515 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -19,6 +19,8 @@ use creaturedex::navigation::inputs::Inputs; use creaturedex::navigation::navigation::{self}; use creaturedex::navigation::outputs::Outputs; use creaturedex::network::wifi::setup_wifi; +use creaturedex::storage::cardstore::CardStore; +use creaturedex::storage::store::Store; use embassy_executor::Spawner; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::{ @@ -102,7 +104,9 @@ async fn main(spawner: Spawner) { ); } - setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; + let store = Store::new(peripherals.FLASH).expect("Could not initialize Flash Store"); + let card_store = CardStore::new(store).expect("Could not initialize Card Store"); + //setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; let mut nfc_driver = NfcPn532Driver::new(AtomicDevice::new(shared_i2c)); if nfc_driver.configure_sam().await.is_ok() { diff --git a/src/lib.rs b/src/lib.rs index 7d65b1d..72137dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![feature(vec_into_chunks)] extern crate alloc; diff --git a/src/storage.rs b/src/storage.rs index 4d0325f..7cfc291 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,2 +1,28 @@ -pub mod dex_db; -pub mod nvs; +struct MemoryRegion { + offset: u32, + size: usize, +} + +impl MemoryRegion { + const fn end(&self) -> usize { + self.offset as usize + self.size + } +} + +const MAX_FLASH: usize = 16 * 1024 * 1024; + +const MAGIC_REGION: MemoryRegion = MemoryRegion { offset: 0, size: 4 }; + +const SETTINGS_REGION: MemoryRegion = MemoryRegion { + offset: MAGIC_REGION.end() as u32, + size: 1024, +}; + +const CARDSTORE_REGION: MemoryRegion = MemoryRegion { + offset: SETTINGS_REGION.end() as u32, + size: MAX_FLASH - SETTINGS_REGION.end(), +}; + +pub mod cardstore; +pub mod settings; +pub mod store; diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs new file mode 100644 index 0000000..1bd5780 --- /dev/null +++ b/src/storage/cardstore.rs @@ -0,0 +1,73 @@ +use alloc::collections::BTreeMap; use embedded_storage::nor_flash::ReadNorFlash; +use alloc::vec::Vec; + +use binary_serde::{BinarySerde, Endianness}; + +//use crate::card::model::NfcPayload; + +use crate::storage::store::{Store, StoreError}; + +use crate::storage::{MemoryRegion,CARDSTORE_REGION}; + +const MAX_CARDS: usize = 20000; + +const COUNT_REGION: MemoryRegion = MemoryRegion { offset: CARDSTORE_REGION.offset, size: size_of::()}; + +#[derive(Debug, BinarySerde, Default, PartialEq, Eq)] +#[repr(C)] +struct AllocationTableEntry { + uuid: u32, + offset: u32, + + creation_date: u64, + last_read_date: u64, + + // how often a card has been read + read_count: u32, + + deleted: bool, + + reserved: [u64; 4], +} + +const ALLOCATION_TABLE_SIZE: usize = size_of::(); + +const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion {offset: COUNT_REGION.end() as u32, size: MAX_CARDS * size_of::()}; + +const CARDS_REGION: MemoryRegion = MemoryRegion { offset: ALLOCATION_TABLE_REGION.end() as u32, size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end()}; + +pub struct CardStore { + store: Store, + count: usize, + allocation_table: BTreeMap +} + +impl CardStore { + pub fn new(mut store: Store) -> Result { + let mut count_bytes: [u8; COUNT_REGION.size] = [0; COUNT_REGION.size]; + + store.read(COUNT_REGION.offset, &mut count_bytes)?; + + let count: usize = usize::from_ne_bytes(count_bytes); + + let mut allocation_table_entries: Vec = Vec::with_capacity(ALLOCATION_TABLE_SIZE * count); + + store + .read(ALLOCATION_TABLE_REGION.offset, &mut allocation_table_entries)?; + + let chunks = allocation_table_entries.into_chunks::(); + + let mut allocation_table = BTreeMap::::new(); + for chunk in chunks { + let allocation_table_entry = AllocationTableEntry::binary_deserialize(chunk.as_ref(), Endianness::Little).unwrap(); + allocation_table.insert(allocation_table_entry.uuid, allocation_table_entry); + } + + Ok(Self { store, count, allocation_table }) + } + + //fn get_card_by_uuid(uuid: &str) {} + + //fn get_card_by_position(position: usize) {} + +} diff --git a/src/storage/dex_db.rs b/src/storage/dex_db.rs deleted file mode 100644 index acd61ed..0000000 --- a/src/storage/dex_db.rs +++ /dev/null @@ -1 +0,0 @@ -// Storage dex_db module diff --git a/src/storage/nvs.rs b/src/storage/nvs.rs deleted file mode 100644 index 5c63f41..0000000 --- a/src/storage/nvs.rs +++ /dev/null @@ -1 +0,0 @@ -// Storage nvs module diff --git a/src/storage/settings.rs b/src/storage/settings.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/storage/store.rs b/src/storage/store.rs new file mode 100644 index 0000000..31c80e2 --- /dev/null +++ b/src/storage/store.rs @@ -0,0 +1,40 @@ +use embedded_storage::nor_flash::NorFlash; +use embedded_storage::nor_flash::ReadNorFlash; +use esp_storage::{FlashStorage, FlashStorageError}; + +pub type StoreError = FlashStorageError; + +const FLASH_ADDR: u32 = 0x9000; + +const INITIALIZATION_SIZE: usize = 0x1004; +const FLASH_INITIALIZE_MAGIC: u32 = 0xde6de6de; + +pub struct Store { + pub storage: FlashStorage<'static>, +} + +fn initialize_flash(storage: &mut FlashStorage) -> Result<(), StoreError> { + let zeros: [u8; INITIALIZATION_SIZE] = [0; INITIALIZATION_SIZE]; + storage.write(FLASH_ADDR, &zeros)?; + storage.write(FLASH_ADDR, &FLASH_INITIALIZE_MAGIC.to_ne_bytes())?; + Ok(()) +} + +impl Store { + pub fn new(flash: esp_hal::peripherals::FLASH<'static>) -> Result { + let mut storage = FlashStorage::new(flash); + + let mut magic: [u8; 4] = [0, 0, 0, 0]; + storage.read(FLASH_ADDR, &mut magic)?; + + if (u32::from_ne_bytes(magic)) != FLASH_INITIALIZE_MAGIC { + initialize_flash(&mut storage)?; + } + + Ok(Self { storage }) + } + + pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), StoreError> { + self.storage.read(FLASH_ADDR + offset, bytes) + } +} -- 2.39.5 From 9d94120fc15d1e104a5c80d3654c4a1939af260a Mon Sep 17 00:00:00 2001 From: rhetenor Date: Tue, 25 Aug 2026 19:33:27 +0000 Subject: [PATCH 02/10] storage: implement get card by uuid --- src/card/decoder.rs | 16 +++++++-------- src/card/model.rs | 31 ++++++++++++++++------------- src/storage/cardstore.rs | 43 +++++++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/card/decoder.rs b/src/card/decoder.rs index 60f2a39..0d2f401 100644 --- a/src/card/decoder.rs +++ b/src/card/decoder.rs @@ -3,20 +3,20 @@ use core::fmt::Display; use crate::card::model::NfcPayload; use alloc::string::{String, ToString}; -pub fn split_nfc_hex(payload: &[u8]) -> Option> { +pub fn split_nfc_hex(payload: &[u8]) -> Option { if payload.len() < 0x35A { return None; } Some(NfcPayload { - event_encoding: &payload[0..2], + event_encoding: payload[0..2].try_into().ok()?, card_type: payload[2], - card_uuid: &payload[3..5], - _reserved: &payload[5..9], - sprite: &payload[9..0x2DB], - packed_card_text: &payload[0x2DB..0x311], - secret: &payload[0x311..0x329], - _opaque_trailer: &payload[0x329..0x35A], + card_uuid: payload[3..5].try_into().ok()?, + _reserved: payload[5..9].try_into().ok()?, + sprite: payload[9..0x2DB].try_into().ok()?, + packed_card_text: payload[0x2DB..0x311].try_into().ok()?, + secret: payload[0x311..0x329].try_into().ok()?, + _opaque_trailer: payload[0x329..0x35A].try_into().ok()?, }) } diff --git a/src/card/model.rs b/src/card/model.rs index bb2ee96..0f08045 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -6,16 +6,19 @@ use num_enum::FromPrimitive; use crate::card::decoder::{Events, decode_known_event, decode_packed_card_text, decode_secret}; use embedded_graphics::pixelcolor::{Rgb565, Rgb888}; -#[derive(Debug, Clone, Copy)] -pub struct NfcPayload<'a> { - pub event_encoding: &'a [u8], +use binary_serde::BinarySerde; + +#[derive(Debug, BinarySerde, PartialEq, Eq, Clone, Copy)] +#[repr(C)] +pub struct NfcPayload { + pub event_encoding: [u8; 2], pub card_type: u8, - pub card_uuid: &'a [u8], - pub _reserved: &'a [u8], - pub sprite: &'a [u8], - pub packed_card_text: &'a [u8], - pub secret: &'a [u8], - pub _opaque_trailer: &'a [u8], + pub card_uuid: [u8; 2], + pub _reserved: [u8; 4], + pub sprite: [u8; 722], + pub packed_card_text: [u8; 54], + pub secret: [u8; 24], + pub _opaque_trailer: [u8; 49], } #[derive(Debug, Clone, Eq, PartialEq, FromPrimitive)] @@ -235,15 +238,15 @@ pub struct Card { pub sprite: Sprite, } -impl<'a> TryFrom> for Card { +impl<'a> TryFrom for Card { type Error = &'static str; - fn try_from(payload: NfcPayload<'a>) -> Result { + fn try_from(payload: NfcPayload) -> Result { let uuid: String = "empty for now".into(); //str::from_utf8(payload.card_uuid).unwrap().into(); - let event = decode_known_event(payload.event_encoding); + let event = decode_known_event(&payload.event_encoding); let cardtype = CardType::from(payload.card_type); - let (name, trait1, trait2, trait3) = decode_packed_card_text(payload.packed_card_text); - let secret = decode_secret(payload.secret); + let (name, trait1, trait2, trait3) = decode_packed_card_text(&payload.packed_card_text); + let secret = decode_secret(&payload.secret); let sprite = Sprite { data: payload.sprite.to_vec(), }; diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index 1bd5780..8996d2b 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -1,10 +1,11 @@ -use alloc::collections::BTreeMap; use embedded_storage::nor_flash::ReadNorFlash; +use alloc::collections::BTreeMap; use alloc::vec::Vec; -use binary_serde::{BinarySerde, Endianness}; +use binary_serde::{BinarySerde, Endianness, DeserializeError}; //use crate::card::model::NfcPayload; +use crate::card::model::NfcPayload; use crate::storage::store::{Store, StoreError}; use crate::storage::{MemoryRegion,CARDSTORE_REGION}; @@ -15,7 +16,7 @@ const COUNT_REGION: MemoryRegion = MemoryRegion { offset: CARDSTORE_REGION.offse #[derive(Debug, BinarySerde, Default, PartialEq, Eq)] #[repr(C)] -struct AllocationTableEntry { +pub struct AllocationTableEntry { uuid: u32, offset: u32, @@ -36,14 +37,33 @@ const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion {offset: COUNT_REGION const CARDS_REGION: MemoryRegion = MemoryRegion { offset: ALLOCATION_TABLE_REGION.end() as u32, size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end()}; +#[derive(Debug)] +pub enum CardStoreError { + Store(StoreError), + CorruptedEntry(DeserializeError), + NoEntry +} + +impl From for CardStoreError { + fn from(error: StoreError) -> CardStoreError { + CardStoreError::Store(error) + } +} + +impl From for CardStoreError { + fn from(error: DeserializeError) -> CardStoreError { + CardStoreError::CorruptedEntry(error) + } +} + pub struct CardStore { store: Store, count: usize, - allocation_table: BTreeMap + pub allocation_table: BTreeMap } impl CardStore { - pub fn new(mut store: Store) -> Result { + pub fn new(mut store: Store) -> Result { let mut count_bytes: [u8; COUNT_REGION.size] = [0; COUNT_REGION.size]; store.read(COUNT_REGION.offset, &mut count_bytes)?; @@ -59,15 +79,20 @@ impl CardStore { let mut allocation_table = BTreeMap::::new(); for chunk in chunks { - let allocation_table_entry = AllocationTableEntry::binary_deserialize(chunk.as_ref(), Endianness::Little).unwrap(); + let allocation_table_entry = AllocationTableEntry::binary_deserialize(chunk.as_ref(), Endianness::Little)?; allocation_table.insert(allocation_table_entry.uuid, allocation_table_entry); } Ok(Self { store, count, allocation_table }) } - //fn get_card_by_uuid(uuid: &str) {} + pub fn get_card_by_uuid(&mut self, uuid: u32) -> Result { + let entry = self.allocation_table.get(&uuid).ok_or(CardStoreError::NoEntry)?; + + let mut payload: [u8; size_of::()] = [0; size_of::()]; + self.store.read(entry.offset, &mut payload)?; + + Ok(NfcPayload::binary_deserialize(payload.as_ref(), Endianness::Little)?) + } - //fn get_card_by_position(position: usize) {} - } -- 2.39.5 From 996c884aecaa4dceadc19f61f237eaaf61983833 Mon Sep 17 00:00:00 2001 From: rhetenor Date: Wed, 26 Aug 2026 19:56:29 +0000 Subject: [PATCH 03/10] implement card storing --- src/card/decoder.rs | 6 ++-- src/card/model.rs | 6 ++-- src/storage/cardstore.rs | 63 +++++++++++++++++++++++++++++++--------- src/storage/store.rs | 4 +++ 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/card/decoder.rs b/src/card/decoder.rs index 0d2f401..b99b23d 100644 --- a/src/card/decoder.rs +++ b/src/card/decoder.rs @@ -1,14 +1,14 @@ use core::fmt::Display; -use crate::card::model::NfcPayload; +use crate::card::model::RawCard; use alloc::string::{String, ToString}; -pub fn split_nfc_hex(payload: &[u8]) -> Option { +pub fn split_nfc_hex(payload: &[u8]) -> Option { if payload.len() < 0x35A { return None; } - Some(NfcPayload { + Some(RawCard { event_encoding: payload[0..2].try_into().ok()?, card_type: payload[2], card_uuid: payload[3..5].try_into().ok()?, diff --git a/src/card/model.rs b/src/card/model.rs index 0f08045..1bc01d8 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -10,7 +10,7 @@ use binary_serde::BinarySerde; #[derive(Debug, BinarySerde, PartialEq, Eq, Clone, Copy)] #[repr(C)] -pub struct NfcPayload { +pub struct RawCard { pub event_encoding: [u8; 2], pub card_type: u8, pub card_uuid: [u8; 2], @@ -238,10 +238,10 @@ pub struct Card { pub sprite: Sprite, } -impl<'a> TryFrom for Card { +impl<'a> TryFrom for Card { type Error = &'static str; - fn try_from(payload: NfcPayload) -> Result { + fn try_from(payload: RawCard) -> Result { let uuid: String = "empty for now".into(); //str::from_utf8(payload.card_uuid).unwrap().into(); let event = decode_known_event(&payload.event_encoding); let cardtype = CardType::from(payload.card_type); diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index 8996d2b..7b65372 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -3,9 +3,9 @@ use alloc::vec::Vec; use binary_serde::{BinarySerde, Endianness, DeserializeError}; -//use crate::card::model::NfcPayload; +//use crate::card::model::Nfcraw_card; -use crate::card::model::NfcPayload; +use crate::card::model::RawCard; use crate::storage::store::{Store, StoreError}; use crate::storage::{MemoryRegion,CARDSTORE_REGION}; @@ -14,16 +14,20 @@ const MAX_CARDS: usize = 20000; const COUNT_REGION: MemoryRegion = MemoryRegion { offset: CARDSTORE_REGION.offset, size: size_of::()}; -#[derive(Debug, BinarySerde, Default, PartialEq, Eq)] +#[derive(Debug, BinarySerde, PartialEq, Eq, Clone)] #[repr(C)] pub struct AllocationTableEntry { uuid: u32, + // this is the numeric offset of the AllocationTableEntry + // as well as the card entry in flash memory + // size_of::() * offset + // size_of::() * offset offset: u32, creation_date: u64, last_read_date: u64, - // how often a card has been read + // how often a card has been read by nfc read_count: u32, deleted: bool, @@ -31,9 +35,16 @@ pub struct AllocationTableEntry { reserved: [u64; 4], } -const ALLOCATION_TABLE_SIZE: usize = size_of::(); +impl AllocationTableEntry { + fn new(uuid: u32, offset: u32, timestamp: u64) -> Self { + Self { + uuid, offset, creation_date: timestamp, last_read_date: timestamp, read_count: 0, deleted: true, reserved: [0; 4] + } + } +} -const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion {offset: COUNT_REGION.end() as u32, size: MAX_CARDS * size_of::()}; + +const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion {offset: COUNT_REGION.end() as u32, size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE}; const CARDS_REGION: MemoryRegion = MemoryRegion { offset: ALLOCATION_TABLE_REGION.end() as u32, size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end()}; @@ -58,7 +69,7 @@ impl From for CardStoreError { pub struct CardStore { store: Store, - count: usize, + count: u32, pub allocation_table: BTreeMap } @@ -68,14 +79,14 @@ impl CardStore { store.read(COUNT_REGION.offset, &mut count_bytes)?; - let count: usize = usize::from_ne_bytes(count_bytes); + let count: u32 = u32::from_ne_bytes(count_bytes); - let mut allocation_table_entries: Vec = Vec::with_capacity(ALLOCATION_TABLE_SIZE * count); + let mut allocation_table_entries: Vec = Vec::with_capacity(AllocationTableEntry::SERIALIZED_SIZE * count as usize); store .read(ALLOCATION_TABLE_REGION.offset, &mut allocation_table_entries)?; - let chunks = allocation_table_entries.into_chunks::(); + let chunks = allocation_table_entries.into_chunks::<{ AllocationTableEntry::SERIALIZED_SIZE }>(); let mut allocation_table = BTreeMap::::new(); for chunk in chunks { @@ -86,13 +97,37 @@ impl CardStore { Ok(Self { store, count, allocation_table }) } - pub fn get_card_by_uuid(&mut self, uuid: u32) -> Result { + pub fn get_card_by_uuid(&mut self, uuid: u32) -> Result { let entry = self.allocation_table.get(&uuid).ok_or(CardStoreError::NoEntry)?; - let mut payload: [u8; size_of::()] = [0; size_of::()]; - self.store.read(entry.offset, &mut payload)?; + let mut raw_card: [u8; size_of::()] = [0; size_of::()]; + self.store.read(entry.offset, &mut raw_card)?; - Ok(NfcPayload::binary_deserialize(payload.as_ref(), Endianness::Little)?) + Ok(RawCard::binary_deserialize(raw_card.as_ref(), Endianness::Little)?) } + pub fn save_raw_card(&mut self, raw_card: RawCard, timestamp: u64) -> Result<(), CardStoreError> { + + let uuid: u32 = (u16::from_ne_bytes(raw_card.event_encoding) as u32) << 16 | (u16::from_ne_bytes(raw_card.card_uuid) as u32); + let entry = match self.allocation_table.values_mut().find(|entry| entry.deleted) { + Some(entry) => entry, + None => { + let entry = AllocationTableEntry::new(uuid, self.count + 1, timestamp); + self.allocation_table.insert(uuid, entry); + self.allocation_table.get_mut(&uuid).expect("entry was just inserted") + } + }; + entry.deleted = false; + + let mut serialized_card: [u8; RawCard::SERIALIZED_SIZE] = [0; RawCard::SERIALIZED_SIZE]; + raw_card.binary_serialize(&mut serialized_card, Endianness::Little); + self.store.write(CARDS_REGION.offset + entry.offset * RawCard::SERIALIZED_SIZE as u32, &mut serialized_card)?; + + let mut serialized_entry: [u8; AllocationTableEntry::SERIALIZED_SIZE] = [0; AllocationTableEntry::SERIALIZED_SIZE]; + entry.binary_serialize(&mut serialized_entry, Endianness::Little); + self.store.write(ALLOCATION_TABLE_REGION.offset + entry.offset * AllocationTableEntry::SERIALIZED_SIZE as u32, &mut serialized_entry)?; + + self.count += 1; + Ok(()) + } } diff --git a/src/storage/store.rs b/src/storage/store.rs index 31c80e2..fdd0a02 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -37,4 +37,8 @@ impl Store { pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), StoreError> { self.storage.read(FLASH_ADDR + offset, bytes) } + + pub fn write(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), StoreError> { + self.storage.write(FLASH_ADDR + offset, bytes) + } } -- 2.39.5 From b9136242ba489860dc435d9e41540e88793c177f Mon Sep 17 00:00:00 2001 From: rhetenor Date: Wed, 26 Aug 2026 20:02:56 +0000 Subject: [PATCH 04/10] card model: save uuid as u32 --- src/card/model.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/card/model.rs b/src/card/model.rs index 1bc01d8..4feaa86 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -225,7 +225,7 @@ pub struct Sprite { #[derive(Debug, Clone)] pub struct Card { - pub uuid: String, + pub uuid: u32, pub event: Events, pub cardtype: CardType, pub name: String, @@ -241,14 +241,16 @@ pub struct Card { impl<'a> TryFrom for Card { type Error = &'static str; - fn try_from(payload: RawCard) -> Result { - let uuid: String = "empty for now".into(); //str::from_utf8(payload.card_uuid).unwrap().into(); - let event = decode_known_event(&payload.event_encoding); - let cardtype = CardType::from(payload.card_type); - let (name, trait1, trait2, trait3) = decode_packed_card_text(&payload.packed_card_text); - let secret = decode_secret(&payload.secret); + fn try_from(raw_card: RawCard) -> Result { + let uuid: u32 = (u16::from_ne_bytes(raw_card.event_encoding) as u32) << 16 + | (u16::from_ne_bytes(raw_card.card_uuid) as u32); + + let event = decode_known_event(&raw_card.event_encoding); + let cardtype = CardType::from(raw_card.card_type); + let (name, trait1, trait2, trait3) = decode_packed_card_text(&raw_card.packed_card_text); + let secret = decode_secret(&raw_card.secret); let sprite = Sprite { - data: payload.sprite.to_vec(), + data: raw_card.sprite.to_vec(), }; Ok(Self { uuid, -- 2.39.5 From 69663bc968d36805fd2e5262856bbaccf20c8480 Mon Sep 17 00:00:00 2001 From: rhetenor Date: Wed, 26 Aug 2026 20:47:17 +0000 Subject: [PATCH 05/10] card storage: make more robust, handle cards that are already seen, implement in nfc read --- src/bin/main.rs | 3 + src/storage/cardstore.rs | 160 +++++++++++++++++++++++++++++---------- 2 files changed, 122 insertions(+), 41 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 2110515..436a76d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -22,6 +22,7 @@ use creaturedex::network::wifi::setup_wifi; use creaturedex::storage::cardstore::CardStore; use creaturedex::storage::store::Store; use embassy_executor::Spawner; +use embassy_time::Instant; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::{ mono_font::{MonoTextStyle, ascii::FONT_6X10}, @@ -29,6 +30,8 @@ use embedded_graphics::{ text::Text, }; use embedded_hal_bus::i2c::AtomicDevice; +use embedded_hal_bus::util::AtomicCell; +use esp_hal::Blocking; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use esp_hal::timer::timg::TimerGroup; diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index 7b65372..8b69c54 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -1,24 +1,27 @@ -use alloc::collections::BTreeMap; +use alloc::collections::BTreeMap; use alloc::vec::Vec; -use binary_serde::{BinarySerde, Endianness, DeserializeError}; +use binary_serde::{BinarySerde, DeserializeError, Endianness}; //use crate::card::model::Nfcraw_card; use crate::card::model::RawCard; use crate::storage::store::{Store, StoreError}; -use crate::storage::{MemoryRegion,CARDSTORE_REGION}; +use crate::storage::{CARDSTORE_REGION, MemoryRegion}; const MAX_CARDS: usize = 20000; -const COUNT_REGION: MemoryRegion = MemoryRegion { offset: CARDSTORE_REGION.offset, size: size_of::()}; +const COUNT_REGION: MemoryRegion = MemoryRegion { + offset: CARDSTORE_REGION.offset, + size: size_of::(), +}; #[derive(Debug, BinarySerde, PartialEq, Eq, Clone)] #[repr(C)] pub struct AllocationTableEntry { uuid: u32, - // this is the numeric offset of the AllocationTableEntry + // this is the numeric offset of the AllocationTableEntry // as well as the card entry in flash memory // size_of::() * offset // size_of::() * offset @@ -26,7 +29,7 @@ pub struct AllocationTableEntry { creation_date: u64, last_read_date: u64, - + // how often a card has been read by nfc read_count: u32, @@ -38,96 +41,171 @@ pub struct AllocationTableEntry { impl AllocationTableEntry { fn new(uuid: u32, offset: u32, timestamp: u64) -> Self { Self { - uuid, offset, creation_date: timestamp, last_read_date: timestamp, read_count: 0, deleted: true, reserved: [0; 4] + uuid, + offset, + creation_date: timestamp, + last_read_date: timestamp, + read_count: 0, + deleted: true, + reserved: [0; 4], } } } +const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion { + offset: COUNT_REGION.end() as u32, + size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE, +}; -const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion {offset: COUNT_REGION.end() as u32, size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE}; - -const CARDS_REGION: MemoryRegion = MemoryRegion { offset: ALLOCATION_TABLE_REGION.end() as u32, size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end()}; +const CARDS_REGION: MemoryRegion = MemoryRegion { + offset: ALLOCATION_TABLE_REGION.end() as u32, + size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end(), +}; #[derive(Debug)] pub enum CardStoreError { Store(StoreError), CorruptedEntry(DeserializeError), - NoEntry + NoEntry, } -impl From for CardStoreError { - fn from(error: StoreError) -> CardStoreError { - CardStoreError::Store(error) +impl From for CardStoreError { + fn from(error: StoreError) -> CardStoreError { + CardStoreError::Store(error) } } -impl From for CardStoreError { - fn from(error: DeserializeError) -> CardStoreError { - CardStoreError::CorruptedEntry(error) +impl From for CardStoreError { + fn from(error: DeserializeError) -> CardStoreError { + CardStoreError::CorruptedEntry(error) } } pub struct CardStore { store: Store, count: u32, - pub allocation_table: BTreeMap + pub allocation_table: BTreeMap, } impl CardStore { pub fn new(mut store: Store) -> Result { let mut count_bytes: [u8; COUNT_REGION.size] = [0; COUNT_REGION.size]; - store.read(COUNT_REGION.offset, &mut count_bytes)?; + store.read(COUNT_REGION.offset, &mut count_bytes)?; let count: u32 = u32::from_ne_bytes(count_bytes); - let mut allocation_table_entries: Vec = Vec::with_capacity(AllocationTableEntry::SERIALIZED_SIZE * count as usize); + let mut allocation_table_entries: Vec = + Vec::with_capacity(AllocationTableEntry::SERIALIZED_SIZE * count as usize); - store - .read(ALLOCATION_TABLE_REGION.offset, &mut allocation_table_entries)?; + store.read( + ALLOCATION_TABLE_REGION.offset, + &mut allocation_table_entries, + )?; - let chunks = allocation_table_entries.into_chunks::<{ AllocationTableEntry::SERIALIZED_SIZE }>(); + let chunks = + allocation_table_entries.into_chunks::<{ AllocationTableEntry::SERIALIZED_SIZE }>(); let mut allocation_table = BTreeMap::::new(); for chunk in chunks { - let allocation_table_entry = AllocationTableEntry::binary_deserialize(chunk.as_ref(), Endianness::Little)?; + let allocation_table_entry = + AllocationTableEntry::binary_deserialize(chunk.as_ref(), Endianness::Little)?; allocation_table.insert(allocation_table_entry.uuid, allocation_table_entry); } - Ok(Self { store, count, allocation_table }) + Ok(Self { + store, + count, + allocation_table, + }) } pub fn get_card_by_uuid(&mut self, uuid: u32) -> Result { - let entry = self.allocation_table.get(&uuid).ok_or(CardStoreError::NoEntry)?; + let entry = self + .allocation_table + .get(&uuid) + .ok_or(CardStoreError::NoEntry)?; let mut raw_card: [u8; size_of::()] = [0; size_of::()]; self.store.read(entry.offset, &mut raw_card)?; - Ok(RawCard::binary_deserialize(raw_card.as_ref(), Endianness::Little)?) + Ok(RawCard::binary_deserialize( + raw_card.as_ref(), + Endianness::Little, + )?) } - pub fn save_raw_card(&mut self, raw_card: RawCard, timestamp: u64) -> Result<(), CardStoreError> { + pub fn save_raw_card( + &mut self, + raw_card: RawCard, + timestamp: u64, + ) -> Result<(), CardStoreError> { + let uuid: u32 = (u16::from_ne_bytes(raw_card.event_encoding) as u32) << 16 + | (u16::from_ne_bytes(raw_card.card_uuid) as u32); + log::info!("Saving card with uuid {:x}", uuid); - let uuid: u32 = (u16::from_ne_bytes(raw_card.event_encoding) as u32) << 16 | (u16::from_ne_bytes(raw_card.card_uuid) as u32); - let entry = match self.allocation_table.values_mut().find(|entry| entry.deleted) { - Some(entry) => entry, - None => { - let entry = AllocationTableEntry::new(uuid, self.count + 1, timestamp); - self.allocation_table.insert(uuid, entry); - self.allocation_table.get_mut(&uuid).expect("entry was just inserted") + // check if we already got the card + // if not find the next deleted entry, + // if none create a new one at the end + let (entry, is_new) = match self.allocation_table.get_mut(&uuid) { + Some(entry) => { + log::debug!("old entry"); + (entry, false) } + None => match self + .allocation_table + .values_mut() + .find(|entry| entry.deleted) + { + Some(entry) => { + log::debug!("deleted entry"); + (entry, true) + } + None => { + log::debug!("new entry"); + let entry = AllocationTableEntry::new(uuid, self.count + 1, timestamp); + self.allocation_table.insert(uuid, entry); + ( + self.allocation_table + .get_mut(&uuid) + .expect("entry was just inserted"), + true, + ) + } + }, }; + + // only write card data if it is new. else it is already written in flash + if is_new { + log::debug!("writing RawCard to flash"); + let mut serialized_card: [u8; RawCard::SERIALIZED_SIZE] = [0; RawCard::SERIALIZED_SIZE]; + raw_card.binary_serialize(&mut serialized_card, Endianness::Little); + self.store.write( + CARDS_REGION.offset + entry.offset * RawCard::SERIALIZED_SIZE as u32, + &mut serialized_card, + )?; + } else { + entry.last_read_date = timestamp; + entry.read_count += 1; + } + entry.deleted = false; - let mut serialized_card: [u8; RawCard::SERIALIZED_SIZE] = [0; RawCard::SERIALIZED_SIZE]; - raw_card.binary_serialize(&mut serialized_card, Endianness::Little); - self.store.write(CARDS_REGION.offset + entry.offset * RawCard::SERIALIZED_SIZE as u32, &mut serialized_card)?; - - let mut serialized_entry: [u8; AllocationTableEntry::SERIALIZED_SIZE] = [0; AllocationTableEntry::SERIALIZED_SIZE]; + log::debug!("writing AllocationTableEntry to flash"); + let mut serialized_entry: [u8; AllocationTableEntry::SERIALIZED_SIZE] = + [0; AllocationTableEntry::SERIALIZED_SIZE]; entry.binary_serialize(&mut serialized_entry, Endianness::Little); - self.store.write(ALLOCATION_TABLE_REGION.offset + entry.offset * AllocationTableEntry::SERIALIZED_SIZE as u32, &mut serialized_entry)?; + self.store.write( + ALLOCATION_TABLE_REGION.offset + + entry.offset * AllocationTableEntry::SERIALIZED_SIZE as u32, + &mut serialized_entry, + )?; self.count += 1; + log::debug!("writing card count to flash"); + let mut serialized_count: [u8; 4] = self.count.to_le_bytes(); + self.store + .write(COUNT_REGION.offset, serialized_count.as_mut_slice())?; Ok(()) } } -- 2.39.5 From ed86e88980342fe3f5d51f843244b10ecf75dd96 Mon Sep 17 00:00:00 2001 From: rhetenor Date: Wed, 26 Aug 2026 20:54:03 +0000 Subject: [PATCH 06/10] nfc read: print error instead of panic on invalid data --- src/bin/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bin/main.rs b/src/bin/main.rs index 436a76d..0570d85 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -109,6 +109,8 @@ async fn main(spawner: Spawner) { let store = Store::new(peripherals.FLASH).expect("Could not initialize Flash Store"); let card_store = CardStore::new(store).expect("Could not initialize Card Store"); + + let rtc = Rtc::new(peripherals.LPWR); //setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; let mut nfc_driver = NfcPn532Driver::new(AtomicDevice::new(shared_i2c)); -- 2.39.5 From 704682b0ae31f908ed4467815ec0fae5f2ca668c Mon Sep 17 00:00:00 2001 From: ede1998 Date: Thu, 27 Aug 2026 21:59:30 +0200 Subject: [PATCH 07/10] Ensure RawCard size matches flash alignment requirements, add debug logs --- Cargo.lock | 1 + Cargo.toml | 1 + src/card/decoder.rs | 1 + src/card/model.rs | 3 +++ src/storage/cardstore.rs | 21 +++++++++++---------- src/storage/store.rs | 1 + 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8038235..b278b60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,7 @@ dependencies = [ "log", "num_enum", "pn532", + "static_assertions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d0f6ded..7ac5eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ ch1115 = { version = "0.1.2", features = ["graphics"] } esp-storage = "0.9.0" embedded-storage = "0.3.1" binary_serde = "1.0.25" +static_assertions = { version = "1.1.0", default-features = false } [build-dependencies] log = "0.4.27" diff --git a/src/card/decoder.rs b/src/card/decoder.rs index b99b23d..2889684 100644 --- a/src/card/decoder.rs +++ b/src/card/decoder.rs @@ -17,6 +17,7 @@ pub fn split_nfc_hex(payload: &[u8]) -> Option { packed_card_text: payload[0x2DB..0x311].try_into().ok()?, secret: payload[0x311..0x329].try_into().ok()?, _opaque_trailer: payload[0x329..0x35A].try_into().ok()?, + _padding: Default::default(), }) } diff --git a/src/card/model.rs b/src/card/model.rs index 4feaa86..56debfb 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -19,8 +19,11 @@ pub struct RawCard { pub packed_card_text: [u8; 54], pub secret: [u8; 24], pub _opaque_trailer: [u8; 49], + pub _padding: [u8; 2], } +static_assertions::const_assert!(RawCard::SERIALIZED_SIZE.is_multiple_of(4)); + #[derive(Debug, Clone, Eq, PartialEq, FromPrimitive)] #[repr(u8)] pub enum CardType { diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index 8b69c54..15ce0f9 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -17,6 +17,17 @@ const COUNT_REGION: MemoryRegion = MemoryRegion { size: size_of::(), }; +const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion { + offset: COUNT_REGION.end() as u32, + size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE, +}; + +const CARDS_REGION: MemoryRegion = MemoryRegion { + offset: ALLOCATION_TABLE_REGION.end() as u32, + size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end(), +}; + + #[derive(Debug, BinarySerde, PartialEq, Eq, Clone)] #[repr(C)] pub struct AllocationTableEntry { @@ -52,16 +63,6 @@ impl AllocationTableEntry { } } -const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion { - offset: COUNT_REGION.end() as u32, - size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE, -}; - -const CARDS_REGION: MemoryRegion = MemoryRegion { - offset: ALLOCATION_TABLE_REGION.end() as u32, - size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end(), -}; - #[derive(Debug)] pub enum CardStoreError { Store(StoreError), diff --git a/src/storage/store.rs b/src/storage/store.rs index fdd0a02..37d3563 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -39,6 +39,7 @@ impl Store { } pub fn write(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), StoreError> { + log::debug!("Writing {} bytes at offset 0x{offset:08x}", bytes.len()); self.storage.write(FLASH_ADDR + offset, bytes) } } -- 2.39.5 From b63195b943835f470a2c6fd6daf12b52b63ac220 Mon Sep 17 00:00:00 2001 From: ede1998 Date: Thu, 27 Aug 2026 22:29:05 +0200 Subject: [PATCH 08/10] ensure all flash writes are aligned --- src/card/model.rs | 2 +- src/storage.rs | 25 ++++++++++++---------- src/storage/cardstore.rs | 30 ++++++++++++++------------- src/storage/store.rs | 45 ++++++++++++++++++++++++++++++---------- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/src/card/model.rs b/src/card/model.rs index 56debfb..101152e 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -241,7 +241,7 @@ pub struct Card { pub sprite: Sprite, } -impl<'a> TryFrom for Card { +impl TryFrom for Card { type Error = &'static str; fn try_from(raw_card: RawCard) -> Result { diff --git a/src/storage.rs b/src/storage.rs index 7cfc291..93a2d8f 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,27 +1,30 @@ -struct MemoryRegion { +pub struct MemoryRegion { offset: u32, size: usize, } impl MemoryRegion { - const fn end(&self) -> usize { + pub const fn new(offset: u32, size: usize) -> Self { + assert!(offset.is_multiple_of(4), "Flash requires 4 bytes alignment"); + assert!(size.is_multiple_of(4), "Flash requires 4 bytes alignment"); + Self { offset, size } + } + + pub const fn end(&self) -> usize { self.offset as usize + self.size } } const MAX_FLASH: usize = 16 * 1024 * 1024; -const MAGIC_REGION: MemoryRegion = MemoryRegion { offset: 0, size: 4 }; +const MAGIC_REGION: MemoryRegion = MemoryRegion::new(0, 4); -const SETTINGS_REGION: MemoryRegion = MemoryRegion { - offset: MAGIC_REGION.end() as u32, - size: 1024, -}; +const SETTINGS_REGION: MemoryRegion = MemoryRegion::new(MAGIC_REGION.end() as u32, 1024); -const CARDSTORE_REGION: MemoryRegion = MemoryRegion { - offset: SETTINGS_REGION.end() as u32, - size: MAX_FLASH - SETTINGS_REGION.end(), -}; +const CARDSTORE_REGION: MemoryRegion = MemoryRegion::new( + SETTINGS_REGION.end() as u32, + MAX_FLASH - SETTINGS_REGION.end(), +); pub mod cardstore; pub mod settings; diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index 15ce0f9..db4fe20 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -12,21 +12,17 @@ use crate::storage::{CARDSTORE_REGION, MemoryRegion}; const MAX_CARDS: usize = 20000; -const COUNT_REGION: MemoryRegion = MemoryRegion { - offset: CARDSTORE_REGION.offset, - size: size_of::(), -}; +pub const COUNT_REGION: MemoryRegion = MemoryRegion::new(CARDSTORE_REGION.offset, size_of::()); -const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion { - offset: COUNT_REGION.end() as u32, - size: MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE, -}; - -const CARDS_REGION: MemoryRegion = MemoryRegion { - offset: ALLOCATION_TABLE_REGION.end() as u32, - size: CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end(), -}; +const ALLOCATION_TABLE_REGION: MemoryRegion = MemoryRegion::new( + COUNT_REGION.end() as u32, + MAX_CARDS * AllocationTableEntry::SERIALIZED_SIZE, +); +const CARDS_REGION: MemoryRegion = MemoryRegion::new( + ALLOCATION_TABLE_REGION.end() as u32, + CARDSTORE_REGION.size - ALLOCATION_TABLE_REGION.end(), +); #[derive(Debug, BinarySerde, PartialEq, Eq, Clone)] #[repr(C)] @@ -47,8 +43,11 @@ pub struct AllocationTableEntry { deleted: bool, reserved: [u64; 4], + _padding: [u8; 3], } +static_assertions::const_assert!(AllocationTableEntry::SERIALIZED_SIZE.is_multiple_of(4)); + impl AllocationTableEntry { fn new(uuid: u32, offset: u32, timestamp: u64) -> Self { Self { @@ -58,7 +57,8 @@ impl AllocationTableEntry { last_read_date: timestamp, read_count: 0, deleted: true, - reserved: [0; 4], + reserved: Default::default(), + _padding: Default::default(), } } } @@ -96,6 +96,8 @@ impl CardStore { let count: u32 = u32::from_ne_bytes(count_bytes); + log::info!("Found {count} cards in flash."); + let mut allocation_table_entries: Vec = Vec::with_capacity(AllocationTableEntry::SERIALIZED_SIZE * count as usize); diff --git a/src/storage/store.rs b/src/storage/store.rs index 37d3563..c9702db 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -2,33 +2,33 @@ use embedded_storage::nor_flash::NorFlash; use embedded_storage::nor_flash::ReadNorFlash; use esp_storage::{FlashStorage, FlashStorageError}; +use crate::storage::MAGIC_REGION; +use crate::storage::cardstore::COUNT_REGION; + pub type StoreError = FlashStorageError; const FLASH_ADDR: u32 = 0x9000; -const INITIALIZATION_SIZE: usize = 0x1004; +const INITIALIZATION_SIZE: usize = COUNT_REGION.end(); const FLASH_INITIALIZE_MAGIC: u32 = 0xde6de6de; pub struct Store { pub storage: FlashStorage<'static>, } -fn initialize_flash(storage: &mut FlashStorage) -> Result<(), StoreError> { - let zeros: [u8; INITIALIZATION_SIZE] = [0; INITIALIZATION_SIZE]; - storage.write(FLASH_ADDR, &zeros)?; - storage.write(FLASH_ADDR, &FLASH_INITIALIZE_MAGIC.to_ne_bytes())?; - Ok(()) -} - impl Store { pub fn new(flash: esp_hal::peripherals::FLASH<'static>) -> Result { let mut storage = FlashStorage::new(flash); let mut magic: [u8; 4] = [0, 0, 0, 0]; - storage.read(FLASH_ADDR, &mut magic)?; + storage.read(FLASH_ADDR + MAGIC_REGION.offset, &mut magic)?; + log::debug!("Magic bytes are {magic:?}"); - if (u32::from_ne_bytes(magic)) != FLASH_INITIALIZE_MAGIC { - initialize_flash(&mut storage)?; + if Self::has_magic_bytes(&mut storage)? { + log::info!("Skipped flash initialization, magic bytes already present."); + } else { + log::info!("Initializing flash because no magic bytes found."); + Self::initialize_flash(&mut storage)?; } Ok(Self { storage }) @@ -42,4 +42,27 @@ impl Store { log::debug!("Writing {} bytes at offset 0x{offset:08x}", bytes.len()); self.storage.write(FLASH_ADDR + offset, bytes) } + + fn has_magic_bytes(storage: &mut FlashStorage) -> Result { + let mut magic: [u8; 4] = [0, 0, 0, 0]; + storage.read(FLASH_ADDR + MAGIC_REGION.offset, &mut magic)?; + log::debug!("Magic bytes are {magic:?}"); + + Ok(u32::from_ne_bytes(magic) == FLASH_INITIALIZE_MAGIC) + } + + fn initialize_flash(storage: &mut FlashStorage) -> Result<(), StoreError> { + let zeros: [u8; INITIALIZATION_SIZE] = [0; INITIALIZATION_SIZE]; + storage.write(FLASH_ADDR, &zeros)?; + storage.write( + FLASH_ADDR + MAGIC_REGION.offset, + &FLASH_INITIALIZE_MAGIC.to_ne_bytes(), + )?; + + if Self::has_magic_bytes(storage)? { + Ok(()) + } else { + Err(StoreError::Other(FLASH_INITIALIZE_MAGIC as i32)) + } + } } -- 2.39.5 From e65162249db5f880edc9d2ac2e18e4c5ddd6653d Mon Sep 17 00:00:00 2001 From: rhetenor Date: Sat, 29 Aug 2026 14:43:15 +0000 Subject: [PATCH 09/10] fix flash initialization by erasing flash before writing --- src/storage/cardstore.rs | 4 ++-- src/storage/store.rs | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/storage/cardstore.rs b/src/storage/cardstore.rs index db4fe20..44ff686 100644 --- a/src/storage/cardstore.rs +++ b/src/storage/cardstore.rs @@ -183,7 +183,7 @@ impl CardStore { log::debug!("writing RawCard to flash"); let mut serialized_card: [u8; RawCard::SERIALIZED_SIZE] = [0; RawCard::SERIALIZED_SIZE]; raw_card.binary_serialize(&mut serialized_card, Endianness::Little); - self.store.write( + self.store.write_erase( CARDS_REGION.offset + entry.offset * RawCard::SERIALIZED_SIZE as u32, &mut serialized_card, )?; @@ -198,7 +198,7 @@ impl CardStore { let mut serialized_entry: [u8; AllocationTableEntry::SERIALIZED_SIZE] = [0; AllocationTableEntry::SERIALIZED_SIZE]; entry.binary_serialize(&mut serialized_entry, Endianness::Little); - self.store.write( + self.store.write_erase( ALLOCATION_TABLE_REGION.offset + entry.offset * AllocationTableEntry::SERIALIZED_SIZE as u32, &mut serialized_entry, diff --git a/src/storage/store.rs b/src/storage/store.rs index c9702db..42f18bf 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -3,13 +3,12 @@ use embedded_storage::nor_flash::ReadNorFlash; use esp_storage::{FlashStorage, FlashStorageError}; use crate::storage::MAGIC_REGION; -use crate::storage::cardstore::COUNT_REGION; pub type StoreError = FlashStorageError; const FLASH_ADDR: u32 = 0x9000; -const INITIALIZATION_SIZE: usize = COUNT_REGION.end(); +const INITIALIZATION_SIZE: usize = 0x1000; const FLASH_INITIALIZE_MAGIC: u32 = 0xde6de6de; pub struct Store { @@ -43,7 +42,17 @@ impl Store { self.storage.write(FLASH_ADDR + offset, bytes) } - fn has_magic_bytes(storage: &mut FlashStorage) -> Result { + pub fn write_erase(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), StoreError> { + log::debug!( + "Erasing and Writing {} bytes at offset 0x{offset:08x}", + bytes.len() + ); + self.storage + .erase(FLASH_ADDR + offset, FLASH_ADDR + bytes.len() as u32)?; + self.storage.write(FLASH_ADDR + offset, bytes) + } + + pub fn has_magic_bytes(storage: &mut FlashStorage) -> Result { let mut magic: [u8; 4] = [0, 0, 0, 0]; storage.read(FLASH_ADDR + MAGIC_REGION.offset, &mut magic)?; log::debug!("Magic bytes are {magic:?}"); @@ -51,9 +60,10 @@ impl Store { Ok(u32::from_ne_bytes(magic) == FLASH_INITIALIZE_MAGIC) } - fn initialize_flash(storage: &mut FlashStorage) -> Result<(), StoreError> { + pub fn initialize_flash(storage: &mut FlashStorage) -> Result<(), StoreError> { + storage.erase(FLASH_ADDR, FLASH_ADDR + INITIALIZATION_SIZE as u32)?; let zeros: [u8; INITIALIZATION_SIZE] = [0; INITIALIZATION_SIZE]; - storage.write(FLASH_ADDR, &zeros)?; + storage.write(FLASH_ADDR + MAGIC_REGION.end() as u32, &zeros)?; storage.write( FLASH_ADDR + MAGIC_REGION.offset, &FLASH_INITIALIZE_MAGIC.to_ne_bytes(), -- 2.39.5 From 16e45d2c11e870bf4b5cd4c4d1ef780d6356e754 Mon Sep 17 00:00:00 2001 From: rhetenor Date: Sat, 29 Aug 2026 14:44:24 +0000 Subject: [PATCH 10/10] wip: add flash info view --- src/background_tasks/nfc.rs | 28 ++++++++++++++++----- src/bin/main.rs | 10 +++++--- src/views.rs | 1 + src/views/flash_info_view.rs | 49 ++++++++++++++++++++++++++++++++++++ src/views/settings_menu.rs | 47 ++++++++++++++++++++++++++++++---- src/views/view.rs | 7 ++++-- 6 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/views/flash_info_view.rs diff --git a/src/background_tasks/nfc.rs b/src/background_tasks/nfc.rs index 1e50d02..04a2fe3 100644 --- a/src/background_tasks/nfc.rs +++ b/src/background_tasks/nfc.rs @@ -1,4 +1,6 @@ +use crate::storage::cardstore::CardStore; use embedded_hal_bus::i2c::AtomicDevice; +use esp_hal::rtc_cntl::Rtc; use esp_hal::{Blocking, i2c::master::I2c, time::Instant}; use crate::{ @@ -14,6 +16,8 @@ use crate::{ )] pub async fn nfc_scanner( mut nfc_driver: NfcPn532Driver>>, + mut card_store: CardStore, + rtc: Rtc<'static>, ) { loop { while CARD_DATA.lock().await.is_none() { @@ -24,10 +28,22 @@ pub async fn nfc_scanner( log::error!("Failed to split NFC card data"); continue; }; - CARD_DATA - .lock() - .await - .replace(Card::try_from(split_data).unwrap()); + let card = match Card::try_from(split_data) { + Ok(card) => card, + Err(err) => { + log::error!("Invalid card data: {err}"); + continue; + } + }; + CARD_DATA.lock().await.replace(card); + + match card_store.save_raw_card(split_data, rtc.current_time_us()) { + Ok(()) => {} + Err(error) => { + log::error!("Error storing card in flash: {:?}", error) + } + } + let elapsed_ms = start.elapsed().as_millis(); log::info!( "NFC read duration: {} ms, card data length: {}", @@ -35,8 +51,8 @@ pub async fn nfc_scanner( card_data.len() ); } - Err(e) => { - log::error!("NFC read failed: {e}"); + Err(_) => { + log::info!("No NFC card found"); } } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 0570d85..4be0fff 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -22,7 +22,6 @@ use creaturedex::network::wifi::setup_wifi; use creaturedex::storage::cardstore::CardStore; use creaturedex::storage::store::Store; use embassy_executor::Spawner; -use embassy_time::Instant; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::{ mono_font::{MonoTextStyle, ascii::FONT_6X10}, @@ -30,13 +29,13 @@ use embedded_graphics::{ text::Text, }; use embedded_hal_bus::i2c::AtomicDevice; -use embedded_hal_bus::util::AtomicCell; -use esp_hal::Blocking; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use esp_hal::timer::timg::TimerGroup; use log::error; +use esp_hal::rtc_cntl::Rtc; + #[panic_handler] fn panic(panic_info: &core::panic::PanicInfo) -> ! { error!("{}", panic_info); @@ -148,7 +147,10 @@ async fn main(spawner: Spawner) { log::info!("Setup complete, entering main loop"); spawner.spawn(navigation::run(inputs, outputs).expect("run task failed")); - spawner.spawn(background_tasks::nfc_scanner(nfc_driver).expect("nfc scanner task failed")); + spawner.spawn( + background_tasks::nfc_scanner(nfc_driver, card_store, rtc) + .expect("nfc scanner task failed"), + ); } fn display_shit(oled: &mut SecondaryDisplay<'static>, text: &str) { diff --git a/src/views.rs b/src/views.rs index 4d465b2..08094c2 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1,4 +1,5 @@ pub mod card_view; +pub mod flash_info_view; pub mod journal_view; pub mod main_menu; pub mod menu_item; diff --git a/src/views/flash_info_view.rs b/src/views/flash_info_view.rs new file mode 100644 index 0000000..7e2ee5d --- /dev/null +++ b/src/views/flash_info_view.rs @@ -0,0 +1,49 @@ +use crate::navigation::inputs::ButtonAction; +use crate::navigation::navigation::Action; +use crate::navigation::navigation::Navigable; +use crate::navigation::navigation::NewState; +use crate::navigation::outputs::Outputs; +use crate::views::view::View; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::DrawTarget; +use embedded_graphics::prelude::RgbColor; +use embedded_graphics::text::Text; + +use embedded_graphics::prelude::Point; + +#[derive(Debug, Clone)] +pub struct FlashInfoView {} + +impl Navigable for FlashInfoView { + fn display(&self, outputs: &mut Outputs) -> impl core::future::Future + Send { + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); + Text::new("Flash Initialized: {:?}", Point::new(40, 40), style); + + core::future::ready(()) + } + + fn handle_input(&self, input: Action) -> impl core::future::Future + Send { + async move { + let Action::Button(input) = input else { + // Skip timer inputs + return NewState { + view: View::FlashInfo(self.clone()), + replace_view: true, + redraw: false, + }; + }; + + let new_menu = match input { + _ => View::FlashInfo(self.clone()), + }; + + NewState { + replace_view: matches!(new_menu, View::FlashInfo(_)), + view: new_menu, + redraw: true, + } + } + } +} diff --git a/src/views/settings_menu.rs b/src/views/settings_menu.rs index f855e91..1b12b7b 100644 --- a/src/views/settings_menu.rs +++ b/src/views/settings_menu.rs @@ -1,11 +1,14 @@ use crate::navigation::navigation::{Action, Navigable, NewState}; use crate::navigation::outputs::Outputs; -use crate::views::{menu_item, view::View}; +use crate::views::{flash_info_view::FlashInfoView, menu_item, view::View}; + +use crate::navigation::inputs::ButtonAction; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::RgbColor; use embedded_graphics_core::draw_target::DrawTarget; -pub const MAX_SELECTED: i8 = 2; + +pub const MAX_SELECTED: i32 = 2; #[derive(Debug, Clone)] pub struct SettingsMenu { @@ -18,16 +21,50 @@ impl Navigable for SettingsMenu { display.clear(Rgb565::BLACK).unwrap(); menu_item::show(display, "System Inforation", 0, self.selected); menu_item::show(display, "Wifi Information", 1, self.selected); + menu_item::show(display, "Flash Information", 2, self.selected); core::future::ready(()) } fn handle_input(&self, input: Action) -> impl core::future::Future + Send { async move { + let Action::Button(input) = input else { + // Skip timer inputs + return NewState { + view: View::Settings(self.clone()), + replace_view: true, + redraw: false, + }; + }; + + let new_menu = match input { + ButtonAction::Up => { + let new_selected = if (self.selected - 1) < 0 { + MAX_SELECTED - 1 + } else { + self.selected - 1 + }; + View::Settings(SettingsMenu { + selected: new_selected, + }) + } + ButtonAction::Down => { + let new_selected = self.selected.wrapping_add(1) % MAX_SELECTED; + View::Settings(SettingsMenu { + selected: new_selected, + }) + } + ButtonAction::Ok => match self.selected { + 2 => View::FlashInfo(FlashInfoView {}), + _ => View::Settings(self.clone()), + }, + _ => View::Settings(self.clone()), + }; + NewState { - view: View::Settings(SettingsMenu { selected: 0 }), - replace_view: true, - redraw: !matches!(input, Action::Timer), + replace_view: matches!(new_menu, View::Settings(_)), + view: new_menu, + redraw: true, } } } diff --git a/src/views/view.rs b/src/views/view.rs index f3da7c9..bd2e1c2 100644 --- a/src/views/view.rs +++ b/src/views/view.rs @@ -1,8 +1,8 @@ use crate::navigation::outputs::Outputs; use crate::views::card_view::CardView; use crate::views::{ - journal_view::JournalView, main_menu::MainMenu, scan_menu::ScanMenu, - settings_menu::SettingsMenu, + flash_info_view::FlashInfoView, journal_view::JournalView, main_menu::MainMenu, + scan_menu::ScanMenu, settings_menu::SettingsMenu, }; use crate::navigation::navigation::{Navigable, NewState}; @@ -14,6 +14,7 @@ pub enum View { Settings(SettingsMenu), Journal(JournalView), Card(CardView), + FlashInfo(FlashInfoView), } impl Navigable for View { @@ -25,6 +26,7 @@ impl Navigable for View { View::Settings(settings_menu) => settings_menu.display(outputs).await, View::Journal(journal_view) => journal_view.display(outputs).await, View::Card(card_view) => card_view.display(outputs).await, + View::FlashInfo(flash_info_view) => flash_info_view.display(outputs).await, } } } @@ -40,6 +42,7 @@ impl Navigable for View { View::Settings(settings_menu) => settings_menu.handle_input(input).await, View::Journal(journal_view) => journal_view.handle_input(input).await, View::Card(card_view) => card_view.handle_input(input).await, + View::FlashInfo(flash_info_view) => flash_info_view.handle_input(input).await, } } } -- 2.39.5