acid/firmware/acid-firmware/src/ui/mod.rs

195 lines
6 KiB
Rust
Raw Normal View History

// #![cfg_attr(not(feature = "simulator"), no_main)]
2026-01-24 00:42:16 +01:00
use core::ops::{Deref, DerefMut};
2026-01-20 02:55:17 +01:00
use alloc::{boxed::Box, ffi::CString};
2026-01-24 00:42:16 +01:00
use ekv::{Database, MountError, flash::PageID};
use embassy_embedded_hal::{adapter::BlockingAsync, flash::partition::Partition};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex};
use embassy_time::{Duration, Instant};
2026-01-24 00:42:16 +01:00
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
use esp_hal::rng::Trng;
use esp_storage::FlashStorage;
2026-01-20 02:55:17 +01:00
use log::{info, warn};
2026-01-24 00:42:16 +01:00
use rmk::futures::TryFutureExt;
2026-01-20 02:55:17 +01:00
use slint::SharedString;
use spectre_api_sys::{SpectreAlgorithm, SpectreCounter, SpectreKeyPurpose, SpectreUserKey};
2026-01-10 19:21:13 +01:00
#[cfg(feature = "limit-fps")]
use crate::FRAME_DURATION_MIN;
use crate::{SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER, ui::backend::SlintBackend, util::DurationExt};
2026-01-10 19:21:13 +01:00
2025-12-31 22:24:26 +01:00
pub mod backend;
2026-01-10 19:21:13 +01:00
pub mod window_adapter;
2025-12-31 22:24:26 +01:00
slint::include_modules!();
2026-01-10 19:21:13 +01:00
2026-01-24 00:42:16 +01:00
type PartitionAcid =
Partition<'static, CriticalSectionRawMutex, BlockingAsync<FlashStorage<'static>>>;
// Workaround for alignment requirements.
#[repr(C, align(4))]
struct AlignedBuf<const N: usize>(pub [u8; N]);
struct EkvFlash<T> {
flash: T,
buffer: Box<AlignedBuf<{ ekv::config::PAGE_SIZE }>>,
}
impl<T> EkvFlash<T> {
fn new(flash: T) -> Self {
Self {
flash,
buffer: {
// Allocate the buffer directly on the heap.
let buffer = Box::new_zeroed();
unsafe { buffer.assume_init() }
},
}
}
}
impl<T> Deref for EkvFlash<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.flash
}
}
impl<T> DerefMut for EkvFlash<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.flash
}
}
impl<T: NorFlash + ReadNorFlash> ekv::flash::Flash for EkvFlash<T> {
type Error = T::Error;
fn page_count(&self) -> usize {
ekv::config::MAX_PAGE_COUNT
}
async fn erase(
&mut self,
page_id: PageID,
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
self.flash
.erase(
(page_id.index() * ekv::config::PAGE_SIZE) as u32,
((page_id.index() + 1) * ekv::config::PAGE_SIZE) as u32,
)
.await
}
async fn read(
&mut self,
page_id: PageID,
offset: usize,
data: &mut [u8],
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
self.flash
.read(address as u32, &mut self.buffer.0[..data.len()])
.await?;
data.copy_from_slice(&self.buffer.0[..data.len()]);
Ok(())
}
async fn write(
&mut self,
page_id: PageID,
offset: usize,
data: &[u8],
) -> Result<(), <EkvFlash<T> as ekv::flash::Flash>::Error> {
let address = page_id.index() * ekv::config::PAGE_SIZE + offset;
self.buffer.0[..data.len()].copy_from_slice(data);
self.flash
.write(address as u32, &self.buffer.0[..data.len()])
.await
}
}
2026-01-10 19:21:13 +01:00
#[embassy_executor::task]
2026-01-24 00:42:16 +01:00
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
let mut db_config = ekv::Config::default();
db_config.random_seed = Trng::try_new()
.expect("A `TrngSource` was not initialized before constructing this `Trng`.")
.random();
let mut db = Database::<_, esp_sync::RawMutex>::new(EkvFlash::new(flash_part_acid), db_config);
#[cfg(feature = "format-db")]
{
warn!("Formatting EKV database...");
db.format()
.await
.unwrap_or_else(|error| panic!("Failed to format the EKV database: {error:?}"));
warn!("EKV database formatted successfully.");
}
match db.mount().await {
Ok(()) => info!("EKV database mounted."),
Err(error) => panic!("Failed to mount the EKV database: {error:?}"),
};
2026-01-10 19:21:13 +01:00
slint::platform::set_platform(Box::new(backend)).expect("backend already initialized");
let main = AppWindow::new().unwrap();
2026-01-20 02:55:17 +01:00
main.on_accepted(|string| {
warn!("Accepted: {string}");
let Ok(c_string) = CString::new(&*string) else {
warn!("String cannot be converted to a C string: {string:?}");
return;
};
unsafe {
let user_key_start = Instant::now();
2026-01-20 02:55:17 +01:00
let user_key = &*spectre_api_sys::spectre_user_key(
c"test".as_ptr(),
c_string.as_ptr(),
SpectreAlgorithm::Current,
);
let user_key_duration = Instant::now().duration_since(user_key_start);
warn!(
"User key derived in {} seconds:\n{user_key:02x?}",
user_key_duration.display_as_secs()
);
let site_key_start = Instant::now();
2026-01-20 02:55:17 +01:00
let site_key = &*spectre_api_sys::spectre_site_key(
user_key as *const SpectreUserKey,
c"example.org".as_ptr(),
SpectreCounter::Initial,
SpectreKeyPurpose::Authentication,
c"".as_ptr(),
);
let site_key_duration = Instant::now().duration_since(site_key_start);
warn!(
"Site key derived in {} seconds:\n{site_key:02x?}",
site_key_duration.display_as_secs()
);
2026-01-20 02:55:17 +01:00
// TODO: Free memory
}
});
2026-01-24 00:42:16 +01:00
run_event_loop(main).await;
}
2026-01-10 19:21:13 +01:00
2026-01-24 00:42:16 +01:00
/// Instead of having a `loop` in the non-async `SlintBackend::run_event_loop`, we achieve
/// async by having only one iteration of the loop run, and `await`ing here.
/// The following block is analogous to `main.run()`.
async fn run_event_loop(main: AppWindow) {
main.show().unwrap();
loop {
slint::run_event_loop().unwrap();
SIGNAL_LCD_SUBMIT.signal(());
#[cfg(feature = "limit-fps")]
embassy_time::Timer::after(FRAME_DURATION_MIN).await;
SIGNAL_UI_RENDER.wait().await;
2026-01-10 19:21:13 +01:00
}
2026-01-24 00:42:16 +01:00
#[expect(unreachable_code)]
main.hide().unwrap();
2026-01-10 19:21:13 +01:00
}