diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
index c0b541d..4cfdc36 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
@@ -358,6 +358,8 @@
Decoders
+
+
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/background-decode.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/background-decode.js
index 752933c..5318fd1 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/background-decode.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/background-decode.js
@@ -5,7 +5,7 @@
(function () {
"use strict";
- const SUPPORTED_DECODERS = ["ft8", "wspr", "hf-aprs"];
+ const SUPPORTED_DECODERS = ["aprs", "ais", "ft8", "wspr", "hf-aprs"];
let backgroundDecodeRole = null;
let currentRigId = null;
@@ -303,6 +303,7 @@
case "no_supported_decoders": return "Unsupported";
case "disabled": return "Disabled";
case "handled_by_scheduler": return "Scheduler";
+ case "handled_by_virtual_channel": return "VChan";
default: return "Inactive";
}
}
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js
index 358d431..cfb4148 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/bookmarks.js
@@ -148,6 +148,8 @@ function bmRender(list) {
// Read decoder checkboxes and return an array of selected decoder names.
function bmReadDecoders() {
const decoders = [];
+ if (document.getElementById("bm-dec-aprs").checked) decoders.push("aprs");
+ if (document.getElementById("bm-dec-ais").checked) decoders.push("ais");
if (document.getElementById("bm-dec-ft8").checked) decoders.push("ft8");
if (document.getElementById("bm-dec-wspr").checked) decoders.push("wspr");
if (document.getElementById("bm-dec-hf-aprs").checked) decoders.push("hf-aprs");
@@ -157,6 +159,8 @@ function bmReadDecoders() {
// Set decoder checkboxes to match the given array.
function bmWriteDecoders(decoders) {
const list = decoders || [];
+ document.getElementById("bm-dec-aprs").checked = list.includes("aprs");
+ document.getElementById("bm-dec-ais").checked = list.includes("ais");
document.getElementById("bm-dec-ft8").checked = list.includes("ft8");
document.getElementById("bm-dec-wspr").checked = list.includes("wspr");
document.getElementById("bm-dec-hf-aprs").checked = list.includes("hf-aprs");
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
index 3a952c5..02d6c4c 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
+++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css
@@ -3559,7 +3559,8 @@ button:focus-visible, input:focus-visible, select:focus-visible {
.bgd-status-state[data-state="waiting_for_spectrum"],
.bgd-status-state[data-state="waiting_for_user"],
.bgd-status-state[data-state="inactive"],
-.bgd-status-state[data-state="handled_by_scheduler"] {
+.bgd-status-state[data-state="handled_by_scheduler"],
+.bgd-status-state[data-state="handled_by_virtual_channel"] {
color: var(--accent-yellow);
}
.bgd-status-state[data-state="missing_bookmark"],
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/background_decode.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/background_decode.rs
index 8063012..7d6eada 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/src/background_decode.rs
+++ b/src/trx-client/trx-frontend/trx-frontend-http/src/background_decode.rs
@@ -19,9 +19,11 @@ use uuid::Uuid;
use crate::server::bookmarks::{Bookmark, BookmarkStore};
use crate::server::scheduler::SchedulerStatusMap;
+use crate::server::vchan::{ClientChannel, ClientChannelManager};
-const SUPPORTED_DECODER_KINDS: &[&str] = &["ft8", "wspr", "hf-aprs"];
+const SUPPORTED_DECODER_KINDS: &[&str] = &["aprs", "ais", "ft8", "wspr", "hf-aprs"];
const CHANNEL_KIND_NAME: &str = "VirtualBackgroundDecodeChannel";
+const VISIBLE_CHANNEL_KIND_NAME: &str = "VirtualChannel";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BackgroundDecodeConfig {
@@ -122,6 +124,7 @@ pub struct BackgroundDecodeManager {
bookmarks: Arc,
context: Arc,
scheduler_status: SchedulerStatusMap,
+ vchan_mgr: Arc,
status: Arc>>,
notify_tx: broadcast::Sender<()>,
}
@@ -132,6 +135,7 @@ impl BackgroundDecodeManager {
bookmarks: Arc,
context: Arc,
scheduler_status: SchedulerStatusMap,
+ vchan_mgr: Arc,
) -> Arc {
let (notify_tx, _) = broadcast::channel(16);
Arc::new(Self {
@@ -139,6 +143,7 @@ impl BackgroundDecodeManager {
bookmarks,
context,
scheduler_status,
+ vchan_mgr,
status: Arc::new(RwLock::new(HashMap::new())),
notify_tx,
})
@@ -280,6 +285,13 @@ impl BackgroundDecodeManager {
&& channel.decoder_kinds == desired.decoder_kinds
}
+ fn virtual_channels_cover_bookmark(&self, rig_id: &str, bookmark: &Bookmark) -> bool {
+ self.vchan_mgr
+ .channels(rig_id)
+ .into_iter()
+ .any(|channel| channel_matches_bookmark(&channel, bookmark))
+ }
+
fn reconcile(&self, runtime: &mut BackgroundRuntimeState, spectrum: &SharedSpectrum) {
let active_rig_id = self.active_rig_id();
@@ -367,6 +379,13 @@ impl BackgroundDecodeManager {
continue;
}
+ if self.virtual_channels_cover_bookmark(&rig_id, bookmark) {
+ status.state = "handled_by_virtual_channel".to_string();
+ status.channel_kind = Some(VISIBLE_CHANNEL_KIND_NAME.to_string());
+ statuses.push(status);
+ continue;
+ }
+
let (Some(center_hz), Some(half_span_hz)) = (center_hz, half_span_hz) else {
status.state = "waiting_for_spectrum".to_string();
statuses.push(status);
@@ -506,6 +525,14 @@ fn supported_decoder_kinds(decoders: &[String]) -> Vec {
out
}
+fn channel_matches_bookmark(channel: &ClientChannel, bookmark: &Bookmark) -> bool {
+ channel.freq_hz == bookmark.freq_hz && normalized_mode(&channel.mode) == normalized_mode(&bookmark.mode)
+}
+
+fn normalized_mode(mode: &str) -> String {
+ mode.trim().to_ascii_lowercase()
+}
+
#[get("/background-decode/{rig_id}")]
pub async fn get_background_decode(
path: web::Path,
diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/server.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/server.rs
index d3be641..97b3c37 100644
--- a/src/trx-client/trx-frontend/trx-frontend-http/src/server.rs
+++ b/src/trx-client/trx-frontend/trx-frontend-http/src/server.rs
@@ -88,16 +88,16 @@ async fn serve(
let background_decode_path = BackgroundDecodeStore::default_path();
let background_decode_store =
Arc::new(BackgroundDecodeStore::open(&background_decode_path));
+ let vchan_mgr = Arc::new(ClientChannelManager::new(4));
let background_decode_mgr = BackgroundDecodeManager::new(
background_decode_store,
bookmark_store.clone(),
context.clone(),
scheduler_status.clone(),
+ vchan_mgr.clone(),
);
background_decode_mgr.spawn();
- let vchan_mgr = Arc::new(ClientChannelManager::new(4));
-
// Wire the audio-command sender so allocate/delete/freq/mode operations on
// virtual channels are forwarded to the audio-client task.
if let Ok(guard) = context.vchan_audio_cmd.lock() {