[fix](trx-frontend-http): start APRS on scheduler PKT channels

Carry bookmark decoder kinds through visible scheduler virtual channels so PKT/APRS scheduler entries start their decode workers instead of acting as audio-only channels.

Verification: cargo test -p trx-frontend-http vchan
Verification: cargo test -p trx-client (fails in existing config::tests::test_default_config)

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-14 13:54:44 +01:00
parent 5590ac5c7d
commit c1e8ce42d2
5 changed files with 98 additions and 22 deletions
+7 -1
View File
@@ -29,7 +29,13 @@ pub enum VChanAudioCmd {
/// Create the server-side DSP channel (if it does not exist) and subscribe
/// to its Opus audio stream. `freq_hz` and `mode` are used if the server
/// needs to create the channel.
Subscribe { uuid: Uuid, freq_hz: u64, mode: String },
Subscribe {
uuid: Uuid,
freq_hz: u64,
mode: String,
bandwidth_hz: u32,
decoder_kinds: Vec<String>,
},
/// Create a hidden server-side DSP channel for background decoding.
/// These channels are not enumerated as user-visible virtual channels and
/// do not request an Opus audio stream back to the frontend.
@@ -402,8 +402,9 @@ fn sync_scheduler_vchannels(
(
bookmark_id.clone(),
bookmark.freq_hz,
bookmark.mode,
bookmark.mode.clone(),
bookmark.bandwidth_hz.unwrap_or(0) as u32,
bookmark_decoder_kinds(&bookmark),
)
})
})
@@ -1740,6 +1741,33 @@ fn bookmark_decoder_state(
(want_aprs, want_hf_aprs, want_ft8, want_wspr)
}
fn bookmark_decoder_kinds(bookmark: &crate::server::bookmarks::Bookmark) -> Vec<String> {
let mut out = Vec::new();
for decoder in bookmark
.decoders
.iter()
.map(|item| item.trim().to_ascii_lowercase())
{
if matches!(
decoder.as_str(),
"aprs" | "ais" | "ft8" | "wspr" | "hf-aprs"
) && !out.iter().any(|existing| existing == &decoder)
{
out.push(decoder);
}
}
if !out.is_empty() {
return out;
}
match bookmark.mode.trim().to_ascii_uppercase().as_str() {
"AIS" => vec!["ais".to_string()],
"PKT" => vec!["aprs".to_string()],
_ => Vec::new(),
}
}
async fn apply_selected_channel(
rig_tx: &mpsc::Sender<RigRequest>,
rig_id: &str,
@@ -85,6 +85,7 @@ struct InternalChannel {
mode: String,
/// Audio filter bandwidth in Hz (0 = mode default).
bandwidth_hz: u32,
decoder_kinds: Vec<String>,
permanent: bool,
scheduler_bookmark_id: Option<String>,
/// Session UUIDs currently subscribed to this channel.
@@ -169,6 +170,7 @@ impl ClientChannelManager {
freq_hz,
mode: mode.to_string(),
bandwidth_hz: 0,
decoder_kinds: Vec::new(),
permanent: true,
scheduler_bookmark_id: None,
session_ids: Vec::new(),
@@ -237,6 +239,7 @@ impl ClientChannelManager {
freq_hz,
mode: mode.to_string(),
bandwidth_hz: 0,
decoder_kinds: Vec::new(),
permanent: false,
scheduler_bookmark_id: None,
session_ids: vec![session_id],
@@ -266,6 +269,8 @@ impl ClientChannelManager {
uuid: id,
freq_hz,
mode: mode.to_string(),
bandwidth_hz: 0,
decoder_kinds: Vec::new(),
});
Ok(snapshot)
@@ -515,7 +520,7 @@ impl ClientChannelManager {
pub fn sync_scheduler_channels(
&self,
rig_id: &str,
desired: &[(String, u64, String, u32)],
desired: &[(String, u64, String, u32, Vec<String>)],
) {
let mut rigs = self.rigs.write().unwrap();
let Some(channels) = rigs.get_mut(rig_id) else {
@@ -523,10 +528,13 @@ impl ClientChannelManager {
};
let mut changed = false;
let desired_map: HashMap<String, (u64, String, u32)> = desired
let desired_map: HashMap<String, (u64, String, u32, Vec<String>)> = desired
.iter()
.map(|(bookmark_id, freq_hz, mode, bandwidth_hz)| {
(bookmark_id.clone(), (*freq_hz, mode.clone(), *bandwidth_hz))
.map(|(bookmark_id, freq_hz, mode, bandwidth_hz, decoder_kinds)| {
(
bookmark_id.clone(),
(*freq_hz, mode.clone(), *bandwidth_hz, decoder_kinds.clone()),
)
})
.collect();
let desired_ids: std::collections::HashSet<&str> =
@@ -553,7 +561,7 @@ impl ClientChannelManager {
let Some(bookmark_id) = channel.scheduler_bookmark_id.as_deref() else {
continue;
};
let Some((freq_hz, mode, bandwidth_hz)) = desired_map.get(bookmark_id) else {
let Some((freq_hz, mode, bandwidth_hz, decoder_kinds)) = desired_map.get(bookmark_id) else {
continue;
};
if channel.freq_hz != *freq_hz {
@@ -580,9 +588,20 @@ impl ClientChannelManager {
});
changed = true;
}
if channel.decoder_kinds != *decoder_kinds {
channel.decoder_kinds = decoder_kinds.clone();
self.send_audio_cmd(VChanAudioCmd::Subscribe {
uuid: channel.id,
freq_hz: channel.freq_hz,
mode: channel.mode.clone(),
bandwidth_hz: channel.bandwidth_hz,
decoder_kinds: channel.decoder_kinds.clone(),
});
changed = true;
}
}
for (bookmark_id, freq_hz, mode, bandwidth_hz) in desired {
for (bookmark_id, freq_hz, mode, bandwidth_hz, decoder_kinds) in desired {
let exists = channels.iter().any(|channel| {
channel.scheduler_bookmark_id.as_deref() == Some(bookmark_id.as_str())
});
@@ -598,6 +617,7 @@ impl ClientChannelManager {
freq_hz: *freq_hz,
mode: mode.clone(),
bandwidth_hz: *bandwidth_hz,
decoder_kinds: decoder_kinds.clone(),
permanent: false,
scheduler_bookmark_id: Some(bookmark_id.clone()),
session_ids: Vec::new(),
@@ -606,13 +626,9 @@ impl ClientChannelManager {
uuid: channel_id,
freq_hz: *freq_hz,
mode: mode.clone(),
bandwidth_hz: *bandwidth_hz,
decoder_kinds: decoder_kinds.clone(),
});
if *bandwidth_hz > 0 {
self.send_audio_cmd(VChanAudioCmd::SetBandwidth {
uuid: channel_id,
bandwidth_hz: *bandwidth_hz,
});
}
changed = true;
}
@@ -655,7 +671,13 @@ mod tests {
mgr.init_rig(rig_id, 14_074_000, "USB");
mgr.sync_scheduler_channels(
rig_id,
&[("bm-ft8".to_string(), 14_074_000, "DIG".to_string(), 3_000)],
&[(
"bm-ft8".to_string(),
14_074_000,
"DIG".to_string(),
3_000,
vec!["ft8".to_string()],
)],
);
let channels = mgr.channels(rig_id);
@@ -679,7 +701,13 @@ mod tests {
.expect("allocate vchan");
mgr.sync_scheduler_channels(
rig_id,
&[("bm-ft8".to_string(), 14_074_000, "DIG".to_string(), 3_000)],
&[(
"bm-ft8".to_string(),
14_074_000,
"DIG".to_string(),
3_000,
vec!["ft8".to_string()],
)],
);
mgr.release_session(session_id);
@@ -699,7 +727,13 @@ mod tests {
mgr.init_rig(rig_id, 14_074_000, "USB");
mgr.sync_scheduler_channels(
rig_id,
&[("bm-aprs".to_string(), 144_800_000, "PKT".to_string(), 12_500)],
&[(
"bm-aprs".to_string(),
144_800_000,
"PKT".to_string(),
12_500,
vec!["aprs".to_string()],
)],
);
let channel_id = mgr.channels(rig_id)[1].id;