fix: resolve display stuck after disconnect and radio task deadlock
- Set disconnected flag on DisplayCommand::Reset to block stale radio status updates from re-rendering dashboard over splash screen - Send StopRx to radio when USB DTR drops so radio stops generating packets after host disconnects - Use try_send for unsolicited responses (RxPacket, error recovery, StopRx Ok) to prevent radio task deadlock on full response channel - Fall back to LED-only event loop when display init fails instead of returning early and orphaning the LED Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
6 files changed
+114
-12
No files matched your search
+10
-2
@@ -131,8 +131,15 @@ pub async fn display_task(
|
||||
};
|
||||
|
||||
let Some(mut display) = crate::board::create_display(parts.i2c).await else {
|
||||
defmt::error!("display init failed, giving up");
|
||||
return;
|
||||
defmt::error!("display init failed, running LED-only loop");
|
||||
loop {
|
||||
match display_commands.receive().await {
|
||||
DisplayCommand::Reset | DisplayCommand::Off => {
|
||||
led.set_rgb(0, 0, 0).await;
|
||||
}
|
||||
DisplayCommand::On => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = DisplayState::new();
|
||||
@@ -207,6 +214,7 @@ pub async fn display_task(
|
||||
}
|
||||
DisplayCommand::Reset => {
|
||||
state = DisplayState::new();
|
||||
state.disconnected = true;
|
||||
render::splash(&mut display, &board_info);
|
||||
let _ = display.flush().await;
|
||||
led.set_rgb(0, 0, 0).await;
|
||||
|
||||
+6
-2
@@ -8,6 +8,7 @@ use embassy_usb::Builder;
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel};
|
||||
use crate::protocol::Command;
|
||||
use super::framing::{self, CobsDecoder, MAX_FRAME};
|
||||
|
||||
const MAX_PACKET: usize = 64;
|
||||
@@ -126,8 +127,11 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>(
|
||||
}
|
||||
|
||||
let connected = receiver.dtr();
|
||||
if was_connected && !connected && has_display {
|
||||
display_commands.send(DisplayCommand::Reset).await;
|
||||
if was_connected && !connected {
|
||||
let _ = commands.try_send(Command::StopRx);
|
||||
if has_display {
|
||||
display_commands.send(DisplayCommand::Reset).await;
|
||||
}
|
||||
}
|
||||
if !was_connected && connected && has_display {
|
||||
display_commands.send(DisplayCommand::On).await;
|
||||
|
||||
+11
-6
@@ -113,19 +113,22 @@ pub async fn radio_task(
|
||||
// copy_len <= MAX_PAYLOAD == Vec capacity, so this cannot fail.
|
||||
let _ = payload.extend_from_slice(&rx_buf[..copy_len]);
|
||||
|
||||
responses
|
||||
.send(Response::RxPacket {
|
||||
if responses
|
||||
.try_send(Response::RxPacket {
|
||||
rssi: pkt_status.rssi,
|
||||
snr: pkt_status.snr,
|
||||
payload,
|
||||
})
|
||||
.await;
|
||||
.is_err()
|
||||
{
|
||||
warn!("RX packet dropped: response channel full");
|
||||
}
|
||||
|
||||
if let Err(e) = start_rx(&mut lora, &cfg).await {
|
||||
warn!("restart RX failed: {}", e);
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Error(ErrorCode::RadioBusy)).await;
|
||||
let _ = responses.try_send(Response::Error(ErrorCode::RadioBusy));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -133,7 +136,7 @@ pub async fn radio_task(
|
||||
if start_rx(&mut lora, &cfg).await.is_err() {
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Error(ErrorCode::RadioBusy)).await;
|
||||
let _ = responses.try_send(Response::Error(ErrorCode::RadioBusy));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -210,7 +213,9 @@ async fn handle_cmd(
|
||||
let _ = lora.enter_standby().await;
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Ok).await;
|
||||
// try_send: StopRx may be sent internally on USB disconnect
|
||||
// when the host isn't draining responses.
|
||||
let _ = responses.try_send(Response::Ok);
|
||||
}
|
||||
Command::DisplayOn | Command::DisplayOff | Command::GetMac => {}
|
||||
Command::Transmit { config, payload } => {
|
||||
|
||||
Reference in new issue
Block a user