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"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -241,6 +247,7 @@ dependencies = [
"serde_derive",
"serde_json",
"serde_repr",
"serde_with",
"serialport",
"tokio",
"tokio-serial",
@@ -386,6 +393,41 @@ dependencies = [
"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]]
name = "dashmap"
version = "4.0.2"
@@ -403,6 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
"serde_core",
]
[[package]]
@@ -428,6 +471,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "either"
version = "1.15.0"
@@ -672,13 +721,19 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 2.13.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.16.1"
@@ -879,6 +934,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
@@ -900,6 +961,17 @@ dependencies = [
"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]]
name = "indexmap"
version = "2.13.0"
@@ -907,7 +979,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
@@ -1516,6 +1590,26 @@ dependencies = [
"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]]
name = "regex"
version = "1.12.2"
@@ -1645,6 +1739,30 @@ dependencies = [
"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]]
name = "scopeguard"
version = "1.2.0"
@@ -1760,6 +1878,37 @@ dependencies = [
"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]]
name = "serialport"
version = "4.8.1"
@@ -2131,7 +2280,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"indexmap 2.13.0",
"toml_datetime",
"winnow 0.5.40",
]
@@ -2142,7 +2291,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"indexmap 2.13.0",
"serde",
"serde_spanned",
"toml_datetime",

View File

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

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ impl ChimemonSource for GpsdSource {
async fn run(mut self, mut chan: ChimemonSourceChannel) {
info!("gpsd task started");
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();
params.json = Some(true);

View File

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

View File

@@ -1,3 +1,4 @@
use std::any::type_name;
use std::str::FromStr;
use std::sync::Arc;
@@ -225,7 +226,7 @@ pub struct Prs10Stats {
pub ocxo_efc: u32,
pub error_signal_volts: f64,
pub detect_signal_volts: f64,
pub freq_offset_ppt: u16,
pub freq_offset_ppt: i16,
pub mag_efc: u16,
pub heat_volts: f64,
pub elec_volts: f64,
@@ -363,7 +364,10 @@ impl Prs10Monitor {
let mut buf = Vec::new();
let read = timeout(self.config.timeout, reader.read_until(b'\r', &mut buf)).await??;
buf.truncate(buf.len() - 1); // strip "\r"
debug!("response: ({read}) `{buf:?}`");
debug!(
"raw response: ({read}) `{}`",
str::from_utf8(&buf).unwrap_or("<garbage>")
);
Ok(buf)
}
@@ -391,13 +395,11 @@ impl Prs10Monitor {
}
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();
cmd.extend_from_slice(id.to_string().as_bytes());
cmd.push(b'?');
let resp = self.cmd_response(&cmd).await?;
let value = str::from_utf8(&resp)?.parse::<f64>()?;
let value = self.get_parsed(&cmd).await?;
debug!("Got: {value}");
Ok(value)
}
@@ -408,7 +410,11 @@ impl Prs10Monitor {
where
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 val = str::from_utf8(&resp)?.parse::<T>()?;
Ok(val)
@@ -430,18 +436,18 @@ impl Prs10Monitor {
}
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 (error, signal) = resp
.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()
.ok_or("Not enough values in response to DS?".to_string())?;
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>> {
debug!("polling status");
let status = self.get_status().await?;
Ok(ChimemonMessage::SourceReport(SourceReport {
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>> {
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,
4.0, 4.0, 4.0, 1.0,
];
debug!("polling stats");
let stats_span = debug_span!("get_stats_serial");
let stats_guard = stats_span.enter();
let ocxo_efc = self.get_ocxo_efc().await?;