fix(trx-server): remove unused RigSdr import in rig_task.rs
Also run cargo fmt to fix formatting issues across trx-server, trx-frontend-http, and trx-configurator. https://claude.ai/code/session_01RsHUyVz2wjQjsEsxJo5owt Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -527,7 +527,9 @@ fn sync_scheduler_vchannels(
|
||||
.last_bookmark_ids
|
||||
.iter()
|
||||
.filter_map(|bookmark_id| {
|
||||
bookmark_store_map.get_for_rig(rig_id, bookmark_id).map(|bookmark| {
|
||||
bookmark_store_map
|
||||
.get_for_rig(rig_id, bookmark_id)
|
||||
.map(|bookmark| {
|
||||
(
|
||||
bookmark_id.clone(),
|
||||
bookmark.freq_hz,
|
||||
@@ -573,7 +575,10 @@ impl DecodeHistoryPayload {
|
||||
|
||||
/// Build the grouped decode history payload from all per-decoder ring-buffers.
|
||||
/// When `rig_filter` is `Some`, only entries recorded for that rig are included.
|
||||
fn collect_decode_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> DecodeHistoryPayload {
|
||||
fn collect_decode_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> DecodeHistoryPayload {
|
||||
DecodeHistoryPayload {
|
||||
ais: crate::server::audio::snapshot_ais_history(context, rig_filter),
|
||||
vdes: crate::server::audio::snapshot_vdes_history(context, rig_filter),
|
||||
@@ -1497,7 +1502,10 @@ pub async fn list_bookmarks(
|
||||
status::index_html(),
|
||||
));
|
||||
}
|
||||
let scope = query.scope.as_deref().filter(|s| !s.is_empty() && *s != "general");
|
||||
let scope = query
|
||||
.scope
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty() && *s != "general");
|
||||
let mut list: Vec<BookmarkWithScope> = match scope {
|
||||
Some(remote) => {
|
||||
// Rig selected: merge general + rig-specific (rig wins on duplicate IDs).
|
||||
@@ -1507,20 +1515,36 @@ pub async fn list_bookmarks(
|
||||
.into_iter()
|
||||
.map(|bm| {
|
||||
let id = bm.id.clone();
|
||||
(id, BookmarkWithScope { bm, scope: "general".into() })
|
||||
(
|
||||
id,
|
||||
BookmarkWithScope {
|
||||
bm,
|
||||
scope: "general".into(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for bm in store_map.store_for(remote).list() {
|
||||
let id = bm.id.clone();
|
||||
map.insert(id, BookmarkWithScope { bm, scope: remote.to_owned() });
|
||||
map.insert(
|
||||
id,
|
||||
BookmarkWithScope {
|
||||
bm,
|
||||
scope: remote.to_owned(),
|
||||
},
|
||||
);
|
||||
}
|
||||
map.into_values().collect()
|
||||
}
|
||||
None => {
|
||||
store_map.general().list().into_iter()
|
||||
.map(|bm| BookmarkWithScope { bm, scope: "general".into() })
|
||||
.collect()
|
||||
}
|
||||
None => store_map
|
||||
.general()
|
||||
.list()
|
||||
.into_iter()
|
||||
.map(|bm| BookmarkWithScope {
|
||||
bm,
|
||||
scope: "general".into(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
if let Some(ref cat) = query.category {
|
||||
if !cat.is_empty() {
|
||||
|
||||
@@ -141,7 +141,10 @@ fn record_vdes(context: &FrontendRuntimeContext, mut msg: VdesMessage) {
|
||||
prune_vdes_history(context, &mut history);
|
||||
}
|
||||
|
||||
fn prune_cw_history(context: &FrontendRuntimeContext, history: &mut VecDeque<(Instant, Option<String>, CwEvent)>) {
|
||||
fn prune_cw_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
history: &mut VecDeque<(Instant, Option<String>, CwEvent)>,
|
||||
) {
|
||||
let cutoff = decode_history_cutoff(context);
|
||||
while let Some((ts, _, _)) = history.front() {
|
||||
if *ts >= cutoff {
|
||||
@@ -281,25 +284,33 @@ fn matches_rig_filter(entry_rig: Option<&str>, filter: Option<&str>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot_aprs_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<AprsPacket> {
|
||||
pub fn snapshot_aprs_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<AprsPacket> {
|
||||
let mut history = context
|
||||
.aprs_history
|
||||
.lock()
|
||||
.expect("aprs history mutex poisoned");
|
||||
prune_aprs_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, pkt)| pkt.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_hf_aprs_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<AprsPacket> {
|
||||
pub fn snapshot_hf_aprs_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<AprsPacket> {
|
||||
let mut history = context
|
||||
.hf_aprs_history
|
||||
.lock()
|
||||
.expect("hf_aprs history mutex poisoned");
|
||||
prune_hf_aprs_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, pkt)| pkt.clone())
|
||||
.collect()
|
||||
@@ -312,7 +323,10 @@ pub fn snapshot_hf_aprs_history(context: &FrontendRuntimeContext, rig_filter: Op
|
||||
/// what the map shows (current position/state) and keeps the response compact.
|
||||
/// The returned vec is sorted ascending by `ts_ms` so the client can replay
|
||||
/// in chronological order.
|
||||
pub fn snapshot_ais_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<AisMessage> {
|
||||
pub fn snapshot_ais_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<AisMessage> {
|
||||
let mut history = context
|
||||
.ais_history
|
||||
.lock()
|
||||
@@ -331,73 +345,97 @@ pub fn snapshot_ais_history(context: &FrontendRuntimeContext, rig_filter: Option
|
||||
out
|
||||
}
|
||||
|
||||
pub fn snapshot_vdes_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<VdesMessage> {
|
||||
pub fn snapshot_vdes_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<VdesMessage> {
|
||||
let mut history = context
|
||||
.vdes_history
|
||||
.lock()
|
||||
.expect("vdes history mutex poisoned");
|
||||
prune_vdes_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, msg)| msg.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_cw_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<CwEvent> {
|
||||
pub fn snapshot_cw_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<CwEvent> {
|
||||
let mut history = context
|
||||
.cw_history
|
||||
.lock()
|
||||
.expect("cw history mutex poisoned");
|
||||
prune_cw_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, evt)| evt.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_ft8_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<Ft8Message> {
|
||||
pub fn snapshot_ft8_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<Ft8Message> {
|
||||
let mut history = context
|
||||
.ft8_history
|
||||
.lock()
|
||||
.expect("ft8 history mutex poisoned");
|
||||
prune_ft8_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, msg)| msg.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_ft4_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<Ft8Message> {
|
||||
pub fn snapshot_ft4_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<Ft8Message> {
|
||||
let mut history = context
|
||||
.ft4_history
|
||||
.lock()
|
||||
.expect("ft4 history mutex poisoned");
|
||||
prune_ft4_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, msg)| msg.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_ft2_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<Ft8Message> {
|
||||
pub fn snapshot_ft2_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<Ft8Message> {
|
||||
let mut history = context
|
||||
.ft2_history
|
||||
.lock()
|
||||
.expect("ft2 history mutex poisoned");
|
||||
prune_ft2_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, msg)| msg.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn snapshot_wspr_history(context: &FrontendRuntimeContext, rig_filter: Option<&str>) -> Vec<WsprMessage> {
|
||||
pub fn snapshot_wspr_history(
|
||||
context: &FrontendRuntimeContext,
|
||||
rig_filter: Option<&str>,
|
||||
) -> Vec<WsprMessage> {
|
||||
let mut history = context
|
||||
.wspr_history
|
||||
.lock()
|
||||
.expect("wspr history mutex poisoned");
|
||||
prune_wspr_history(context, &mut history);
|
||||
history.iter()
|
||||
history
|
||||
.iter()
|
||||
.filter(|(_, rid, _)| matches_rig_filter(rid.as_deref(), rig_filter))
|
||||
.map(|(_, _, msg)| msg.clone())
|
||||
.collect()
|
||||
|
||||
@@ -202,10 +202,7 @@ impl SchedulerStoreMap {
|
||||
/// List configs from all known per-rig stores.
|
||||
pub fn list_all(&self) -> Vec<SchedulerConfig> {
|
||||
let stores = self.stores.lock().unwrap_or_else(|e| e.into_inner());
|
||||
stores
|
||||
.values()
|
||||
.filter_map(|s| s.get_config())
|
||||
.collect()
|
||||
stores.values().filter_map(|s| s.get_config()).collect()
|
||||
}
|
||||
|
||||
/// One-time migration: extract `sch:{remote}` entries from legacy
|
||||
@@ -887,7 +884,10 @@ pub async fn get_scheduler(
|
||||
store_map: web::Data<Arc<SchedulerStoreMap>>,
|
||||
) -> impl Responder {
|
||||
let remote = path.into_inner();
|
||||
let config = store_map.store_for(&remote).get_config().unwrap_or(SchedulerConfig {
|
||||
let config = store_map
|
||||
.store_for(&remote)
|
||||
.get_config()
|
||||
.unwrap_or(SchedulerConfig {
|
||||
remote: remote.clone(),
|
||||
mode: SchedulerMode::Disabled,
|
||||
grayline: None,
|
||||
|
||||
@@ -160,7 +160,12 @@ impl ClientChannelManager {
|
||||
}
|
||||
}
|
||||
// Fall back to global sender.
|
||||
if let Some(tx) = self.audio_cmd.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
|
||||
if let Some(tx) = self
|
||||
.audio_cmd
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.as_ref()
|
||||
{
|
||||
let _ = tx.try_send(cmd);
|
||||
}
|
||||
}
|
||||
@@ -540,7 +545,11 @@ impl ClientChannelManager {
|
||||
|
||||
/// Return the channel a session is currently subscribed to.
|
||||
pub fn session_channel(&self, session_id: Uuid) -> Option<(String, Uuid)> {
|
||||
self.sessions.read().unwrap_or_else(|e| e.into_inner()).get(&session_id).cloned()
|
||||
self.sessions
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.get(&session_id)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Return the selected channel's tune metadata.
|
||||
|
||||
@@ -217,7 +217,12 @@ fn check_server_sections(
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
if let Some(general) = table.get("general").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(general, SERVER_GENERAL_KEYS, &format!("{}[general].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
general,
|
||||
SERVER_GENERAL_KEYS,
|
||||
&format!("{}[general].", prefix),
|
||||
warnings,
|
||||
);
|
||||
validate_log_level(general, &format!("{}[general]", prefix), errors);
|
||||
validate_coordinates(general, &format!("{}[general]", prefix), errors);
|
||||
}
|
||||
@@ -225,13 +230,23 @@ fn check_server_sections(
|
||||
if let Some(rig) = table.get("rig").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(rig, RIG_KEYS, &format!("{}[rig].", prefix), warnings);
|
||||
if let Some(access) = rig.get("access").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(access, ACCESS_KEYS, &format!("{}[rig.access].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
access,
|
||||
ACCESS_KEYS,
|
||||
&format!("{}[rig.access].", prefix),
|
||||
warnings,
|
||||
);
|
||||
validate_access(access, &format!("{}[rig.access]", prefix), errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(listen) = table.get("listen").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(listen, LISTEN_KEYS, &format!("{}[listen].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
listen,
|
||||
LISTEN_KEYS,
|
||||
&format!("{}[listen].", prefix),
|
||||
warnings,
|
||||
);
|
||||
validate_port(listen, "port", &format!("{}[listen]", prefix), errors);
|
||||
}
|
||||
|
||||
@@ -241,7 +256,12 @@ fn check_server_sections(
|
||||
}
|
||||
|
||||
if let Some(behavior) = table.get("behavior").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(behavior, BEHAVIOR_KEYS, &format!("{}[behavior].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
behavior,
|
||||
BEHAVIOR_KEYS,
|
||||
&format!("{}[behavior].", prefix),
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,21 +272,41 @@ fn check_client_sections(
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
if let Some(general) = table.get("general").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(general, CLIENT_GENERAL_KEYS, &format!("{}[general].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
general,
|
||||
CLIENT_GENERAL_KEYS,
|
||||
&format!("{}[general].", prefix),
|
||||
warnings,
|
||||
);
|
||||
validate_log_level(general, &format!("{}[general]", prefix), errors);
|
||||
}
|
||||
|
||||
if let Some(remote) = table.get("remote").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(remote, REMOTE_KEYS, &format!("{}[remote].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
remote,
|
||||
REMOTE_KEYS,
|
||||
&format!("{}[remote].", prefix),
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(frontends) = table.get("frontends").and_then(|v| v.as_table()) {
|
||||
check_unknown_keys(frontends, FRONTENDS_KEYS, &format!("{}[frontends].", prefix), warnings);
|
||||
check_unknown_keys(
|
||||
frontends,
|
||||
FRONTENDS_KEYS,
|
||||
&format!("{}[frontends].", prefix),
|
||||
warnings,
|
||||
);
|
||||
if let Some(http) = frontends.get("http").and_then(|v| v.as_table()) {
|
||||
validate_port(http, "port", &format!("{}[frontends.http]", prefix), errors);
|
||||
}
|
||||
if let Some(rigctl) = frontends.get("rigctl").and_then(|v| v.as_table()) {
|
||||
validate_port(rigctl, "port", &format!("{}[frontends.rigctl]", prefix), errors);
|
||||
validate_port(
|
||||
rigctl,
|
||||
"port",
|
||||
&format!("{}[frontends.rigctl]", prefix),
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,12 +325,21 @@ fn validate_log_level(table: &toml_edit::Table, context: &str, errors: &mut Vec<
|
||||
}
|
||||
|
||||
fn validate_coordinates(table: &toml_edit::Table, context: &str, errors: &mut Vec<String>) {
|
||||
if let Some(lat) = table.get("latitude").and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64))) {
|
||||
if let Some(lat) = table
|
||||
.get("latitude")
|
||||
.and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64)))
|
||||
{
|
||||
if !(-90.0..=90.0).contains(&lat) {
|
||||
errors.push(format!("{}.latitude {} is out of range (-90..90)", context, lat));
|
||||
errors.push(format!(
|
||||
"{}.latitude {} is out of range (-90..90)",
|
||||
context, lat
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(lon) = table.get("longitude").and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64))) {
|
||||
if let Some(lon) = table
|
||||
.get("longitude")
|
||||
.and_then(|v| v.as_float().or_else(|| v.as_integer().map(|i| i as f64)))
|
||||
{
|
||||
if !(-180.0..=180.0).contains(&lon) {
|
||||
errors.push(format!(
|
||||
"{}.longitude {} is out of range (-180..180)",
|
||||
@@ -309,12 +358,7 @@ fn validate_coordinates(table: &toml_edit::Table, context: &str, errors: &mut Ve
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_port(
|
||||
table: &toml_edit::Table,
|
||||
key: &str,
|
||||
context: &str,
|
||||
errors: &mut Vec<String>,
|
||||
) {
|
||||
fn validate_port(table: &toml_edit::Table, key: &str, context: &str, errors: &mut Vec<String>) {
|
||||
if let Some(port) = table.get(key).and_then(|v| v.as_integer()) {
|
||||
if let Some(enabled) = table.get("enabled").and_then(|v| v.as_bool()) {
|
||||
if enabled && port <= 0 {
|
||||
|
||||
@@ -12,7 +12,10 @@ use std::path::PathBuf;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "trx-configurator", about = "Interactive configuration generator for trx-rs")]
|
||||
#[command(
|
||||
name = "trx-configurator",
|
||||
about = "Interactive configuration generator for trx-rs"
|
||||
)]
|
||||
struct Cli {
|
||||
/// Generate a default config without interactive prompts
|
||||
#[arg(long)]
|
||||
@@ -70,7 +73,10 @@ fn main() {
|
||||
"client" => ConfigType::Client,
|
||||
"combined" => ConfigType::Combined,
|
||||
other => {
|
||||
eprintln!("Unknown config type '{}'. Use: server, client, combined", other);
|
||||
eprintln!(
|
||||
"Unknown config type '{}'. Use: server, client, combined",
|
||||
other
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ pub struct FrontendsSetup {
|
||||
// ── Prompt functions ────────────────────────────────────────────────────
|
||||
|
||||
pub fn prompt_config_type() -> ConfigType {
|
||||
let items = &["Server (trx-server.toml)", "Client (trx-client.toml)", "Combined (trx-rs.toml)"];
|
||||
let items = &[
|
||||
"Server (trx-server.toml)",
|
||||
"Client (trx-client.toml)",
|
||||
"Combined (trx-rs.toml)",
|
||||
];
|
||||
let sel = Select::new()
|
||||
.with_prompt("What configuration would you like to generate?")
|
||||
.items(items)
|
||||
|
||||
@@ -16,8 +16,7 @@ use crate::ConfigType;
|
||||
fn commented(table: &mut Table, key: &str, val: Item, comment: &str) {
|
||||
table.insert(key, val);
|
||||
if let Some(mut kv) = table.key_mut(key) {
|
||||
kv.leaf_decor_mut()
|
||||
.set_prefix(format!("# {}\n", comment));
|
||||
kv.leaf_decor_mut().set_prefix(format!("# {}\n", comment));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,24 +26,40 @@ fn header_comment(table: &mut Table, comment: &str) {
|
||||
|
||||
// ── Server document builder ─────────────────────────────────────────────
|
||||
|
||||
fn build_server_tables(
|
||||
general: ServerGeneral,
|
||||
rig: RigSetup,
|
||||
listen: ListenSetup,
|
||||
) -> Table {
|
||||
fn build_server_tables(general: ServerGeneral, rig: RigSetup, listen: ListenSetup) -> Table {
|
||||
let mut root = Table::new();
|
||||
|
||||
// [general]
|
||||
{
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "General settings");
|
||||
commented(&mut t, "callsign", value(&general.callsign), "Station callsign");
|
||||
commented(&mut t, "log_level", value(&general.log_level), "Log level (trace, debug, info, warn, error)");
|
||||
commented(
|
||||
&mut t,
|
||||
"callsign",
|
||||
value(&general.callsign),
|
||||
"Station callsign",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"log_level",
|
||||
value(&general.log_level),
|
||||
"Log level (trace, debug, info, warn, error)",
|
||||
);
|
||||
if let Some(lat) = general.latitude {
|
||||
commented(&mut t, "latitude", value(lat), "Station latitude (decimal degrees, WGS84)");
|
||||
commented(
|
||||
&mut t,
|
||||
"latitude",
|
||||
value(lat),
|
||||
"Station latitude (decimal degrees, WGS84)",
|
||||
);
|
||||
}
|
||||
if let Some(lon) = general.longitude {
|
||||
commented(&mut t, "longitude", value(lon), "Station longitude (decimal degrees, WGS84)");
|
||||
commented(
|
||||
&mut t,
|
||||
"longitude",
|
||||
value(lon),
|
||||
"Station longitude (decimal degrees, WGS84)",
|
||||
);
|
||||
}
|
||||
root.insert("general", Item::Table(t));
|
||||
}
|
||||
@@ -53,18 +68,38 @@ fn build_server_tables(
|
||||
{
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "Rig backend configuration");
|
||||
commented(&mut t, "model", value(&rig.model), "Rig model (ft817, ft450d, soapysdr)");
|
||||
commented(&mut t, "initial_freq_hz", value(144_300_000i64), "Initial frequency in Hz");
|
||||
commented(
|
||||
&mut t,
|
||||
"model",
|
||||
value(&rig.model),
|
||||
"Rig model (ft817, ft450d, soapysdr)",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"initial_freq_hz",
|
||||
value(144_300_000i64),
|
||||
"Initial frequency in Hz",
|
||||
);
|
||||
commented(&mut t, "initial_mode", value("USB"), "Initial mode");
|
||||
|
||||
// [rig.access]
|
||||
let mut access = Table::new();
|
||||
header_comment(&mut access, "Rig access method");
|
||||
commented(&mut access, "type", value(&rig.access_type), "Access type: serial, tcp, or sdr");
|
||||
commented(
|
||||
&mut access,
|
||||
"type",
|
||||
value(&rig.access_type),
|
||||
"Access type: serial, tcp, or sdr",
|
||||
);
|
||||
match rig.access_type.as_str() {
|
||||
"serial" => {
|
||||
if let Some(port) = &rig.serial_port {
|
||||
commented(&mut access, "port", value(port.as_str()), "Serial port path");
|
||||
commented(
|
||||
&mut access,
|
||||
"port",
|
||||
value(port.as_str()),
|
||||
"Serial port path",
|
||||
);
|
||||
}
|
||||
if let Some(baud) = rig.serial_baud {
|
||||
commented(&mut access, "baud", value(baud as i64), "Baud rate");
|
||||
@@ -80,7 +115,12 @@ fn build_server_tables(
|
||||
}
|
||||
"sdr" => {
|
||||
if let Some(args) = &rig.sdr_args {
|
||||
commented(&mut access, "args", value(args.as_str()), "SoapySDR device args string");
|
||||
commented(
|
||||
&mut access,
|
||||
"args",
|
||||
value(args.as_str()),
|
||||
"SoapySDR device args string",
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -93,10 +133,25 @@ fn build_server_tables(
|
||||
{
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "Polling and retry behavior");
|
||||
commented(&mut t, "poll_interval_ms", value(500i64), "Rig polling interval (ms)");
|
||||
commented(&mut t, "poll_interval_tx_ms", value(100i64), "Polling interval during TX (ms)");
|
||||
commented(
|
||||
&mut t,
|
||||
"poll_interval_ms",
|
||||
value(500i64),
|
||||
"Rig polling interval (ms)",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"poll_interval_tx_ms",
|
||||
value(100i64),
|
||||
"Polling interval during TX (ms)",
|
||||
);
|
||||
commented(&mut t, "max_retries", value(3i64), "Maximum retry attempts");
|
||||
commented(&mut t, "retry_base_delay_ms", value(100i64), "Base retry delay (ms)");
|
||||
commented(
|
||||
&mut t,
|
||||
"retry_base_delay_ms",
|
||||
value(100i64),
|
||||
"Base retry delay (ms)",
|
||||
);
|
||||
root.insert("behavior", Item::Table(t));
|
||||
}
|
||||
|
||||
@@ -105,8 +160,18 @@ fn build_server_tables(
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "JSON TCP listener for client connections");
|
||||
commented(&mut t, "enabled", value(true), "Enable the TCP listener");
|
||||
commented(&mut t, "listen", value(&listen.listen), "IP address to listen on");
|
||||
commented(&mut t, "port", value(listen.port as i64), "TCP port for client connections");
|
||||
commented(
|
||||
&mut t,
|
||||
"listen",
|
||||
value(&listen.listen),
|
||||
"IP address to listen on",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"port",
|
||||
value(listen.port as i64),
|
||||
"TCP port for client connections",
|
||||
);
|
||||
root.insert("listen", Item::Table(t));
|
||||
}
|
||||
|
||||
@@ -118,22 +183,34 @@ fn build_server_tables(
|
||||
commented(&mut t, "listen", value("127.0.0.1"), "Audio listen address");
|
||||
commented(&mut t, "port", value(4531i64), "Audio TCP port");
|
||||
commented(&mut t, "sample_rate", value(48000i64), "Sample rate in Hz");
|
||||
commented(&mut t, "channels", value(2i64), "Channel count (1 = mono, 2 = stereo)");
|
||||
commented(&mut t, "frame_duration_ms", value(20i64), "Opus frame duration (ms)");
|
||||
commented(&mut t, "bitrate_bps", value(256000i64), "Opus bitrate (bps)");
|
||||
commented(
|
||||
&mut t,
|
||||
"channels",
|
||||
value(2i64),
|
||||
"Channel count (1 = mono, 2 = stereo)",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"frame_duration_ms",
|
||||
value(20i64),
|
||||
"Opus frame duration (ms)",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"bitrate_bps",
|
||||
value(256000i64),
|
||||
"Opus bitrate (bps)",
|
||||
);
|
||||
root.insert("audio", Item::Table(t));
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
pub fn build_server(
|
||||
general: ServerGeneral,
|
||||
rig: RigSetup,
|
||||
listen: ListenSetup,
|
||||
) -> DocumentMut {
|
||||
pub fn build_server(general: ServerGeneral, rig: RigSetup, listen: ListenSetup) -> DocumentMut {
|
||||
let mut doc = DocumentMut::new();
|
||||
doc.decor_mut().set_prefix("# trx-server configuration\n# Generated by trx-configurator\n");
|
||||
doc.decor_mut()
|
||||
.set_prefix("# trx-server configuration\n# Generated by trx-configurator\n");
|
||||
let tables = build_server_tables(general, rig, listen);
|
||||
for (key, item) in tables.iter() {
|
||||
doc.insert(key, item.clone());
|
||||
@@ -154,8 +231,18 @@ fn build_client_tables(
|
||||
{
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "General settings");
|
||||
commented(&mut t, "callsign", value(&general.callsign), "Station callsign");
|
||||
commented(&mut t, "log_level", value(&general.log_level), "Log level (trace, debug, info, warn, error)");
|
||||
commented(
|
||||
&mut t,
|
||||
"callsign",
|
||||
value(&general.callsign),
|
||||
"Station callsign",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"log_level",
|
||||
value(&general.log_level),
|
||||
"Log level (trace, debug, info, warn, error)",
|
||||
);
|
||||
root.insert("general", Item::Table(t));
|
||||
}
|
||||
|
||||
@@ -163,8 +250,18 @@ fn build_client_tables(
|
||||
{
|
||||
let mut t = Table::new();
|
||||
header_comment(&mut t, "Remote server connection");
|
||||
commented(&mut t, "url", value(&remote.url), "Server address (host:port)");
|
||||
commented(&mut t, "poll_interval_ms", value(750i64), "State poll interval (ms)");
|
||||
commented(
|
||||
&mut t,
|
||||
"url",
|
||||
value(&remote.url),
|
||||
"Server address (host:port)",
|
||||
);
|
||||
commented(
|
||||
&mut t,
|
||||
"poll_interval_ms",
|
||||
value(750i64),
|
||||
"State poll interval (ms)",
|
||||
);
|
||||
|
||||
let mut auth = Table::new();
|
||||
if let Some(token) = &remote.token {
|
||||
@@ -182,26 +279,61 @@ fn build_client_tables(
|
||||
header_comment(&mut frontends_table, "Frontend configuration");
|
||||
|
||||
let mut http = Table::new();
|
||||
commented(&mut http, "enabled", value(frontends.http_enabled), "Enable HTTP web frontend");
|
||||
commented(
|
||||
&mut http,
|
||||
"enabled",
|
||||
value(frontends.http_enabled),
|
||||
"Enable HTTP web frontend",
|
||||
);
|
||||
commented(&mut http, "listen", value("127.0.0.1"), "Listen address");
|
||||
commented(&mut http, "port", value(frontends.http_port as i64), "HTTP port");
|
||||
commented(
|
||||
&mut http,
|
||||
"port",
|
||||
value(frontends.http_port as i64),
|
||||
"HTTP port",
|
||||
);
|
||||
frontends_table.insert("http", Item::Table(http));
|
||||
|
||||
let mut rigctl = Table::new();
|
||||
commented(&mut rigctl, "enabled", value(frontends.rigctl_enabled), "Enable Hamlib rigctl frontend");
|
||||
commented(
|
||||
&mut rigctl,
|
||||
"enabled",
|
||||
value(frontends.rigctl_enabled),
|
||||
"Enable Hamlib rigctl frontend",
|
||||
);
|
||||
commented(&mut rigctl, "listen", value("127.0.0.1"), "Listen address");
|
||||
commented(&mut rigctl, "port", value(frontends.rigctl_port as i64), "rigctl port");
|
||||
commented(
|
||||
&mut rigctl,
|
||||
"port",
|
||||
value(frontends.rigctl_port as i64),
|
||||
"rigctl port",
|
||||
);
|
||||
frontends_table.insert("rigctl", Item::Table(rigctl));
|
||||
|
||||
let mut http_json = Table::new();
|
||||
commented(&mut http_json, "enabled", value(true), "Enable JSON-over-TCP frontend");
|
||||
commented(&mut http_json, "listen", value("127.0.0.1"), "Listen address");
|
||||
commented(
|
||||
&mut http_json,
|
||||
"enabled",
|
||||
value(true),
|
||||
"Enable JSON-over-TCP frontend",
|
||||
);
|
||||
commented(
|
||||
&mut http_json,
|
||||
"listen",
|
||||
value("127.0.0.1"),
|
||||
"Listen address",
|
||||
);
|
||||
commented(&mut http_json, "port", value(0i64), "Port (0 = ephemeral)");
|
||||
frontends_table.insert("http_json", Item::Table(http_json));
|
||||
|
||||
let mut audio = Table::new();
|
||||
commented(&mut audio, "enabled", value(true), "Enable audio client");
|
||||
commented(&mut audio, "server_port", value(4531i64), "Server audio port");
|
||||
commented(
|
||||
&mut audio,
|
||||
"server_port",
|
||||
value(4531i64),
|
||||
"Server audio port",
|
||||
);
|
||||
frontends_table.insert("audio", Item::Table(audio));
|
||||
|
||||
root.insert("frontends", Item::Table(frontends_table));
|
||||
@@ -216,7 +348,8 @@ pub fn build_client(
|
||||
frontends: FrontendsSetup,
|
||||
) -> DocumentMut {
|
||||
let mut doc = DocumentMut::new();
|
||||
doc.decor_mut().set_prefix("# trx-client configuration\n# Generated by trx-configurator\n");
|
||||
doc.decor_mut()
|
||||
.set_prefix("# trx-client configuration\n# Generated by trx-configurator\n");
|
||||
let tables = build_client_tables(general, remote, frontends);
|
||||
for (key, item) in tables.iter() {
|
||||
doc.insert(key, item.clone());
|
||||
@@ -235,7 +368,8 @@ pub fn build_combined(
|
||||
frontends: FrontendsSetup,
|
||||
) -> DocumentMut {
|
||||
let mut doc = DocumentMut::new();
|
||||
doc.decor_mut().set_prefix("# trx-rs combined configuration\n# Generated by trx-configurator\n");
|
||||
doc.decor_mut()
|
||||
.set_prefix("# trx-rs combined configuration\n# Generated by trx-configurator\n");
|
||||
|
||||
let server = build_server_tables(s_general, rig, listen);
|
||||
let mut server_item = Item::Table(server);
|
||||
@@ -294,7 +428,9 @@ pub fn build_default(config_type: ConfigType) -> DocumentMut {
|
||||
match config_type {
|
||||
ConfigType::Server => build_server(s_general, rig, listen),
|
||||
ConfigType::Client => build_client(c_general, remote, frontends),
|
||||
ConfigType::Combined => build_combined(s_general, rig, listen, c_general, remote, frontends),
|
||||
ConfigType::Combined => {
|
||||
build_combined(s_general, rig, listen, c_general, remote, frontends)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +448,8 @@ pub fn write_file(doc: &DocumentMut, path: &Path) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(path, doc.to_string()).map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
|
||||
std::fs::write(path, doc.to_string())
|
||||
.map_err(|e| format!("Failed to write {}: {}", path.display(), e))?;
|
||||
println!("Wrote {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -138,10 +138,7 @@ impl StreamErrorLogger {
|
||||
fn log(&self, err: &str) {
|
||||
let now = Instant::now();
|
||||
let kind = classify_stream_error(err);
|
||||
let mut state = self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
|
||||
|
||||
// First occurrence or changed error class: log as error once.
|
||||
if state.last_kind != Some(kind) {
|
||||
@@ -1023,7 +1020,10 @@ fn run_playback(
|
||||
if rx.is_empty() {
|
||||
let _ = stream.pause();
|
||||
playing = false;
|
||||
ring_writer.lock().unwrap_or_else(|e| e.into_inner()).clear();
|
||||
ring_writer
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.clear();
|
||||
info!("Audio playback: paused (idle)");
|
||||
if channel_closed {
|
||||
return Ok(());
|
||||
@@ -1051,7 +1051,10 @@ fn run_playback(
|
||||
let _ = stream.pause();
|
||||
playing = false;
|
||||
}
|
||||
ring_writer.lock().unwrap_or_else(|e| e.into_inner()).clear();
|
||||
ring_writer
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.clear();
|
||||
|
||||
if channel_closed {
|
||||
return Ok(());
|
||||
|
||||
@@ -21,7 +21,7 @@ use trx_core::rig::controller::{
|
||||
};
|
||||
use trx_core::rig::request::RigRequest;
|
||||
use trx_core::rig::state::{RigMode, RigSnapshot, RigState};
|
||||
use trx_core::rig::{RigCat, RigRxStatus, RigSdr, RigTxStatus};
|
||||
use trx_core::rig::{RigCat, RigRxStatus, RigTxStatus};
|
||||
use trx_core::{DynResult, RigError, RigResult};
|
||||
|
||||
use crate::audio::DecoderHistories;
|
||||
@@ -564,7 +564,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_sdr_gain(gain_db).await {
|
||||
return Err(RigError::communication(format!("set_sdr_gain: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_sdr_gain")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_sdr_gain"));
|
||||
}
|
||||
ctx.state.filter = ctx.rig.as_sdr_ref().and_then(|s| s.filter_state());
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
@@ -574,7 +576,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_sdr_lna_gain(gain_db).await {
|
||||
return Err(RigError::communication(format!("set_sdr_lna_gain: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_sdr_lna_gain")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_sdr_lna_gain"));
|
||||
}
|
||||
ctx.state.filter = ctx.rig.as_sdr_ref().and_then(|s| s.filter_state());
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
@@ -584,7 +588,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_sdr_agc(enabled).await {
|
||||
return Err(RigError::communication(format!("set_sdr_agc: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_sdr_agc")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_sdr_agc"));
|
||||
}
|
||||
ctx.state.filter = ctx.rig.as_sdr_ref().and_then(|s| s.filter_state());
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
@@ -597,7 +603,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_sdr_squelch(enabled, threshold_db).await {
|
||||
return Err(RigError::communication(format!("set_sdr_squelch: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_sdr_squelch")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_sdr_squelch"));
|
||||
}
|
||||
ctx.state.filter = ctx.rig.as_sdr_ref().and_then(|s| s.filter_state());
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
@@ -609,7 +617,9 @@ async fn process_command(
|
||||
"set_sdr_noise_blanker: {e}"
|
||||
)));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_sdr_noise_blanker")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_sdr_noise_blanker"));
|
||||
}
|
||||
ctx.state.filter = ctx.rig.as_sdr_ref().and_then(|s| s.filter_state());
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
@@ -619,7 +629,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_wfm_deemphasis(deemphasis_us).await {
|
||||
return Err(RigError::communication(format!("set_wfm_deemphasis: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_wfm_deemphasis")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_wfm_deemphasis"));
|
||||
}
|
||||
if let Some(f) = ctx.state.filter.as_mut() {
|
||||
f.wfm_deemphasis_us = deemphasis_us;
|
||||
}
|
||||
@@ -631,7 +643,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_wfm_stereo(enabled).await {
|
||||
return Err(RigError::communication(format!("set_wfm_stereo: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_wfm_stereo")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_wfm_stereo"));
|
||||
}
|
||||
if let Some(f) = ctx.state.filter.as_mut() {
|
||||
f.wfm_stereo = enabled;
|
||||
}
|
||||
@@ -643,7 +657,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_wfm_denoise(level).await {
|
||||
return Err(RigError::communication(format!("set_wfm_denoise: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_wfm_denoise")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_wfm_denoise"));
|
||||
}
|
||||
if let Some(f) = ctx.state.filter.as_mut() {
|
||||
f.wfm_denoise = level;
|
||||
}
|
||||
@@ -655,7 +671,9 @@ async fn process_command(
|
||||
if let Err(e) = sdr.set_center_freq(freq).await {
|
||||
return Err(RigError::communication(format!("set_center_freq: {e}")));
|
||||
}
|
||||
} else { return Err(RigError::not_supported("set_center_freq")); }
|
||||
} else {
|
||||
return Err(RigError::not_supported("set_center_freq"));
|
||||
}
|
||||
*ctx.poll_pause_until = Some(Instant::now() + Duration::from_millis(200));
|
||||
return snapshot_from(ctx.state);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user