Compare commits

...

2 Commits

Author SHA1 Message Date
d464cf8ee6 improve parsing of durations 2026-02-03 19:38:02 -08:00
2e8e731d80 prs10: fix signedness for parsing some values 2026-02-03 19:37:36 -08:00
7 changed files with 202 additions and 36 deletions

157
Cargo.lock generated
View File

@@ -159,6 +159,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@@ -241,6 +247,7 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"serde_with",
"serialport", "serialport",
"tokio", "tokio",
"tokio-serial", "tokio-serial",
@@ -386,6 +393,41 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.114",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "4.0.2" version = "4.0.2"
@@ -403,6 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde_core",
] ]
[[package]] [[package]]
@@ -428,6 +471,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
@@ -672,13 +721,19 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http",
"indexmap", "indexmap 2.13.0",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.16.1"
@@ -879,6 +934,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.1.0" version = "1.1.0"
@@ -900,6 +961,17 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.13.0"
@@ -907,7 +979,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.16.1",
"serde",
"serde_core",
] ]
[[package]] [[package]]
@@ -1516,6 +1590,26 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.2" version = "1.12.2"
@@ -1645,6 +1739,30 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "schemars"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]]
name = "schemars"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -1760,6 +1878,37 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.13.0",
"schemars 0.9.0",
"schemars 1.2.1",
"serde_core",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "serialport" name = "serialport"
version = "4.8.1" version = "4.8.1"
@@ -2131,7 +2280,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.13.0",
"toml_datetime", "toml_datetime",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -2142,7 +2291,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.13.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",

View File

@@ -34,6 +34,7 @@ gethostname = "1.1.0"
bitflags = "2.10.0" bitflags = "2.10.0"
influxdb2 = "0.3.9" influxdb2 = "0.3.9"
chrono = "0.4.43" chrono = "0.4.43"
serde_with = "3.16.1"
[dependencies.chrony-candm] [dependencies.chrony-candm]
git = "https://github.com/aws/chrony-candm" git = "https://github.com/aws/chrony-candm"

View File

