Compare commits
1
Commits
main
...
3ff655c18f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ff655c18f
|
Generated
+1
@@ -312,6 +312,7 @@ dependencies = [
|
|||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
"embedded-graphics-core",
|
"embedded-graphics-core",
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
|
"embedded-hal-async",
|
||||||
"embedded-hal-bus",
|
"embedded-hal-bus",
|
||||||
"embedded-io 0.7.1",
|
"embedded-io 0.7.1",
|
||||||
"esp-alloc",
|
"esp-alloc",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pn532 = "0.5.0"
|
|||||||
ndef = "0.5.0"
|
ndef = "0.5.0"
|
||||||
num_enum = { version = "0.7.6", default-features = false }
|
num_enum = { version = "0.7.6", default-features = false }
|
||||||
embassy-sync = "0.8.0"
|
embassy-sync = "0.8.0"
|
||||||
|
embedded-hal-async = "1.0.0"
|
||||||
|
|
||||||
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
|
# For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
+6
-6
@@ -75,12 +75,12 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
let button_config = InputConfig::default().with_pull(Pull::Up);
|
let button_config = InputConfig::default().with_pull(Pull::Up);
|
||||||
let inputs = Inputs {
|
let inputs = Inputs {
|
||||||
up: Input::new(peripherals.GPIO38, button_config.clone()),
|
up: Input::new(peripherals.GPIO38, button_config),
|
||||||
down: Input::new(peripherals.GPIO39, button_config.clone()),
|
down: Input::new(peripherals.GPIO39, button_config),
|
||||||
left: Input::new(peripherals.GPIO40, button_config.clone()),
|
left: Input::new(peripherals.GPIO40, button_config),
|
||||||
right: Input::new(peripherals.GPIO41, button_config.clone()),
|
right: Input::new(peripherals.GPIO41, button_config),
|
||||||
back: Input::new(peripherals.GPIO42, button_config.clone()),
|
back: Input::new(peripherals.GPIO42, button_config),
|
||||||
ok: Input::new(peripherals.GPIO45, button_config.clone()),
|
ok: Input::new(peripherals.GPIO45, button_config),
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Setup complete, entering main loop");
|
log::info!("Setup complete, entering main loop");
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
use embassy_futures::select::select_array;
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
use embedded_hal::digital::InputPin;
|
||||||
|
use embedded_hal_async::digital::Wait;
|
||||||
use esp_hal::gpio::Input;
|
use esp_hal::gpio::Input;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -16,3 +20,62 @@ pub struct Inputs {
|
|||||||
pub back: Input<'static>,
|
pub back: Input<'static>,
|
||||||
pub ok: Input<'static>,
|
pub ok: Input<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ButtonAction {
|
||||||
|
Up = 0,
|
||||||
|
Down = 1,
|
||||||
|
Left = 2,
|
||||||
|
Right = 3,
|
||||||
|
Ok = 4,
|
||||||
|
Back = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inputs {
|
||||||
|
pub async fn wait_for_press(&mut self) -> ButtonAction {
|
||||||
|
let (_, index) = select_array([
|
||||||
|
wait_for_button(&mut self.up),
|
||||||
|
wait_for_button(&mut self.down),
|
||||||
|
wait_for_button(&mut self.left),
|
||||||
|
wait_for_button(&mut self.right),
|
||||||
|
wait_for_button(&mut self.ok),
|
||||||
|
wait_for_button(&mut self.back),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match index {
|
||||||
|
0 => ButtonAction::Up,
|
||||||
|
1 => ButtonAction::Down,
|
||||||
|
2 => ButtonAction::Back,
|
||||||
|
3 => ButtonAction::Ok,
|
||||||
|
4 => ButtonAction::Ok,
|
||||||
|
5 => ButtonAction::Back,
|
||||||
|
id => panic!("Unknown button {id} pressed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for a button press, filtering out fast transients and mechanical bounce.
|
||||||
|
async fn wait_for_button<P>(pin: &mut P)
|
||||||
|
where
|
||||||
|
P: Wait + InputPin,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
// 1. Wait for the hardware interrupt (could be a real press or crosstalk)
|
||||||
|
let _ = pin.wait_for_falling_edge().await;
|
||||||
|
|
||||||
|
// 2. Debounce window: let the noise settle
|
||||||
|
Timer::after(Duration::from_millis(30)).await;
|
||||||
|
|
||||||
|
// 3. Verify the pin is still held down
|
||||||
|
// (Adapt unwrap() based on your specific HAL's Error type)
|
||||||
|
if pin.is_low().unwrap_or(false) {
|
||||||
|
// OPTIONAL: Wait for the button to be released before returning
|
||||||
|
// so holding it down doesn't spam triggers.
|
||||||
|
// let _ = pin.wait_for_rising_edge().await;
|
||||||
|
// Timer::after(Duration::from_millis(30)).await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use crate::card::model::Card;
|
use crate::card::model::Card;
|
||||||
|
use crate::navigation::inputs::ButtonAction;
|
||||||
use crate::navigation::state::NavigationState;
|
use crate::navigation::state::NavigationState;
|
||||||
use crate::views::view::View;
|
use crate::views::view::View;
|
||||||
use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs};
|
use crate::{display::primary_lcd::PrimaryLcdDisplay, navigation::inputs::Inputs};
|
||||||
|
|
||||||
use embassy_futures::select::{Either, Either6, select};
|
use embassy_futures::select::{Either, select};
|
||||||
use embassy_futures::select::select6;
|
|
||||||
use embassy_time::{Duration, Timer};
|
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
|
use embassy_time::{Duration, Timer};
|
||||||
|
|
||||||
pub type Mutex<T> = embassy_sync::mutex::Mutex<CriticalSectionRawMutex, T>;
|
pub type Mutex<T> = embassy_sync::mutex::Mutex<CriticalSectionRawMutex, T>;
|
||||||
pub type Signal<T> = embassy_sync::signal::Signal<CriticalSectionRawMutex, T>;
|
pub type Signal<T> = embassy_sync::signal::Signal<CriticalSectionRawMutex, T>;
|
||||||
@@ -16,12 +15,7 @@ const DEBOUNCE_DURATION_MILLIS: u64 = 100;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Up,
|
Button(ButtonAction),
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Ok,
|
|
||||||
Back,
|
|
||||||
Timer,
|
Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,39 +34,29 @@ pub trait Navigable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'static>, ) {
|
pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'static>) {
|
||||||
log::info!("starting navigation");
|
log::info!("starting navigation");
|
||||||
|
|
||||||
let mut state = NavigationState::new();
|
let mut state = NavigationState::new();
|
||||||
state.screens.last().unwrap().display(display).await;
|
state.screens.last().unwrap().display(display).await;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let selection_buttons = select6(
|
let selection = select(
|
||||||
inputs.up.wait_for_low(),
|
inputs.wait_for_press(),
|
||||||
inputs.down.wait_for_low(),
|
Timer::after(Duration::from_millis(DEBOUNCE_DURATION_MILLIS)),
|
||||||
inputs.left.wait_for_low(),
|
)
|
||||||
inputs.right.wait_for_low(),
|
.await;
|
||||||
inputs.ok.wait_for_low(),
|
|
||||||
inputs.back.wait_for_low(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let selection = select(selection_buttons, Timer::after(Duration::from_millis(DEBOUNCE_DURATION_MILLIS))).await;
|
|
||||||
|
|
||||||
let action = match selection {
|
let action = match selection {
|
||||||
Either::First(selection_buttons) => match selection_buttons {
|
Either::First(button) => Action::Button(button),
|
||||||
Either6::First(_) => Action::Up,
|
|
||||||
Either6::Second(_) => Action::Down,
|
|
||||||
Either6::Third(_) => Action::Back,
|
|
||||||
Either6::Fourth(_) => Action::Ok,
|
|
||||||
Either6::Fifth(_) => Action::Ok,
|
|
||||||
Either6::Sixth(_) => Action::Back,
|
|
||||||
},
|
|
||||||
Either::Second(_) => Action::Timer,
|
Either::Second(_) => Action::Timer,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("Action: {:?}", action);
|
if Action::Timer != action {
|
||||||
|
log::info!("Action: {:?}", action);
|
||||||
|
}
|
||||||
|
|
||||||
let new_state = if action == Action::Back {
|
let new_state = if action == Action::Button(ButtonAction::Back) {
|
||||||
if state.screens.len() > 1 {
|
if state.screens.len() > 1 {
|
||||||
state.screens.pop();
|
state.screens.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-37
@@ -1,10 +1,10 @@
|
|||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use core::future;
|
|
||||||
|
|
||||||
use crate::card::model::Card;
|
use crate::card::model::Card;
|
||||||
use crate::card::{decoder::split_nfc_hex, mock::*};
|
use crate::card::{decoder::split_nfc_hex, mock::*};
|
||||||
use crate::display::primary_lcd::PrimaryLcdDisplay;
|
use crate::display::primary_lcd::PrimaryLcdDisplay;
|
||||||
|
|
||||||
|
use crate::navigation::inputs::ButtonAction;
|
||||||
use crate::views::card_view::CardView;
|
use crate::views::card_view::CardView;
|
||||||
use crate::views::journal_view::JournalView;
|
use crate::views::journal_view::JournalView;
|
||||||
use crate::views::{menu_item, settings_menu::SettingsMenu, view::View};
|
use crate::views::{menu_item, settings_menu::SettingsMenu, view::View};
|
||||||
@@ -37,43 +37,54 @@ impl Navigable for MainMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
|
fn handle_input(&self, input: Action) -> impl core::future::Future<Output = NewState> + Send {
|
||||||
let card_mock1 = Card::try_from(split_nfc_hex(&MOCK_PAYLOAD3).unwrap()).unwrap();
|
async move {
|
||||||
let card_mock2 = Card::try_from(split_nfc_hex(&MOCK_PAYLOAD2).unwrap()).unwrap();
|
let Action::Button(input) = input else {
|
||||||
|
// Skip timer inputs
|
||||||
let new_menu = match input {
|
return NewState {
|
||||||
Action::Up => {
|
view: View::Main(self.clone()),
|
||||||
let new_selected = if (self.selected - 1) < 0 {
|
replace_view: true,
|
||||||
MAX_SELECTED - 1
|
redraw: false,
|
||||||
} else {
|
|
||||||
self.selected - 1
|
|
||||||
};
|
};
|
||||||
View::Main(MainMenu {
|
};
|
||||||
selected: new_selected,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Action::Down => {
|
|
||||||
let new_selected = self.selected.wrapping_add(1) % MAX_SELECTED;
|
|
||||||
View::Main(MainMenu {
|
|
||||||
selected: new_selected,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Action::Ok => match self.selected {
|
|
||||||
0 => View::Scan(crate::views::scan_menu::ScanMenu { progress: 0 }),
|
|
||||||
1 => View::Card(CardView { card: card_mock1 }),
|
|
||||||
2 => View::Journal(JournalView {
|
|
||||||
cards: vec![card_mock1, card_mock2],
|
|
||||||
selected_card: 0,
|
|
||||||
}),
|
|
||||||
3 => View::Settings(SettingsMenu { selected: 0 }),
|
|
||||||
_ => View::Main(self.clone()),
|
|
||||||
},
|
|
||||||
_ => View::Main(self.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
future::ready(NewState {
|
let card_mock1 = Card::try_from(split_nfc_hex(&MOCK_PAYLOAD3).unwrap()).unwrap();
|
||||||
replace_view: matches!(new_menu, View::Main(_)),
|
let card_mock2 = Card::try_from(split_nfc_hex(&MOCK_PAYLOAD2).unwrap()).unwrap();
|
||||||
view: new_menu,
|
|
||||||
redraw: !matches!(input, Action::Timer),
|
let new_menu = match input {
|
||||||
})
|
ButtonAction::Up => {
|
||||||
|
let new_selected = if (self.selected - 1) < 0 {
|
||||||
|
MAX_SELECTED - 1
|
||||||
|
} else {
|
||||||
|
self.selected - 1
|
||||||
|
};
|
||||||
|
View::Main(MainMenu {
|
||||||
|
selected: new_selected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ButtonAction::Down => {
|
||||||
|
let new_selected = self.selected.wrapping_add(1) % MAX_SELECTED;
|
||||||
|
View::Main(MainMenu {
|
||||||
|
selected: new_selected,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ButtonAction::Ok => match self.selected {
|
||||||
|
0 => View::Scan(crate::views::scan_menu::ScanMenu { progress: 0 }),
|
||||||
|
1 => View::Card(CardView { card: card_mock1 }),
|
||||||
|
2 => View::Journal(JournalView {
|
||||||
|
cards: vec![card_mock1, card_mock2],
|
||||||
|
selected_card: 0,
|
||||||
|
}),
|
||||||
|
3 => View::Settings(SettingsMenu { selected: 0 }),
|
||||||
|
_ => View::Main(self.clone()),
|
||||||
|
},
|
||||||
|
_ => View::Main(self.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
NewState {
|
||||||
|
replace_view: matches!(new_menu, View::Main(_)),
|
||||||
|
view: new_menu,
|
||||||
|
redraw: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::display::primary_lcd::PrimaryLcdDisplay;
|
use crate::display::primary_lcd::PrimaryLcdDisplay;
|
||||||
|
use crate::navigation::inputs::ButtonAction;
|
||||||
use crate::navigation::navigation::CARD_DATA;
|
use crate::navigation::navigation::CARD_DATA;
|
||||||
use crate::navigation::navigation::NewState;
|
use crate::navigation::navigation::NewState;
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ impl Navigable for ScanMenu {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let new_progress = match input {
|
let new_progress = match input {
|
||||||
Action::Up => self.progress.wrapping_add(5),
|
Action::Button(ButtonAction::Up) => self.progress.wrapping_add(5),
|
||||||
_ => self.progress,
|
_ => self.progress,
|
||||||
};
|
};
|
||||||
NewState {
|
NewState {
|
||||||
@@ -104,7 +105,7 @@ impl Navigable for ScanMenu {
|
|||||||
progress: new_progress,
|
progress: new_progress,
|
||||||
}),
|
}),
|
||||||
replace_view: true,
|
replace_view: true,
|
||||||
redraw: matches!(input, Action::Up | Action::Timer),
|
redraw: matches!(input, Action::Button(ButtonAction::Up) | Action::Timer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user