| 1 | // Copyright © 2025 David Haig |
| 2 | // SPDX-License-Identifier: MIT |
| 3 | |
| 4 | use embassy_sync::channel::Channel; |
| 5 | use slint::ComponentHandle; |
| 6 | use slint_generated::{Globals, MainWindow}; |
| 7 | |
| 8 | use crate::{error, warn}; |
| 9 | |
| 10 | #[cfg_attr (feature = "defmt" , derive(defmt::Format))] |
| 11 | #[derive (Debug, Clone)] |
| 12 | pub enum Action { |
| 13 | HardwareUserBtnPressed(bool), |
| 14 | TouchscreenToggleBtn(bool), |
| 15 | } |
| 16 | |
| 17 | #[cfg (feature = "mcu" )] |
| 18 | type ActionChannelType = Channel<embassy_sync::blocking_mutex::raw::ThreadModeRawMutex, Action, 2>; |
| 19 | |
| 20 | #[cfg (feature = "simulator" )] |
| 21 | type ActionChannelType = |
| 22 | Channel<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, Action, 2>; |
| 23 | |
| 24 | pub static ACTION: ActionChannelType = Channel::new(); |
| 25 | |
| 26 | // see mcu::hardware or simulator::hardware modules for impl |
| 27 | // depending on features used |
| 28 | pub trait Hardware { |
| 29 | fn green_led_set_high(&mut self) {} |
| 30 | |
| 31 | fn green_led_set_low(&mut self) {} |
| 32 | } |
| 33 | |
| 34 | pub struct Controller<'a, Hardware> { |
| 35 | main_window: &'a MainWindow, |
| 36 | hardware: Hardware, |
| 37 | } |
| 38 | |
| 39 | impl<'a, H> Controller<'a, H> |
| 40 | where |
| 41 | H: Hardware, |
| 42 | { |
| 43 | pub fn new(main_window: &'a MainWindow, hardware: H) -> Self { |
| 44 | Self { main_window, hardware } |
| 45 | } |
| 46 | |
| 47 | pub async fn run(&mut self) { |
| 48 | self.set_action_event_handlers(); |
| 49 | |
| 50 | loop { |
| 51 | let action = ACTION.receive().await; |
| 52 | |
| 53 | match self.process_action(action).await { |
| 54 | Ok(()) => { |
| 55 | // all good |
| 56 | } |
| 57 | Err(e) => { |
| 58 | error!("process action: {:?}" , e); |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | pub async fn process_action(&mut self, action: Action) -> Result<(), ()> { |
| 65 | let globals = self.main_window.global::<Globals>(); |
| 66 | |
| 67 | match action { |
| 68 | Action::HardwareUserBtnPressed(is_pressed) => { |
| 69 | globals.set_hardware_user_btn_pressed(is_pressed); |
| 70 | } |
| 71 | Action::TouchscreenToggleBtn(on) => { |
| 72 | if on { |
| 73 | self.hardware.green_led_set_low(); |
| 74 | } else { |
| 75 | self.hardware.green_led_set_high() |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | Ok(()) |
| 80 | } |
| 81 | |
| 82 | // user initiated action event handlers |
| 83 | fn set_action_event_handlers(&self) { |
| 84 | let globals = self.main_window.global::<Globals>(); |
| 85 | globals.on_toggle_btn(|on| send_action(Action::TouchscreenToggleBtn(on))); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | pub fn send_action(a: Action) { |
| 90 | // use non-blocking try_send here because this function needs is called from sync code (the gui callbacks) |
| 91 | match ACTION.try_send(a) { |
| 92 | Ok(_) => { |
| 93 | // see loop in `fn run()` for dequeue |
| 94 | } |
| 95 | Err(a) => { |
| 96 | // this could happen because the controller is slow to respond or we are making too many requests |
| 97 | warn!("user action queue full, could not add: {:?}" , a) |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |