[refactor](trx-config): extract client/server config into a shared crate
The setup wizard, the server and the client each carried their own idea of what a valid config looks like: trx-configurator validated with hand-written toml_edit key lists while the binaries validated with serde plus their own validate(). Nothing kept the three in sync. Move ServerConfig, ClientConfig, the section loader, the shared validators and the endpoint-URL parsing into a new trx-config crate that all three depend on, so there is one definition of the config to drift from. The binaries keep a thin crate::config re-export so their internal paths are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7 Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
Generated
+17
-4
@@ -3031,10 +3031,6 @@ dependencies = [
|
|||||||
name = "trx-app"
|
name = "trx-app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
|
||||||
"serde",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"toml",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
@@ -3115,6 +3111,7 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"trx-app",
|
"trx-app",
|
||||||
|
"trx-config",
|
||||||
"trx-core",
|
"trx-core",
|
||||||
"trx-frontend",
|
"trx-frontend",
|
||||||
"trx-frontend-http",
|
"trx-frontend-http",
|
||||||
@@ -3124,6 +3121,20 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trx-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"trx-core",
|
||||||
|
"trx-decode-log",
|
||||||
|
"trx-reporting",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "trx-configurator"
|
name = "trx-configurator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3133,6 +3144,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio-serial",
|
"tokio-serial",
|
||||||
"toml_edit 0.22.27",
|
"toml_edit 0.22.27",
|
||||||
|
"trx-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3294,6 +3306,7 @@ dependencies = [
|
|||||||
"trx-app",
|
"trx-app",
|
||||||
"trx-aprs",
|
"trx-aprs",
|
||||||
"trx-backend",
|
"trx-backend",
|
||||||
|
"trx-config",
|
||||||
"trx-core",
|
"trx-core",
|
||||||
"trx-cw",
|
"trx-cw",
|
||||||
"trx-decode-log",
|
"trx-decode-log",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ members = [
|
|||||||
"src/trx-core",
|
"src/trx-core",
|
||||||
"src/trx-protocol",
|
"src/trx-protocol",
|
||||||
"src/trx-app",
|
"src/trx-app",
|
||||||
|
"src/trx-config",
|
||||||
"src/trx-reporting",
|
"src/trx-reporting",
|
||||||
"src/trx-server",
|
"src/trx-server",
|
||||||
"src/trx-server/trx-backend",
|
"src/trx-server/trx-backend",
|
||||||
|
|||||||
@@ -9,9 +9,5 @@ edition = "2021"
|
|||||||
license = "GPL-2.0-or-later"
|
license = "GPL-2.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true }
|
|
||||||
toml = { workspace = true }
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
dirs = "6"
|
|
||||||
thiserror = "2"
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
pub mod config;
|
//! Shared application helpers.
|
||||||
|
//!
|
||||||
|
//! Configuration types and their loader live in the `trx-config` crate.
|
||||||
|
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod shared_config;
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use config::{ConfigError, ConfigFile};
|
|
||||||
pub use logging::init_logging;
|
pub use logging::init_logging;
|
||||||
pub use shared_config::{validate_log_level, validate_tokens};
|
|
||||||
pub use util::normalize_name;
|
pub use util::normalize_name;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ uuid = { workspace = true }
|
|||||||
cpal = "0.15"
|
cpal = "0.15"
|
||||||
opus = "0.3"
|
opus = "0.3"
|
||||||
trx-app = { path = "../trx-app" }
|
trx-app = { path = "../trx-app" }
|
||||||
|
trx-config = { path = "../trx-config" }
|
||||||
trx-core = { path = "../trx-core" }
|
trx-core = { path = "../trx-core" }
|
||||||
trx-protocol = { path = "../trx-protocol" }
|
trx-protocol = { path = "../trx-protocol" }
|
||||||
trx-frontend = { path = "trx-frontend" }
|
trx-frontend = { path = "trx-frontend" }
|
||||||
|
|||||||
+5
-1214
File diff suppressed because it is too large
Load Diff
@@ -22,30 +22,16 @@ use trx_protocol::rig_command_to_client;
|
|||||||
use trx_protocol::types::RigEntry;
|
use trx_protocol::types::RigEntry;
|
||||||
use trx_protocol::{ClientCommand, ClientEnvelope, ClientResponse, MeterUpdate};
|
use trx_protocol::{ClientCommand, ClientEnvelope, ClientResponse, MeterUpdate};
|
||||||
|
|
||||||
const DEFAULT_REMOTE_PORT: u16 = 4530;
|
// Endpoint parsing lives in `trx-config` so config validation and the
|
||||||
const DEFAULT_AUDIO_PORT: u16 = 4531;
|
// connection code agree on what a URL means.
|
||||||
|
pub use trx_config::url::{parse_audio_url, parse_remote_url, RemoteEndpoint};
|
||||||
|
|
||||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
|
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
const IO_TIMEOUT: Duration = Duration::from_secs(15);
|
const IO_TIMEOUT: Duration = Duration::from_secs(15);
|
||||||
const SPECTRUM_IO_TIMEOUT: Duration = Duration::from_secs(3);
|
const SPECTRUM_IO_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
const MAX_JSON_LINE_BYTES: usize = 256 * 1024;
|
const MAX_JSON_LINE_BYTES: usize = 256 * 1024;
|
||||||
const MAX_CONSECUTIVE_POLL_FAILURES: u32 = 3;
|
const MAX_CONSECUTIVE_POLL_FAILURES: u32 = 3;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RemoteEndpoint {
|
|
||||||
pub host: String,
|
|
||||||
pub port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemoteEndpoint {
|
|
||||||
pub fn connect_addr(&self) -> String {
|
|
||||||
if self.host.contains(':') && !self.host.starts_with('[') {
|
|
||||||
format!("[{}]:{}", self.host, self.port)
|
|
||||||
} else {
|
|
||||||
format!("{}:{}", self.host, self.port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep remote spectrum reasonably responsive without returning to the old
|
// Keep remote spectrum reasonably responsive without returning to the old
|
||||||
// timeout churn caused by a much tighter request cadence.
|
// timeout churn caused by a much tighter request cadence.
|
||||||
const SPECTRUM_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
const SPECTRUM_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
@@ -1195,86 +1181,6 @@ async fn read_limited_line<R: AsyncBufRead + Unpin>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_remote_url(url: &str) -> Result<RemoteEndpoint, String> {
|
|
||||||
parse_endpoint_url(url, DEFAULT_REMOTE_PORT, "remote")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_audio_url(url: &str) -> Result<RemoteEndpoint, String> {
|
|
||||||
parse_endpoint_url(url, DEFAULT_AUDIO_PORT, "audio")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_endpoint_url(url: &str, default_port: u16, kind: &str) -> Result<RemoteEndpoint, String> {
|
|
||||||
let trimmed = url.trim();
|
|
||||||
if trimmed.is_empty() {
|
|
||||||
return Err(format!("{kind} url is empty"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let addr = trimmed
|
|
||||||
.strip_prefix("tcp://")
|
|
||||||
.or_else(|| trimmed.strip_prefix("http-json://"))
|
|
||||||
.or_else(|| trimmed.strip_prefix("audio://"))
|
|
||||||
.unwrap_or(trimmed);
|
|
||||||
|
|
||||||
parse_host_port(addr, default_port, kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_host_port(input: &str, default_port: u16, kind: &str) -> Result<RemoteEndpoint, String> {
|
|
||||||
if let Some(rest) = input.strip_prefix('[') {
|
|
||||||
let closing = rest
|
|
||||||
.find(']')
|
|
||||||
.ok_or_else(|| format!("invalid {kind} url: missing closing ']' for IPv6 host"))?;
|
|
||||||
let host = &rest[..closing];
|
|
||||||
let remainder = &rest[closing + 1..];
|
|
||||||
if host.is_empty() {
|
|
||||||
return Err(format!("invalid {kind} url: host is empty"));
|
|
||||||
}
|
|
||||||
let port = if remainder.is_empty() {
|
|
||||||
default_port
|
|
||||||
} else if let Some(port_str) = remainder.strip_prefix(':') {
|
|
||||||
parse_port(port_str, kind)?
|
|
||||||
} else {
|
|
||||||
return Err(format!("invalid {kind} url: expected ':<port>' after ']'"));
|
|
||||||
};
|
|
||||||
return Ok(RemoteEndpoint {
|
|
||||||
host: host.to_string(),
|
|
||||||
port,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.contains(':') {
|
|
||||||
if input.matches(':').count() > 1 {
|
|
||||||
return Err(format!(
|
|
||||||
"invalid {kind} url: IPv6 host must be bracketed like [::1]:4532"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let (host, port_str) = input
|
|
||||||
.rsplit_once(':')
|
|
||||||
.ok_or_else(|| format!("invalid {kind} url: expected host:port"))?;
|
|
||||||
if host.is_empty() {
|
|
||||||
return Err(format!("invalid {kind} url: host is empty"));
|
|
||||||
}
|
|
||||||
return Ok(RemoteEndpoint {
|
|
||||||
host: host.to_string(),
|
|
||||||
port: parse_port(port_str, kind)?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RemoteEndpoint {
|
|
||||||
host: input.to_string(),
|
|
||||||
port: default_port,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_port(port_str: &str, kind: &str) -> Result<u16, String> {
|
|
||||||
let port: u16 = port_str
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| format!("invalid {kind} port: '{port_str}'"))?;
|
|
||||||
if port == 0 {
|
|
||||||
return Err(format!("invalid {kind} port: 0"));
|
|
||||||
}
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "trx-config"
|
||||||
|
version.workspace = true
|
||||||
|
edition = "2021"
|
||||||
|
license = "GPL-2.0-or-later"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
toml = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
dirs = "6"
|
||||||
|
thiserror = "2"
|
||||||
|
trx-core = { path = "../trx-core" }
|
||||||
|
trx-decode-log = { path = "../decoders/trx-decode-log" }
|
||||||
|
trx-reporting = { path = "../trx-reporting" }
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Configuration types shared by `trx-server`, `trx-client` and
|
||||||
|
//! `trx-configurator`.
|
||||||
|
//!
|
||||||
|
//! Keeping the structs, the loader and the validators in one crate means the
|
||||||
|
//! setup wizard checks a config with exactly the same code the binaries load
|
||||||
|
//! it with, so the two can never drift apart.
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod file;
|
||||||
|
pub mod server;
|
||||||
|
pub mod shared;
|
||||||
|
pub mod url;
|
||||||
|
|
||||||
|
pub use client::ClientConfig;
|
||||||
|
pub use file::{ConfigError, ConfigFile};
|
||||||
|
pub use server::ServerConfig;
|
||||||
|
pub use shared::{validate_log_level, validate_tokens};
|
||||||
|
pub use url::{parse_audio_url, parse_remote_url, RemoteEndpoint};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,173 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Parsing for the `host:port` endpoint URLs used by the client's `[[remotes]]`
|
||||||
|
//! and `[frontends.audio]` settings.
|
||||||
|
//!
|
||||||
|
//! These live next to the config structs because validation needs them; the
|
||||||
|
//! client re-exports them for its connection code.
|
||||||
|
|
||||||
|
/// Default port for the server's JSON control listener.
|
||||||
|
pub const DEFAULT_REMOTE_PORT: u16 = 4530;
|
||||||
|
/// Default port for the server's Opus audio listener.
|
||||||
|
pub const DEFAULT_AUDIO_PORT: u16 = 4531;
|
||||||
|
|
||||||
|
/// A resolved `host:port` pair.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RemoteEndpoint {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteEndpoint {
|
||||||
|
/// Format as a connect string, bracketing bare IPv6 hosts.
|
||||||
|
pub fn connect_addr(&self) -> String {
|
||||||
|
if self.host.contains(':') && !self.host.starts_with('[') {
|
||||||
|
format!("[{}]:{}", self.host, self.port)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", self.host, self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a remote control URL, defaulting to port 4530.
|
||||||
|
pub fn parse_remote_url(url: &str) -> Result<RemoteEndpoint, String> {
|
||||||
|
parse_endpoint_url(url, DEFAULT_REMOTE_PORT, "remote")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an audio stream URL, defaulting to port 4531.
|
||||||
|
pub fn parse_audio_url(url: &str) -> Result<RemoteEndpoint, String> {
|
||||||
|
parse_endpoint_url(url, DEFAULT_AUDIO_PORT, "audio")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_endpoint_url(url: &str, default_port: u16, kind: &str) -> Result<RemoteEndpoint, String> {
|
||||||
|
let trimmed = url.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return Err(format!("{kind} url is empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = trimmed
|
||||||
|
.strip_prefix("tcp://")
|
||||||
|
.or_else(|| trimmed.strip_prefix("http-json://"))
|
||||||
|
.or_else(|| trimmed.strip_prefix("audio://"))
|
||||||
|
.unwrap_or(trimmed);
|
||||||
|
|
||||||
|
parse_host_port(addr, default_port, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_host_port(input: &str, default_port: u16, kind: &str) -> Result<RemoteEndpoint, String> {
|
||||||
|
if let Some(rest) = input.strip_prefix('[') {
|
||||||
|
let closing = rest
|
||||||
|
.find(']')
|
||||||
|
.ok_or_else(|| format!("invalid {kind} url: missing closing ']' for IPv6 host"))?;
|
||||||
|
let host = &rest[..closing];
|
||||||
|
let remainder = &rest[closing + 1..];
|
||||||
|
if host.is_empty() {
|
||||||
|
return Err(format!("invalid {kind} url: host is empty"));
|
||||||
|
}
|
||||||
|
let port = if remainder.is_empty() {
|
||||||
|
default_port
|
||||||
|
} else if let Some(port_str) = remainder.strip_prefix(':') {
|
||||||
|
parse_port(port_str, kind)?
|
||||||
|
} else {
|
||||||
|
return Err(format!("invalid {kind} url: expected ':<port>' after ']'"));
|
||||||
|
};
|
||||||
|
return Ok(RemoteEndpoint {
|
||||||
|
host: host.to_string(),
|
||||||
|
port,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.contains(':') {
|
||||||
|
if input.matches(':').count() > 1 {
|
||||||
|
return Err(format!(
|
||||||
|
"invalid {kind} url: IPv6 host must be bracketed like [::1]:4532"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let (host, port_str) = input
|
||||||
|
.rsplit_once(':')
|
||||||
|
.ok_or_else(|| format!("invalid {kind} url: expected host:port"))?;
|
||||||
|
if host.is_empty() {
|
||||||
|
return Err(format!("invalid {kind} url: host is empty"));
|
||||||
|
}
|
||||||
|
return Ok(RemoteEndpoint {
|
||||||
|
host: host.to_string(),
|
||||||
|
port: parse_port(port_str, kind)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RemoteEndpoint {
|
||||||
|
host: input.to_string(),
|
||||||
|
port: default_port,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_port(port_str: &str, kind: &str) -> Result<u16, String> {
|
||||||
|
let port: u16 = port_str
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| format!("invalid {kind} port: '{port_str}'"))?;
|
||||||
|
if port == 0 {
|
||||||
|
return Err(format!("invalid {kind} port: 0"));
|
||||||
|
}
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_remote_url_defaults_port() {
|
||||||
|
let ep = parse_remote_url("example.com").unwrap();
|
||||||
|
assert_eq!(ep.host, "example.com");
|
||||||
|
assert_eq!(ep.port, DEFAULT_REMOTE_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_audio_url_defaults_port() {
|
||||||
|
let ep = parse_audio_url("example.com").unwrap();
|
||||||
|
assert_eq!(ep.port, DEFAULT_AUDIO_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_strips_schemes() {
|
||||||
|
for url in &[
|
||||||
|
"tcp://host:9000",
|
||||||
|
"http-json://host:9000",
|
||||||
|
"audio://host:9000",
|
||||||
|
] {
|
||||||
|
let ep = parse_remote_url(url).unwrap();
|
||||||
|
assert_eq!(ep.host, "host");
|
||||||
|
assert_eq!(ep.port, 9000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_bracketed_ipv6() {
|
||||||
|
let ep = parse_remote_url("[::1]:4532").unwrap();
|
||||||
|
assert_eq!(ep.host, "::1");
|
||||||
|
assert_eq!(ep.port, 4532);
|
||||||
|
assert_eq!(ep.connect_addr(), "[::1]:4532");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_rejects_unbracketed_ipv6() {
|
||||||
|
assert!(parse_remote_url("::1:4532").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_rejects_empty_host() {
|
||||||
|
assert!(parse_remote_url(":4530").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_rejects_zero_port() {
|
||||||
|
assert!(parse_remote_url("host:0").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_rejects_empty_url() {
|
||||||
|
assert!(parse_remote_url(" ").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ clap = { workspace = true, features = ["derive"] }
|
|||||||
dialoguer = "0.11"
|
dialoguer = "0.11"
|
||||||
tokio-serial = { workspace = true }
|
tokio-serial = { workspace = true }
|
||||||
toml_edit = "0.22"
|
toml_edit = "0.22"
|
||||||
|
trx-config = { path = "../trx-config" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ cpal = "0.15"
|
|||||||
num-complex = "0.4"
|
num-complex = "0.4"
|
||||||
opus = "0.3"
|
opus = "0.3"
|
||||||
trx-app = { path = "../trx-app" }
|
trx-app = { path = "../trx-app" }
|
||||||
|
trx-config = { path = "../trx-config" }
|
||||||
trx-backend = { path = "trx-backend", features = ["soapysdr"] }
|
trx-backend = { path = "trx-backend", features = ["soapysdr"] }
|
||||||
trx-ais = { path = "../decoders/trx-ais" }
|
trx-ais = { path = "../decoders/trx-ais" }
|
||||||
trx-vdes = { path = "../decoders/trx-vdes" }
|
trx-vdes = { path = "../decoders/trx-vdes" }
|
||||||
|
|||||||
+5
-1518
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user