Skip to content

Commit c16d40c

Browse files
committed
init pub sub channel state sharing
1 parent 46953d5 commit c16d40c

File tree

3 files changed

+214
-58
lines changed

3 files changed

+214
-58
lines changed

src/bin/control_panel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use desk_control_panel::meeting_duration::MeetingDuration;
1010
use desk_control_panel::meeting_instruction::{self, MeetingSignInstruction};
1111
use embassy_executor::Spawner;
12-
use embassy_time::{Duration, Ticker, Timer};
12+
use embassy_time::{Duration, Timer};
1313
use esp_backtrace as _;
1414
use esp_hal::clock::CpuClock;
1515
use esp_hal::gpio::{Input, InputConfig, Pull};

src/bin/meeting_sign.rs

Lines changed: 209 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
holding buffers for the duration of a data transfer."
77
)]
88

9-
use desk_control_panel::meeting_instruction::{self, MeetingSignInstruction};
9+
use desk_control_panel::meeting_instruction::{
10+
self, MeetingSignInstruction, UART_COMMUNICATION_TIMEOUT,
11+
};
1012
use embassy_executor::Spawner;
13+
use embassy_futures::select::{select, Either};
1114
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
1215
use embassy_sync::mutex::Mutex;
13-
use embassy_time::{Duration, Timer};
16+
use embassy_sync::pubsub::{ImmediatePublisher, PubSubChannel, Subscriber};
17+
use embassy_time::Timer;
1418
use esp_hal::{
1519
clock::CpuClock,
1620
gpio::{AnyPin, Level, Output, OutputConfig},
@@ -34,47 +38,42 @@ esp_bootloader_esp_idf::esp_app_desc!();
3438
// Global static for the panic pin
3539
static mut PANIC_PIN: Option<Output<'static>> = None;
3640

37-
type LEDsMutex = Mutex<CriticalSectionRawMutex, LEDs<'static>>;
38-
static LEDS: StaticCell<LEDsMutex> = StaticCell::new();
39-
40-
struct LEDs<'a> {
41-
led_outs: [Output<'a>; 9],
41+
#[derive(Clone)]
42+
enum MeetingSignState {
43+
NoUart,
44+
Uart,
4245
}
43-
impl<'a> LEDs<'a> {
44-
pub fn new(pins: [AnyPin<'a>; 9]) -> Self {
45-
// Convert each pin to an Output
46-
let led_outs = pins.map(|pin| Output::new(pin, Level::Low, OutputConfig::default()));
47-
48-
Self { led_outs }
49-
}
5046

51-
pub fn toggle_all(&mut self) {
52-
for led in &mut self.led_outs {
53-
led.toggle();
54-
}
55-
}
56-
57-
pub fn set_portion_high(&mut self, numerator: f32, denominator: f32) {
58-
let num_on_leds = if numerator <= 0.0 || denominator <= 0.0 {
59-
warn!(
60-
"Invalid portion values: numerator {}, denominator {}",
61-
numerator, denominator
62-
);
63-
0 // This will turn off all LEDs
64-
} else {
65-
(NUM_LEDS as f32 * numerator / denominator).round() as usize
66-
};
47+
type LEDsMutex = Mutex<CriticalSectionRawMutex, LEDs<'static>>;
48+
static LEDS: StaticCell<LEDsMutex> = StaticCell::new();
6749

68-
for (led_idx, led) in self.led_outs.iter_mut().enumerate() {
69-
// if led_idx + 1 <= num_on_leds {
70-
if led_idx < num_on_leds {
71-
led.set_high();
72-
} else {
73-
led.set_low();
74-
}
75-
}
76-
}
77-
}
50+
const STATE_PUB_SUB_CAPACITY: usize = 1;
51+
const STATE_NUM_PUBLISHERS: usize = 0;
52+
const STATE_NUM_SUBSCRIBERS: usize = 3;
53+
type MeetingSignStatePubSubChannel = PubSubChannel<
54+
CriticalSectionRawMutex,
55+
MeetingSignState,
56+
STATE_PUB_SUB_CAPACITY,
57+
STATE_NUM_SUBSCRIBERS,
58+
STATE_NUM_PUBLISHERS,
59+
>;
60+
type MeetingSignStatePublisher<'a> = ImmediatePublisher<
61+
'a,
62+
CriticalSectionRawMutex,
63+
MeetingSignState,
64+
STATE_PUB_SUB_CAPACITY,
65+
STATE_NUM_SUBSCRIBERS,
66+
STATE_NUM_PUBLISHERS,
67+
>;
68+
type MeetingSignStateSubscriber<'a> = Subscriber<
69+
'a,
70+
CriticalSectionRawMutex,
71+
MeetingSignState,
72+
STATE_PUB_SUB_CAPACITY,
73+
STATE_NUM_SUBSCRIBERS,
74+
STATE_NUM_PUBLISHERS,
75+
>;
76+
static MEETING_SIGN_STATE: StaticCell<MeetingSignStatePubSubChannel> = StaticCell::new();
7877

7978
#[esp_hal_embassy::main]
8079
async fn main(spawner: Spawner) {
@@ -97,7 +96,7 @@ async fn main(spawner: Spawner) {
9796
}
9897

9998
// WARN: These LED pins need to match the emergency hardcoded LED pins
100-
let led_pins: [AnyPin; 9] = [
99+
let led_pins: [AnyPin; NUM_LEDS] = [
101100
peripherals.GPIO5.into(),
102101
peripherals.GPIO6.into(),
103102
peripherals.GPIO7.into(),
@@ -109,7 +108,25 @@ async fn main(spawner: Spawner) {
109108
peripherals.GPIO0.into(),
110109
];
111110
let leds = LEDS.init(Mutex::new(LEDs::new(led_pins)));
112-
spawner.spawn(led_test_task(leds)).ok();
111+
112+
let meeting_sign_state = MEETING_SIGN_STATE.init(MeetingSignStatePubSubChannel::new());
113+
114+
// We only care about the latest state, so we use immediate publishers
115+
let state_publisher_1 = meeting_sign_state.immediate_publisher();
116+
let state_publisher_2 = meeting_sign_state.immediate_publisher();
117+
118+
// Initialize the state to NoUart
119+
state_publisher_1.publish_immediate(MeetingSignState::NoUart);
120+
121+
let state_subscriber_1 = meeting_sign_state
122+
.subscriber()
123+
.expect("Failed to create state subscriber 1");
124+
let state_subscriber_2 = meeting_sign_state
125+
.subscriber()
126+
.expect("Failed to create state subscriber 2");
127+
let mut state_subscriber_3 = meeting_sign_state
128+
.subscriber()
129+
.expect("Failed to create state subscriber 3");
113130

114131
let rx_pin = peripherals.GPIO1;
115132
let uart_config = Config::default().with_rx(
@@ -120,27 +137,107 @@ async fn main(spawner: Spawner) {
120137
.with_rx(rx_pin)
121138
.into_async();
122139

123-
spawner.spawn(uart_reader(uart)).ok();
124-
125-
Timer::after(Duration::from_secs(5)).await;
126-
panic!("Ahhhh");
127-
}
140+
spawner
141+
.spawn(uart_reader(uart, state_publisher_1, state_subscriber_1))
142+
.ok();
143+
spawner
144+
.spawn(uart_timeout_monitor(state_publisher_2, state_subscriber_2))
145+
.ok();
146+
147+
// Initialize hardcoded timer if no UART within threshold
148+
match select(
149+
state_subscriber_3.next_message_pure(),
150+
Timer::after(UART_COMMUNICATION_TIMEOUT),
151+
)
152+
.await
153+
{
154+
Either::First(state) => {
155+
match state {
156+
MeetingSignState::NoUart => {
157+
info!("State changed to NoUart.");
158+
// Turn off all LEDs
159+
leds.lock().await.set_portion_high(1.0, 2.0);
160+
}
161+
MeetingSignState::Uart => {
162+
info!("State changed to Uart.");
163+
// Set LEDs to indicate UART communication
164+
leds.lock().await.set_portion_high(1.0, 1.0);
165+
}
166+
}
167+
}
168+
Either::Second(_) => {
169+
info!("No initial state change detected within {}s, initializing LEDs according to hardcoded timer...",
170+
UART_COMMUNICATION_TIMEOUT.as_secs());
171+
}
172+
}
128173

129-
#[embassy_executor::task]
130-
async fn led_test_task(leds: &'static LEDsMutex) {
131-
let mut counter = 0u16;
132174
loop {
175+
match select(
176+
state_subscriber_3.next_message_pure(),
177+
Timer::after_secs(60),
178+
)
179+
.await
133180
{
134-
let mut leds = leds.lock().await;
135-
leds.toggle_all();
181+
Either::First(state) => {
182+
match state {
183+
MeetingSignState::NoUart => {
184+
info!("State changed to NoUart.");
185+
// Turn off all LEDs
186+
leds.lock().await.set_portion_high(1.0, 2.0);
187+
}
188+
MeetingSignState::Uart => {
189+
info!("State changed to Uart.");
190+
// Set LEDs to indicate UART communication
191+
leds.lock().await.set_portion_high(1.0, 1.0);
192+
}
193+
}
194+
}
195+
Either::Second(_) => {
196+
info!("No state change detected within 60 seconds, updating LEDs according to internal timer...");
197+
}
198+
}
199+
}
200+
}
201+
202+
struct LEDs<'a> {
203+
led_outs: [Output<'a>; NUM_LEDS],
204+
}
205+
impl<'a> LEDs<'a> {
206+
pub fn new(pins: [AnyPin<'a>; NUM_LEDS]) -> Self {
207+
// Convert each pin to an Output
208+
let led_outs = pins.map(|pin| Output::new(pin, Level::Low, OutputConfig::default()));
209+
210+
Self { led_outs }
211+
}
212+
213+
/// Set the portion of LEDs to high based on the given numerator and denominator.
214+
pub fn set_portion_high(&mut self, numerator: f32, denominator: f32) {
215+
let num_on_leds = if numerator <= 0.0 || denominator <= 0.0 {
216+
warn!(
217+
"Invalid portion values: numerator {}, denominator {}",
218+
numerator, denominator
219+
);
220+
0 // This will turn off all LEDs
221+
} else {
222+
(NUM_LEDS as f32 * numerator / denominator).round() as usize
223+
};
224+
225+
for (led_idx, led) in self.led_outs.iter_mut().enumerate() {
226+
if led_idx < num_on_leds {
227+
led.set_high();
228+
} else {
229+
led.set_low();
230+
}
136231
}
137-
counter = counter.wrapping_add(1);
138-
Timer::after(Duration::from_millis(500)).await;
139232
}
140233
}
141234

142235
#[embassy_executor::task]
143-
async fn uart_reader(mut uart: Uart<'static, Async>) {
236+
async fn uart_reader(
237+
mut uart: Uart<'static, Async>,
238+
state_publisher: MeetingSignStatePublisher<'static>,
239+
mut state_subscriber: MeetingSignStateSubscriber<'static>,
240+
) {
144241
debug!("Starting UART reader task");
145242

146243
// Buffers sized appropriately for the MeetingInstruction payload
@@ -167,6 +264,18 @@ async fn uart_reader(mut uart: Uart<'static, Async>) {
167264
match postcard::from_bytes::<MeetingSignInstruction>(decoded_data) {
168265
Ok(instruction) => {
169266
debug!("Received: {:?}", instruction);
267+
match state_subscriber.try_next_message_pure() {
268+
None | Some(MeetingSignState::NoUart) => {
269+
// If we were not in UART state, switch to UART
270+
debug!("Switching state to UART");
271+
state_publisher
272+
.publish_immediate(MeetingSignState::Uart);
273+
}
274+
Some(MeetingSignState::Uart) => {
275+
// Already in UART state, just log
276+
debug!("Already in UART state, processing instruction");
277+
}
278+
}
170279
}
171280
Err(e) => warn!("Deserialization error: {:?}", e),
172281
}
@@ -189,6 +298,51 @@ async fn uart_reader(mut uart: Uart<'static, Async>) {
189298
}
190299
}
191300

301+
#[embassy_executor::task]
302+
async fn uart_timeout_monitor(
303+
state_publisher: MeetingSignStatePublisher<'static>,
304+
mut state_subscriber: MeetingSignStateSubscriber<'static>,
305+
) {
306+
debug!("Starting UART timeout monitor task");
307+
308+
loop {
309+
// Wait for UART state
310+
loop {
311+
match state_subscriber.next_message_pure().await {
312+
MeetingSignState::Uart => break, // Start monitoring
313+
MeetingSignState::NoUart => continue, // Keep waiting
314+
}
315+
}
316+
317+
// Now we're in UART state, start the timeout monitoring
318+
let mut timeout_timer = Timer::after(UART_COMMUNICATION_TIMEOUT);
319+
320+
loop {
321+
match select(state_subscriber.next_message_pure(), &mut timeout_timer).await {
322+
Either::First(state) => {
323+
match state {
324+
MeetingSignState::Uart => {
325+
// New UART message received, reset the timer
326+
debug!("UART activity detected, resetting timeout");
327+
timeout_timer = Timer::after(UART_COMMUNICATION_TIMEOUT);
328+
}
329+
MeetingSignState::NoUart => {
330+
// Someone else set it to NoUART, stop monitoring
331+
debug!("State changed to NoUART, stopping timeout monitoring");
332+
break;
333+
}
334+
}
335+
}
336+
Either::Second(_) => {
337+
// Timeout occurred
338+
debug!("UART communication timed out, signaling NoUART");
339+
state_publisher.publish_immediate(MeetingSignState::NoUart);
340+
break; // Exit inner loop, will wait for next UART state
341+
}
342+
}
343+
}
344+
}
345+
}
192346
#[panic_handler]
193347
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
194348
// Disable interrupts to prevent further issues
@@ -198,8 +352,6 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! {
198352
if let Some(ref mut pin) = PANIC_PIN {
199353
pin.set_low();
200354
} else {
201-
// Fallback: directly access GPIO registers if pin wasn't initialized
202-
// This is a last resort and uses unsafe register access
203355
emergency_gpio3_low();
204356
}
205357

src/meeting_instruction.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
use crate::meeting_duration::MeetingDuration;
44
use defmt::Format;
5+
use embassy_time::Duration;
56
use serde::{Deserialize, Serialize};
67

78
// fifo_full_threshold (RX)
89
pub const READ_BUF_SIZE: usize = 64;
910
// EOT (CTRL-D)
1011
pub const AT_CMD: u8 = 0x04;
1112

13+
pub const UART_COMMUNICATION_INTERVAL: Duration = Duration::from_secs(1);
14+
pub const UART_COMMUNICATION_TIMEOUT: Duration = Duration::from_secs(4);
15+
1216
/// Validated time duration in quarter-second units for UART communication.
1317
///
1418
/// This wrapper ensures durations are properly validated before being sent over UART.

0 commit comments

Comments
 (0)