From a1dfeead643aacdc7f3b092cb86070a22b030771 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 16:01:21 +0000 Subject: [PATCH 01/29] Add midi thru task --- firmware/foundation/src/application/midi_task.rs | 10 ++++++++++ firmware/foundation/src/application/mod.rs | 1 + firmware/foundation/src/midi.rs | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 firmware/foundation/src/application/midi_task.rs diff --git a/firmware/foundation/src/application/midi_task.rs b/firmware/foundation/src/application/midi_task.rs new file mode 100644 index 0000000..59daf69 --- /dev/null +++ b/firmware/foundation/src/application/midi_task.rs @@ -0,0 +1,10 @@ +use crate::application::channels::MidiOutChannel; +use crate::midi::MidiReader; + +async fn midi_thru_task(mut reader: MR, midi_out_channel: MidiOutChannel) -> ! { + loop { + if let Some(packet) = reader.read_midi_packet().await.unwrap() { + midi_out_channel.send(packet).await; + } + } +} diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index a2f8129..9c10cd3 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -1,6 +1,7 @@ mod button_task; mod channels; mod display_task; +mod midi_task; pub mod state; use crate::midi::{MidiReader, MidiWriter}; diff --git a/firmware/foundation/src/midi.rs b/firmware/foundation/src/midi.rs index a5d9a95..48a8e80 100644 --- a/firmware/foundation/src/midi.rs +++ b/firmware/foundation/src/midi.rs @@ -1,3 +1,5 @@ +use core::fmt::Debug; + #[derive(Copy, Clone, Debug)] pub enum NoteName { C, @@ -200,7 +202,7 @@ impl MidiParser { } pub trait MidiReader { - type Error; + type Error: Debug; /// Asynchronously read a MIDI packet, returning None if the stream ends fn read_midi_packet(&mut self) From 1229aedd40d950af6e28e8d4cd55541f58682c38 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 16:03:24 +0000 Subject: [PATCH 02/29] Abstract for midi out task --- firmware/foundation/src/application/midi_task.rs | 9 ++++++++- firmware/foundation/src/midi.rs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/firmware/foundation/src/application/midi_task.rs b/firmware/foundation/src/application/midi_task.rs index 59daf69..b056422 100644 --- a/firmware/foundation/src/application/midi_task.rs +++ b/firmware/foundation/src/application/midi_task.rs @@ -1,5 +1,5 @@ use crate::application::channels::MidiOutChannel; -use crate::midi::MidiReader; +use crate::midi::{MidiReader, MidiWriter}; async fn midi_thru_task(mut reader: MR, midi_out_channel: MidiOutChannel) -> ! { loop { @@ -8,3 +8,10 @@ async fn midi_thru_task(mut reader: MR, midi_out_channel: MidiOu } } } + +async fn midi_out_task(mut writer: MW, midi_out_channel: MidiOutChannel) -> ! { + loop { + let packet = midi_out_channel.receive().await; + writer.write_midi_packet(&packet).await.unwrap(); + } +} diff --git a/firmware/foundation/src/midi.rs b/firmware/foundation/src/midi.rs index 48a8e80..846d612 100644 --- a/firmware/foundation/src/midi.rs +++ b/firmware/foundation/src/midi.rs @@ -210,7 +210,7 @@ pub trait MidiReader { } pub trait MidiWriter { - type Error; + type Error: Debug; /// Asynchronously write a MIDI packet fn write_midi_packet( From 7af86c67a830a6793e3d222474c1f9cb5d44ee29 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 16:04:46 +0000 Subject: [PATCH 03/29] Add note --- firmware/foundation/src/application/midi_task.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/foundation/src/application/midi_task.rs b/firmware/foundation/src/application/midi_task.rs index b056422..f5ecedf 100644 --- a/firmware/foundation/src/application/midi_task.rs +++ b/firmware/foundation/src/application/midi_task.rs @@ -4,6 +4,7 @@ use crate::midi::{MidiReader, MidiWriter}; async fn midi_thru_task(mut reader: MR, midi_out_channel: MidiOutChannel) -> ! { loop { if let Some(packet) = reader.read_midi_packet().await.unwrap() { + // TODO: If we decide to support MIDI command input in future, this would be a good place to process those midi_out_channel.send(packet).await; } } From f114e0b4deaa755b84c11f0405486a903f4d013b Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 17:04:45 +0000 Subject: [PATCH 04/29] Add storage task --- .../foundation/src/application/channels.rs | 31 +++++++++++++++++++ firmware/foundation/src/application/mod.rs | 1 + .../src/application/storage_task.rs | 18 +++++++++++ firmware/foundation/src/storage/mod.rs | 7 +++-- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 firmware/foundation/src/application/storage_task.rs diff --git a/firmware/foundation/src/application/channels.rs b/firmware/foundation/src/application/channels.rs index 5ee8e47..fbe3d3a 100644 --- a/firmware/foundation/src/application/channels.rs +++ b/firmware/foundation/src/application/channels.rs @@ -1,6 +1,7 @@ use crate::layout::DisplayText; use crate::midi::MidiPacket; use crate::protocol::Colour; +use embassy_sync::blocking_mutex::CriticalSectionMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; @@ -25,4 +26,34 @@ pub enum ButtonEvent { pub type ButtonEventChannel = Channel; // TODO: Add channel for state updates +pub enum StorageStateEvent { + PresetUpdate { + preset_name: DisplayText, + // Display 1 + display_1_top_row_text: Option, + display_1_top_row_color: Option, + display_1_bottom_row_text: Option, + display_1_bottom_row_color: Option, + // Display 2 + display_2_top_row_text: Option, + display_2_top_row_color: Option, + display_2_bottom_row_text: Option, + display_2_bottom_row_color: Option, + // Display 3 + display_3_top_row_text: Option, + display_3_top_row_color: Option, + display_3_bottom_row_text: Option, + display_3_bottom_row_color: Option, + // Display 4 + display_4_top_row_text: Option, + display_4_top_row_color: Option, + display_4_bottom_row_text: Option, + display_4_bottom_row_color: Option, + // TODO: Button actions + }, + SavePreset, +} + +pub type StorageStateEventChannel = Channel; + // TODO: Add channel for button events diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index 9c10cd3..bd97b8f 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -3,6 +3,7 @@ mod channels; mod display_task; mod midi_task; pub mod state; +mod storage_task; use crate::midi::{MidiReader, MidiWriter}; use embedded_graphics::draw_target::DrawTarget; diff --git a/firmware/foundation/src/application/storage_task.rs b/firmware/foundation/src/application/storage_task.rs new file mode 100644 index 0000000..b2fdb4f --- /dev/null +++ b/firmware/foundation/src/application/storage_task.rs @@ -0,0 +1,18 @@ +use crate::application::channels::{StorageStateEvent, StorageStateEventChannel}; +use crate::storage::StorageManager; + +async fn storage_read_task( + storage_manager: SM, + storage_state_event_channel: StorageStateEventChannel, +) -> ! { + let _presets = storage_manager + .load_presets() + .expect("Failed to load presets from storage"); + loop { + let event = storage_state_event_channel.receive().await; + match event { + StorageStateEvent::PresetUpdate { .. } => todo!(), + StorageStateEvent::SavePreset => todo!(), + } + } +} diff --git a/firmware/foundation/src/storage/mod.rs b/firmware/foundation/src/storage/mod.rs index 1e8bd83..0462f66 100644 --- a/firmware/foundation/src/storage/mod.rs +++ b/firmware/foundation/src/storage/mod.rs @@ -1,8 +1,11 @@ use crate::storage::state::Presets; +use core::fmt::Debug; pub mod state; pub trait StorageManager { - fn load_presets(&self) -> Presets; - fn save_presets(&mut self, presets: &Presets); + type Error: Debug; + + fn load_presets(&self) -> Result; + fn save_presets(&mut self, presets: &Presets) -> Result<(), Self::Error>; } From c666506f9b78733fde7b146dba4cfee1dc56a2d3 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 17:06:10 +0000 Subject: [PATCH 05/29] Move tasks --- firmware/foundation/src/application/mod.rs | 4 ---- .../foundation/src/application/{ => tasks}/button_task.rs | 0 .../foundation/src/application/{ => tasks}/display_task.rs | 0 firmware/foundation/src/application/{ => tasks}/midi_task.rs | 0 .../foundation/src/application/{ => tasks}/storage_task.rs | 0 5 files changed, 4 deletions(-) rename firmware/foundation/src/application/{ => tasks}/button_task.rs (100%) rename firmware/foundation/src/application/{ => tasks}/display_task.rs (100%) rename firmware/foundation/src/application/{ => tasks}/midi_task.rs (100%) rename firmware/foundation/src/application/{ => tasks}/storage_task.rs (100%) diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index bd97b8f..b490b94 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -1,9 +1,5 @@ -mod button_task; mod channels; -mod display_task; -mod midi_task; pub mod state; -mod storage_task; use crate::midi::{MidiReader, MidiWriter}; use embedded_graphics::draw_target::DrawTarget; diff --git a/firmware/foundation/src/application/button_task.rs b/firmware/foundation/src/application/tasks/button_task.rs similarity index 100% rename from firmware/foundation/src/application/button_task.rs rename to firmware/foundation/src/application/tasks/button_task.rs diff --git a/firmware/foundation/src/application/display_task.rs b/firmware/foundation/src/application/tasks/display_task.rs similarity index 100% rename from firmware/foundation/src/application/display_task.rs rename to firmware/foundation/src/application/tasks/display_task.rs diff --git a/firmware/foundation/src/application/midi_task.rs b/firmware/foundation/src/application/tasks/midi_task.rs similarity index 100% rename from firmware/foundation/src/application/midi_task.rs rename to firmware/foundation/src/application/tasks/midi_task.rs diff --git a/firmware/foundation/src/application/storage_task.rs b/firmware/foundation/src/application/tasks/storage_task.rs similarity index 100% rename from firmware/foundation/src/application/storage_task.rs rename to firmware/foundation/src/application/tasks/storage_task.rs From 50ff3113947ae594ca6c8558b17c60761db164ac Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 17:53:37 +0000 Subject: [PATCH 06/29] Add error type --- firmware/firmware/src/main.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index 772ad80..3c1a45f 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -23,6 +23,7 @@ use esp_alloc as _; use esp_backtrace as _; use core::cell::RefCell; +use core::fmt::Debug; use core::marker::PhantomData; use core::ops::Add; use core::str::FromStr; @@ -121,13 +122,19 @@ struct FakeStorageManager<'a> { phantom_data: PhantomData<&'a ()>, } +#[derive(Debug)] +struct DummyError {} + impl<'a> StorageManager for FakeStorageManager<'a> { - fn load_presets(&self) -> Presets { - heapless::Vec::new() + type Error = DummyError; + + fn load_presets(&self) -> Result { + Ok(heapless::Vec::new()) } - fn save_presets(&mut self, _presets: &Presets) { + fn save_presets(&mut self, _presets: &Presets) -> Result<(), Self::Error> { // Do nothing + Ok(()) } } @@ -259,15 +266,6 @@ async fn main(spawner: Spawner) -> ! { .into_async(); let (mut rx, mut tx) = uart.split(); - // spawner - // .spawn(midi_thru_task(rx)) - // .expect("Unable to spawn MIDI thru task"); - // info!("MIDI thru task spawned"); - // spawner - // .spawn(midi_out_task(tx)) - // .expect("Unable to spawn MIDI out task"); - // info!("MIDI out task spawned"); - info!("Startup complete."); let mut midi_reader = UartMidiReader::new(&mut rx); From faf1e3ae92f660538c4acc74361b04e6842bac63 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 17:56:14 +0000 Subject: [PATCH 07/29] Remove unused import --- firmware/foundation/src/application/channels.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/firmware/foundation/src/application/channels.rs b/firmware/foundation/src/application/channels.rs index fbe3d3a..8f984d2 100644 --- a/firmware/foundation/src/application/channels.rs +++ b/firmware/foundation/src/application/channels.rs @@ -1,7 +1,6 @@ use crate::layout::DisplayText; use crate::midi::MidiPacket; use crate::protocol::Colour; -use embassy_sync::blocking_mutex::CriticalSectionMutex; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; From 6d895ea294bab955b8ee8a388d60bd508d7b5722 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 17:58:32 +0000 Subject: [PATCH 08/29] Add channels to internal struct --- firmware/foundation/src/application/channels.rs | 2 +- firmware/foundation/src/application/state.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/firmware/foundation/src/application/channels.rs b/firmware/foundation/src/application/channels.rs index 8f984d2..fa999ea 100644 --- a/firmware/foundation/src/application/channels.rs +++ b/firmware/foundation/src/application/channels.rs @@ -53,6 +53,6 @@ pub enum StorageStateEvent { SavePreset, } -pub type StorageStateEventChannel = Channel; +pub type StorageStateUpdateChannel = Channel; // TODO: Add channel for button events diff --git a/firmware/foundation/src/application/state.rs b/firmware/foundation/src/application/state.rs index b92cc2f..c8c0e6f 100644 --- a/firmware/foundation/src/application/state.rs +++ b/firmware/foundation/src/application/state.rs @@ -1,4 +1,6 @@ -use crate::application::channels::MidiOutChannel; +use crate::application::channels::{ + ButtonEventChannel, DisplayStateUpdateChannel, MidiOutChannel, StorageStateUpdateChannel, +}; use crate::application::{Displays, MidiStreams}; use crate::midi::{MidiReader, MidiWriter}; use crate::storage::StorageManager; @@ -7,6 +9,9 @@ use embedded_graphics::pixelcolor::Rgb565; struct InternalChannels<'a> { midi_out: &'a mut MidiOutChannel, + display_state_update: &'a mut DisplayStateUpdateChannel, + storage_state_update: &'a mut StorageStateUpdateChannel, + button_event: &'a mut ButtonEventChannel, } pub struct Application< From eede730d4271f56783763d446f39cd1360324d22 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:12:11 +0000 Subject: [PATCH 09/29] Add channels to application state --- firmware/foundation/src/application/state.rs | 37 +++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/firmware/foundation/src/application/state.rs b/firmware/foundation/src/application/state.rs index c8c0e6f..c6563b4 100644 --- a/firmware/foundation/src/application/state.rs +++ b/firmware/foundation/src/application/state.rs @@ -40,8 +40,11 @@ impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: Stor display_4: &'a mut D, midi_reader: &'a mut MR, midi_writer: &'a mut MW, - midi_out_channel: &'a mut MidiOutChannel, storage_manager: &'a mut SM, + midi_out_channel: &'a mut MidiOutChannel, + display_state_update_channel: &'a mut DisplayStateUpdateChannel, + storage_state_update_channel: &'a mut StorageStateUpdateChannel, + button_event_channel: &'a mut ButtonEventChannel, ) -> Self { // Maybe a good idea to create the channels here? Self { @@ -57,6 +60,9 @@ impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: Stor }, channels: InternalChannels { midi_out: midi_out_channel, + display_state_update: display_state_update_channel, + storage_state_update: storage_state_update_channel, + button_event: button_event_channel, }, storage_manager, } @@ -77,8 +83,11 @@ pub struct ApplicationBuilder< display_4: Option<&'a mut D>, midi_reader: Option<&'a mut MR>, midi_writer: Option<&'a mut MW>, - midi_out_channel: Option<&'a mut MidiOutChannel>, storage_manager: Option<&'a mut SM>, + midi_out_channel: Option<&'a mut MidiOutChannel>, + display_state_update_channel: Option<&'a mut DisplayStateUpdateChannel>, + storage_state_update_channel: Option<&'a mut StorageStateUpdateChannel>, + button_event_channel: Option<&'a mut ButtonEventChannel>, } impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: StorageManager> @@ -93,7 +102,10 @@ impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: Stor midi_reader: None, midi_writer: None, midi_out_channel: None, + display_state_update_channel: None, + storage_state_update_channel: None, storage_manager: None, + button_event_channel: None, } } @@ -122,8 +134,17 @@ impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: Stor self } - pub fn with_midi_out_channel(mut self, channel: &'a mut MidiOutChannel) -> Self { - self.midi_out_channel = Some(channel); + pub fn with_channels( + mut self, + midi_out_channel: &'a mut MidiOutChannel, + display_state_update_channel: &'a mut DisplayStateUpdateChannel, + storage_state_update_channel: &'a mut StorageStateUpdateChannel, + button_event_channel: &'a mut ButtonEventChannel, + ) -> Self { + self.midi_out_channel = Some(midi_out_channel); + self.display_state_update_channel = Some(display_state_update_channel); + self.storage_state_update_channel = Some(storage_state_update_channel); + self.button_event_channel = Some(button_event_channel); self } @@ -140,8 +161,14 @@ impl<'a, D: DrawTarget, MR: MidiReader, MW: MidiWriter, SM: Stor self.display_4.expect("Display 4 is required"), self.midi_reader.expect("MIDI reader is required"), self.midi_writer.expect("MIDI writer is required"), - self.midi_out_channel.expect("MIDI out channel is required"), self.storage_manager.expect("Storage manager is required"), + self.midi_out_channel.expect("MIDI out channel required"), + self.display_state_update_channel + .expect("Display state update channel required"), + self.storage_state_update_channel + .expect("Storage state update channel required"), + self.button_event_channel + .expect("Button event channel required"), ) } } From 8cd5991e6a88f4140345aff9dc3ab8b002ff2893 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:42:10 +0000 Subject: [PATCH 10/29] Initialise channels in firmware --- firmware/firmware/src/main.rs | 7 +++++++ firmware/foundation/src/application/mod.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index 3c1a45f..14dd199 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -52,6 +52,7 @@ use esp_hal::uart::{ Config as UartConfig, DataBits, Parity, RxError, StopBits, TxError, Uart, UartRx, UartTx, }; use esp_println::println; +use foundation::application::channels; use foundation::layout::DisplayLayout; use foundation::storage::StorageManager; use foundation::storage::state::Presets; @@ -277,6 +278,12 @@ async fn main(spawner: Spawner) -> ! { .with_midi_reader(&mut midi_reader) .with_midi_writer(&mut midi_writer) .with_storage_manager(&mut storage_manager) + .with_channels( + &mut channels::MidiOutChannel::new(), + &mut channels::DisplayStateUpdateChannel::new(), + &mut channels::StorageStateUpdateChannel::new(), + &mut channels::ButtonEventChannel::new(), + ) .build(); loop { diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index b490b94..e11f144 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -1,4 +1,4 @@ -mod channels; +pub mod channels; pub mod state; use crate::midi::{MidiReader, MidiWriter}; From c2c86bb1b16f60f4e5245e047eca3eb57d346fc0 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:50:53 +0000 Subject: [PATCH 11/29] Add midi module for firmware --- firmware/firmware/src/main.rs | 63 +++-------------------------------- firmware/firmware/src/midi.rs | 49 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 58 deletions(-) create mode 100644 firmware/firmware/src/midi.rs diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index 14dd199..1b09278 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -8,6 +8,7 @@ #![deny(clippy::large_stack_frames)] extern crate alloc; +mod midi; include!(concat!(env!("OUT_DIR"), "/version.rs")); @@ -29,11 +30,7 @@ use core::ops::Add; use core::str::FromStr; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; use embassy_executor::{Spawner, task}; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::{ - blocking_mutex::{Mutex, raw::NoopRawMutex}, - channel::Channel, -}; +use embassy_sync::blocking_mutex::{Mutex, raw::NoopRawMutex}; use embassy_time::Delay; use embassy_time::Timer; use embedded_graphics::draw_target::DrawTarget; @@ -42,15 +39,12 @@ use embedded_graphics::prelude::*; use embedded_graphics::primitives::PrimitiveStyleBuilder; use embedded_graphics::text::{Alignment, Baseline, TextStyleBuilder}; use embedded_graphics::{pixelcolor::Rgb565, text::Text}; -use esp_hal::Async; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::spi::master::Spi; use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; -use esp_hal::uart::{ - Config as UartConfig, DataBits, Parity, RxError, StopBits, TxError, Uart, UartRx, UartTx, -}; +use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, TxError, Uart}; use esp_println::println; use foundation::application::channels; use foundation::layout::DisplayLayout; @@ -58,10 +52,11 @@ use foundation::storage::StorageManager; use foundation::storage::state::Presets; use foundation::{ application::state::ApplicationBuilder, - midi::{MidiPacket, MidiParser, MidiReader, MidiWriter}, + midi::{MidiReader, MidiWriter}, }; use heapless::String; use log::info; +use midi::{UartMidiReader, UartMidiWriter}; use mipidsi::models::ST7789; use mipidsi::options::Rotation::Deg270; use mipidsi::options::{ColorInversion, Orientation}; @@ -70,54 +65,6 @@ use mipidsi::options::{ColorInversion, Orientation}; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); -static MIDI_OUT_CHANNEL: Channel = Channel::new(); - -struct UartMidiReader<'a, 'b> { - uart: &'a mut UartRx<'b, Async>, - parser: MidiParser, -} - -impl<'a, 'b> UartMidiReader<'a, 'b> { - fn new(uart: &'a mut UartRx<'b, Async>) -> Self { - Self { - uart, - parser: MidiParser::default(), - } - } -} - -impl<'a, 'b> MidiReader for UartMidiReader<'a, 'b> { - type Error = RxError; - - async fn read_midi_packet(&mut self) -> Result, Self::Error> { - let mut buf = [0u8; 1]; - self.uart.read_async(&mut buf).await?; - - Ok(self.parser.feed(buf[0])) - } -} - -struct UartMidiWriter<'a, 'b> { - uart: &'a mut UartTx<'b, Async>, -} - -impl<'a, 'b> UartMidiWriter<'a, 'b> { - fn new(uart: &'a mut UartTx<'b, Async>) -> Self { - Self { uart } - } -} - -impl<'a, 'b> MidiWriter for UartMidiWriter<'a, 'b> { - type Error = TxError; - - async fn write_midi_packet(&mut self, packet: &MidiPacket) -> Result<(), Self::Error> { - self.uart - .write_async(&packet.data[..packet.len as usize]) - .await?; - Ok(()) - } -} - #[derive(Default)] struct FakeStorageManager<'a> { phantom_data: PhantomData<&'a ()>, diff --git a/firmware/firmware/src/midi.rs b/firmware/firmware/src/midi.rs new file mode 100644 index 0000000..358f3dd --- /dev/null +++ b/firmware/firmware/src/midi.rs @@ -0,0 +1,49 @@ +use esp_hal::Async; +use esp_hal::uart::{RxError, TxError, UartRx, UartTx}; +use foundation::midi::{MidiPacket, MidiParser, MidiReader, MidiWriter}; + +pub(crate) struct UartMidiReader<'a, 'b> { + uart: &'a mut UartRx<'b, Async>, + parser: MidiParser, +} + +impl<'a, 'b> UartMidiReader<'a, 'b> { + fn new(uart: &'a mut UartRx<'b, Async>) -> Self { + Self { + uart, + parser: MidiParser::default(), + } + } +} + +impl<'a, 'b> MidiReader for UartMidiReader<'a, 'b> { + type Error = RxError; + + async fn read_midi_packet(&mut self) -> Result, Self::Error> { + let mut buf = [0u8; 1]; + self.uart.read_async(&mut buf).await?; + + Ok(self.parser.feed(buf[0])) + } +} + +pub(crate) struct UartMidiWriter<'a, 'b> { + uart: &'a mut UartTx<'b, Async>, +} + +impl<'a, 'b> UartMidiWriter<'a, 'b> { + fn new(uart: &'a mut UartTx<'b, Async>) -> Self { + Self { uart } + } +} + +impl<'a, 'b> MidiWriter for UartMidiWriter<'a, 'b> { + type Error = TxError; + + async fn write_midi_packet(&mut self, packet: &MidiPacket) -> Result<(), Self::Error> { + self.uart + .write_async(&packet.data[..packet.len as usize]) + .await?; + Ok(()) + } +} From 54c54f763ed4260271a7593e5a17571341f2a245 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:53:28 +0000 Subject: [PATCH 12/29] Remove tasks --- firmware/firmware/src/main.rs | 24 ++---------------------- firmware/firmware/src/midi.rs | 8 ++++---- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index 1b09278..12fc18b 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -29,7 +29,7 @@ use core::marker::PhantomData; use core::ops::Add; use core::str::FromStr; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; -use embassy_executor::{Spawner, task}; +use embassy_executor::Spawner; use embassy_sync::blocking_mutex::{Mutex, raw::NoopRawMutex}; use embassy_time::Delay; use embassy_time::Timer; @@ -44,7 +44,7 @@ use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::spi::master::Spi; use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; -use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, TxError, Uart}; +use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart}; use esp_println::println; use foundation::application::channels; use foundation::layout::DisplayLayout; @@ -86,26 +86,6 @@ impl<'a> StorageManager for FakeStorageManager<'a> { } } -/// Forward MIDI messages IN to the MIDI_OUT_CHANNEL -#[task] -async fn midi_thru_task(mut reader: UartMidiReader<'static, 'static>) { - loop { - if let Some(packet) = reader.read_midi_packet().await.unwrap() { - MIDI_OUT_CHANNEL.send(packet).await; - } - } -} - -/// Read MIDI messages from the MIDI_OUT_CHANNEL and send them out over UART -#[task] -async fn midi_out_task(mut writer: UartMidiWriter<'static, 'static>) { - loop { - let packet = MIDI_OUT_CHANNEL.receive().await; - let res: Result<(), TxError> = writer.write_midi_packet(&packet).await; - res.unwrap(); - } -} - #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" diff --git a/firmware/firmware/src/midi.rs b/firmware/firmware/src/midi.rs index 358f3dd..d664618 100644 --- a/firmware/firmware/src/midi.rs +++ b/firmware/firmware/src/midi.rs @@ -2,13 +2,13 @@ use esp_hal::Async; use esp_hal::uart::{RxError, TxError, UartRx, UartTx}; use foundation::midi::{MidiPacket, MidiParser, MidiReader, MidiWriter}; -pub(crate) struct UartMidiReader<'a, 'b> { +pub struct UartMidiReader<'a, 'b> { uart: &'a mut UartRx<'b, Async>, parser: MidiParser, } impl<'a, 'b> UartMidiReader<'a, 'b> { - fn new(uart: &'a mut UartRx<'b, Async>) -> Self { + pub fn new(uart: &'a mut UartRx<'b, Async>) -> Self { Self { uart, parser: MidiParser::default(), @@ -27,12 +27,12 @@ impl<'a, 'b> MidiReader for UartMidiReader<'a, 'b> { } } -pub(crate) struct UartMidiWriter<'a, 'b> { +pub struct UartMidiWriter<'a, 'b> { uart: &'a mut UartTx<'b, Async>, } impl<'a, 'b> UartMidiWriter<'a, 'b> { - fn new(uart: &'a mut UartTx<'b, Async>) -> Self { + pub fn new(uart: &'a mut UartTx<'b, Async>) -> Self { Self { uart } } } From f13abc9e0263ca819cd09ec2023418738414db75 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:54:08 +0000 Subject: [PATCH 13/29] Rename --- .../src/application/tasks/{button_task.rs => button.rs} | 0 .../src/application/tasks/{display_task.rs => display.rs} | 0 .../foundation/src/application/tasks/{midi_task.rs => midi.rs} | 0 .../src/application/tasks/{storage_task.rs => storage.rs} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename firmware/foundation/src/application/tasks/{button_task.rs => button.rs} (100%) rename firmware/foundation/src/application/tasks/{display_task.rs => display.rs} (100%) rename firmware/foundation/src/application/tasks/{midi_task.rs => midi.rs} (100%) rename firmware/foundation/src/application/tasks/{storage_task.rs => storage.rs} (100%) diff --git a/firmware/foundation/src/application/tasks/button_task.rs b/firmware/foundation/src/application/tasks/button.rs similarity index 100% rename from firmware/foundation/src/application/tasks/button_task.rs rename to firmware/foundation/src/application/tasks/button.rs diff --git a/firmware/foundation/src/application/tasks/display_task.rs b/firmware/foundation/src/application/tasks/display.rs similarity index 100% rename from firmware/foundation/src/application/tasks/display_task.rs rename to firmware/foundation/src/application/tasks/display.rs diff --git a/firmware/foundation/src/application/tasks/midi_task.rs b/firmware/foundation/src/application/tasks/midi.rs similarity index 100% rename from firmware/foundation/src/application/tasks/midi_task.rs rename to firmware/foundation/src/application/tasks/midi.rs diff --git a/firmware/foundation/src/application/tasks/storage_task.rs b/firmware/foundation/src/application/tasks/storage.rs similarity index 100% rename from firmware/foundation/src/application/tasks/storage_task.rs rename to firmware/foundation/src/application/tasks/storage.rs From 50ad5aebe430aa9060b1e90dcca25fd58fc48b53 Mon Sep 17 00:00:00 2001 From: issy Date: Sun, 15 Mar 2026 18:57:27 +0000 Subject: [PATCH 14/29] Move stuff out of main module file --- firmware/foundation/src/application/mod.rs | 16 ---------------- firmware/foundation/src/application/state.rs | 13 ++++++++++++- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index e11f144..9290e5e 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -1,18 +1,2 @@ pub mod channels; pub mod state; - -use crate::midi::{MidiReader, MidiWriter}; -use embedded_graphics::draw_target::DrawTarget; -use embedded_graphics::pixelcolor::Rgb565; - -struct Displays<'a, D: DrawTarget> { - display_1: &'a mut D, - display_2: &'a mut D, - display_3: &'a mut D, - display_4: &'a mut D, -} - -struct MidiStreams<'a, MR: MidiReader, MW: MidiWriter> { - reader: &'a mut MR, - writer: &'a mut MW, -} diff --git a/firmware/foundation/src/application/state.rs b/firmware/foundation/src/application/state.rs index c6563b4..191d1c2 100644 --- a/firmware/foundation/src/application/state.rs +++ b/firmware/foundation/src/application/state.rs @@ -1,12 +1,23 @@ use crate::application::channels::{ ButtonEventChannel, DisplayStateUpdateChannel, MidiOutChannel, StorageStateUpdateChannel, }; -use crate::application::{Displays, MidiStreams}; use crate::midi::{MidiReader, MidiWriter}; use crate::storage::StorageManager; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::pixelcolor::Rgb565; +struct Displays<'a, D: DrawTarget> { + display_1: &'a mut D, + display_2: &'a mut D, + display_3: &'a mut D, + display_4: &'a mut D, +} + +struct MidiStreams<'a, MR: MidiReader, MW: MidiWriter> { + reader: &'a mut MR, + writer: &'a mut MW, +} + struct InternalChannels<'a> { midi_out: &'a mut MidiOutChannel, display_state_update: &'a mut DisplayStateUpdateChannel, From c543d2ff78902cb3cc80fc74eef748233323453a Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 20:09:26 +0000 Subject: [PATCH 15/29] Serde Serde Serde Serde Serde --- firmware/Cargo.lock | 2 ++ firmware/foundation/Cargo.toml | 3 ++- firmware/foundation/src/protocol.rs | 7 ++++--- firmware/foundation/src/storage/state.rs | 9 +++++---- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index b67cb28..2d758af 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -1814,6 +1814,7 @@ dependencies = [ "embedded-graphics", "heapless 0.9.2", "prost", + "serde", ] [[package]] @@ -2132,6 +2133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" dependencies = [ "hash32", + "serde_core", "stable_deref_trait", ] diff --git a/firmware/foundation/Cargo.toml b/firmware/foundation/Cargo.toml index 1806cfe..18bfff4 100644 --- a/firmware/foundation/Cargo.toml +++ b/firmware/foundation/Cargo.toml @@ -11,5 +11,6 @@ path = "src/lib.rs" [dependencies] embedded-graphics = { version = "0.8.2" } prost = { version = "0.14.3", default-features = false, features = ["derive"] } -heapless = { version = "0.9.2" } +heapless = { version = "0.9.2", features = ["serde"] } embassy-sync = { version = "0.7.2", features = [] } +serde = { version = "1.0.228", features = ["derive"] } diff --git a/firmware/foundation/src/protocol.rs b/firmware/foundation/src/protocol.rs index 25fbb01..b5fd7a8 100644 --- a/firmware/foundation/src/protocol.rs +++ b/firmware/foundation/src/protocol.rs @@ -1,7 +1,7 @@ +use crate::generated::device_v1 as pb; use alloc::string::String; use alloc::vec::Vec; - -use crate::generated::device_v1 as pb; +use serde::{Deserialize, Serialize}; const PROTOCOL_VERSION: u32 = 1; @@ -22,10 +22,11 @@ pub struct Capabilities { #[derive(Debug)] pub struct MidiCommand { + // TODO: Fix placeholder: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum Colour { Red = 1, Green = 2, diff --git a/firmware/foundation/src/storage/state.rs b/firmware/foundation/src/storage/state.rs index c726f1a..7d332a9 100644 --- a/firmware/foundation/src/storage/state.rs +++ b/firmware/foundation/src/storage/state.rs @@ -1,13 +1,14 @@ use crate::midi::MidiPacket; use crate::protocol::Colour; use heapless::{String, Vec}; +use serde::{Deserialize, Serialize}; pub const MAX_PRESETS: usize = 128; pub const MAX_STRING_LENGTH: usize = 16; pub const NUM_OF_BUTTONS: usize = 8; pub const MAX_BUTTON_ACTIONS: usize = 8; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] pub enum MidiCommand { ProgramChange { channel: u8, @@ -55,13 +56,13 @@ impl Into for MidiCommand { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] pub enum ButtonType { Momentary, Toggle, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct ButtonConfig { pub name: String, pub button_type: ButtonType, @@ -71,7 +72,7 @@ pub struct ButtonConfig { pub off_actions: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct StoredPreset { pub name: String, pub buttons: Vec, From 6c813c12a0146900bc7561b4f213badefc8e0219 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 20:28:31 +0000 Subject: [PATCH 16/29] Implement custom errors --- firmware/foundation/src/storage/mod.rs | 14 +++++++++---- firmware/simulator/src/storage.rs | 29 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/firmware/foundation/src/storage/mod.rs b/firmware/foundation/src/storage/mod.rs index 0462f66..330f41f 100644 --- a/firmware/foundation/src/storage/mod.rs +++ b/firmware/foundation/src/storage/mod.rs @@ -3,9 +3,15 @@ use core::fmt::Debug; pub mod state; -pub trait StorageManager { - type Error: Debug; +pub enum StorageManagerLoadError { + ErrorReadingFromStorage, + NoValueStored, + ErrorDeserializingData, +} - fn load_presets(&self) -> Result; - fn save_presets(&mut self, presets: &Presets) -> Result<(), Self::Error>; +pub enum StorageManagerSaveError {} + +pub trait StorageManager { + fn load_presets(&self) -> Result; + fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError>; } diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index ffe37bc..b6abd61 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -1,5 +1,8 @@ +use foundation::storage::state::Presets; +use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use web_sys::Storage; #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] pub enum MidiCommand { @@ -141,3 +144,29 @@ impl From for foundation::storage::state::ButtonConfig { } } } + +pub struct LocalStorageManager<'a> { + local_storage: &'a mut Storage, +} + +const STORAGE_KEY_PRESETS: &str = "presets"; +const STORAGE_KEY_PRESET_ID: &str = "preset_id"; + +impl StorageManager for LocalStorageManager<'_> { + fn load_presets(&self) -> Result { + let value = self + .local_storage + .get_item(STORAGE_KEY_PRESETS) + .map_err(|_| StorageManagerLoadError::ErrorReadingFromStorage)? + .ok_or(StorageManagerLoadError::NoValueStored)?; + + let f: Presets = serde_json::from_slice(value.as_bytes()) + .map_err(|_| StorageManagerLoadError::ErrorDeserializingData)?; + let val = f.into_iter().map(|p| p.into()).collect(); + Ok(val) + } + + fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { + todo!() + } +} From 3857e1fd69e7ec2d1c681b651102a33c2ef09275 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 21:47:49 +0000 Subject: [PATCH 17/29] Map correctly --- firmware/simulator/src/storage.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index b6abd61..c680f47 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -2,6 +2,7 @@ use foundation::storage::state::Presets; use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use std::vec::Vec; use web_sys::Storage; #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] @@ -145,6 +146,21 @@ impl From for foundation::storage::state::ButtonConfig { } } +impl From for foundation::storage::state::StoredPreset { + fn from(value: Preset) -> Self { + foundation::storage::state::StoredPreset { + name: heapless::String::from_str(value.name.as_str()).unwrap(), + buttons: heapless::Vec::from_iter( + value + .buttons + .iter() + .map(|b| b.clone().into()) + .collect::>(), + ), + } + } +} + pub struct LocalStorageManager<'a> { local_storage: &'a mut Storage, } @@ -160,9 +176,9 @@ impl StorageManager for LocalStorageManager<'_> { .map_err(|_| StorageManagerLoadError::ErrorReadingFromStorage)? .ok_or(StorageManagerLoadError::NoValueStored)?; - let f: Presets = serde_json::from_slice(value.as_bytes()) + let presets: Vec = serde_json::from_slice(value.as_bytes()) .map_err(|_| StorageManagerLoadError::ErrorDeserializingData)?; - let val = f.into_iter().map(|p| p.into()).collect(); + let val = presets.into_iter().map(|p| p.into()).collect(); Ok(val) } From d6b89099737428d5ce1f1826ed5da5f1b40e0d2d Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 21:48:37 +0000 Subject: [PATCH 18/29] Private --- firmware/simulator/src/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index c680f47..c54e53e 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -6,7 +6,7 @@ use std::vec::Vec; use web_sys::Storage; #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] -pub enum MidiCommand { +enum MidiCommand { ProgramChange { channel: u8, program: u8, From 5dc96980aebc25c6343455e39d752e085dd8ab32 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 21:59:45 +0000 Subject: [PATCH 19/29] Almost implemented saving --- firmware/foundation/src/storage/mod.rs | 5 ++++- firmware/simulator/src/storage.rs | 14 ++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/firmware/foundation/src/storage/mod.rs b/firmware/foundation/src/storage/mod.rs index 330f41f..cb12742 100644 --- a/firmware/foundation/src/storage/mod.rs +++ b/firmware/foundation/src/storage/mod.rs @@ -9,7 +9,10 @@ pub enum StorageManagerLoadError { ErrorDeserializingData, } -pub enum StorageManagerSaveError {} +pub enum StorageManagerSaveError { + ErrorDeserializingData, + ErrorWritingToStorage, +} pub trait StorageManager { fn load_presets(&self) -> Result; diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index c54e53e..13e8b98 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -176,13 +176,19 @@ impl StorageManager for LocalStorageManager<'_> { .map_err(|_| StorageManagerLoadError::ErrorReadingFromStorage)? .ok_or(StorageManagerLoadError::NoValueStored)?; - let presets: Vec = serde_json::from_slice(value.as_bytes()) + let deserialized: Vec = serde_json::from_slice(value.as_bytes()) .map_err(|_| StorageManagerLoadError::ErrorDeserializingData)?; - let val = presets.into_iter().map(|p| p.into()).collect(); - Ok(val) + let mapped = deserialized.into_iter().map(|p| p.into()).collect(); + Ok(mapped) } fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { - todo!() + let mapped: Vec = presets.into_iter().map(|p| p.clone().into()).collect(); + let serialized = serde_json::to_string(&mapped) + .map_err(|_| StorageManagerSaveError::ErrorDeserializingData)?; + + self.local_storage + .set_item(STORAGE_KEY_PRESETS, serialized.as_str()) + .map_err(|_| StorageManagerSaveError::ErrorWritingToStorage) } } From 7d3f5e7525380bb1bed1a9f07d452ce3ad388730 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:07:15 +0000 Subject: [PATCH 20/29] Start to implement reverse mapping --- firmware/simulator/src/storage.rs | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index 13e8b98..35f990c 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -5,6 +5,14 @@ use std::str::FromStr; use std::vec::Vec; use web_sys::Storage; +pub trait BiMap: Sized { + /// Convert `Self` into `T` + fn to(self) -> T; + + /// Convert `T` into `Self` + fn from(t: T) -> Self; +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] enum MidiCommand { ProgramChange { @@ -161,6 +169,76 @@ impl From for foundation::storage::state::StoredPreset { } } +impl From for MidiCommand { + fn from(value: foundation::storage::state::MidiCommand) -> Self { + match value { + foundation::storage::state::MidiCommand::ProgramChange { channel, program } => { + MidiCommand::ProgramChange { channel, program } + } + foundation::storage::state::MidiCommand::ControllerChange { + channel, + controller, + value, + } => MidiCommand::ControllerChange { + channel, + controller, + value, + }, + foundation::storage::state::MidiCommand::NoteOn { + channel, + note, + velocity, + } => MidiCommand::NoteOn { + channel, + note, + velocity, + }, + foundation::storage::state::MidiCommand::NoteOff { + channel, + note, + velocity, + } => MidiCommand::NoteOff { + channel, + note, + velocity, + }, + } + } +} + +impl From for ButtonConfig { + fn from(value: foundation::storage::state::ButtonConfig) -> Self { + ButtonConfig { + name: value.name.to_string(), + button_type: value.button_type.into(), + colour: value.colour.into(), + on_actions: value + .on_actions + .into_iter() + .map(|m| m.clone().into()) + .collect(), + off_actions: value + .off_actions + .into_iter() + .map(|m| m.clone().into()) + .collect(), + } + } +} + +impl From for Preset { + fn from(value: foundation::storage::state::StoredPreset) -> Self { + Preset { + name: value.name.to_string(), + buttons: value + .buttons + .into_iter() + .map(|b| b.clone().into()) + .collect(), + } + } +} + pub struct LocalStorageManager<'a> { local_storage: &'a mut Storage, } From c54ac79fb7ff8b8eacd37cbf833a144d43dafa93 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:16:33 +0000 Subject: [PATCH 21/29] Start to swap out `From` for custom trait --- firmware/simulator/src/storage.rs | 93 +++++++++++++++---------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index 35f990c..edbbf03 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -5,12 +5,13 @@ use std::str::FromStr; use std::vec::Vec; use web_sys::Storage; -pub trait BiMap: Sized { +/// A trait for types that can be converted to and from another type `T` +pub trait Convertible: Sized { /// Convert `Self` into `T` fn to(self) -> T; /// Convert `T` into `Self` - fn from(t: T) -> Self; + fn from(value: T) -> Self; } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] @@ -78,9 +79,9 @@ impl From for foundation::storage::state::ButtonType { } } -impl From for foundation::protocol::Colour { - fn from(value: Colour) -> Self { - match value { +impl Convertible for Colour { + fn to(self) -> foundation::protocol::Colour { + match self { Colour::Red => foundation::protocol::Colour::Red, Colour::Green => foundation::protocol::Colour::Green, Colour::Blue => foundation::protocol::Colour::Blue, @@ -91,11 +92,15 @@ impl From for foundation::protocol::Colour { Colour::White => foundation::protocol::Colour::White, } } + + fn from(value: foundation::protocol::Colour) -> Self { + todo!() + } } -impl From for foundation::storage::state::MidiCommand { - fn from(value: MidiCommand) -> Self { - match value { +impl Convertible for MidiCommand { + fn to(self) -> foundation::storage::state::MidiCommand { + match self { MidiCommand::ProgramChange { channel, program } => { foundation::storage::state::MidiCommand::ProgramChange { channel, program } } @@ -128,48 +133,7 @@ impl From for foundation::storage::state::MidiCommand { }, } } -} -impl From for foundation::storage::state::ButtonConfig { - fn from(value: ButtonConfig) -> Self { - foundation::storage::state::ButtonConfig { - name: heapless::String::from_str(value.name.as_str()).unwrap(), - button_type: value.button_type.into(), - colour: value.colour.into(), - on_actions: heapless::Vec::from_iter( - value - .on_actions - .iter() - .map(|m| m.clone().into()) - .collect::>(), - ), - off_actions: heapless::Vec::from_iter( - value - .off_actions - .iter() - .map(|m| m.clone().into()) - .collect::>(), - ), - } - } -} - -impl From for foundation::storage::state::StoredPreset { - fn from(value: Preset) -> Self { - foundation::storage::state::StoredPreset { - name: heapless::String::from_str(value.name.as_str()).unwrap(), - buttons: heapless::Vec::from_iter( - value - .buttons - .iter() - .map(|b| b.clone().into()) - .collect::>(), - ), - } - } -} - -impl From for MidiCommand { fn from(value: foundation::storage::state::MidiCommand) -> Self { match value { foundation::storage::state::MidiCommand::ProgramChange { channel, program } => { @@ -206,6 +170,37 @@ impl From for MidiCommand { } } +impl From for foundation::storage::state::ButtonConfig { + fn from(value: ButtonConfig) -> Self { + foundation::storage::state::ButtonConfig { + name: heapless::String::from_str(value.name.as_str()).unwrap(), + button_type: value.button_type.into(), + colour: value.colour.into(), + on_actions: heapless::Vec::from_iter( + value.on_actions.iter().map(|m| m.to()).collect::>(), + ), + off_actions: heapless::Vec::from_iter( + value.off_actions.iter().map(|m| m.to()).collect::>(), + ), + } + } +} + +impl From for foundation::storage::state::StoredPreset { + fn from(value: Preset) -> Self { + foundation::storage::state::StoredPreset { + name: heapless::String::from_str(value.name.as_str()).unwrap(), + buttons: heapless::Vec::from_iter( + value + .buttons + .iter() + .map(|b| b.clone().into()) + .collect::>(), + ), + } + } +} + impl From for ButtonConfig { fn from(value: foundation::storage::state::ButtonConfig) -> Self { ButtonConfig { From e691badb5690884c98b35ee05be8c4e2cdfeead0 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:20:22 +0000 Subject: [PATCH 22/29] More implementing --- firmware/simulator/src/storage.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index edbbf03..2219eda 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -70,13 +70,20 @@ pub struct Preset { buttons: Vec, } -impl From for foundation::storage::state::ButtonType { - fn from(value: ButtonType) -> Self { - match value { +impl Convertible for ButtonType { + fn to(self) -> foundation::storage::state::ButtonType { + match self { ButtonType::Momentary => foundation::storage::state::ButtonType::Momentary, ButtonType::Toggle => foundation::storage::state::ButtonType::Toggle, } } + + fn from(value: foundation::storage::state::ButtonType) -> Self { + match value { + foundation::storage::state::ButtonType::Momentary => ButtonType::Momentary, + foundation::storage::state::ButtonType::Toggle => ButtonType::Toggle, + } + } } impl Convertible for Colour { @@ -94,7 +101,16 @@ impl Convertible for Colour { } fn from(value: foundation::protocol::Colour) -> Self { - todo!() + match value { + foundation::protocol::Colour::Red => Colour::Red, + foundation::protocol::Colour::Green => Colour::Green, + foundation::protocol::Colour::Blue => Colour::Blue, + foundation::protocol::Colour::Yellow => Colour::Yellow, + foundation::protocol::Colour::Orange => Colour::Orange, + foundation::protocol::Colour::Purple => Colour::Purple, + foundation::protocol::Colour::Cyan => Colour::Cyan, + foundation::protocol::Colour::White => Colour::White, + } } } @@ -174,8 +190,8 @@ impl From for foundation::storage::state::ButtonConfig { fn from(value: ButtonConfig) -> Self { foundation::storage::state::ButtonConfig { name: heapless::String::from_str(value.name.as_str()).unwrap(), - button_type: value.button_type.into(), - colour: value.colour.into(), + button_type: value.button_type.to(), + colour: value.colour.to(), on_actions: heapless::Vec::from_iter( value.on_actions.iter().map(|m| m.to()).collect::>(), ), From 022167da4381455146e986e04dff9ed63bea1162 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:25:22 +0000 Subject: [PATCH 23/29] Lots of stuff happening here --- firmware/simulator/src/storage.rs | 34 +++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index 2219eda..21ef814 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -186,20 +186,38 @@ impl Convertible for MidiCommand { } } -impl From for foundation::storage::state::ButtonConfig { - fn from(value: ButtonConfig) -> Self { +impl Convertible for ButtonConfig { + fn to(self) -> foundation::storage::state::ButtonConfig { foundation::storage::state::ButtonConfig { - name: heapless::String::from_str(value.name.as_str()).unwrap(), - button_type: value.button_type.to(), - colour: value.colour.to(), + name: heapless::String::from_str(self.name.as_str()).unwrap(), + button_type: self.button_type.to(), + colour: self.colour.to(), on_actions: heapless::Vec::from_iter( - value.on_actions.iter().map(|m| m.to()).collect::>(), + self.on_actions.iter().map(|m| m.to()).collect::>(), ), off_actions: heapless::Vec::from_iter( - value.off_actions.iter().map(|m| m.to()).collect::>(), + self.off_actions.iter().map(|m| m.to()).collect::>(), ), } } + + fn from(value: foundation::storage::state::ButtonConfig) -> Self { + ButtonConfig { + name: value.name.to_string(), + button_type: Convertible::from(value.button_type), + colour: Convertible::from(value.colour), + on_actions: value + .on_actions + .into_iter() + .map(|m| Convertible::from(m)) + .collect(), + off_actions: value + .off_actions + .into_iter() + .map(|m| Convertible::from(m)) + .collect(), + } + } } impl From for foundation::storage::state::StoredPreset { @@ -210,7 +228,7 @@ impl From for foundation::storage::state::StoredPreset { value .buttons .iter() - .map(|b| b.clone().into()) + .map(|b| b.clone().to()) .collect::>(), ), } From 86c3533341b4d58f5354ab33adc821dbe21dddd1 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:31:03 +0000 Subject: [PATCH 24/29] Finally --- firmware/foundation/src/lib.rs | 9 ++++++ firmware/simulator/src/storage.rs | 54 ++++++++----------------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/firmware/foundation/src/lib.rs b/firmware/foundation/src/lib.rs index f8f82a9..6746f02 100644 --- a/firmware/foundation/src/lib.rs +++ b/firmware/foundation/src/lib.rs @@ -12,3 +12,12 @@ pub mod layout; pub mod midi; pub mod protocol; pub mod storage; + +/// A trait for types that can be converted to and from another type `T` +pub trait Convertible: Sized { + /// Convert `Self` into `T` + fn to(self) -> T; + + /// Convert `T` into `Self` + fn from(value: T) -> Self; +} diff --git a/firmware/simulator/src/storage.rs b/firmware/simulator/src/storage.rs index 21ef814..16e2b09 100644 --- a/firmware/simulator/src/storage.rs +++ b/firmware/simulator/src/storage.rs @@ -1,19 +1,11 @@ -use foundation::storage::state::Presets; +use foundation::Convertible; +use foundation::storage::state::{Presets, StoredPreset}; use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::vec::Vec; use web_sys::Storage; -/// A trait for types that can be converted to and from another type `T` -pub trait Convertible: Sized { - /// Convert `Self` into `T` - fn to(self) -> T; - - /// Convert `T` into `Self` - fn from(value: T) -> Self; -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] enum MidiCommand { ProgramChange { @@ -220,49 +212,26 @@ impl Convertible for ButtonConfig { } } -impl From for foundation::storage::state::StoredPreset { - fn from(value: Preset) -> Self { +impl Convertible for Preset { + fn to(self) -> StoredPreset { foundation::storage::state::StoredPreset { - name: heapless::String::from_str(value.name.as_str()).unwrap(), + name: heapless::String::from_str(self.name.as_str()).unwrap(), buttons: heapless::Vec::from_iter( - value - .buttons + self.buttons .iter() .map(|b| b.clone().to()) .collect::>(), ), } } -} - -impl From for ButtonConfig { - fn from(value: foundation::storage::state::ButtonConfig) -> Self { - ButtonConfig { - name: value.name.to_string(), - button_type: value.button_type.into(), - colour: value.colour.into(), - on_actions: value - .on_actions - .into_iter() - .map(|m| m.clone().into()) - .collect(), - off_actions: value - .off_actions - .into_iter() - .map(|m| m.clone().into()) - .collect(), - } - } -} -impl From for Preset { - fn from(value: foundation::storage::state::StoredPreset) -> Self { + fn from(value: StoredPreset) -> Self { Preset { name: value.name.to_string(), buttons: value .buttons .into_iter() - .map(|b| b.clone().into()) + .map(|b| Convertible::from(b)) .collect(), } } @@ -285,12 +254,15 @@ impl StorageManager for LocalStorageManager<'_> { let deserialized: Vec = serde_json::from_slice(value.as_bytes()) .map_err(|_| StorageManagerLoadError::ErrorDeserializingData)?; - let mapped = deserialized.into_iter().map(|p| p.into()).collect(); + let mapped = deserialized.into_iter().map(|p| p.to()).collect(); Ok(mapped) } fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { - let mapped: Vec = presets.into_iter().map(|p| p.clone().into()).collect(); + let mapped: Vec = presets + .into_iter() + .map(|p| Convertible::from(p.clone())) + .collect(); let serialized = serde_json::to_string(&mapped) .map_err(|_| StorageManagerSaveError::ErrorDeserializingData)?; From affe99651355f07706ed4dae45c34af5bbcecaac Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:34:08 +0000 Subject: [PATCH 25/29] Maybe fix serde build --- firmware/foundation/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/foundation/Cargo.toml b/firmware/foundation/Cargo.toml index 18bfff4..7723cf0 100644 --- a/firmware/foundation/Cargo.toml +++ b/firmware/foundation/Cargo.toml @@ -13,4 +13,4 @@ embedded-graphics = { version = "0.8.2" } prost = { version = "0.14.3", default-features = false, features = ["derive"] } heapless = { version = "0.9.2", features = ["serde"] } embassy-sync = { version = "0.7.2", features = [] } -serde = { version = "1.0.228", features = ["derive"] } +serde = { version = "1.0.228", default-features = false, features = ["derive"] } From 34cb33f8b6cb1bbaf8403afb5ae2c30599685cb4 Mon Sep 17 00:00:00 2001 From: issy Date: Mon, 16 Mar 2026 22:36:51 +0000 Subject: [PATCH 26/29] Fix firmware build --- firmware/firmware/src/main.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index 12fc18b..e174465 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -48,8 +48,8 @@ use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart}; use esp_println::println; use foundation::application::channels; use foundation::layout::DisplayLayout; -use foundation::storage::StorageManager; use foundation::storage::state::Presets; +use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; use foundation::{ application::state::ApplicationBuilder, midi::{MidiReader, MidiWriter}, @@ -70,17 +70,12 @@ struct FakeStorageManager<'a> { phantom_data: PhantomData<&'a ()>, } -#[derive(Debug)] -struct DummyError {} - impl<'a> StorageManager for FakeStorageManager<'a> { - type Error = DummyError; - - fn load_presets(&self) -> Result { + fn load_presets(&self) -> Result { Ok(heapless::Vec::new()) } - fn save_presets(&mut self, _presets: &Presets) -> Result<(), Self::Error> { + fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { // Do nothing Ok(()) } From a49fe664c6671efde86969b7e542863bd834dcd1 Mon Sep 17 00:00:00 2001 From: issy Date: Sat, 21 Mar 2026 12:19:54 +0000 Subject: [PATCH 27/29] Some tidying --- firmware/firmware/Cargo.toml | 2 +- firmware/firmware/src/main.rs | 37 ++++--------------- firmware/firmware/src/storage.rs | 19 ++++++++++ firmware/foundation/Cargo.toml | 2 +- firmware/foundation/src/application/mod.rs | 1 + firmware/foundation/src/application/state.rs | 8 ++-- .../src/application/tasks/display.rs | 1 + .../foundation/src/application/tasks/mod.rs | 4 ++ .../src/application/tasks/storage.rs | 4 +- firmware/foundation/src/storage/mod.rs | 2 + 10 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 firmware/firmware/src/storage.rs create mode 100644 firmware/foundation/src/application/tasks/mod.rs diff --git a/firmware/firmware/Cargo.toml b/firmware/firmware/Cargo.toml index c27f080..720c194 100644 --- a/firmware/firmware/Cargo.toml +++ b/firmware/firmware/Cargo.toml @@ -2,7 +2,7 @@ name = "firmware" version = "0.1.0" edition = "2024" -rust-version = "1.88" +rust-version = "1.94.0" [[bin]] name = "firmware" diff --git a/firmware/firmware/src/main.rs b/firmware/firmware/src/main.rs index e174465..56c74ec 100644 --- a/firmware/firmware/src/main.rs +++ b/firmware/firmware/src/main.rs @@ -9,6 +9,7 @@ extern crate alloc; mod midi; +mod storage; include!(concat!(env!("OUT_DIR"), "/version.rs")); @@ -23,16 +24,15 @@ use esp_alloc as _; )] use esp_backtrace as _; +use crate::storage::FakeStorageManager; + use core::cell::RefCell; -use core::fmt::Debug; -use core::marker::PhantomData; use core::ops::Add; use core::str::FromStr; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; use embassy_executor::Spawner; use embassy_sync::blocking_mutex::{Mutex, raw::NoopRawMutex}; use embassy_time::Delay; -use embassy_time::Timer; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::mono_font::{MonoTextStyleBuilder, ascii::FONT_10X20}; use embedded_graphics::prelude::*; @@ -45,15 +45,9 @@ use esp_hal::spi::master::Spi; use esp_hal::time::Rate; use esp_hal::timer::timg::TimerGroup; use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart}; -use esp_println::println; use foundation::application::channels; +use foundation::application::state::ApplicationBuilder; use foundation::layout::DisplayLayout; -use foundation::storage::state::Presets; -use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; -use foundation::{ - application::state::ApplicationBuilder, - midi::{MidiReader, MidiWriter}, -}; use heapless::String; use log::info; use midi::{UartMidiReader, UartMidiWriter}; @@ -65,22 +59,6 @@ use mipidsi::options::{ColorInversion, Orientation}; // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); -#[derive(Default)] -struct FakeStorageManager<'a> { - phantom_data: PhantomData<&'a ()>, -} - -impl<'a> StorageManager for FakeStorageManager<'a> { - fn load_presets(&self) -> Result { - Ok(heapless::Vec::new()) - } - - fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { - // Do nothing - Ok(()) - } -} - #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" @@ -208,8 +186,7 @@ async fn main(spawner: Spawner) -> ! { ) .build(); - loop { - Timer::after_secs(5).await; - println!("Heartbeat"); - } + // Start app tasks here + + core::future::pending().await } diff --git a/firmware/firmware/src/storage.rs b/firmware/firmware/src/storage.rs new file mode 100644 index 0000000..b17f4f0 --- /dev/null +++ b/firmware/firmware/src/storage.rs @@ -0,0 +1,19 @@ +use core::marker::PhantomData; +use foundation::storage::state::Presets; +use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; + +#[derive(Default)] +pub struct FakeStorageManager<'a> { + phantom_data: PhantomData<&'a ()>, +} + +impl<'a> StorageManager for FakeStorageManager<'a> { + fn load_presets(&self) -> Result { + Ok(heapless::Vec::new()) + } + + fn save_presets(&mut self, presets: &Presets) -> Result<(), StorageManagerSaveError> { + // Do nothing + Ok(()) + } +} diff --git a/firmware/foundation/Cargo.toml b/firmware/foundation/Cargo.toml index 7723cf0..f277d76 100644 --- a/firmware/foundation/Cargo.toml +++ b/firmware/foundation/Cargo.toml @@ -2,7 +2,7 @@ name = "foundation" version = "0.1.0" edition = "2024" -rust-version = "1.88" +rust-version = "1.94.0" [lib] name = "foundation" diff --git a/firmware/foundation/src/application/mod.rs b/firmware/foundation/src/application/mod.rs index 9290e5e..6755b82 100644 --- a/firmware/foundation/src/application/mod.rs +++ b/firmware/foundation/src/application/mod.rs @@ -1,2 +1,3 @@ pub mod channels; pub mod state; +mod tasks; diff --git a/firmware/foundation/src/application/state.rs b/firmware/foundation/src/application/state.rs index 191d1c2..6590d15 100644 --- a/firmware/foundation/src/application/state.rs +++ b/firmware/foundation/src/application/state.rs @@ -7,10 +7,10 @@ use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::pixelcolor::Rgb565; struct Displays<'a, D: DrawTarget> { - display_1: &'a mut D, - display_2: &'a mut D, - display_3: &'a mut D, - display_4: &'a mut D, + pub(crate) display_1: &'a mut D, + pub(crate) display_2: &'a mut D, + pub(crate) display_3: &'a mut D, + pub(crate) display_4: &'a mut D, } struct MidiStreams<'a, MR: MidiReader, MW: MidiWriter> { diff --git a/firmware/foundation/src/application/tasks/display.rs b/firmware/foundation/src/application/tasks/display.rs index f499e84..d6bdb12 100644 --- a/firmware/foundation/src/application/tasks/display.rs +++ b/firmware/foundation/src/application/tasks/display.rs @@ -31,5 +31,6 @@ pub async fn display_task< 3 => &mut display_4_layout, _ => continue, // Invalid display index, ignore the message }; + // TODO: Update layout for display } } diff --git a/firmware/foundation/src/application/tasks/mod.rs b/firmware/foundation/src/application/tasks/mod.rs new file mode 100644 index 0000000..e997d52 --- /dev/null +++ b/firmware/foundation/src/application/tasks/mod.rs @@ -0,0 +1,4 @@ +pub mod button; +pub mod display; +pub mod midi; +pub mod storage; diff --git a/firmware/foundation/src/application/tasks/storage.rs b/firmware/foundation/src/application/tasks/storage.rs index b2fdb4f..4f716ad 100644 --- a/firmware/foundation/src/application/tasks/storage.rs +++ b/firmware/foundation/src/application/tasks/storage.rs @@ -1,9 +1,9 @@ -use crate::application::channels::{StorageStateEvent, StorageStateEventChannel}; +use crate::application::channels::{StorageStateEvent, StorageStateUpdateChannel}; use crate::storage::StorageManager; async fn storage_read_task( storage_manager: SM, - storage_state_event_channel: StorageStateEventChannel, + storage_state_event_channel: StorageStateUpdateChannel, ) -> ! { let _presets = storage_manager .load_presets() diff --git a/firmware/foundation/src/storage/mod.rs b/firmware/foundation/src/storage/mod.rs index cb12742..99e005c 100644 --- a/firmware/foundation/src/storage/mod.rs +++ b/firmware/foundation/src/storage/mod.rs @@ -3,12 +3,14 @@ use core::fmt::Debug; pub mod state; +#[derive(Debug)] pub enum StorageManagerLoadError { ErrorReadingFromStorage, NoValueStored, ErrorDeserializingData, } +#[derive(Debug)] pub enum StorageManagerSaveError { ErrorDeserializingData, ErrorWritingToStorage, From 6d82e9e01d267e1a146827019cc76940fa8b6b0d Mon Sep 17 00:00:00 2001 From: issy Date: Sat, 21 Mar 2026 12:20:27 +0000 Subject: [PATCH 28/29] Make structs crate-public --- firmware/firmware/src/storage.rs | 7 ++----- firmware/foundation/src/application/state.rs | 7 +++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/firmware/firmware/src/storage.rs b/firmware/firmware/src/storage.rs index b17f4f0..917aca1 100644 --- a/firmware/firmware/src/storage.rs +++ b/firmware/firmware/src/storage.rs @@ -1,13 +1,10 @@ -use core::marker::PhantomData; use foundation::storage::state::Presets; use foundation::storage::{StorageManager, StorageManagerLoadError, StorageManagerSaveError}; #[derive(Default)] -pub struct FakeStorageManager<'a> { - phantom_data: PhantomData<&'a ()>, -} +pub struct FakeStorageManager; -impl<'a> StorageManager for FakeStorageManager<'a> { +impl StorageManager for FakeStorageManager { fn load_presets(&self) -> Result { Ok(heapless::Vec::new()) } diff --git a/firmware/foundation/src/application/state.rs b/firmware/foundation/src/application/state.rs index 6590d15..092f1a3 100644 --- a/firmware/foundation/src/application/state.rs +++ b/firmware/foundation/src/application/state.rs @@ -6,19 +6,19 @@ use crate::storage::StorageManager; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::pixelcolor::Rgb565; -struct Displays<'a, D: DrawTarget> { +pub(crate) struct Displays<'a, D: DrawTarget> { pub(crate) display_1: &'a mut D, pub(crate) display_2: &'a mut D, pub(crate) display_3: &'a mut D, pub(crate) display_4: &'a mut D, } -struct MidiStreams<'a, MR: MidiReader, MW: MidiWriter> { +pub(crate) struct MidiStreams<'a, MR: MidiReader, MW: MidiWriter> { reader: &'a mut MR, writer: &'a mut MW, } -struct InternalChannels<'a> { +pub(crate) struct InternalChannels<'a> { midi_out: &'a mut MidiOutChannel, display_state_update: &'a mut DisplayStateUpdateChannel, storage_state_update: &'a mut StorageStateUpdateChannel, @@ -32,7 +32,6 @@ pub struct Application< MW: MidiWriter, SM: StorageManager, > { - // TODO: Make these no longer public eventually? pub(crate) displays: Displays<'a, D>, pub(crate) midi_streams: MidiStreams<'a, MR, MW>, pub(crate) channels: InternalChannels<'a>, From 2c6a765e973cf622017680cb7ece343feb793251 Mon Sep 17 00:00:00 2001 From: issy Date: Sat, 21 Mar 2026 12:20:39 +0000 Subject: [PATCH 29/29] Start to implement protocol frame parser --- firmware/foundation/src/protocol.rs | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/firmware/foundation/src/protocol.rs b/firmware/foundation/src/protocol.rs index b5fd7a8..66e8ee5 100644 --- a/firmware/foundation/src/protocol.rs +++ b/firmware/foundation/src/protocol.rs @@ -1,6 +1,9 @@ use crate::generated::device_v1 as pb; +use crate::generated::device_v1::Envelope; +use crate::midi::MidiPacket; use alloc::string::String; use alloc::vec::Vec; +use core::fmt::Debug; use serde::{Deserialize, Serialize}; const PROTOCOL_VERSION: u32 = 1; @@ -178,3 +181,143 @@ impl Message { } } } + +enum RxState { + ReadingLength, + ReadingPayload { len: usize, buf: Vec }, +} + +enum ReadError { + ErrorReadingLength, + ErrorReadingPayload, +} + +trait ProtocolReader { + /// Asynchronously read a payload, returning None if the stream ends + fn read_payload(&mut self) -> impl Future, ReadError>>; +} + +enum ParseState { + Idle, + ReadingLength, + ReadingPayload, +} + +struct ProtocolParser { + state: Option, + data: Vec, + index: usize, +} + +struct DefaultProtocolReader { + parser: ProtocolParser, +} + +impl ProtocolReader for DefaultProtocolReader { + async fn read_payload(&mut self) -> Result, ReadError> { + Ok(None) + } +} + +/// Slop + +/// CRC8 using a simple polynomial +fn crc8(data: &[u8]) -> u8 { + let mut crc = 0u8; + for &b in data { + crc ^= b; + for _ in 0..8 { + crc = if (crc & 0x80) != 0 { + crc << 1 ^ 0x07 + } else { + crc << 1 + }; + } + } + crc +} + +pub struct FrameDecoder { + state: State, + len_buf: [u8; 2], + len_pos: usize, + payload: [u8; N], + payload_pos: usize, + expected_len: usize, +} + +#[derive(Debug)] +enum State { + Sync1, + Sync2, + Len, + Payload, + Crc, +} + +impl FrameDecoder { + pub const fn new() -> Self { + Self { + state: State::Sync1, + len_buf: [0; 2], + len_pos: 0, + payload: [0; N], + payload_pos: 0, + expected_len: 0, + } + } + + /// Push a single byte + /// Returns Some(&payload) when a full valid frame is received + pub fn push(&mut self, byte: u8) -> Option<&[u8]> { + match self.state { + State::Sync1 => { + if byte == 0xAA { + self.state = State::Sync2; + } + } + State::Sync2 => { + if byte == 0x55 { + self.state = State::Len; + self.len_pos = 0; + } else { + // Stay in sync1 if second header byte fails + self.state = State::Sync1; + } + } + State::Len => { + self.len_buf[self.len_pos] = byte; + self.len_pos += 1; + if self.len_pos == 2 { + self.expected_len = u16::from_le_bytes(self.len_buf) as usize; + if self.expected_len == 0 || self.expected_len > N { + // Invalid length - resync + self.state = State::Sync1; + } else { + self.payload_pos = 0; + self.state = State::Payload; + } + } + } + State::Payload => { + self.payload[self.payload_pos] = byte; + self.payload_pos += 1; + if self.payload_pos == self.expected_len { + self.state = State::Crc; + } + } + State::Crc => { + let calc_crc = crc8(&self.payload[..self.expected_len]); + if calc_crc == byte { + // Success + self.state = State::Sync1; + return Some(&self.payload[..self.expected_len]); + } else { + // CRC failed - discard frame + self.state = State::Sync1; + } + } + } + None + } +}