From 81890b15a8a1bbf5324a26a4b217cc641bf03f69 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Fri, 13 Feb 2026 00:17:16 +0100 Subject: [PATCH] [fix](trx-core): expose rig min tuning step and align UI tuning Add min_freq_step_hz to RigCapabilities, set backend values, and make HTTP frontend parse suffix-less frequency input using the selected unit while snapping set/jog frequencies to rig step granularity. Co-authored-by: Codex Signed-off-by: Stanislaw Grams --- src/trx-client/src/remote_client.rs | 1 + .../trx-frontend-http-json/src/server.rs | 1 + .../trx-frontend-http/assets/web/app.js | 83 ++++++++++++++++--- .../trx-frontend/trx-frontend-http/src/api.rs | 1 + src/trx-core/src/rig/controller/handlers.rs | 2 + src/trx-core/src/rig/controller/machine.rs | 1 + src/trx-core/src/rig/mod.rs | 6 ++ src/trx-server/src/listener.rs | 1 + src/trx-server/trx-backend/src/dummy.rs | 1 + .../trx-backend/trx-backend-ft450d/src/lib.rs | 1 + .../trx-backend/trx-backend-ft817/src/lib.rs | 1 + 11 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index df9f534..aaf3312 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -371,6 +371,7 @@ mod tests { model: "Dummy".to_string(), revision: "1".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![Band { low_hz: 7_000_000, high_hz: 7_200_000, diff --git a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs index 308a86f..68f5ed8 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs @@ -294,6 +294,7 @@ mod tests { model: "Dummy".to_string(), revision: "1".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![Band { low_hz: 14_000_000, high_hz: 14_350_000, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 8f61270..40bd54b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -45,6 +45,7 @@ let sigMeasuring = false; let sigSamples = []; let lastFreqHz = null; let jogStep = loadSetting("jogStep", 1000); +let minFreqStepHz = 1; const VFO_COLORS = ["var(--accent-green)", "var(--accent-yellow)"]; function vfoColor(idx) { if (idx < VFO_COLORS.length) return VFO_COLORS[idx]; @@ -89,9 +90,9 @@ function formatFreq(hz) { function formatFreqForStep(hz, step) { if (!Number.isFinite(hz)) return "--"; - if (step === 1_000_000) return (hz / 1_000_000).toFixed(6); - if (step === 1_000) return (hz / 1_000).toFixed(3); - if (step === 1) return String(Math.round(hz)); + if (step >= 1_000_000) return (hz / 1_000_000).toFixed(6); + if (step >= 1_000) return (hz / 1_000).toFixed(3); + if (step >= 1) return String(Math.round(hz)); return formatFreq(hz); } @@ -116,11 +117,11 @@ function parseFreqInput(val, defaultStep) { num *= 1_000; } else if (!unit) { // Use currently selected input unit when user omits suffix. - if (defaultStep === 1_000_000) { + if (defaultStep >= 1_000_000) { num *= 1_000_000; - } else if (defaultStep === 1_000) { + } else if (defaultStep >= 1_000) { num *= 1_000; - } else if (defaultStep === 1) { + } else if (defaultStep >= 1) { // already Hz } else { // Fallback heuristic. @@ -136,6 +137,54 @@ function parseFreqInput(val, defaultStep) { return Math.round(num); } +function normalizeMinFreqStep(cap) { + const val = Number(cap && cap.min_freq_step_hz); + if (!Number.isFinite(val) || val < 1) return 1; + return Math.round(val); +} + +function alignFreqToRigStep(hz) { + if (!Number.isFinite(hz)) return hz; + const step = Math.max(1, minFreqStepHz); + return Math.round(hz / step) * step; +} + +function updateJogStepSupport(cap) { + const nextMinStep = normalizeMinFreqStep(cap); + minFreqStepHz = nextMinStep; + + const stepRoot = document.getElementById("jog-step"); + if (!stepRoot) return; + const buttons = Array.from(stepRoot.querySelectorAll("button[data-step]")); + if (buttons.length === 0) return; + + buttons.forEach((btn) => { + const base = Number(btn.dataset.baseStep || btn.dataset.step); + if (Number.isFinite(base) && base > 0) { + btn.dataset.baseStep = String(Math.round(base)); + btn.dataset.step = String(Math.max(Math.round(base), minFreqStepHz)); + } + }); + + const steps = buttons + .map((btn) => Number(btn.dataset.step)) + .filter((s) => Number.isFinite(s) && s > 0); + if (steps.length === 0) return; + + const current = Number(jogStep); + const desired = + Number.isFinite(current) && current >= minFreqStepHz ? current : Math.max(steps[0], minFreqStepHz); + + jogStep = steps.reduce((best, s) => (Math.abs(s - desired) < Math.abs(best - desired) ? s : best), steps[0]); + saveSetting("jogStep", jogStep); + + buttons.forEach((btn) => { + btn.classList.toggle("active", Number(btn.dataset.step) === jogStep); + }); + + refreshFreqDisplay(); +} + function normalizeMode(modeVal) { if (typeof modeVal === "string") return modeVal; if (modeVal && typeof modeVal === "object") { @@ -256,6 +305,7 @@ function render(update) { } } if (update.info && update.info.capabilities) { + updateJogStepSupport(update.info.capabilities); updateSupportedBands(update.info.capabilities); } if (update.status && update.status.freq && typeof update.status.freq.hz === "number") { @@ -572,7 +622,8 @@ pttBtn.addEventListener("click", async () => { }); freqBtn.addEventListener("click", async () => { - const parsed = parseFreqInput(freqEl.value, jogStep); + const parsedRaw = parseFreqInput(freqEl.value, jogStep); + const parsed = alignFreqToRigStep(parsedRaw); if (parsed === null) { showHint("Freq missing", 1500); return; @@ -612,7 +663,7 @@ const jogStepEl = document.getElementById("jog-step"); async function jogFreq(direction) { if (lastLocked) { showHint("Locked", 1500); return; } if (lastFreqHz === null) return; - const newHz = lastFreqHz + direction * jogStep; + const newHz = alignFreqToRigStep(lastFreqHz + direction * jogStep); if (!freqAllowed(newHz)) { showHint("Out of supported bands", 1500); return; @@ -679,7 +730,7 @@ window.addEventListener("mouseup", () => { jogStepEl.addEventListener("click", (e) => { const btn = e.target.closest("button[data-step]"); if (!btn) return; - jogStep = parseInt(btn.dataset.step, 10); + jogStep = Math.max(parseInt(btn.dataset.step, 10), minFreqStepHz); jogStepEl.querySelectorAll("button").forEach((b) => b.classList.remove("active")); btn.classList.add("active"); saveSetting("jogStep", jogStep); @@ -687,9 +738,17 @@ jogStepEl.addEventListener("click", (e) => { }); // Restore active jog step button from saved setting -jogStepEl.querySelectorAll("button").forEach((b) => { - b.classList.toggle("active", parseInt(b.dataset.step, 10) === jogStep); -}); +{ + const buttons = Array.from(jogStepEl.querySelectorAll("button[data-step]")); + const active = + buttons.find((b) => parseInt(b.dataset.step, 10) === jogStep) || + buttons.find((b) => parseInt(b.dataset.step, 10) === 1000) || + buttons[0]; + if (active) { + jogStep = parseInt(active.dataset.step, 10); + buttons.forEach((b) => b.classList.toggle("active", b === active)); + } +} modeBtn.addEventListener("click", async () => { const mode = modeEl.value || ""; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs index 05df604..2acb02b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs @@ -614,6 +614,7 @@ impl From for RigInfo { model: "Rig".to_string(), revision: "".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![], supported_modes: vec![], num_vfos: 0, diff --git a/src/trx-core/src/rig/controller/handlers.rs b/src/trx-core/src/rig/controller/handlers.rs index f28fd36..0854dd0 100644 --- a/src/trx-core/src/rig/controller/handlers.rs +++ b/src/trx-core/src/rig/controller/handlers.rs @@ -542,6 +542,7 @@ mod tests { model: "Mock".to_string(), revision: "1.0".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![], supported_modes: vec![], num_vfos: 2, @@ -595,6 +596,7 @@ mod tests { model: "Mock".to_string(), revision: "1.0".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![], supported_modes: vec![], num_vfos: 2, diff --git a/src/trx-core/src/rig/controller/machine.rs b/src/trx-core/src/rig/controller/machine.rs index 80c0b82..6257a30 100644 --- a/src/trx-core/src/rig/controller/machine.rs +++ b/src/trx-core/src/rig/controller/machine.rs @@ -439,6 +439,7 @@ mod tests { model: "Mock".to_string(), revision: "1.0".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![], supported_modes: vec![], num_vfos: 2, diff --git a/src/trx-core/src/rig/mod.rs b/src/trx-core/src/rig/mod.rs index d58e5c4..8710afc 100644 --- a/src/trx-core/src/rig/mod.rs +++ b/src/trx-core/src/rig/mod.rs @@ -39,6 +39,8 @@ pub struct RigInfo { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RigCapabilities { + #[serde(default = "default_min_freq_step_hz")] + pub min_freq_step_hz: u64, pub supported_bands: Vec, pub supported_modes: Vec, pub num_vfos: usize, @@ -51,6 +53,10 @@ pub struct RigCapabilities { pub split: bool, } +fn default_min_freq_step_hz() -> u64 { + 1 +} + /// Common interface for rig backends. pub trait Rig { fn info(&self) -> &RigInfo; diff --git a/src/trx-server/src/listener.rs b/src/trx-server/src/listener.rs index 8432d6f..6b96bcf 100644 --- a/src/trx-server/src/listener.rs +++ b/src/trx-server/src/listener.rs @@ -344,6 +344,7 @@ mod tests { model: "Dummy".to_string(), revision: "1".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![Band { low_hz: 7_000_000, high_hz: 7_200_000, diff --git a/src/trx-server/trx-backend/src/dummy.rs b/src/trx-server/trx-backend/src/dummy.rs index ca4194d..1c75849 100644 --- a/src/trx-server/trx-backend/src/dummy.rs +++ b/src/trx-server/trx-backend/src/dummy.rs @@ -37,6 +37,7 @@ impl DummyRig { model: "dummy".to_string(), revision: "1.0".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 1, supported_bands: vec![ Band { low_hz: 1_800_000, diff --git a/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs b/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs index 547883d..c454504 100644 --- a/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs @@ -36,6 +36,7 @@ impl Ft450d { model: "FT-450D".to_string(), revision: "".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 10, supported_bands: vec![ // Transmit-capable amateur bands (HF + 6m) Band { diff --git a/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs b/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs index 39e7d37..1a6c52c 100644 --- a/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs @@ -37,6 +37,7 @@ impl Ft817 { model: "FT-817".to_string(), revision: "".to_string(), capabilities: RigCapabilities { + min_freq_step_hz: 10, supported_bands: vec![ // Transmit-capable amateur bands Band {