diff --git a/Cargo.lock b/Cargo.lock index 6b5b672..b278b60 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,17 +362,20 @@ 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", "num_enum", "pn532", + "static_assertions", ] [[package]] @@ -2796,6 +2829,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 +3312,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..7ac5eb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,10 @@ 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" +static_assertions = { version = "1.1.0", default-features = false } [build-dependencies] log = "0.4.27" 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 a1a26b8..4be0fff 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::{ @@ -32,6 +34,8 @@ 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); @@ -102,7 +106,11 @@ 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"); + + let rtc = Rtc::new(peripherals.LPWR); + //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() { @@ -139,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/card/decoder.rs b/src/card/decoder.rs index 60f2a39..2889684 100644 --- a/src/card/decoder.rs +++ b/src/card/decoder.rs @@ -1,22 +1,23 @@ 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 { - event_encoding: &payload[0..2], + Some(RawCard { + 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()?, + _padding: Default::default(), }) } diff --git a/src/card/model.rs b/src/card/model.rs index bb2ee96..101152e 100644 --- a/src/card/model.rs +++ b/src/card/model.rs @@ -6,18 +6,24 @@ 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 RawCard { + 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], + 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 { @@ -222,7 +228,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, @@ -235,17 +241,19 @@ pub struct Card { pub sprite: Sprite, } -impl<'a> TryFrom> for Card { +impl TryFrom for Card { type Error = &'static str; - fn try_from(payload: NfcPayload<'a>) -> 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, 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..93a2d8f 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,2 +1,31 @@ -pub mod dex_db; -pub mod nvs; +pub struct MemoryRegion { + offset: u32, + size: usize, +} + +impl MemoryRegion { + 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::new(0, 4); + +const SETTINGS_REGION: MemoryRegion = MemoryRegion::new(MAGIC_REGION.end() as u32, 1024); + +const CARDSTORE_REGION: MemoryRegion = MemoryRegion::new( + SETTINGS_REGION.end() as u32, + 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..44ff686 --- /dev/null +++ b/src/storage/cardstore.rs @@ -0,0 +1,214 @@ +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +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::{CARDSTORE_REGION, MemoryRegion}; + +const MAX_CARDS: usize = 20000; + +pub const COUNT_REGION: MemoryRegion = MemoryRegion::new(CARDSTORE_REGION.offset, size_of::()); + +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)] +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 by nfc + read_count: u32, + + 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 { + uuid, + offset, + creation_date: timestamp, + last_read_date: timestamp, + read_count: 0, + deleted: true, + reserved: Default::default(), + _padding: Default::default(), + } + } +} + +#[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: u32, + 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)?; + + 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); + + store.read( + ALLOCATION_TABLE_REGION.offset, + &mut allocation_table_entries, + )?; + + 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)?; + allocation_table.insert(allocation_table_entry.uuid, allocation_table_entry); + } + + 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 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, + )?) + } + + 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); + + // 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_erase( + 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; + + 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_erase( + 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(()) + } +} 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..42f18bf --- /dev/null +++ b/src/storage/store.rs @@ -0,0 +1,78 @@ +use embedded_storage::nor_flash::NorFlash; +use embedded_storage::nor_flash::ReadNorFlash; +use esp_storage::{FlashStorage, FlashStorageError}; + +use crate::storage::MAGIC_REGION; + +pub type StoreError = FlashStorageError; + +const FLASH_ADDR: u32 = 0x9000; + +const INITIALIZATION_SIZE: usize = 0x1000; +const FLASH_INITIALIZE_MAGIC: u32 = 0xde6de6de; + +pub struct Store { + pub storage: FlashStorage<'static>, +} + +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 + MAGIC_REGION.offset, &mut magic)?; + log::debug!("Magic bytes are {magic:?}"); + + 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 }) + } + + 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> { + log::debug!("Writing {} bytes at offset 0x{offset:08x}", bytes.len()); + self.storage.write(FLASH_ADDR + offset, bytes) + } + + 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:?}"); + + Ok(u32::from_ne_bytes(magic) == FLASH_INITIALIZE_MAGIC) + } + + 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 + MAGIC_REGION.end() as u32, &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)) + } + } +} 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, } } }