Report TODs to influx

This commit is contained in:
Keenan Tims 2023-09-14 22:18:11 -07:00
parent 7c782e5800
commit 41284649c9
Signed by: ktims
GPG Key ID: 11230674D69038D4
6 changed files with 787 additions and 570 deletions

1235
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ tokio-serial = "5.4.4"
bytes = "1.2.1" bytes = "1.2.1"
chrono = "0.4.23" chrono = "0.4.23"
libc = "0.2.137" libc = "0.2.137"
reqwest = { version = "0.11.13", features = ["rustls-tls"], default-features = false }
[dependencies.chrony-candm] [dependencies.chrony-candm]
git = "https://github.com/aws/chrony-candm" git = "https://github.com/aws/chrony-candm"

View File

@ -18,6 +18,13 @@
name = "hwmon0" name = "hwmon0"
sensor = "temp1_input" sensor = "temp1_input"
[sources.uccm]
enabled = true
port = "/dev/ttyUSB0"
status_interval = 10
measurement = "uccm_gpsdo"
[targets] [targets]
[targets.chrony] [targets.chrony]
enabled = true enabled = true

View File

@ -116,6 +116,7 @@ pub struct UCCMConfig {
pub baud: u32, pub baud: u32,
pub status_interval: std::time::Duration, pub status_interval: std::time::Duration,
pub timeout: std::time::Duration, pub timeout: std::time::Duration,
pub measurement: String,
} }
impl Default for UCCMConfig { impl Default for UCCMConfig {
@ -126,6 +127,7 @@ impl Default for UCCMConfig {
baud: 57600, baud: 57600,
status_interval: std::time::Duration::from_secs(10), status_interval: std::time::Duration::from_secs(10),
timeout: std::time::Duration::from_secs(1), timeout: std::time::Duration::from_secs(1),
measurement: "uccm_gpsdo".into(),
} }
} }
} }

View File

