[fix](trx-frontend-rigctl): add rigctld dumpcaps handshake
Return setting=value lines with a done terminator for dumpcaps commands so Hamlib netrigctl_open can parse capabilities. Add a unit test that verifies dumpcaps output formatting. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -218,6 +218,12 @@ async fn process_command(
|
|||||||
Ok(snapshot) => ok_response(dump_state_lines(&snapshot)),
|
Ok(snapshot) => ok_response(dump_state_lines(&snapshot)),
|
||||||
Err(e) => err_response(&e),
|
Err(e) => err_response(&e),
|
||||||
},
|
},
|
||||||
|
"1" | "\\dump_caps" | "dump_caps" | "\\dumpcaps" | "dumpcaps" => {
|
||||||
|
match request_snapshot(rig_tx).await {
|
||||||
|
Ok(snapshot) => dump_caps_response(&snapshot),
|
||||||
|
Err(e) => err_response(&e),
|
||||||
|
}
|
||||||
|
}
|
||||||
"i" | "I" => {
|
"i" | "I" => {
|
||||||
let snapshot = match current_snapshot(state_rx) {
|
let snapshot = match current_snapshot(state_rx) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
@@ -345,6 +351,53 @@ fn dump_state_lines(_snapshot: &RigSnapshot) -> Vec<String> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dump_caps_response(snapshot: &RigSnapshot) -> String {
|
||||||
|
// netrigctl_open expects `setting=value` lines terminated by `done`.
|
||||||
|
// Unknown keys are tolerated by Hamlib, but malformed lines are not.
|
||||||
|
let mut resp = String::new();
|
||||||
|
let push = |buf: &mut String, key: &str, val: String| {
|
||||||
|
buf.push_str(key);
|
||||||
|
buf.push('=');
|
||||||
|
buf.push_str(&val);
|
||||||
|
buf.push('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
push(&mut resp, "protocol_version", "1".to_string());
|
||||||
|
push(&mut resp, "rig_model", "2".to_string());
|
||||||
|
push(&mut resp, "model_name", snapshot.info.model.clone());
|
||||||
|
push(
|
||||||
|
&mut resp,
|
||||||
|
"mfg_name",
|
||||||
|
snapshot.info.manufacturer.clone(),
|
||||||
|
);
|
||||||
|
push(
|
||||||
|
&mut resp,
|
||||||
|
"backend_version",
|
||||||
|
snapshot.info.revision.clone(),
|
||||||
|
);
|
||||||
|
push(
|
||||||
|
&mut resp,
|
||||||
|
"vfo_count",
|
||||||
|
snapshot.info.capabilities.num_vfos.to_string(),
|
||||||
|
);
|
||||||
|
push(
|
||||||
|
&mut resp,
|
||||||
|
"has_vfo_b",
|
||||||
|
if snapshot.info.capabilities.num_vfos >= 2 {
|
||||||
|
"1".to_string()
|
||||||
|
} else {
|
||||||
|
"0".to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
push(
|
||||||
|
&mut resp,
|
||||||
|
"can_ptt",
|
||||||
|
if snapshot.status.tx.is_some() { "1" } else { "0" }.to_string(),
|
||||||
|
);
|
||||||
|
resp.push_str("done\n");
|
||||||
|
resp
|
||||||
|
}
|
||||||
|
|
||||||
fn active_vfo_label(snapshot: &RigSnapshot) -> String {
|
fn active_vfo_label(snapshot: &RigSnapshot) -> String {
|
||||||
// Normalize to VFOA/VFOB/... for hamlib compatibility.
|
// Normalize to VFOA/VFOB/... for hamlib compatibility.
|
||||||
snapshot
|
snapshot
|
||||||
@@ -405,3 +458,74 @@ fn is_true(s: &str) -> bool {
|
|||||||
fn is_false(s: &str) -> bool {
|
fn is_false(s: &str) -> bool {
|
||||||
matches!(s, "0" | "off" | "OFF" | "false" | "False" | "FALSE")
|
matches!(s, "0" | "off" | "OFF" | "false" | "False" | "FALSE")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use trx_core::rig::{RigAccessMethod, RigCapabilities, RigInfo, RigStatus, RigTxStatus};
|
||||||
|
|
||||||
|
fn test_snapshot() -> RigSnapshot {
|
||||||
|
RigSnapshot {
|
||||||
|
info: RigInfo {
|
||||||
|
manufacturer: "TRX".to_string(),
|
||||||
|
model: "Virtual".to_string(),
|
||||||
|
revision: "0.1.0".to_string(),
|
||||||
|
capabilities: RigCapabilities {
|
||||||
|
min_freq_step_hz: 1,
|
||||||
|
supported_bands: vec![],
|
||||||
|
supported_modes: vec![RigMode::USB],
|
||||||
|
num_vfos: 2,
|
||||||
|
lock: false,
|
||||||
|
lockable: false,
|
||||||
|
attenuator: false,
|
||||||
|
preamp: false,
|
||||||
|
rit: false,
|
||||||
|
rpt: false,
|
||||||
|
split: false,
|
||||||
|
},
|
||||||
|
access: RigAccessMethod::Tcp {
|
||||||
|
addr: "127.0.0.1:4532".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: RigStatus {
|
||||||
|
freq: Freq { hz: 7_100_000 },
|
||||||
|
mode: RigMode::USB,
|
||||||
|
tx_en: false,
|
||||||
|
vfo: None,
|
||||||
|
tx: Some(RigTxStatus {
|
||||||
|
power: None,
|
||||||
|
limit: None,
|
||||||
|
swr: None,
|
||||||
|
alc: None,
|
||||||
|
}),
|
||||||
|
rx: None,
|
||||||
|
lock: None,
|
||||||
|
},
|
||||||
|
band: None,
|
||||||
|
enabled: Some(true),
|
||||||
|
initialized: true,
|
||||||
|
server_callsign: None,
|
||||||
|
server_version: None,
|
||||||
|
server_latitude: None,
|
||||||
|
server_longitude: None,
|
||||||
|
pskreporter_status: None,
|
||||||
|
aprs_decode_enabled: false,
|
||||||
|
cw_decode_enabled: false,
|
||||||
|
ft8_decode_enabled: false,
|
||||||
|
wspr_decode_enabled: false,
|
||||||
|
cw_auto: false,
|
||||||
|
cw_wpm: 0,
|
||||||
|
cw_tone_hz: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dump_caps_is_setting_value_and_ends_with_done() {
|
||||||
|
let response = dump_caps_response(&test_snapshot());
|
||||||
|
let lines: Vec<&str> = response.lines().collect();
|
||||||
|
assert!(lines.iter().all(|line| *line == "done" || line.contains('=')));
|
||||||
|
assert_eq!(lines.last(), Some(&"done"));
|
||||||
|
assert!(response.contains("model_name=Virtual\n"));
|
||||||
|
assert!(response.contains("mfg_name=TRX\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user