Better stats

This commit is contained in:
Данила Горнушко 2024-05-31 17:40:47 +03:00
parent bdc817f277
commit 8c4537086c
3 changed files with 160 additions and 119 deletions

9
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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!")
);
}