@ -3,6 +3,7 @@ mod chrony_refclock;
mod hwmon; mod hwmon;
mod uccm; mod uccm;
use chrono::NaiveDateTime;
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use env_logger::{self, Env}; use env_logger::{self, Env};
use futures::{future::join_all, prelude::*}; use futures::{future::join_all, prelude::*};
@ -46,7 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("{} v{} starting...", PROGRAM_NAME, VERSION); info!("{} v{} starting...", PROGRAM_NAME, VERSION);
let fig = load_config(Path::new(&args.config_file)); let fig = load_config(Path::new(&args.config_file));
warn!("{:?}", fig); debug!("{:?}", fig);
let config: Config = fig.extract()?; let config: Config = fig.extract()?;
let mut tasks = Vec::new(); let mut tasks = Vec::new();
@ -88,8 +89,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}; };
let uccm = if config.sources.uccm.enabled { let uccm = if config.sources.uccm.enabled {
info!("Spawning UCCMMonitor");
Some(UCCMMonitor::new(config.to_owned())) Some(UCCMMonitor::new(config.to_owned()))
} else { } else {
info!("UCCMMonitor not configured");
None None
}; };
match uccm { match uccm {
@ -114,25 +117,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut influxrx = sourcechan.subscribe(); let mut influxrx = sourcechan.subscribe();
tasks.push(tokio::spawn(async move { tasks.push(tokio::spawn(async move {
loop { loop {
let msg = influxrx.recv().await.unwrap(); match influxrx.recv().await {
match msg { Ok(msg) => match msg {
ChimemonMessage::DataPoint(dp) => { ChimemonMessage::DataPoint(dp) => {
debug!("Writing datapoint to influx: {:?}", dp); debug!("Writing datapoint to influx: {:?}", dp);
influx influx
.write(&config.influxdb.bucket, stream::iter([dp])) .write(&config.influxdb.bucket, stream::iter([dp]))
.await .await
.expect("Error writing to influxdb"); .unwrap_or_else(|e| error!("Error writing to influxdb {:?}", e));
} }
ChimemonMessage::DataPoints(dps) => { ChimemonMessage::DataPoints(dps) => {
debug!("Writing datapoints to influx: {:?}", dps); debug!("Writing datapoints to influx: {:?}", dps);
influx influx
.write(&config.influxdb.bucket, stream::iter(dps)) .write(&config.influxdb.bucket, stream::iter(dps))
.await .await
.expect("Error writing to influxdb"); .unwrap_or_else(|e| error!("Error writing to influxdb {:?}", e));
} }
ChimemonMessage::TimeReport(tr) => { ChimemonMessage::TimeReport(tr) => {
debug!("GPS TOD: {:?}", tr); debug!("GPS TOD: {:?}", tr);
} }
},
Err(e) => error!("Unable to receive from channel: {:?}", e),
} }
} }
})); }));

View File

@ -6,10 +6,13 @@ use chimemon::{
ChimemonMessage, ChimemonSource, ChimemonSourceChannel, Config, TimeReport, UCCMConfig, ChimemonMessage, ChimemonSource, ChimemonSourceChannel, Config, TimeReport, UCCMConfig,
}; };
use chrono::{Duration, NaiveDateTime, Utc}; use chrono::{Duration, NaiveDateTime, Utc};
use influxdb2::models::data_point::DataPointBuilder;
use influxdb2::models::DataPoint;
use log::{debug, info, warn}; use log::{debug, info, warn};
use std::io::Cursor; use std::io::Cursor;
use std::str; use std::str;
use std::sync::Arc; use std::sync::Arc;
use std::time::UNIX_EPOCH;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf};
use tokio::join; use tokio::join;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -31,7 +34,7 @@ pub struct UCCMMonitor {
rx: ReadHalf<SerialStream>, rx: ReadHalf<SerialStream>,
tx: WriteHalf<SerialStream>, tx: WriteHalf<SerialStream>,
pub info: Option<UCCMInfo>, pub info: Option<UCCMInfo>,
config: UCCMConfig, config: Config,
} }
#[derive(Debug)] #[derive(Debug)]
@ -41,6 +44,28 @@ pub struct UCCMTODReport {
pub flags: UCCMFlags, pub flags: UCCMFlags,
} }
impl UCCMTODReport {
pub fn as_builder(&self, measurement: &String) -> DataPointBuilder {
let mut builder = DataPoint::builder(measurement).timestamp(self.time.timestamp_nanos());
builder = builder.field("leaps", self.leaps as i64);
builder = builder.field("osc_lock", self.flags.contains(UCCMFlags::OSC_LOCK));
builder = builder.field("leap_flag", self.flags.contains(UCCMFlags::LEAP_FLAG));
builder = builder.field("init_unlock", self.flags.contains(UCCMFlags::INIT_UNLOCK));
builder = builder.field("init_no_sats", self.flags.contains(UCCMFlags::INIT_NO_SATS));
builder = builder.field(
"have_gps_time",
self.flags.contains(UCCMFlags::HAVE_GPS_TIME),
);
builder = builder.field("power_fail", self.flags.contains(UCCMFlags::POWER_FAIL));
builder = builder.field("no_gps_sync", self.flags.contains(UCCMFlags::NO_GPS_SYNC));
builder = builder.field("no_gps_sync2", self.flags.contains(UCCMFlags::NO_GPS_SYNC2));
builder = builder.field("ant_fault", self.flags.contains(UCCMFlags::NO_ANT));
builder = builder.field("gps_los", self.flags.contains(UCCMFlags::GPS_LOS));
builder
}
}
bitflags! { bitflags! {
pub struct UCCMFlags: u32 { pub struct UCCMFlags: u32 {
const OSC_LOCK = (1<<29); const OSC_LOCK = (1<<29);
@ -128,7 +153,7 @@ impl TryFrom<&[u8]> for UCCMTODReport {
debug!("TOD buffer: `{:#?}`", String::from_utf8(strbuf.to_vec())); debug!("TOD buffer: `{:#?}`", String::from_utf8(strbuf.to_vec()));
let resp: Vec<u8> = strbuf let resp: Vec<u8> = strbuf
.split(|c| *c == ' ' as u8) .split(|c| *c == ' ' as u8)
.map(|x| u8::from_str_radix(str::from_utf8(x).unwrap(), 16).unwrap()) .map(|x| u8::from_str_radix(str::from_utf8(x).unwrap_or(""), 16).unwrap_or(0))
.collect(); .collect();
let mut rdr = Cursor::new(resp); let mut rdr = Cursor::new(resp);
@ -181,19 +206,20 @@ impl UCCMMonitor {
rx, rx,
tx, tx,
info: None, info: None,
config: config.sources.uccm, config: config,
} }
} }
pub async fn send_cmd(&mut self, cmd: &[u8]) -> Result<String, std::io::Error> { pub async fn send_cmd(&mut self, cmd: &[u8]) -> Result<String, std::io::Error> {
debug!("cmd: `{:?}`", String::from_utf8_lossy(cmd)); debug!("cmd: `{:?}`", String::from_utf8_lossy(cmd));
self.tx.write_all(cmd).await; self.tx.write_all(cmd).await.unwrap();
self.tx.write(&[b'\n']).await; self.tx.write(&[b'\n']).await.unwrap();
let mut reader = BufReader::new(&mut self.rx); let mut reader = BufReader::new(&mut self.rx);
let mut resp = String::new(); let mut resp = String::new();
while !resp.contains("UCCM>") { while !resp.contains("UCCM>") {
debug!("'{}' doesn't contain UCCM>", resp);
let mut buf = Vec::new(); let mut buf = Vec::new();
reader.read_until(b'>', &mut buf).await; reader.read_until(b'>', &mut buf).await.unwrap();
resp.push_str(&String::from_utf8_lossy(&buf)); resp.push_str(&String::from_utf8_lossy(&buf));
} }
@ -238,8 +264,10 @@ async fn rx_loop(
mut rx: ReadHalf<SerialStream>, mut rx: ReadHalf<SerialStream>,
chan: ChimemonSourceChannel, chan: ChimemonSourceChannel,
state: Arc<Mutex<UCCMMonitorParseState>>, state: Arc<Mutex<UCCMMonitorParseState>>,
config: Config,
) { ) {
let mut rdbuf = BytesMut::with_capacity(1024); let mut rdbuf = BytesMut::with_capacity(1024);
let mut last_sent_report = Utc::now().naive_utc() - config.sources.uccm.status_interval;
loop { loop {
match tokio::io::AsyncReadExt::read_buf(&mut rx, &mut rdbuf).await { match tokio::io::AsyncReadExt::read_buf(&mut rx, &mut rdbuf).await {
Ok(n) => { Ok(n) => {
@ -263,7 +291,10 @@ async fn rx_loop(
Ok(tod) => { Ok(tod) => {
let sysnow = Utc::now().naive_utc(); let sysnow = Utc::now().naive_utc();
let offset = tod.time - Duration::seconds(tod.leaps as i64) - sysnow; let offset = tod.time - Duration::seconds(tod.leaps as i64) - sysnow;
debug!("System time: {:#?} GPS time: {:#?} Leaps: {:#?}", sysnow, tod.time, tod.leaps); debug!(
"System time: {:#?} GPS time: {:#?} Leaps: {:#?}",
sysnow, tod.time, tod.leaps
);
debug!("TOD offset: {}ms", offset.num_milliseconds()); debug!("TOD offset: {}ms", offset.num_milliseconds());
info!("{:#?}", tod); info!("{:#?}", tod);
let valid = tod.leaps > 0 let valid = tod.leaps > 0
@ -282,6 +313,17 @@ async fn rx_loop(
valid, valid,
})) }))
.expect("Unable to send to channel"); .expect("Unable to send to channel");
if sysnow - last_sent_report
>= Duration::from_std(config.sources.uccm.status_interval).unwrap()
{
let mut builder = tod.as_builder(&config.sources.uccm.measurement);
for (key, value) in &config.influxdb.tags {
builder = builder.tag(key, value)
}
chan.send(ChimemonMessage::DataPoint(builder.build().unwrap()))
.expect("Unable to send to channel");
last_sent_report = sysnow;
}
} }
Err(e) => { Err(e) => {
warn!("Unable to parse TOD frame: {}", e); warn!("Unable to parse TOD frame: {}", e);
@ -312,7 +354,12 @@ impl ChimemonSource for UCCMMonitor {
let state = Arc::new(Mutex::<UCCMMonitorParseState>::new( let state = Arc::new(Mutex::<UCCMMonitorParseState>::new(
UCCMMonitorParseState::Idle, UCCMMonitorParseState::Idle,
)); ));
let rx_handle = tokio::spawn(rx_loop(self.rx, chan.clone(), state.clone())); let rx_handle = tokio::spawn(rx_loop(
self.rx,
chan.clone(),
state.clone(),
self.config.clone(),
));
// let tx_handle = tokio::spawn(async move { // let tx_handle = tokio::spawn(async move {
// let mut interval = interval(self.config.status_interval); // let mut interval = interval(self.config.status_interval);
// loop { // loop {
@ -323,6 +370,6 @@ impl ChimemonSource for UCCMMonitor {
// } // }
// }); // });
join!(rx_handle); join!(rx_handle).0.unwrap();
} }
} }