[feat](trx-rs): add cw auto/manual controls
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -274,6 +274,9 @@ async fn async_init() -> DynResult<AppState> {
|
||||
server_longitude: None,
|
||||
aprs_decode_enabled: false,
|
||||
cw_decode_enabled: false,
|
||||
cw_auto: true,
|
||||
cw_wpm: 15,
|
||||
cw_tone_hz: 700,
|
||||
aprs_decode_reset_seq: 0,
|
||||
cw_decode_reset_seq: 0,
|
||||
};
|
||||
|
||||
@@ -148,6 +148,9 @@ fn map_rig_command(cmd: trx_core::RigCommand) -> ClientCommand {
|
||||
trx_core::RigCommand::Unlock => ClientCommand::Unlock,
|
||||
trx_core::RigCommand::SetAprsDecodeEnabled(enabled) => ClientCommand::SetAprsDecodeEnabled { enabled },
|
||||
trx_core::RigCommand::SetCwDecodeEnabled(enabled) => ClientCommand::SetCwDecodeEnabled { enabled },
|
||||
trx_core::RigCommand::SetCwAuto(enabled) => ClientCommand::SetCwAuto { enabled },
|
||||
trx_core::RigCommand::SetCwWpm(wpm) => ClientCommand::SetCwWpm { wpm },
|
||||
trx_core::RigCommand::SetCwToneHz(tone_hz) => ClientCommand::SetCwToneHz { tone_hz },
|
||||
trx_core::RigCommand::ResetAprsDecoder => ClientCommand::ResetAprsDecoder,
|
||||
trx_core::RigCommand::ResetCwDecoder => ClientCommand::ResetCwDecoder,
|
||||
}
|
||||
@@ -190,6 +193,9 @@ pub fn state_from_snapshot(snapshot: trx_core::RigSnapshot) -> RigState {
|
||||
server_longitude: snapshot.server_longitude,
|
||||
aprs_decode_enabled: snapshot.aprs_decode_enabled,
|
||||
cw_decode_enabled: snapshot.cw_decode_enabled,
|
||||
cw_auto: snapshot.cw_auto,
|
||||
cw_wpm: snapshot.cw_wpm,
|
||||
cw_tone_hz: snapshot.cw_tone_hz,
|
||||
aprs_decode_reset_seq: 0,
|
||||
cw_decode_reset_seq: 0,
|
||||
}
|
||||
|
||||
@@ -267,6 +267,25 @@ function render(update) {
|
||||
pttBtn.style.color = "";
|
||||
}
|
||||
}
|
||||
const cwAutoEl = document.getElementById("cw-auto");
|
||||
const cwWpmEl = document.getElementById("cw-wpm");
|
||||
const cwToneEl = document.getElementById("cw-tone");
|
||||
if (cwAutoEl && typeof update.cw_auto === "boolean") {
|
||||
cwAutoEl.checked = update.cw_auto;
|
||||
}
|
||||
if (cwWpmEl && typeof update.cw_wpm === "number") {
|
||||
cwWpmEl.value = update.cw_wpm;
|
||||
}
|
||||
if (cwToneEl && typeof update.cw_tone_hz === "number") {
|
||||
cwToneEl.value = update.cw_tone_hz;
|
||||
}
|
||||
if (cwWpmEl && cwToneEl && typeof update.cw_auto === "boolean") {
|
||||
const disabled = update.cw_auto;
|
||||
cwWpmEl.disabled = disabled;
|
||||
cwWpmEl.readOnly = disabled;
|
||||
cwToneEl.disabled = disabled;
|
||||
cwToneEl.readOnly = disabled;
|
||||
}
|
||||
if (update.status && update.status.vfo && Array.isArray(update.status.vfo.entries)) {
|
||||
const entries = update.status.vfo.entries;
|
||||
const activeIdx = Number.isInteger(update.status.vfo.active) ? update.status.vfo.active : null;
|
||||
|
||||
@@ -157,9 +157,9 @@
|
||||
<div id="cw-signal-indicator" class="cw-signal-off"></div>
|
||||
</div>
|
||||
<div class="cw-config">
|
||||
<label class="cw-auto-label">Auto <input type="checkbox" id="cw-auto" checked disabled /></label>
|
||||
<label>WPM <input type="number" id="cw-wpm" min="5" max="40" value="15" readonly /></label>
|
||||
<label>Tone (Hz) <input type="number" id="cw-tone" min="300" max="1200" value="700" readonly /></label>
|
||||
<label class="cw-auto-label">Auto <input type="checkbox" id="cw-auto" checked /></label>
|
||||
<label>WPM <input type="number" id="cw-wpm" min="5" max="40" value="15" /></label>
|
||||
<label>Tone (Hz) <input type="number" id="cw-tone" min="300" max="1200" value="700" /></label>
|
||||
</div>
|
||||
<div id="cw-output"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
// --- CW (Morse) Decoder Plugin (server-side decode) ---
|
||||
const cwStatusEl = document.getElementById("cw-status");
|
||||
const cwOutputEl = document.getElementById("cw-output");
|
||||
const cwAutoInput = document.getElementById("cw-auto");
|
||||
const cwWpmInput = document.getElementById("cw-wpm");
|
||||
const cwToneInput = document.getElementById("cw-tone");
|
||||
const cwSignalIndicator = document.getElementById("cw-signal-indicator");
|
||||
const CW_MAX_LINES = 200;
|
||||
|
||||
function applyCwAutoUi(enabled) {
|
||||
if (cwAutoInput) cwAutoInput.checked = enabled;
|
||||
if (cwWpmInput) {
|
||||
cwWpmInput.disabled = enabled;
|
||||
cwWpmInput.readOnly = enabled;
|
||||
}
|
||||
if (cwToneInput) {
|
||||
cwToneInput.disabled = enabled;
|
||||
cwToneInput.readOnly = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
if (cwAutoInput) {
|
||||
cwAutoInput.addEventListener("change", async () => {
|
||||
const enabled = cwAutoInput.checked;
|
||||
applyCwAutoUi(enabled);
|
||||
try { await postPath(`/set_cw_auto?enabled=${enabled ? 1 : 0}`); }
|
||||
catch (e) { console.error("CW auto toggle failed", e); }
|
||||
});
|
||||
}
|
||||
|
||||
if (cwWpmInput) {
|
||||
cwWpmInput.addEventListener("change", async () => {
|
||||
if (cwAutoInput && cwAutoInput.checked) return;
|
||||
const wpm = Math.max(5, Math.min(40, Number(cwWpmInput.value)));
|
||||
cwWpmInput.value = wpm;
|
||||
try { await postPath(`/set_cw_wpm?wpm=${encodeURIComponent(wpm)}`); }
|
||||
catch (e) { console.error("CW WPM set failed", e); }
|
||||
});
|
||||
}
|
||||
|
||||
if (cwToneInput) {
|
||||
cwToneInput.addEventListener("change", async () => {
|
||||
if (cwAutoInput && cwAutoInput.checked) return;
|
||||
const tone = Math.max(300, Math.min(1200, Number(cwToneInput.value)));
|
||||
cwToneInput.value = tone;
|
||||
try { await postPath(`/set_cw_tone?tone_hz=${encodeURIComponent(tone)}`); }
|
||||
catch (e) { console.error("CW tone set failed", e); }
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("cw-clear-btn").addEventListener("click", async () => {
|
||||
cwOutputEl.innerHTML = "";
|
||||
try { await postPath("/clear_cw_decode"); } catch (e) { console.error("CW clear failed", e); }
|
||||
@@ -34,6 +76,8 @@ window.onServerCw = function(evt) {
|
||||
cwOutputEl.scrollTop = cwOutputEl.scrollHeight;
|
||||
}
|
||||
cwSignalIndicator.className = evt.signal_on ? "cw-signal-on" : "cw-signal-off";
|
||||
cwWpmInput.value = evt.wpm;
|
||||
cwToneInput.value = evt.tone_hz;
|
||||
if (!cwAutoInput || cwAutoInput.checked) {
|
||||
cwWpmInput.value = evt.wpm;
|
||||
cwToneInput.value = evt.tone_hz;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,7 +232,7 @@ small { color: var(--text-muted); }
|
||||
.cw-line { line-height: 1.5; }
|
||||
.cw-signal-on { width: 10px; height: 10px; border-radius: 50%; background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green); flex-shrink: 0; }
|
||||
.cw-signal-off { width: 10px; height: 10px; border-radius: 50%; background: var(--border-light); flex-shrink: 0; }
|
||||
.cw-auto-label { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.82rem; color: var(--text-muted); cursor: pointer; }
|
||||
.cw-config .cw-auto-label { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.82rem; color: var(--text-muted); cursor: pointer; flex-direction: row; }
|
||||
.cw-auto-label input[type="checkbox"] { margin: 0; cursor: pointer; }
|
||||
.cw-config input[type="number"][readonly] { opacity: 0.6; }
|
||||
|
||||
|
||||
@@ -282,6 +282,45 @@ pub async fn toggle_cw_decode(
|
||||
send_command(&rig_tx, RigCommand::SetCwDecodeEnabled(!enabled)).await
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct CwAutoQuery {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[post("/set_cw_auto")]
|
||||
pub async fn set_cw_auto(
|
||||
query: web::Query<CwAutoQuery>,
|
||||
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
send_command(&rig_tx, RigCommand::SetCwAuto(query.enabled)).await
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct CwWpmQuery {
|
||||
pub wpm: u32,
|
||||
}
|
||||
|
||||
#[post("/set_cw_wpm")]
|
||||
pub async fn set_cw_wpm(
|
||||
query: web::Query<CwWpmQuery>,
|
||||
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
send_command(&rig_tx, RigCommand::SetCwWpm(query.wpm)).await
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct CwToneQuery {
|
||||
pub tone_hz: u32,
|
||||
}
|
||||
|
||||
#[post("/set_cw_tone")]
|
||||
pub async fn set_cw_tone(
|
||||
query: web::Query<CwToneQuery>,
|
||||
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
send_command(&rig_tx, RigCommand::SetCwToneHz(query.tone_hz)).await
|
||||
}
|
||||
|
||||
#[post("/clear_aprs_decode")]
|
||||
pub async fn clear_aprs_decode(
|
||||
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
||||
@@ -311,6 +350,9 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
.service(set_tx_limit)
|
||||
.service(toggle_aprs_decode)
|
||||
.service(toggle_cw_decode)
|
||||
.service(set_cw_auto)
|
||||
.service(set_cw_wpm)
|
||||
.service(set_cw_tone)
|
||||
.service(clear_aprs_decode)
|
||||
.service(clear_cw_decode)
|
||||
.service(crate::server::audio::audio_ws)
|
||||
@@ -443,6 +485,9 @@ async fn wait_for_view(mut rx: watch::Receiver<RigState>) -> Result<RigSnapshot,
|
||||
server_longitude: state.server_longitude,
|
||||
aprs_decode_enabled: state.aprs_decode_enabled,
|
||||
cw_decode_enabled: state.cw_decode_enabled,
|
||||
cw_auto: state.cw_auto,
|
||||
cw_wpm: state.cw_wpm,
|
||||
cw_tone_hz: state.cw_tone_hz,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user