@@ -12,6 +12,7 @@ use figment::{
use gethostname::gethostname; use gethostname::gethostname;
use influxdb2::models::DataPoint; use influxdb2::models::DataPoint;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_with::{DurationSeconds, serde_as};
use tokio::sync::broadcast::*; use tokio::sync::broadcast::*;
use std::{fmt::Debug, path::Path, sync::Arc}; use std::{fmt::Debug, path::Path, sync::Arc};
@@ -40,12 +41,16 @@ impl Default for InfluxConfig {
} }
} }
#[serde_as]
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct ChronyConfig { pub struct ChronyConfig {
pub enabled: bool, pub enabled: bool,
pub timeout: u64, #[serde_as(as = "DurationSeconds<u64>")]
pub tracking_interval: u64, pub timeout: std::time::Duration,
pub sources_interval: u64, #[serde_as(as = "DurationSeconds<u64>")]
pub tracking_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub sources_interval: std::time::Duration,
pub measurement_prefix: String, pub measurement_prefix: String,
pub tracking_measurement: String, pub tracking_measurement: String,
pub sources_measurement: String, pub sources_measurement: String,
@@ -56,9 +61,9 @@ impl Default for ChronyConfig {
fn default() -> Self { fn default() -> Self {
ChronyConfig { ChronyConfig {
enabled: false, enabled: false,
timeout: 5, timeout: std::time::Duration::from_secs(5),
tracking_interval: 60, tracking_interval: std::time::Duration::from_secs(60),
sources_interval: 300, sources_interval: std::time::Duration::from_secs(300),
measurement_prefix: "chrony.".into(), measurement_prefix: "chrony.".into(),
tracking_measurement: "tracking".into(), tracking_measurement: "tracking".into(),
sources_measurement: "sources".into(), sources_measurement: "sources".into(),
@@ -88,10 +93,12 @@ pub struct HwmonSensorConfig {
pub sensor: String, pub sensor: String,
} }
#[serde_as]
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct HwmonConfig { pub struct HwmonConfig {
pub enabled: bool, pub enabled: bool,
pub interval: u64, #[serde_as(as = "DurationSeconds<u64>")]
pub interval: std::time::Duration,
pub measurement: String, pub measurement: String,
pub sensors: Map<String, HwmonSensorConfig>, pub sensors: Map<String, HwmonSensorConfig>,
} }
@@ -100,17 +107,19 @@ impl Default for HwmonConfig {
fn default() -> Self { fn default() -> Self {
HwmonConfig { HwmonConfig {
enabled: false, enabled: false,
interval: 60, interval: std::time::Duration::from_secs(60),
measurement: "hwmon".into(), measurement: "hwmon".into(),
sensors: map! {}, sensors: map! {},
} }
} }
} }
#[serde_as]
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GpsdConfig { pub struct GpsdConfig {
pub enabled: bool, pub enabled: bool,
pub interval: u64, #[serde_as(as = "DurationSeconds<u64>")]
pub interval: std::time::Duration,
pub host: String, pub host: String,
} }
@@ -118,19 +127,23 @@ impl Default for GpsdConfig {
fn default() -> Self { fn default() -> Self {
GpsdConfig { GpsdConfig {
enabled: false, enabled: false,
interval: 60, interval: std::time::Duration::from_secs(60),
host: "localhost:2947".into(), host: "localhost:2947".into(),
} }
} }
} }
#[serde_as]
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Prs10Config { pub struct Prs10Config {
pub enabled: bool, pub enabled: bool,
pub port: String, pub port: String,
pub baud: u32, pub baud: u32,
#[serde_as(as = "DurationSeconds<u64>")]
pub timeout: std::time::Duration, pub timeout: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub status_interval: std::time::Duration, pub status_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub stats_interval: std::time::Duration, pub stats_interval: std::time::Duration,
} }
@@ -217,12 +230,15 @@ pub struct SourceReport {
pub details: Arc<dyn SourceReportDetails>, pub details: Arc<dyn SourceReportDetails>,
} }
#[serde_as]
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct UCCMConfig { pub struct UCCMConfig {
pub enabled: bool, pub enabled: bool,
pub port: String, pub port: String,
pub baud: u32, pub baud: u32,
#[serde_as(as = "DurationSeconds<u64>")]
pub status_interval: std::time::Duration, pub status_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub timeout: std::time::Duration, pub timeout: std::time::Duration,
pub measurement: String, pub measurement: String,
} }

View File

@@ -162,7 +162,7 @@ impl ChronyClient {
.expect("Unable to parse host:port:"); .expect("Unable to parse host:port:");
let client_options = ClientOptions { let client_options = ClientOptions {
n_tries: 3, n_tries: 3,
timeout: Duration::from_secs(config.sources.chrony.timeout), timeout: config.sources.chrony.timeout,
}; };
ChronyClient { ChronyClient {
server, server,
@@ -298,12 +298,8 @@ impl ChimemonSource for ChronyClient {
async fn run(self, chan: ChimemonSourceChannel) { async fn run(self, chan: ChimemonSourceChannel) {
info!("Chrony task started"); info!("Chrony task started");
let mut t_interval = tokio::time::interval(Duration::from_secs( let mut t_interval = tokio::time::interval(self.config.sources.chrony.tracking_interval);
self.config.sources.chrony.tracking_interval, let mut s_interval = tokio::time::interval(self.config.sources.chrony.sources_interval);
));
let mut s_interval = tokio::time::interval(Duration::from_secs(
self.config.sources.chrony.sources_interval,
));
let t_future = async { let t_future = async {
let lchan = chan.clone(); let lchan = chan.clone();

View File

@@ -166,7 +166,7 @@ impl ChimemonSource for GpsdSource {
async fn run(mut self, mut chan: ChimemonSourceChannel) { async fn run(mut self, mut chan: ChimemonSourceChannel) {
info!("gpsd task started"); info!("gpsd task started");
self.conn.conn().await.unwrap(); self.conn.conn().await.unwrap();
let mut ticker = interval(Duration::from_secs(self.config.sources.gpsd.interval)); let mut ticker = interval(self.config.sources.gpsd.interval);
let mut params = WatchParams::default(); let mut params = WatchParams::default();
params.json = Some(true); params.json = Some(true);

View File

@@ -113,8 +113,7 @@ impl HwmonSource {
impl ChimemonSource for HwmonSource { impl ChimemonSource for HwmonSource {
async fn run(self, chan: ChimemonSourceChannel) { async fn run(self, chan: ChimemonSourceChannel) {
info!("hwmon task started"); info!("hwmon task started");
let mut interval = let mut interval = tokio::time::interval(self.config.sources.hwmon.interval);
tokio::time::interval(Duration::from_secs(self.config.sources.hwmon.interval));
loop { loop {
interval.tick().await; interval.tick().await;
let mut values = Vec::new(); let mut values = Vec::new();

View File

@@ -1,3 +1,4 @@
use std::any::type_name;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@@ -225,7 +226,7 @@ pub struct Prs10Stats {
pub ocxo_efc: u32, pub ocxo_efc: u32,
pub error_signal_volts: f64, pub error_signal_volts: f64,
pub detect_signal_volts: f64, pub detect_signal_volts: f64,
pub freq_offset_ppt: u16, pub freq_offset_ppt: i16,
pub mag_efc: u16, pub mag_efc: u16,
pub heat_volts: f64, pub heat_volts: f64,
pub elec_volts: f64, pub elec_volts: f64,
@@ -363,7 +364,10 @@ impl Prs10Monitor {
let mut buf = Vec::new(); let mut buf = Vec::new();
let read = timeout(self.config.timeout, reader.read_until(b'\r', &mut buf)).await??; let read = timeout(self.config.timeout, reader.read_until(b'\r', &mut buf)).await??;
buf.truncate(buf.len() - 1); // strip "\r" buf.truncate(buf.len() - 1); // strip "\r"
debug!("response: ({read}) `{buf:?}`"); debug!(
"raw response: ({read}) `{}`",
str::from_utf8(&buf).unwrap_or("<garbage>")
);
Ok(buf) Ok(buf)
} }
@@ -391,13 +395,11 @@ impl Prs10Monitor {
} }
pub async fn get_analog(&mut self, id: u16) -> Result<f64, Box<dyn std::error::Error>> { pub async fn get_analog(&mut self, id: u16) -> Result<f64, Box<dyn std::error::Error>> {
debug!("Getting analog value {id}");
let mut cmd = b"AD".to_vec(); let mut cmd = b"AD".to_vec();
cmd.extend_from_slice(id.to_string().as_bytes()); cmd.extend_from_slice(id.to_string().as_bytes());
cmd.push(b'?'); cmd.push(b'?');
let resp = self.cmd_response(&cmd).await?; let value = self.get_parsed(&cmd).await?;
let value = str::from_utf8(&resp)?.parse::<f64>()?; debug!("Got: {value}");
Ok(value) Ok(value)
} }
@@ -408,7 +410,11 @@ impl Prs10Monitor {
where where
T::Err: std::error::Error + 'static, T::Err: std::error::Error + 'static,
{ {
debug!("Getting int value for command {cmd:?}"); debug!(
"Getting parsed <{}> value for command {}",
type_name::<T>(),
str::from_utf8(cmd).unwrap_or("<garbage>"),
);
let resp = self.cmd_response(cmd).await?; let resp = self.cmd_response(cmd).await?;
let val = str::from_utf8(&resp)?.parse::<T>()?; let val = str::from_utf8(&resp)?.parse::<T>()?;
Ok(val) Ok(val)
@@ -430,18 +436,18 @@ impl Prs10Monitor {
} }
pub async fn get_detected_signals(&mut self) -> Result<(f64, f64), Box<dyn std::error::Error>> { pub async fn get_detected_signals(&mut self) -> Result<(f64, f64), Box<dyn std::error::Error>> {
debug!("Getting detected signals pair"); debug!("Getting i16,i16 -> f64,f64 detected signals pair");
let resp = self.cmd_response(b"DS?").await?; let resp = self.cmd_response(b"DS?").await?;
let (error, signal) = resp let (error, signal) = resp
.splitn(2, |c| *c == b',') .splitn(2, |c| *c == b',')
.map(|s| str::from_utf8(s).unwrap().parse::<u16>()) .map(|s| str::from_utf8(s).unwrap().parse::<i16>())
.collect_tuple() .collect_tuple()
.ok_or("Not enough values in response to DS?".to_string())?; .ok_or("Not enough values in response to DS?".to_string())?;
Ok((error? as f64 * 0.15e-6, signal? as f64 * 0.001)) Ok((error? as f64 * 0.15e-6, signal? as f64 * 0.001))
} }
#[instrument(skip_all)]
async fn status_poll(&mut self) -> Result<ChimemonMessage, Box<dyn std::error::Error>> { async fn status_poll(&mut self) -> Result<ChimemonMessage, Box<dyn std::error::Error>> {
debug!("polling status");
let status = self.get_status().await?; let status = self.get_status().await?;
Ok(ChimemonMessage::SourceReport(SourceReport { Ok(ChimemonMessage::SourceReport(SourceReport {
name: "prs10".into(), name: "prs10".into(),
@@ -454,14 +460,13 @@ impl Prs10Monitor {
})) }))
} }
#[instrument(skip_all)]
async fn stats_poll(&mut self) -> Result<ChimemonMessage, Box<dyn std::error::Error>> { async fn stats_poll(&mut self) -> Result<ChimemonMessage, Box<dyn std::error::Error>> {
const ANALOG_SCALING: [f64; 20] = [ const ANALOG_SCALING: [f64; 20] = [
0.0, 10.0, 10.0, 10.0, 10.0, 1.0, 1.0, 1.0, 1.0, 4.0, 100.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 10.0, 10.0, 10.0, 10.0, 1.0, 1.0, 1.0, 1.0, 4.0, 100.0, 1.0, 1.0, 1.0, 1.0, 1.0,
4.0, 4.0, 4.0, 1.0, 4.0, 4.0, 4.0, 1.0,
]; ];
debug!("polling stats");
let stats_span = debug_span!("get_stats_serial"); let stats_span = debug_span!("get_stats_serial");
let stats_guard = stats_span.enter(); let stats_guard = stats_span.enter();
let ocxo_efc = self.get_ocxo_efc().await?; let ocxo_efc = self.get_ocxo_efc().await?;