refactor, prs10 stats, etc
This commit is contained in:
629
src/sources/uccm.rs
Normal file
629
src/sources/uccm.rs
Normal file
@@ -0,0 +1,629 @@
|
||||
use crate::{
|
||||
ChimemonMessage, ChimemonSource, ChimemonSourceChannel, Config, SourceMetric,
|
||||
SourceReportDetails, TimeReport,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use bitflags::bitflags;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use bytes::{Buf, BytesMut};
|
||||
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
||||
use figment::value::Map;
|
||||
use influxdb2::models::DataPoint;
|
||||
use influxdb2::models::data_point::DataPointBuilder;
|
||||
use itertools::Itertools;
|
||||
use std::io::{BufRead, Cursor};
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf};
|
||||
use tokio::join;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::sleep;
|
||||
use tokio_serial::{SerialPort, SerialStream};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
pub const GPS_EPOCH: i64 = 315964800; // Doesn't seem possible to have a const DateTime object
|
||||
pub type UccmEndian = BigEndian;
|
||||
|
||||
pub enum UCCMMonitorParseState {
|
||||
Idle,
|
||||
ReadStatus,
|
||||
ReadLoopDiag,
|
||||
ReadTOD,
|
||||
}
|
||||
|
||||
pub struct UCCMMonitor {
|
||||
// pub port: SerialStream,
|
||||
rx: ReadHalf<SerialStream>,
|
||||
tx: WriteHalf<SerialStream>,
|
||||
pub info: Option<UCCMInfo>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMTODReport {
|
||||
pub time: DateTime<Utc>, // TAI timestamp
|
||||
pub leaps: i8,
|
||||
pub flags: UCCMFlags,
|
||||
}
|
||||
|
||||
impl SourceReportDetails for UCCMTODReport {
|
||||
fn is_healthy(&self) -> bool {
|
||||
self.flags.contains(UCCMFlags::OSC_LOCK)
|
||||
&& self.flags.contains(UCCMFlags::HAVE_GPS_TIME)
|
||||
&& !self.flags.contains(UCCMFlags::INIT_UNLOCK)
|
||||
&& !self.flags.contains(UCCMFlags::INIT_NO_SATS)
|
||||
&& !self.flags.contains(UCCMFlags::POWER_FAIL)
|
||||
&& !self.flags.contains(UCCMFlags::NO_GPS_SYNC)
|
||||
&& !self.flags.contains(UCCMFlags::NO_GPS_SYNC2)
|
||||
&& !self.flags.contains(UCCMFlags::NO_ANT)
|
||||
&& !self.flags.contains(UCCMFlags::GPS_LOS)
|
||||
}
|
||||
|
||||
fn to_metrics(&self) -> Vec<SourceMetric> {
|
||||
let no_tags = Arc::new(vec![]);
|
||||
vec![
|
||||
SourceMetric::new_int("leaps", self.leaps as i64, no_tags.clone()),
|
||||
SourceMetric::new_bool(
|
||||
"osc_lock",
|
||||
self.flags.contains(UCCMFlags::OSC_LOCK),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"leap_flag",
|
||||
self.flags.contains(UCCMFlags::LEAP_FLAG),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"init_unlock",
|
||||
self.flags.contains(UCCMFlags::INIT_UNLOCK),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"init_no_sats",
|
||||
self.flags.contains(UCCMFlags::INIT_NO_SATS),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"have_gps_time",
|
||||
self.flags.contains(UCCMFlags::HAVE_GPS_TIME),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"power_fail",
|
||||
self.flags.contains(UCCMFlags::POWER_FAIL),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"no_gps_sync",
|
||||
self.flags.contains(UCCMFlags::NO_GPS_SYNC),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"no_gps_sync2",
|
||||
self.flags.contains(UCCMFlags::NO_GPS_SYNC2),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"ant_fault",
|
||||
self.flags.contains(UCCMFlags::NO_ANT),
|
||||
no_tags.clone(),
|
||||
),
|
||||
SourceMetric::new_bool(
|
||||
"gps_los",
|
||||
self.flags.contains(UCCMFlags::GPS_LOS),
|
||||
no_tags.clone(),
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl UCCMTODReport {
|
||||
pub fn as_builder(&self, measurement: &String, tags: &Map<String, String>) -> DataPointBuilder {
|
||||
let mut builder =
|
||||
DataPoint::builder(measurement).timestamp(self.time.timestamp_nanos_opt().unwrap());
|
||||
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 = tags
|
||||
.iter()
|
||||
.fold(builder, |builder, (k, v)| builder.tag(k, v));
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMLoopDiagReport {
|
||||
pub ocxo: f32,
|
||||
}
|
||||
|
||||
impl UCCMLoopDiagReport {
|
||||
pub fn as_builder(&self, measurement: &String, tags: &Map<String, String>) -> DataPointBuilder {
|
||||
let mut builder = DataPoint::builder(measurement);
|
||||
builder = builder.field("ocxo_offset", self.ocxo as f64);
|
||||
builder = tags
|
||||
.iter()
|
||||
.fold(builder, |builder, (k, v)| builder.tag(k, v));
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMGpsSvTracking {
|
||||
pub sv: u8,
|
||||
pub cno: u8,
|
||||
}
|
||||
|
||||
impl UCCMGpsSvTracking {
|
||||
fn as_builder(&self, measurement: &String, tags: &Map<String, String>) -> DataPointBuilder {
|
||||
let mut builder = DataPoint::builder(measurement)
|
||||
.field("sv_cno", self.cno as i64)
|
||||
.tag("sv_id", self.sv.to_string());
|
||||
builder = tags
|
||||
.iter()
|
||||
.fold(builder, |builder, (k, v)| builder.tag(k, v));
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMGPSSatsReport {
|
||||
pub tracked_svs: Vec<UCCMGpsSvTracking>,
|
||||
}
|
||||
|
||||
impl UCCMGPSSatsReport {
|
||||
pub fn build(&self, measurement: &String, tags: &Map<String, String>) -> Vec<DataPoint> {
|
||||
self.tracked_svs
|
||||
.iter()
|
||||
.map(|sv| sv.as_builder(measurement, tags))
|
||||
.map(|b| b.build().unwrap())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMFlags: u32 {
|
||||
const OSC_LOCK = (1<<29);
|
||||
const LEAP_FLAG = (1<<25);
|
||||
const INIT_UNLOCK = (1<<24);
|
||||
|
||||
const INIT_NO_SATS = (1<<19);
|
||||
const HAVE_GPS_TIME = (1<<18);
|
||||
const POWER_FAIL = (1<<17);
|
||||
|
||||
const MODEL_SYMMETRICOM = (1<<15);
|
||||
const MODEL_TRIMBLE = (1<<14);
|
||||
const NO_GPS_SYNC = (1<<11);
|
||||
const NO_GPS_SYNC2 = (1<<9);
|
||||
|
||||
const MODEL_SYMMETRICOM2 = (1<<7);
|
||||
const MODEL_TRIMBLE2 = (1<<6);
|
||||
const NO_ANT = (1<<5);
|
||||
const GPS_LOS = (1<<4);
|
||||
}
|
||||
}
|
||||
|
||||
/* FLAGS:
|
||||
MSB 31 x
|
||||
30 ALWAYS 1
|
||||
29 oscillator locked
|
||||
28..26 x
|
||||
25 leap flag
|
||||
24 initial unlock
|
||||
|
||||
23..20 x
|
||||
19 initialized no sats (trimble)
|
||||
18 have gps time flag
|
||||
17 power fail
|
||||
16 x
|
||||
|
||||
15..14 model/protocol ( 01 = trimble 10 = symmetricom )
|
||||
13..12 x
|
||||
11 no gps sync
|
||||
10 always 1
|
||||
9 no gps sync
|
||||
8 always 1
|
||||
|
||||
7..6 model/protocol ( 01 = trimble 10 = symmetricom )
|
||||
5 no ant
|
||||
4 gps los
|
||||
LSB 3..0 x
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GPSSVInfo {
|
||||
pub tracked: bool,
|
||||
pub in_view: bool,
|
||||
pub prn: u8,
|
||||
pub elevation: usize,
|
||||
pub azimuth: usize,
|
||||
pub cn0: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UCCMStatusReport {
|
||||
pub tfom: u8,
|
||||
pub ffom: u8,
|
||||
pub gps_phase: f32,
|
||||
pub gps_pps_valid: bool,
|
||||
pub gps_svs: [GPSSVInfo; 32],
|
||||
pub gps_time: NaiveDateTime,
|
||||
pub ant_voltage: f32,
|
||||
pub ant_current: f32,
|
||||
pub temp: f32,
|
||||
pub efc_dac: u32,
|
||||
pub freq_error: f32,
|
||||
}
|
||||
|
||||
impl SourceReportDetails for UCCMStatusReport {
|
||||
fn is_healthy(&self) -> bool {
|
||||
self.gps_pps_valid
|
||||
}
|
||||
fn to_metrics(&self) -> Vec<SourceMetric> {
|
||||
let no_tags = Arc::new(vec![]);
|
||||
vec![
|
||||
SourceMetric::new_int("tfom", self.tfom as i64, no_tags.clone()),
|
||||
SourceMetric::new_int("ffom", self.ffom as i64, no_tags.clone()),
|
||||
SourceMetric::new_float("gps_phase", self.gps_phase as f64, no_tags.clone()),
|
||||
// TODO: sv info
|
||||
// TOOD: timestamp
|
||||
SourceMetric::new_float("ant_voltage", self.ant_voltage as f64, no_tags.clone()),
|
||||
SourceMetric::new_float("ant_current", self.ant_current as f64, no_tags.clone()),
|
||||
SourceMetric::new_float("temp", self.temp as f64, no_tags.clone()),
|
||||
SourceMetric::new_int("efc_dac", self.efc_dac as i64, no_tags.clone()),
|
||||
SourceMetric::new_float("freq_error", self.freq_error as f64, no_tags.clone()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UCCMInfo {
|
||||
pub vendor: String,
|
||||
pub model: String,
|
||||
pub serial: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for UCCMTODReport {
|
||||
type Error = String;
|
||||
fn try_from(strbuf: &[u8]) -> Result<Self, Self::Error> {
|
||||
debug!("TOD buffer: `{:#?}`", String::from_utf8(strbuf.to_vec()));
|
||||
let resp: Vec<u8> = strbuf
|
||||
.split(|c| *c == b' ')
|
||||
.map(|x| u8::from_str_radix(str::from_utf8(x).unwrap_or(""), 16).unwrap_or(0))
|
||||
.collect();
|
||||
let mut rdr = Cursor::new(resp);
|
||||
|
||||
// Sync flag
|
||||
let c = rdr.read_u16::<UccmEndian>().unwrap();
|
||||
if c != 0xc500 {
|
||||
return Err(format!("Missing start delimter (got: `{}`)", c));
|
||||
}
|
||||
|
||||
// Consume 25 unknown bytes
|
||||
rdr.advance(25);
|
||||
let time = rdr.read_u32::<UccmEndian>().unwrap();
|
||||
rdr.advance(1); // consume padding
|
||||
let leaps = rdr.read_i8().unwrap();
|
||||
let flags = rdr.read_u32::<UccmEndian>().unwrap();
|
||||
debug!("Flags: {}", flags);
|
||||
rdr.advance(6); // Consume padding and checksum, don't check it
|
||||
let c = rdr.read_u8().unwrap();
|
||||
if c != 0xca {
|
||||
return Err(format!("Missing end delimiter (got: `{}`)", c));
|
||||
}
|
||||
debug!("TOD time: {} leaps: {} flags: {}", time, leaps, flags);
|
||||
|
||||
Ok(UCCMTODReport {
|
||||
time: DateTime::from_timestamp(GPS_EPOCH + time as i64, 0).unwrap(),
|
||||
leaps,
|
||||
flags: UCCMFlags::from_bits_truncate(flags),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for UCCMLoopDiagReport {
|
||||
type Error = std::io::Error;
|
||||
fn try_from(strbuf: &str) -> Result<Self, Self::Error> {
|
||||
debug!("LoopDiag buffer: `{:#?}`", strbuf);
|
||||
|
||||
if &strbuf[0..4] != "OCXO" {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Invalid response (expected `OCXO`)",
|
||||
))
|
||||
} else {
|
||||
let mut cursor = Cursor::new(strbuf);
|
||||
let mut lines = BufRead::lines(&mut cursor);
|
||||
let ocxo_line = lines.next().ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"No lines!",
|
||||
))??;
|
||||
let ocxo_val = ocxo_line
|
||||
.split(':')
|
||||
.nth(1)
|
||||
.ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"no colon!",
|
||||
))?
|
||||
.trim();
|
||||
let ocxo_val_f = ocxo_val
|
||||
.parse::<f32>()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
Ok(UCCMLoopDiagReport { ocxo: ocxo_val_f })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for UCCMGPSSatsReport {
|
||||
type Error = std::io::Error;
|
||||
fn try_from(strbuf: &str) -> Result<Self, Self::Error> {
|
||||
let resp = strbuf.split_once('\n').unwrap().0.trim();
|
||||
let (nsats, cnos) = resp.split_once(' ').ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Invalid response (expected `NSATS CNOS`)",
|
||||
))?;
|
||||
let nsats = nsats.parse::<u8>().map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Invalid number of sats ({e})"),
|
||||
)
|
||||
})?;
|
||||
let tracked_svs = cnos
|
||||
.split(',')
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.map(|chunk| chunk.collect_tuple().unwrap())
|
||||
.inspect(|chunk| debug!(" chunk: {:?}", chunk))
|
||||
.map(|(sv_s, cno_s)| UCCMGpsSvTracking {
|
||||
sv: sv_s.parse().unwrap(),
|
||||
cno: cno_s.parse().unwrap(),
|
||||
})
|
||||
.collect_vec();
|
||||
assert_eq!(nsats as usize, tracked_svs.len());
|
||||
Ok(UCCMGPSSatsReport { tracked_svs })
|
||||
}
|
||||
}
|
||||
|
||||
impl UCCMMonitor {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let builder = tokio_serial::new(&config.sources.uccm.port, config.sources.uccm.baud)
|
||||
.timeout(config.sources.uccm.timeout)
|
||||
.data_bits(tokio_serial::DataBits::Eight)
|
||||
.parity(tokio_serial::Parity::None)
|
||||
.stop_bits(tokio_serial::StopBits::One)
|
||||
.flow_control(tokio_serial::FlowControl::None);
|
||||
let mut port = SerialStream::open(&builder).expect("Must be able to open serial port");
|
||||
port.set_exclusive(true).expect("Can't lock serial port");
|
||||
info!(
|
||||
"Opened serial port {}@{}",
|
||||
port.name().unwrap(),
|
||||
port.baud_rate().unwrap()
|
||||
);
|
||||
let (rx, tx) = tokio::io::split(port);
|
||||
UCCMMonitor {
|
||||
// port,
|
||||
rx,
|
||||
tx,
|
||||
info: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_cmd(&mut self, cmd: &[u8]) -> Result<String, std::io::Error> {
|
||||
debug!("cmd: `{:?}`", String::from_utf8_lossy(cmd));
|
||||
self.tx.write_all(cmd).await.unwrap();
|
||||
self.tx.write_u8(b'\n').await.unwrap();
|
||||
let mut reader = BufReader::new(&mut self.rx);
|
||||
let mut resp = String::new();
|
||||
while !resp.contains("UCCM>") {
|
||||
debug!("'{}' doesn't contain UCCM>", resp);
|
||||
let mut buf = Vec::new();
|
||||
reader.read_until(b'>', &mut buf).await.unwrap();
|
||||
resp.push_str(&String::from_utf8_lossy(&buf));
|
||||
}
|
||||
|
||||
// Remove the command we sent from the response
|
||||
resp.replace_range(0..resp.find('\n').unwrap_or(0) + 1, "");
|
||||
resp = resp.replace("\r", "");
|
||||
debug!("cmd response: `{:?}`", resp);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn get_info(&mut self) -> Result<(), std::io::Error> {
|
||||
self.send_cmd(b"SCPI").await.unwrap_or_default();
|
||||
self.send_cmd(b"TOD DI").await.unwrap_or_default();
|
||||
sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
let resp = self.send_cmd(b"*IDN?").await.unwrap_or_default();
|
||||
let info: Vec<&str> = resp.lines().next().unwrap().split(',').collect();
|
||||
debug!("Response length: {}", info.len());
|
||||
if info.len() != 4 {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Unsupported,
|
||||
"Unexpected *IDN? response",
|
||||
));
|
||||
}
|
||||
self.info = Some(UCCMInfo {
|
||||
vendor: info[0].into(),
|
||||
model: info[1].into(),
|
||||
serial: info[2].into(),
|
||||
version: info[3].into(),
|
||||
});
|
||||
info!(
|
||||
"Found {} {} s/n: {} version: {}",
|
||||
self.info.as_ref().unwrap().vendor,
|
||||
self.info.as_ref().unwrap().model,
|
||||
self.info.as_ref().unwrap().serial,
|
||||
self.info.as_ref().unwrap().version
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rx_loop(
|
||||
mut self,
|
||||
chan: ChimemonSourceChannel,
|
||||
state: Arc<Mutex<UCCMMonitorParseState>>,
|
||||
) {
|
||||
let mut rdbuf = BytesMut::with_capacity(1024);
|
||||
let mut last_loop_diag: Option<UCCMLoopDiagReport> = None;
|
||||
let mut last_gps_sats: Option<UCCMGPSSatsReport> = None;
|
||||
|
||||
let mut last_sent_report = Utc::now() - self.config.sources.uccm.status_interval;
|
||||
|
||||
loop {
|
||||
match tokio::io::AsyncReadExt::read_buf(&mut self.rx, &mut rdbuf).await {
|
||||
Ok(n) => {
|
||||
if n == 0 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
match *state.lock().await {
|
||||
UCCMMonitorParseState::Idle => {
|
||||
while !rdbuf.starts_with(b"c5 00") && rdbuf.remaining() > 0 {
|
||||
rdbuf.advance(1);
|
||||
}
|
||||
if rdbuf.len() < (44 * 2 + 43) {
|
||||
// TOD frame is 44 bytes, plus 43 spaces
|
||||
continue;
|
||||
};
|
||||
let frame = rdbuf.split_to(44 * 2 + 43);
|
||||
match UCCMTODReport::try_from(&frame[..]) {
|
||||
Ok(tod) => {
|
||||
let sysnow = Utc::now();
|
||||
let offset = tod.time - Duration::seconds(tod.leaps as i64) - sysnow;
|
||||
debug!(
|
||||
"System time: {:#?} GPS time: {:#?} Leaps: {:#?}",
|
||||
sysnow, tod.time, tod.leaps
|
||||
);
|
||||
debug!("TOD offset: {}ms", offset.num_milliseconds());
|
||||
info!("{:#?}", tod);
|
||||
let valid = tod.leaps > 0
|
||||
&& tod
|
||||
.flags
|
||||
.contains(UCCMFlags::OSC_LOCK | UCCMFlags::HAVE_GPS_TIME);
|
||||
chan.send(ChimemonMessage::TimeReport(TimeReport {
|
||||
system_time: sysnow,
|
||||
offset,
|
||||
leaps: tod.leaps as isize,
|
||||
leap_flag: tod.flags.contains(UCCMFlags::LEAP_FLAG),
|
||||
valid,
|
||||
}))
|
||||
.expect("Unable to send to channel");
|
||||
if sysnow - last_sent_report
|
||||
>= Duration::from_std(self.config.sources.uccm.status_interval)
|
||||
.unwrap()
|
||||
{
|
||||
let mut points = vec![
|
||||
tod.as_builder(
|
||||
&self.config.sources.uccm.measurement,
|
||||
&self.config.influxdb.tags,
|
||||
)
|
||||
.build()
|
||||
.unwrap(),
|
||||
];
|
||||
if let Some(loop_diag) = &last_loop_diag {
|
||||
points.push(
|
||||
loop_diag
|
||||
.as_builder(
|
||||
&self.config.sources.uccm.measurement,
|
||||
&self.config.influxdb.tags,
|
||||
)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
if let Some(gps_sats) = &last_gps_sats {
|
||||
points.extend(gps_sats.build(
|
||||
&self.config.sources.uccm.measurement,
|
||||
&self.config.influxdb.tags,
|
||||
));
|
||||
}
|
||||
|
||||
chan.send(ChimemonMessage::DataPoints(points))
|
||||
.expect("Unable to send to channel");
|
||||
last_sent_report = sysnow;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Unable to parse TOD frame: {}", e);
|
||||
rdbuf.clear();
|
||||
}
|
||||
}
|
||||
let loop_diag_resp = self.send_cmd(b"DIAG:LOOP?").await.unwrap();
|
||||
let gps_sats_resp = self.send_cmd(b"GPSSAT").await.unwrap();
|
||||
let loop_report = UCCMLoopDiagReport::try_from(loop_diag_resp.as_str());
|
||||
let gps_report = UCCMGPSSatsReport::try_from(gps_sats_resp.as_str());
|
||||
if let Ok(loop_report) = loop_report {
|
||||
last_loop_diag = Some(loop_report)
|
||||
} else {
|
||||
warn!(
|
||||
"Unable to parse loop diag report `{}`: {}",
|
||||
loop_diag_resp,
|
||||
loop_report.unwrap_err()
|
||||
);
|
||||
}
|
||||
if let Ok(gps_report) = gps_report {
|
||||
last_gps_sats = Some(gps_report)
|
||||
} else {
|
||||
warn!(
|
||||
"Unable to parse GPS sats report `{}`: {}",
|
||||
gps_sats_resp,
|
||||
gps_report.unwrap_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
UCCMMonitorParseState::ReadLoopDiag => todo!(),
|
||||
UCCMMonitorParseState::ReadStatus => todo!(),
|
||||
UCCMMonitorParseState::ReadTOD => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChimemonSource for UCCMMonitor {
|
||||
async fn run(mut self, chan: ChimemonSourceChannel) {
|
||||
info!("UCCM task starting");
|
||||
if self.get_info().await.is_err() {
|
||||
warn!("Error starting UCCM");
|
||||
return;
|
||||
}
|
||||
self.send_cmd(b"SCPI").await.unwrap();
|
||||
self.send_cmd(b"SYSTem:PRESet").await.unwrap();
|
||||
self.send_cmd(b"OUTPut:TP:SELection PP1S").await.unwrap();
|
||||
self.send_cmd(b"TESTMODE EN").await.unwrap();
|
||||
self.send_cmd(b"TOD EN").await.unwrap();
|
||||
let state = Arc::new(Mutex::<UCCMMonitorParseState>::new(
|
||||
UCCMMonitorParseState::Idle,
|
||||
));
|
||||
|
||||
let rx_handle = tokio::spawn(self.rx_loop(chan.clone(), state.clone()));
|
||||
|
||||
// let tx_handle = tokio::spawn(async move {
|
||||
// let mut interval = interval(self.config.status_interval);
|
||||
// loop {
|
||||
// interval.tick().await;
|
||||
// let wfut = self.tx.write_all(b"SYST:STAT?\n");
|
||||
// *state.lock().await = UCCMMonitorParseState::ReadStatus;
|
||||
// wfut.await;
|
||||
// }
|
||||
// });
|
||||
|
||||
join!(rx_handle).0.unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user