Better stats
This commit is contained in:
parent
bdc817f277
commit
8c4537086c
3 changed files with 160 additions and 119 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -75,12 +75,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
|
|
@ -271,9 +265,8 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||
|
||||
[[package]]
|
||||
name = "hailsens_logger"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "hailsens_logger"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -12,6 +12,5 @@ colored = "2.1.0"
|
|||
hound = "3.5.1"
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
ctrlc = "3.2.0"
|
||||
anyhow = "1.0.86"
|
||||
crc = "3.2.1"
|
||||
textplots = "0.8.6"
|
||||
|
|
|
|||
267
src/main.rs
267
src/main.rs
|
|
@ -1,5 +1,4 @@
|
|||
use anyhow::{Context, Result};
|
||||
use chrono::Local;
|
||||
use chrono::{Local, Timelike};
|
||||
use clap::{arg, Parser};
|
||||
use crc::{Crc, CRC_16_IBM_3740};
|
||||
use env_logger::Builder;
|
||||
|
|
@ -9,29 +8,63 @@ use serialport::SerialPort;
|
|||
use std::collections::HashMap;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::slice;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{fmt, slice};
|
||||
use textplots::{Chart, Plot, Shape};
|
||||
|
||||
const MAX_DATA_LEN: usize = 1024 * 2 + 4 + 2;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Stats {
|
||||
goert_since_startup: u32,
|
||||
goert_in_10min: u32,
|
||||
amp_since_startup: u32,
|
||||
amp_in_10min: u32,
|
||||
#[derive(Clone, Copy)]
|
||||
struct StatsOnDetect {
|
||||
hail_since_startup: u32,
|
||||
hail_in_10min: u32,
|
||||
noise_since_startup: u32,
|
||||
noise_in_10min: u32,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
#[derive(Clone, Copy)]
|
||||
struct Stats10Min {
|
||||
hail: u32,
|
||||
noise: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum StatsOptions {
|
||||
DetectedNew(StatsOnDetect),
|
||||
Stats(Stats10Min),
|
||||
None,
|
||||
}
|
||||
|
||||
impl fmt::Display for StatsOnDetect {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"New signal detected! Hail (total/10 min): {}/{}. Noise detected (total/10 min): {}/{}.",
|
||||
self.hail_since_startup,
|
||||
self.hail_in_10min,
|
||||
self.noise_since_startup,
|
||||
self.noise_in_10min
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stats10Min {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"In last 10 min: Hail detected: {}. Noise detected: {}.",
|
||||
self.hail, self.noise,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StatsOnDetect {
|
||||
fn eq(&self, other: &Self, noise_logging: bool) -> bool {
|
||||
let hail_cmp = (self.goert_in_10min == other.goert_in_10min)
|
||||
&& (self.goert_since_startup == other.goert_since_startup);
|
||||
let noise_cmp = (self.amp_in_10min == other.amp_in_10min)
|
||||
&& (self.amp_since_startup == other.amp_since_startup);
|
||||
let hail_cmp = self.hail_since_startup == other.hail_since_startup;
|
||||
let noise_cmp = self.noise_since_startup == other.noise_since_startup;
|
||||
hail_cmp && (!noise_logging || noise_cmp)
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +163,7 @@ fn main() {
|
|||
.unwrap();
|
||||
let command = format!("@{} name\n", args.device_id);
|
||||
let response = process_command(&mut port, command).unwrap_or_default();
|
||||
debug!("We got some response: {}", response);
|
||||
debug!("We got some response: {}", response.trim());
|
||||
if response.contains("'HAILSENS'") {
|
||||
info!("Detected hailsens.")
|
||||
} else {
|
||||
|
|
@ -163,17 +196,7 @@ fn main() {
|
|||
.expect("Couldn't get winsize") as usize;
|
||||
debug!("Expected buf size is: {}", exp_buf_size(winsize));
|
||||
|
||||
info!("Setting correct date/time on hailsens...");
|
||||
let formatted_date_time = Local::now().format("%Y-%m-%dT%H:%M:%S").to_string();
|
||||
let request_str = format!("@{} date {}\n", args.device_id, formatted_date_time);
|
||||
let response = process_command(&mut port, request_str).expect("Couldn't set date/time!");
|
||||
info!(
|
||||
"Hailsens date/time: {}",
|
||||
response
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.expect("Couldn't get date/time!")
|
||||
);
|
||||
sync_datetime(&mut port, args.device_id);
|
||||
|
||||
if args.reset_counters {
|
||||
info!("Resetting counters...");
|
||||
|
|
@ -198,89 +221,97 @@ fn main() {
|
|||
.unwrap();
|
||||
debug!("Press Ctrl-C to stop");
|
||||
|
||||
let mut last_chunk_id: Option<Stats> = None;
|
||||
let mut prev_stats: Option<StatsOnDetect> = None;
|
||||
let mut err_counter: u32 = 0;
|
||||
let samples_needed = args.save_to_wav || args.plot_data;
|
||||
let mut needed_retry = false;
|
||||
let mut last_hour_value = Local::now().hour();
|
||||
|
||||
while running.load(Ordering::SeqCst) {
|
||||
// getting stats
|
||||
let cur_chunk_id = match get_stats(&mut port, args.device_id) {
|
||||
Err(e) => {
|
||||
error!("Couldn't read adcstats: {}", e);
|
||||
// update hailsens datetime once in an hour
|
||||
if last_hour_value != Local::now().hour() {
|
||||
sync_datetime(&mut port, args.device_id);
|
||||
last_hour_value = Local::now().hour();
|
||||
}
|
||||
match get_stats(&mut port, args.device_id) {
|
||||
StatsOptions::None => {
|
||||
error!("Couldn't read adcstats!");
|
||||
handle_error_with_timeout(&mut err_counter);
|
||||
continue;
|
||||
}
|
||||
Ok(value) => {
|
||||
StatsOptions::Stats(val) => info!("{val}"),
|
||||
StatsOptions::DetectedNew(cur_stats) => {
|
||||
err_counter = err_counter.saturating_sub(1);
|
||||
value
|
||||
if prev_stats.map_or(true, |v| !v.eq(&cur_stats, args.noise_logging)) {
|
||||
info!("{cur_stats}");
|
||||
if samples_needed
|
||||
&& prev_stats.map_or(args.first_sample, |v| {
|
||||
v.hail_since_startup != cur_stats.hail_since_startup
|
||||
})
|
||||
{
|
||||
if needed_retry {
|
||||
info!("Retrying...");
|
||||
} else {
|
||||
debug!("New data available: {}", cur_stats.hail_since_startup);
|
||||
debug!("Getting new chunk!");
|
||||
}
|
||||
match get_chunk(&mut port, args.device_id, args.baud_rate, winsize) {
|
||||
Some(data) => {
|
||||
debug!("Received new data with len: {}", data.len());
|
||||
|
||||
if args.plot_data {
|
||||
let points: Vec<(f32, f32)> = data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &x)| (i as f32, x as f32))
|
||||
.collect();
|
||||
Chart::new(280, 50, 0.0, 100.0)
|
||||
.lineplot(&Shape::Lines(&points))
|
||||
.display();
|
||||
}
|
||||
|
||||
if args.save_to_wav {
|
||||
let path = Path::new(&args.path);
|
||||
let filename =
|
||||
format!("{}_{}.wav", args.name, current_timestamp_ms());
|
||||
let path = path.join(filename);
|
||||
|
||||
// Create a new WAV file
|
||||
let mut writer = WavWriter::create(&path, spec)
|
||||
.expect("Can't create new WAV file!");
|
||||
let mut i16_writer = writer.get_i16_writer(data.len() as u32);
|
||||
for sample in data {
|
||||
unsafe {
|
||||
i16_writer.write_sample_unchecked(sample);
|
||||
}
|
||||
}
|
||||
i16_writer
|
||||
.flush()
|
||||
.expect("Something went wrong while writing WAV file");
|
||||
info!(
|
||||
"Sample: {}. WAV file {} has been written.",
|
||||
cur_stats.hail_since_startup,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
err_counter = err_counter.saturating_sub(1);
|
||||
needed_retry = false;
|
||||
}
|
||||
_ => {
|
||||
error!("Couldn't receive new data!");
|
||||
handle_error_with_timeout(&mut err_counter);
|
||||
needed_retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !needed_retry {
|
||||
prev_stats = Some(cur_stats);
|
||||
}
|
||||
}
|
||||
};
|
||||
if last_chunk_id.map_or(true, |v| !v.eq(&cur_chunk_id, args.noise_logging)) {
|
||||
info!(
|
||||
"Hail detected (total/10 min): {}/{}. Noise detected (total/10 min): {}/{}.",
|
||||
cur_chunk_id.goert_since_startup,
|
||||
cur_chunk_id.goert_in_10min,
|
||||
cur_chunk_id.amp_since_startup,
|
||||
cur_chunk_id.amp_in_10min
|
||||
);
|
||||
if samples_needed && (last_chunk_id.is_some() || args.first_sample) {
|
||||
if needed_retry {
|
||||
info!("Retrying...");
|
||||
} else {
|
||||
debug!("New data available: {}", cur_chunk_id.goert_since_startup);
|
||||
debug!("Getting new chunk!");
|
||||
}
|
||||
match get_chunk(&mut port, args.device_id, args.baud_rate, winsize) {
|
||||
Some(data) => {
|
||||
debug!("Received new data with len: {}", data.len());
|
||||
|
||||
if args.plot_data {
|
||||
let points: Vec<(f32, f32)> = data.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &x)| (i as f32, x as f32))
|
||||
.collect();
|
||||
Chart::new(280, 50, 0.0, 100.0).lineplot(&Shape::Lines(&points)).display();
|
||||
}
|
||||
|
||||
if args.save_to_wav {
|
||||
let path = Path::new(&args.path);
|
||||
let filename = format!("{}_{}.wav", args.name, current_timestamp_ms());
|
||||
let path = path.join(filename);
|
||||
|
||||
// Create a new WAV file
|
||||
let mut writer =
|
||||
WavWriter::create(&path, spec).expect("Can't create new WAV file!");
|
||||
let mut i16_writer = writer.get_i16_writer(data.len() as u32);
|
||||
for sample in data {
|
||||
unsafe {
|
||||
i16_writer.write_sample_unchecked(sample);
|
||||
}
|
||||
}
|
||||
i16_writer
|
||||
.flush()
|
||||
.expect("Something went wrong while writing WAV file");
|
||||
info!(
|
||||
"Sample: {}. WAV file {} has been written.",
|
||||
cur_chunk_id.goert_since_startup,
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
err_counter = err_counter.saturating_sub(1);
|
||||
needed_retry = false;
|
||||
}
|
||||
_ => {
|
||||
error!("Couldn't receive new data!");
|
||||
handle_error_with_timeout(&mut err_counter);
|
||||
needed_retry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !needed_retry {
|
||||
last_chunk_id = Some(cur_chunk_id);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis((1000 / args.request_rate) as u64));
|
||||
}
|
||||
}
|
||||
|
|
@ -294,20 +325,24 @@ fn handle_error_with_timeout(err_counter: &mut u32) {
|
|||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
fn get_stats(port: &mut Box<dyn SerialPort>, id: u32) -> Result<Stats> {
|
||||
fn get_stats(port: &mut Box<dyn SerialPort>, id: u32) -> StatsOptions {
|
||||
let command = format!("@{} adcstats\n", id);
|
||||
let response = process_command(port, command).unwrap_or_default();
|
||||
let mut split = response.split_whitespace();
|
||||
let goert_since_startup: u32 = split.next().context("Missing value")?.parse()?;
|
||||
let goert_in_10min: u32 = split.next().context("Missing value")?.parse()?;
|
||||
let amp_since_startup: u32 = split.next().context("Missing value")?.parse()?;
|
||||
let amp_in_10min: u32 = split.next().context("Missing value")?.parse()?;
|
||||
Ok(Stats {
|
||||
goert_since_startup,
|
||||
goert_in_10min,
|
||||
amp_since_startup,
|
||||
amp_in_10min,
|
||||
})
|
||||
let split: Vec<&str> = response.split_whitespace().collect();
|
||||
let num_parse_err = "Can't parse num in ADC stats!";
|
||||
match split.len() {
|
||||
2 => StatsOptions::Stats(Stats10Min {
|
||||
hail: split[0].parse().expect(num_parse_err),
|
||||
noise: split[1].parse().expect(num_parse_err),
|
||||
}),
|
||||
4 => StatsOptions::DetectedNew(StatsOnDetect {
|
||||
hail_since_startup: split[0].parse().expect(num_parse_err),
|
||||
hail_in_10min: split[1].parse().expect(num_parse_err),
|
||||
noise_since_startup: split[2].parse().expect(num_parse_err),
|
||||
noise_in_10min: split[3].parse().expect(num_parse_err),
|
||||
}),
|
||||
_ => StatsOptions::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_set_param(
|
||||
|
|
@ -551,3 +586,17 @@ fn current_timestamp_ms() -> u128 {
|
|||
.expect("Time went backwards");
|
||||
since_the_epoch.as_millis()
|
||||
}
|
||||
|
||||
fn sync_datetime(port: &mut Box<dyn SerialPort>, id: u32) {
|
||||
info!("Setting correct date/time on hailsens...");
|
||||
let formatted_date_time = Local::now().format("%Y-%m-%dT%H:%M:%S").to_string();
|
||||
let request_str = format!("@{} date {}\n", id, formatted_date_time);
|
||||
let response = process_command(port, request_str).expect("Couldn't set date/time!");
|
||||
info!(
|
||||
"Hailsens date/time: {}",
|
||||
response
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.expect("Couldn't get date/time!")
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue