From f237bcaf9c901b13124d07b3ccf756c96579860d Mon Sep 17 00:00:00 2001 From: Stephen D <webmaster@scd31.com> Date: Sun, 30 Jun 2024 15:49:41 -0300 Subject: [PATCH] message storage --- Cargo.lock | 7 + Cargo.toml | 1 + src/controller.rs | 63 +++++---- src/gui/chat.rs | 43 +++--- src/gui/chat_list.rs | 48 ++++--- src/gui/textbox.rs | 16 ++- src/main.rs | 1 - src/message.rs | 90 ------------- src/storage/contact.rs | 7 +- src/storage/message.rs | 296 +++++++++++++++++++++++++++++++++++++++++ src/storage/mod.rs | 54 ++++++-- 11 files changed, 454 insertions(+), 172 deletions(-) delete mode 100644 src/message.rs create mode 100644 src/storage/message.rs diff --git a/Cargo.lock b/Cargo.lock index 81ceba8..f4f3f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,7 @@ dependencies = [ "rp2040-hal", "rtic-monotonics", "rtt-target", + "sectorize", "usb-device 0.2.9", "usbd-serial", ] @@ -755,6 +756,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sectorize" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad334105ac17abdd7ba69b0e76658de21fc02b2a093ae5135b1e3e378ccffd3" + [[package]] name = "semver" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 4a48507..83b14ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ panic-rtt-target = "0.1.3" embedded-text = "0.7.1" crc = "3.2.1" rp2040-flash = "0.5.0" +sectorize = "0.1.0" diff --git a/src/controller.rs b/src/controller.rs index 2bf76ae..ca76689 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -46,10 +46,10 @@ impl View { Self::ContactOptions(opts, contact_id) } - pub fn from_main_menu_id(id: usize, contacts: &ContactIter) -> Option<Self> { + pub fn from_main_menu_id(id: usize, contacts: &ContactIter, storage: &Storage) -> Option<Self> { match id { 0 => Some(Self::new_contacts(0, contacts)), - 1 => Some(Self::ChatList(ChatList::new(0))), + 1 => Some(Self::ChatList(ChatList::new(storage, 0))), _ => None, } } @@ -73,8 +73,7 @@ impl View { 0 => { let contact = contacts .nth(contact_id) - .unwrap_or_else(|| storage.new_contact()) - .clone(); + .unwrap_or_else(|| storage.new_contact()); Some(View::ContactView( ContactView::new(contact, false), contact_id, @@ -85,8 +84,7 @@ impl View { 1 => { let contact = contacts .nth(contact_id) - .unwrap_or_else(|| storage.new_contact()) - .clone(); + .unwrap_or_else(|| storage.new_contact()); Some(View::ContactView( ContactView::new(contact, true), contact_id, @@ -94,35 +92,34 @@ impl View { )) } - _ => None, - } - } -} + 2 => { + let contact = contacts + .nth(contact_id) + .unwrap_or_else(|| storage.new_contact()); -impl Default for View { - fn default() -> Self { - Self::new_main_menu(0) - } -} + Some(View::Chat(Chat::new(contact.callsign, contact.ssid), 0)) + } -impl Element for View { - type KeyPushReturn = (); + _ => unreachable!(), + } + } fn render<E, DT: DrawTarget<Color = Rgb565, Error = E>>( &mut self, dt: &mut DT, + storage: &Storage, ) -> Result<(), E> { match self { View::MainMenu(m) => m.render(dt), View::Contacts(c) => c.render(dt), View::ContactOptions(c, _) => c.render(dt), View::ContactView(c, _, _) => c.render(dt), - View::ChatList(c) => c.render(dt), - View::Chat(c, _) => c.render(dt), + View::ChatList(c) => c.render(dt, storage), + View::Chat(c, _) => c.render(dt, storage), } } - fn key_push(&mut self, k: KeyCode) { + fn key_push(&mut self, k: KeyCode, storage: &mut Storage) { match self { View::MainMenu(m) => m.key_push(k), View::Contacts(c) => c.key_push(k), @@ -131,7 +128,7 @@ impl Element for View { c.key_push(k); } View::ChatList(c) => c.key_push(k), - View::Chat(c, _) => c.key_push(k), + View::Chat(c, _) => c.key_push(k, storage), } } @@ -142,11 +139,17 @@ impl Element for View { View::ContactOptions(c, _) => c.touchpad_scroll(x, y), View::ContactView(c, _, _) => c.touchpad_scroll(x, y), View::ChatList(c) => c.touchpad_scroll(x, y), - View::Chat(c, _) => c.touchpad_scroll(x, y), + View::Chat(_c, _) => {} // c.touchpad_scroll(x, y), } } } +impl Default for View { + fn default() -> Self { + Self::new_main_menu(0) + } +} + pub struct Controller { status: StatusBar, cur_view: View, @@ -176,7 +179,7 @@ impl Element for Controller { self.status.render(dt)?; - self.cur_view.render(dt)?; + self.cur_view.render(dt, &self.storage)?; Ok(()) } @@ -185,7 +188,7 @@ impl Element for Controller { match (&mut self.cur_view, k) { (View::MainMenu(m), KeyCode::Touchpad) => { if let Some(new_view) = - View::from_main_menu_id(m.selected(), &self.storage.contacts()) + View::from_main_menu_id(m.selected(), &self.storage.contacts(), &self.storage) { self.cur_view = new_view; } @@ -210,7 +213,13 @@ impl Element for Controller { } (View::ChatList(cl), KeyCode::Touchpad) => { - self.cur_view = View::Chat(Chat::new(), cl.selected()); + let (callsign, ssid) = self + .storage + .message_addressees() + .nth(cl.selected()) + .unwrap(); + + self.cur_view = View::Chat(Chat::new(callsign, ssid), cl.selected()); } (View::MainMenu(_), KeyCode::Back) => {} @@ -227,7 +236,7 @@ impl Element for Controller { } (View::Chat(_, selected_id), KeyCode::Back) => { - self.cur_view = View::ChatList(ChatList::new(*selected_id)); + self.cur_view = View::ChatList(ChatList::new(&self.storage, *selected_id)); } (_, KeyCode::Back) => { @@ -243,7 +252,7 @@ impl Element for Controller { } } - (_, k) => self.cur_view.key_push(k), + (_, k) => self.cur_view.key_push(k, &mut self.storage), } } diff --git a/src/gui/chat.rs b/src/gui/chat.rs index c4e791c..0a378bd 100644 --- a/src/gui/chat.rs +++ b/src/gui/chat.rs @@ -7,10 +7,13 @@ use embedded_graphics::{ Drawable, }; use embedded_text::{style::TextBoxStyleBuilder, TextBox}; +use heapless::String; +use rtt_target::rdbg; use crate::{ app::WIDTH, - message::{MessageDirection, MessageGroup}, + keyboard::KeyCode, + storage::{Message, MessageDirection, Storage, MAX_CONTACT_CALLSIGN_LENGTH}, }; use super::{textbox::TextBox as MyTextBox, Element}; @@ -19,23 +22,21 @@ const MESSAGE_BOX_WIDTH: u32 = 200; const MESSAGE_BOX_HEIGHT: u32 = 64; pub struct Chat { - message_group: MessageGroup, + callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>, + ssid: u8, textbox: MyTextBox, } impl Chat { - pub fn new() -> Self { + pub fn new(callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>, ssid: u8) -> Self { Self { - message_group: MessageGroup::default(), + callsign, + ssid, textbox: MyTextBox::new(1, 218, 31, 1), } } -} - -impl Element for Chat { - type KeyPushReturn = (); - fn render< + pub fn render< E, DT: embedded_graphics::prelude::DrawTarget< Color = embedded_graphics::pixelcolor::Rgb565, @@ -44,6 +45,7 @@ impl Element for Chat { >( &mut self, dt: &mut DT, + storage: &Storage, ) -> Result<(), E> { let textbox_style = TextBoxStyleBuilder::new().build(); let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::BLACK); @@ -52,17 +54,17 @@ impl Element for Chat { .build(); let mut y = 28; - for msg in self.message_group.iter() { + for msg in storage.messages(&self.callsign, self.ssid) { let bounds = Rectangle::new( Point::new(4, y), Size::new(MESSAGE_BOX_WIDTH, MESSAGE_BOX_HEIGHT), ); let mut text_box = - TextBox::with_textbox_style(msg.content, bounds, character_style, textbox_style); + TextBox::with_textbox_style(msg.content(), bounds, character_style, textbox_style); let mut actual_bounds = calculate_bounding_box(&text_box); - if msg.direction == MessageDirection::To { + if msg.direction() == MessageDirection::To { let x = i32::try_from(WIDTH).unwrap() - i32::try_from(actual_bounds.size.width).unwrap() - 4; @@ -83,8 +85,16 @@ impl Element for Chat { Ok(()) } - fn key_push(&mut self, k: crate::keyboard::KeyCode) { - self.textbox.key_push(k); + pub fn key_push(&mut self, k: KeyCode, storage: &mut Storage) { + if k == KeyCode::Enter { + let text = self.textbox.clear(); + let message = + Message::new(self.callsign.clone(), self.ssid, text, MessageDirection::To); + + storage.push_message(message); + } else { + self.textbox.key_push(k); + } } } @@ -101,7 +111,10 @@ fn calculate_bounding_box<S: TextRenderer>(tb: &TextBox<'_, S>) -> Rectangle { y += 1; } else { x += 1; - max_x = x; + + if x > max_x { + max_x = x; + } } } diff --git a/src/gui/chat_list.rs b/src/gui/chat_list.rs index bae3605..a3741d9 100644 --- a/src/gui/chat_list.rs +++ b/src/gui/chat_list.rs @@ -10,12 +10,9 @@ use embedded_graphics::{ }; use heapless::String; -use crate::{app::WIDTH, message::MessageGroup}; +use crate::{app::WIDTH, storage::Storage}; -use super::{ - scroll_tracker::{ScrollAction, ScrollTracker}, - Element, -}; +use super::scroll_tracker::{ScrollAction, ScrollTracker}; const STATUS_BAR_HEIGHT: i32 = 40; const GROUP_HEIGHT: i32 = 40; @@ -24,33 +21,27 @@ const BACKGROUND_COLOR: Rgb565 = Rgb565::new(31, 41, 0); // #FFA500 const BORDER_COLOR: Rgb565 = Rgb565::new(31, 26, 0); // #FF6600 pub struct ChatList { - groups: [MessageGroup; 3], tracker: ScrollTracker, selected: usize, + len: usize, } impl ChatList { - pub fn new(selected: usize) -> Self { + pub fn new(storage: &Storage, selected: usize) -> Self { + let len = storage.message_addressees().count(); + Self { - groups: [ - MessageGroup::default(), - MessageGroup::default(), - MessageGroup::default(), - ], tracker: ScrollTracker::new(), selected, + len, } } pub fn selected(&self) -> usize { self.selected } -} - -impl Element for ChatList { - type KeyPushReturn = (); - fn render< + pub fn render< E, DT: embedded_graphics::prelude::DrawTarget< Color = embedded_graphics::pixelcolor::Rgb565, @@ -59,6 +50,7 @@ impl Element for ChatList { >( &mut self, dt: &mut DT, + storage: &Storage, ) -> Result<(), E> { let unselected_rect_style = PrimitiveStyleBuilder::new() .stroke_color(BORDER_COLOR) @@ -72,7 +64,7 @@ impl Element for ChatList { .fill_color(Rgb565::WHITE) .build(); - for (idx, group) in self.groups.iter().enumerate() { + for (idx, (callsign, ssid)) in storage.message_addressees().enumerate() { let i = i32::try_from(idx).unwrap(); let x = 0; @@ -104,8 +96,20 @@ impl Element for ChatList { .text_color(Rgb565::BLACK) .build(); + let name = storage + .contacts() + .find(|c| c.callsign == callsign && c.ssid == ssid) + .map(|c| c.name); + let mut text: String<25> = String::new(); - write!(&mut text, "{}", group).unwrap(); + match name { + Some(x) => { + text = String::try_from(x.as_str()).unwrap(); + } + None => { + write!(&mut text, "{}-{}", callsign, ssid).unwrap(); + } + }; Text::with_baseline(&text, Point::new(text_x, text_y), text_style, Baseline::Top) .draw(dt)?; } @@ -113,9 +117,9 @@ impl Element for ChatList { Ok(()) } - fn key_push(&mut self, _: crate::keyboard::KeyCode) {} + pub fn key_push(&mut self, _: crate::keyboard::KeyCode) {} - fn touchpad_scroll(&mut self, _x: i8, y: i8) { + pub fn touchpad_scroll(&mut self, _x: i8, y: i8) { match self.tracker.scroll(y) { ScrollAction::Previous => { if self.selected > 0 { @@ -123,7 +127,7 @@ impl Element for ChatList { } } ScrollAction::Next => { - if self.selected < self.groups.len() - 1 { + if self.selected < self.len - 1 { self.selected += 1; } } diff --git a/src/gui/textbox.rs b/src/gui/textbox.rs index 9f1e506..e989fbf 100644 --- a/src/gui/textbox.rs +++ b/src/gui/textbox.rs @@ -1,3 +1,5 @@ +use core::mem; + use embedded_graphics::{ draw_target::DrawTarget, geometry::{Point, Size}, @@ -18,6 +20,7 @@ const BACKGROUND_COLOR: Rgb565 = Rgb565::new(0xFF, 0xFF, 0xFF); pub const BORDER_WIDTH: u32 = 3; const CHAR_WIDTH: u32 = 10; const CHAR_HEIGHT: u32 = 20; +const MAX_TEXT_LENGTH: usize = 500; pub struct TextBox { // in # of chars @@ -32,7 +35,7 @@ pub struct TextBox { x: i32, y: i32, - text: String<32>, + text: String<MAX_TEXT_LENGTH>, pub selected: bool, } @@ -77,6 +80,17 @@ impl TextBox { &self.text } + pub fn clear(&mut self) -> String<MAX_TEXT_LENGTH> { + let mut other = String::new(); + mem::swap(&mut self.text, &mut other); + + self.cursor_x = 0; + self.cursor_y = 0; + self.cursor_i = 0; + + other + } + fn width(&self) -> u32 { BORDER_WIDTH * 2 + CHAR_WIDTH * self.char_width } diff --git a/src/main.rs b/src/main.rs index eb5bcff..1e52d22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ mod controller; mod drivers; mod gui; mod keyboard; -mod message; mod storage; mod touchpad; mod voltage; diff --git a/src/message.rs b/src/message.rs deleted file mode 100644 index 111ac81..0000000 --- a/src/message.rs +++ /dev/null @@ -1,90 +0,0 @@ -use core::fmt::Display; - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum MessageDirection { - From, - To, -} - -#[derive(Copy, Clone)] -pub struct Message { - pub content: &'static str, - pub direction: MessageDirection, -} - -// A single column in the message list -// TODO rename this to MessageIdentity? May be more straightforward -pub struct MessageGroup { - callsign: &'static str, - ssid: u8, -} - -impl MessageGroup { - pub fn iter(&self) -> MessageGroupIter { - MessageGroupIter::new(self) - } -} - -impl Display for MessageGroup { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // TODO will need to somehow look up the actual contact name - write!(f, "{}-{}", self.callsign, self.ssid) - } -} - -impl Default for MessageGroup { - // Stubbing for now - fn default() -> Self { - Self { - callsign: "VA3QAK", - ssid: 3, - } - } -} - -pub struct MessageGroupIter<'a> { - group: &'a MessageGroup, - i: usize, -} - -impl<'a> MessageGroupIter<'a> { - pub fn new(group: &'a MessageGroup) -> Self { - Self { group, i: 0 } - } -} - -impl<'a> Iterator for MessageGroupIter<'a> { - type Item = Message; - - fn next(&mut self) -> Option<Self::Item> { - // Stubbing for now - let stubbed = [ - Message { - content: "Hello", - direction: MessageDirection::From, - }, - Message { - content: "Hi", - direction: MessageDirection::To, - }, - Message { - content: "How are you?", - direction: MessageDirection::From, - }, - Message { - content: "I'm good", - direction: MessageDirection::To, - }, - ]; - - if self.i >= stubbed.len() { - None - } else { - let out = stubbed[self.i]; - - self.i += 1; - - Some(out) - } - } -} diff --git a/src/storage/contact.rs b/src/storage/contact.rs index ce8fd81..ae8f865 100644 --- a/src/storage/contact.rs +++ b/src/storage/contact.rs @@ -6,14 +6,15 @@ use crate::storage::DATA_START; use super::{Storage, X25}; -const CONTACT_HEADER_START: usize = DATA_START; -const CONTACT_HEADER_SIZE: usize = 3; +pub const CONTACT_HEADER_START: usize = DATA_START; +pub const CONTACT_HEADER_SIZE: usize = 3; pub const MAX_CONTACTS: usize = 100; pub const MAX_CONTACT_NAME_LENGTH: usize = 20; pub const MAX_CONTACT_CALLSIGN_LENGTH: usize = 10; // 1 byte for each length, 1 byte for SSID, 2 bytes for checksum -const CONTACT_LENGTH: usize = MAX_CONTACT_NAME_LENGTH + 1 + 1 + MAX_CONTACT_CALLSIGN_LENGTH + 1 + 2; +pub const CONTACT_LENGTH: usize = + MAX_CONTACT_NAME_LENGTH + 1 + 1 + MAX_CONTACT_CALLSIGN_LENGTH + 1 + 2; #[derive(Copy, Clone)] pub struct ContactGroup { diff --git a/src/storage/message.rs b/src/storage/message.rs new file mode 100644 index 0000000..a0ba89b --- /dev/null +++ b/src/storage/message.rs @@ -0,0 +1,296 @@ +use core::slice; + +use heapless::{FnvIndexSet, String, Vec}; + +use super::{ + contact::{CONTACT_HEADER_SIZE, CONTACT_HEADER_START, CONTACT_LENGTH}, + Storage, MAX_CONTACTS, MAX_CONTACT_CALLSIGN_LENGTH, X25, +}; + +const MESSAGE_HEADER_START: usize = + CONTACT_HEADER_START + CONTACT_HEADER_SIZE + MAX_CONTACTS * CONTACT_LENGTH; +// Includes 2 byte checksum +const MESSAGE_HEADER_SIZE: usize = 6; +const MAX_MESSAGE_BODY_LENGTH: usize = 500; +// 1 byte callsign length +// 2 byte message length +// 1 byte SSID +// 1 byte direction + received +// 1 byte send attempts +// 2 byte checksum +const MESSAGE_LENGTH: usize = + 1 + 2 + 1 + 1 + 1 + MAX_MESSAGE_BODY_LENGTH + MAX_CONTACT_CALLSIGN_LENGTH + 2; +const MAX_MESSAGES: usize = 1024; + +#[derive(Copy, Clone)] +pub struct MessageGroup { + header: MessageHeader, +} + +impl MessageGroup { + pub fn new() -> Self { + Self { + header: MessageHeader::read(), + } + } + + pub fn push_message(&mut self, storage: &mut Storage, message: Message) { + if usize::from(self.header.len) == MAX_MESSAGES { + // overwrite the first message + message.write(storage, self.header.start.into()); + self.header.start += 1; + self.header.start %= self.header.len; + } else { + // append to end + message.write(storage, self.header.len.into()); + self.header.len += 1; + } + + self.header.write(storage); + } + + pub fn iter<'a, 'b, 'c>( + &'a self, + storage: &'b Storage, + callsign: &'c str, + ssid: u8, + ) -> MessageIter<'a, 'b, 'c> { + MessageIter::new(self, storage, callsign, ssid) + } + + pub fn iter_addressees<'a, 'b>(&'a self, storage: &'b Storage) -> AddresseeIter<'a, 'b> { + AddresseeIter::new(self, storage) + } +} + +#[derive(Copy, Clone)] +pub struct MessageHeader { + start: u16, + len: u16, +} + +impl MessageHeader { + fn read() -> Self { + let bytes = + unsafe { slice::from_raw_parts(MESSAGE_HEADER_START as *mut u8, MESSAGE_HEADER_SIZE) }; + let start = u16::from_le_bytes([bytes[0], bytes[1]]); + let len = u16::from_le_bytes([bytes[2], bytes[3]]); + let checksum_expected = u16::from_le_bytes([bytes[4], bytes[5]]); + let checksum_actual = X25.checksum(&bytes[0..4]); + + if checksum_expected == checksum_actual { + Self { start, len } + } else { + Self { start: 0, len: 0 } + } + } + + fn write(&self, storage: &mut Storage) { + let mut buf = [0; MESSAGE_HEADER_SIZE]; + buf[0..2].copy_from_slice(&self.start.to_le_bytes()); + buf[2..4].copy_from_slice(&self.len.to_le_bytes()); + let checksum = X25.checksum(&buf[0..4]); + buf[4..].copy_from_slice(&checksum.to_le_bytes()); + + storage.write_data(MESSAGE_HEADER_START, &buf); + } +} + +pub struct Message { + // Storing the entire callsign/ssid per message is kind of a waste + // but it's simple. We can fix it later if it becomes an issue + callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>, + ssid: u8, + message: String<MAX_MESSAGE_BODY_LENGTH>, + direction: MessageDirection, + received: bool, + send_attempts: u8, +} + +impl Message { + pub fn new( + callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>, + ssid: u8, + message: String<MAX_MESSAGE_BODY_LENGTH>, + direction: MessageDirection, + ) -> Self { + Self { + callsign, + ssid, + message, + direction, + received: false, + send_attempts: 0, + } + } + + pub fn content(&self) -> &str { + &self.message + } + + pub fn direction(&self) -> MessageDirection { + self.direction + } + + fn read(storage: &Storage, idx: usize) -> Option<Self> { + let start_addr = MESSAGE_HEADER_START + MESSAGE_HEADER_SIZE + idx * MESSAGE_LENGTH; + + let bytes = storage.read_slice::<MESSAGE_LENGTH>(start_addr); + let checksum_expected = + u16::from_le_bytes([bytes[MESSAGE_LENGTH - 2], bytes[MESSAGE_LENGTH - 1]]); + let checksum_actual = X25.checksum(&bytes[..(MESSAGE_LENGTH - 2)]); + + if checksum_expected != checksum_actual { + return None; + } + + let callsign_length: usize = bytes[0].into(); + let message_length: usize = u16::from_le_bytes([bytes[1], bytes[2]]).into(); + let ssid = bytes[3]; + let direction = MessageDirection::from(bytes[4] & 0x01 > 0); + let received = bytes[4] & 0x02 > 0; + let send_attempts = bytes[5]; + let callsign_bytes = &bytes[6..][..callsign_length]; + let message_bytes = &bytes[(6 + MAX_CONTACT_CALLSIGN_LENGTH)..][..message_length]; + + let callsign = String::from_utf8(Vec::from_slice(callsign_bytes).ok()?).ok()?; + let message = String::from_utf8(Vec::from_slice(message_bytes).ok()?).ok()?; + + Some(Self { + callsign, + ssid, + message, + direction, + received, + send_attempts, + }) + } + + fn write(&self, storage: &mut Storage, idx: usize) { + let start_addr = MESSAGE_HEADER_START + MESSAGE_HEADER_SIZE + idx * MESSAGE_LENGTH; + + let callsign_bytes = self.callsign.as_bytes(); + let message_bytes = self.message.as_bytes(); + + let mut buf = [0; MESSAGE_LENGTH]; + buf[0] = callsign_bytes.len().try_into().unwrap(); + buf[1..3].copy_from_slice(&u16::try_from(self.message.len()).unwrap().to_le_bytes()); + buf[3] = self.ssid; + buf[4] = (u8::from(self.received) << 1) | u8::from(bool::from(self.direction)); + buf[5] = self.send_attempts; + buf[6..][..callsign_bytes.len()].copy_from_slice(callsign_bytes); + buf[(6 + MAX_CONTACT_CALLSIGN_LENGTH)..][..(message_bytes.len())] + .copy_from_slice(message_bytes); + + let checksum = X25.checksum(&buf[..(MESSAGE_LENGTH - 2)]); + buf[(MESSAGE_LENGTH - 2)..].copy_from_slice(&checksum.to_le_bytes()); + + storage.write_data(start_addr, &buf); + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum MessageDirection { + From, + To, +} + +impl From<bool> for MessageDirection { + fn from(value: bool) -> Self { + if value { + MessageDirection::From + } else { + MessageDirection::To + } + } +} + +impl From<MessageDirection> for bool { + fn from(value: MessageDirection) -> Self { + match value { + MessageDirection::From => true, + MessageDirection::To => false, + } + } +} + +#[derive(Clone)] +pub struct MessageIter<'a, 'b, 'c> { + group: &'a MessageGroup, + storage: &'b Storage, + callsign: &'c str, + ssid: u8, + i: u16, +} + +impl<'a, 'b, 'c> MessageIter<'a, 'b, 'c> { + pub fn new(group: &'a MessageGroup, storage: &'b Storage, callsign: &'c str, ssid: u8) -> Self { + Self { + group, + storage, + callsign, + ssid, + i: 0, + } + } +} + +impl<'a, 'b, 'c> Iterator for MessageIter<'a, 'b, 'c> { + type Item = Message; + + fn next(&mut self) -> Option<Self::Item> { + while self.i < self.group.header.len { + let idx = (self.i + self.group.header.start) % self.group.header.len; + self.i += 1; + + if let Some(msg) = Message::read(self.storage, idx.into()) { + if msg.callsign == self.callsign && msg.ssid == self.ssid { + return Some(msg); + } + } + } + + None + } +} + +// Orders from newest to oldest +pub struct AddresseeIter<'a, 'b> { + group: &'a MessageGroup, + storage: &'b Storage, + i: u16, + seen: FnvIndexSet<(String<MAX_CONTACT_CALLSIGN_LENGTH>, u8), MAX_MESSAGES>, +} + +impl<'a, 'b> AddresseeIter<'a, 'b> { + pub fn new(group: &'a MessageGroup, storage: &'b Storage) -> Self { + Self { + group, + storage, + i: group.header.len, + seen: FnvIndexSet::new(), + } + } +} + +impl<'a, 'b> Iterator for AddresseeIter<'a, 'b> { + type Item = (String<MAX_CONTACT_CALLSIGN_LENGTH>, u8); + + fn next(&mut self) -> Option<Self::Item> { + while self.i > 0 { + self.i -= 1; + + let idx = (self.i + self.group.header.start) % self.group.header.len; + if let Some(msg) = Message::read(self.storage, idx.into()) { + let ident = (msg.callsign, msg.ssid); + if !self.seen.contains(&ident) { + self.seen.insert(ident.clone()).unwrap(); + + return Some(ident); + } + } + } + + None + } +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 0b628ea..12e2c1b 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,13 +1,18 @@ mod contact; +mod message; use contact::ContactGroup; pub use contact::{ Contact, ContactIter, MAX_CONTACTS, MAX_CONTACT_CALLSIGN_LENGTH, MAX_CONTACT_NAME_LENGTH, }; +pub use message::{Message, MessageDirection, MessageGroup, MessageIter}; +use sectorize::SectorizeIter; use core::slice::from_raw_parts; use rp2040_flash::flash; +use self::message::AddresseeIter; + const SECTOR_SIZE: usize = 4096; const FLASH_START: usize = 0x10000000; // First 6MB is for code @@ -19,12 +24,14 @@ const X25: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_IBM_SDLC); pub struct Storage { contacts: ContactGroup, + messages: MessageGroup, } impl Storage { pub fn new() -> Self { Self { contacts: ContactGroup::new(), + messages: MessageGroup::new(), } } @@ -54,21 +61,42 @@ impl Storage { self.contacts.len() } + // Punch through our message methods + + pub fn push_message(&mut self, message: Message) { + let mut messages = self.messages; + messages.push_message(self, message); + self.messages = messages; + } + + pub fn messages<'a, 'c>(&'a self, callsign: &'c str, ssid: u8) -> MessageIter<'a, 'a, 'c> { + self.messages.iter(self, callsign, ssid) + } + + pub fn message_addressees(&self) -> AddresseeIter { + self.messages.iter_addressees(self) + } + fn write_data(&mut self, start_addr: usize, buf: &[u8]) { - let start_sector = start_addr / SECTOR_SIZE; - let num_sectors = buf.len() / SECTOR_SIZE + 1; - let diff = start_addr % SECTOR_SIZE; - - for s in 0..num_sectors { - let existing_start = if s == 0 { diff } else { 0 }; - let existing_end = (diff + (s + 1) * SECTOR_SIZE).min(existing_start + buf.len()); - let mut existing = self.read_slice((start_sector + s) * SECTOR_SIZE); - let buf_start = (s * SECTOR_SIZE).min(buf.len()); - let buf_end = (buf_start + SECTOR_SIZE).min(buf.len()); + // rprintln!("Write {:x?} to {:x}", buf, start_addr); + + for s in SectorizeIter::new(SECTOR_SIZE, start_addr, buf.len()) { + let mut existing = self.read_slice(s.sector_index * SECTOR_SIZE); + + /* rprintln!( + "Sector {}: {}..{} <- {}..{}", + s.sector_index, + s.sector_start, + s.sector_end, + s.input_start, + s.input_end + ); */ + // only write if there's a difference - if existing[existing_start..existing_end] != buf[buf_start..buf_end] { - existing[existing_start..existing_end].copy_from_slice(&buf[buf_start..buf_end]); - self.write_sector((start_sector + s) * SECTOR_SIZE, &existing); + if existing[s.sector_start..s.sector_end] != buf[s.input_start..s.input_end] { + existing[s.sector_start..s.sector_end] + .copy_from_slice(&buf[s.input_start..s.input_end]); + self.write_sector(s.sector_index * SECTOR_SIZE, &existing); } } } -- GitLab