1// Copyright © 2025 David Haig
2// SPDX-License-Identifier: MIT
3
4use embassy_sync::channel::Channel;
5use slint::ComponentHandle;
6use slint_generated::{Globals, MainWindow};
7
8use crate::{error, warn};
9
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11#[derive(Debug, Clone)]
12pub enum Action {
13 HardwareUserBtnPressed(bool),
14 TouchscreenToggleBtn(bool),
15}
16
17#[cfg(feature = "mcu")]
18type ActionChannelType = Channel<embassy_sync::blocking_mutex::raw::ThreadModeRawMutex, Action, 2>;
19
20#[cfg(feature = "simulator")]
21type ActionChannelType =
22 Channel<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, Action, 2>;
23
24pub static ACTION: ActionChannelType = Channel::new();
25
26// see mcu::hardware or simulator::hardware modules for impl
27// depending on features used
28pub trait Hardware {
29 fn green_led_set_high(&mut self) {}
30
31 fn green_led_set_low(&mut self) {}
32}
33
34pub struct Controller<'a, Hardware> {
35 main_window: &'a MainWindow,
36 hardware: Hardware,
37}
38
39impl<'a, H> Controller<'a, H>
40where
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
89pub 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