[feat](trx-client): configure spectrum guard tuning

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-02 20:00:50 +01:00
parent 5ed55c6103
commit 50f8c12487
5 changed files with 75 additions and 6 deletions
@@ -1167,7 +1167,8 @@ function effectiveSpectrumCoverageSpanHz(sampleRateHz) {
const sampleRate = Number(sampleRateHz);
if (!Number.isFinite(sampleRate) || sampleRate <= 0) return 0;
// Keep a guard band at the spectrum edges; practical usable span is slightly smaller.
return sampleRate * 1.0;
const ratio = Number.isFinite(spectrumUsableSpanRatio) ? spectrumUsableSpanRatio : 1.0;
return sampleRate * Math.max(0.01, Math.min(1.0, ratio));
}
function requiredCenterFreqForCoverageInFrame(data, freqHz, bandwidthHz = coverageGuardBandwidthHz()) {
@@ -1180,7 +1181,7 @@ function requiredCenterFreqForCoverageInFrame(data, freqHz, bandwidthHz = covera
const safeBw = Math.max(0, Number.isFinite(bandwidthHz) ? bandwidthHz : 0);
const halfSpanHz = sampleRate / 2;
const requiredHalfSpanHz = safeBw / 2 + SPECTRUM_COVERAGE_MARGIN_HZ;
const requiredHalfSpanHz = safeBw / 2 + spectrumCoverageMarginHz;
if (requiredHalfSpanHz * 2 >= sampleRate) {
return alignFreqToRigStep(Math.round(freqHz));
}
@@ -1258,7 +1259,7 @@ function sweetSpotCandidateForFrame(data, freqHz, bandwidthHz) {
const halfUsableSpanHz = usableSpanHz / 2;
const fullHalfSpanHz = sampleRate / 2;
const guardHalfSpanHz = bandwidthHz / 2 + SPECTRUM_COVERAGE_MARGIN_HZ;
const guardHalfSpanHz = bandwidthHz / 2 + spectrumCoverageMarginHz;
if (guardHalfSpanHz * 2 >= usableSpanHz) {
const fallbackCenterHz = requiredCenterFreqForCoverageInFrame(data, freqHz, bandwidthHz);
if (!Number.isFinite(fallbackCenterHz)) return null;
@@ -1346,7 +1347,7 @@ function sweetSpotProbeCenters(data, freqHz, bandwidthHz) {
if (!Number.isFinite(usableSpanHz) || usableSpanHz <= 0) return [];
const halfUsableSpanHz = usableSpanHz / 2;
const guardHalfSpanHz = bandwidthHz / 2 + SPECTRUM_COVERAGE_MARGIN_HZ;
const guardHalfSpanHz = bandwidthHz / 2 + spectrumCoverageMarginHz;
if (guardHalfSpanHz * 2 >= usableSpanHz) {
return [alignFreqToRigStep(Math.round(freqHz))];
}
@@ -1446,7 +1447,7 @@ function tunedFrequencyForCenterCoverage(centerHz, freqHz = lastFreqHz, bandwidt
const safeBw = Math.max(0, Number.isFinite(bandwidthHz) ? bandwidthHz : 0);
const halfSpanHz = sampleRate / 2;
const requiredHalfSpanHz = safeBw / 2 + SPECTRUM_COVERAGE_MARGIN_HZ;
const requiredHalfSpanHz = safeBw / 2 + spectrumCoverageMarginHz;
if (requiredHalfSpanHz * 2 >= sampleRate) {
return alignFreqToRigStep(Math.round(centerHz));
}
@@ -1650,7 +1651,8 @@ let serverActiveRigId = null;
let serverLat = null;
let serverLon = null;
let initialMapZoom = 10;
const SPECTRUM_COVERAGE_MARGIN_HZ = 50_000;
let spectrumCoverageMarginHz = 50_000;
let spectrumUsableSpanRatio = 1.0;
function updateFooterBuildInfo() {
const serverEl = document.getElementById("footer-server-build");
@@ -1702,6 +1704,18 @@ function render(update) {
if (typeof update.initial_map_zoom === "number" && Number.isFinite(update.initial_map_zoom)) {
initialMapZoom = Math.max(1, Math.round(update.initial_map_zoom));
}
if (
typeof update.spectrum_coverage_margin_hz === "number" &&
Number.isFinite(update.spectrum_coverage_margin_hz)
) {
spectrumCoverageMarginHz = Math.max(1, Math.round(update.spectrum_coverage_margin_hz));
}
if (
typeof update.spectrum_usable_span_ratio === "number" &&
Number.isFinite(update.spectrum_usable_span_ratio)
) {
spectrumUsableSpanRatio = Math.max(0.01, Math.min(1.0, Number(update.spectrum_usable_span_ratio)));
}
updateTitle();
updateFooterBuildInfo();
@@ -41,6 +41,8 @@ struct FrontendMeta {
owner_website_name: Option<String>,
show_sdr_gain_control: bool,
initial_map_zoom: u8,
spectrum_coverage_margin_hz: u32,
spectrum_usable_span_ratio: f32,
}
#[get("/status")]
@@ -99,6 +101,14 @@ fn inject_frontend_meta(json: &str, meta: FrontendMeta) -> String {
"initial_map_zoom".to_string(),
serde_json::json!(meta.initial_map_zoom),
);
map.insert(
"spectrum_coverage_margin_hz".to_string(),
serde_json::json!(meta.spectrum_coverage_margin_hz),
);
map.insert(
"spectrum_usable_span_ratio".to_string(),
serde_json::json!(meta.spectrum_usable_span_ratio),
);
serde_json::to_string(&value).unwrap_or_else(|_| json.to_string())
}
@@ -118,6 +128,8 @@ fn frontend_meta_from_context(
owner_website_name: owner_website_name_from_context(context),
show_sdr_gain_control: show_sdr_gain_control_from_context(context),
initial_map_zoom: initial_map_zoom_from_context(context),
spectrum_coverage_margin_hz: spectrum_coverage_margin_hz_from_context(context),
spectrum_usable_span_ratio: spectrum_usable_span_ratio_from_context(context),
}
}
@@ -167,6 +179,14 @@ fn initial_map_zoom_from_context(context: &FrontendRuntimeContext) -> u8 {
context.http_initial_map_zoom
}
fn spectrum_coverage_margin_hz_from_context(context: &FrontendRuntimeContext) -> u32 {
context.http_spectrum_coverage_margin_hz
}
fn spectrum_usable_span_ratio_from_context(context: &FrontendRuntimeContext) -> f32 {
context.http_spectrum_usable_span_ratio
}
#[get("/events")]
pub async fn events(
state: web::Data<watch::Receiver<RigState>>,