initial integration with telem viz

This commit is contained in:
2026-01-02 15:36:50 -05:00
parent 59b5679dda
commit 275cb07c4c
16 changed files with 1408 additions and 190 deletions

View File

@@ -5,10 +5,16 @@ edition = "2024"
[dependencies]
anyhow = { workspace = true }
fern = { workspace = true }
log = { workspace = true }
chrono = { workspace = true }
ctrlc = { workspace = true }
serde = { workspace = true }
derive_more = {workspace = true, features = ["display"]}
fern = { workspace = true }
log = { workspace = true }
postcard = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, optional = true, features = ["net"] }
tokio-util = {workspace = true, optional = true}
[features]
async = [ "dep:tokio", "dep:tokio-util" ]

View File

@@ -50,6 +50,12 @@ pub struct CommandHeader<'a> {
pub data: &'a [u8],
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OwnedCommandHeader {
pub name: String,
pub data: Vec<u8>,
}
pub trait Command: Serialize + DeserializeOwned {}
impl Command for () {}

View File

@@ -1,10 +1,9 @@
#![warn(clippy::all, clippy::pedantic)]
use log::info;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub mod command;
pub mod logger;
pub mod on_drop;
pub mod telemetry;
pub mod udp;
@@ -12,10 +11,25 @@ pub mod udp;
///
/// # Errors
/// If a system error occurred while trying to set the ctrl-c handler
pub fn add_ctrlc_handler(flag: Arc<AtomicBool>) -> anyhow::Result<()> {
pub fn add_ctrlc_handler_arc(
flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
) -> anyhow::Result<()> {
ctrlc::set_handler(move || {
info!("Shutdown Requested");
flag.store(false, Ordering::Relaxed);
flag.store(false, std::sync::atomic::Ordering::Relaxed);
})?;
Ok(())
}
/// Add a ctrl-c handler which will set an atomic flag to `false` when ctrl-c is detected
///
/// # Errors
/// If a system error occurred while trying to set the ctrl-c handler
#[cfg(feature = "async")]
pub fn add_ctrlc_handler_cancel(cancel: tokio_util::sync::CancellationToken) -> anyhow::Result<()> {
ctrlc::set_handler(move || {
info!("Shutdown Requested");
cancel.cancel();
})?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig};
use log::debug;
use log::{LevelFilter, debug};
use std::fs::create_dir_all;
use std::str::FromStr;
use std::{env, thread};
@@ -46,6 +46,9 @@ pub fn setup_logger(package_name: &'static str) -> Result<()> {
.chain(
fern::Dispatch::new()
.level(log_level)
.level_for("tungstenite", LevelFilter::Warn)
.level_for("tokio_tungstenite", LevelFilter::Warn)
.level_for("api", LevelFilter::Info)
.chain(std::io::stdout()),
)
.chain(fern::log_file(log_file.clone())?)

18
common/src/on_drop.rs Normal file
View File

@@ -0,0 +1,18 @@
use log::trace;
use std::any::type_name;
pub struct OnDrop<F: FnMut()> {
func: F,
}
pub fn on_drop<F: FnMut()>(func: F) -> OnDrop<F> {
trace!("on_drop<F={}>()", type_name::<F>());
OnDrop { func }
}
impl<F: FnMut()> Drop for OnDrop<F> {
fn drop(&mut self) {
trace!("OnDrop<F={}>::drop()", type_name::<F>());
(self.func)();
}
}

View File

@@ -1,5 +1,6 @@
use chrono::serde::ts_nanoseconds;
use chrono::{DateTime, Utc};
use derive_more::Display;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -9,7 +10,7 @@ pub struct Telemetry {
pub message: TelemetryMessage,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Display, Serialize, Deserialize)]
pub enum SwitchBank {
A,
B,

View File

@@ -1,7 +1,6 @@
use crate::command::{Command, CommandHeader};
use crate::udp::UdpRecvPostcardError::ExtraData;
use crate::udp::UdpSendPostcardError::LengthMismatch;
use log::error;
use serde::{Deserialize, Serialize};
use std::io::ErrorKind;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
@@ -30,7 +29,7 @@ pub enum UdpSendPostcardError {
}
pub trait UdpSocketExt {
/// Receive a CBOR encoded message from this UDP Socket
/// Receive a Postcard encoded message from this UDP Socket
///
/// # Errors
/// An error that could have occurred while trying to receive this message.
@@ -63,26 +62,51 @@ pub trait UdpSocketExt {
) -> Result<(), UdpSendPostcardError>;
}
fn recv_postcard_inner<'de, T: Deserialize<'de>>(
result: std::io::Result<(usize, SocketAddr)>,
buffer: &'de mut [u8],
) -> Result<(T, SocketAddr), UdpRecvPostcardError> {
match result {
Ok((size, addr)) => match postcard::take_from_bytes::<T>(&buffer[..size]) {
Ok((res, rem)) => {
if !rem.is_empty() {
return Err(ExtraData { amount: rem.len() });
}
Ok((res, addr))
}
Err(err) => Err(err.into()),
},
Err(err) => match err.kind() {
ErrorKind::WouldBlock | ErrorKind::TimedOut => Err(UdpRecvPostcardError::NoData),
_ => Err(err.into()),
},
}
}
fn send_inner(
send_result: Result<usize, std::io::Error>,
expected_size: usize,
) -> Result<(), UdpSendPostcardError> {
match send_result {
Ok(size_sent) => {
if expected_size != size_sent {
return Err(LengthMismatch {
expected: expected_size,
actual: size_sent,
});
}
Ok(())
}
Err(e) => Err(e.into()),
}
}
impl UdpSocketExt for UdpSocket {
fn recv_postcard<'de, T: Deserialize<'de>>(
&self,
buffer: &'de mut [u8],
) -> Result<(T, SocketAddr), UdpRecvPostcardError> {
match self.recv_from(buffer) {
Ok((size, addr)) => match postcard::take_from_bytes::<T>(&buffer[..size]) {
Ok((res, rem)) => {
if !rem.is_empty() {
return Err(ExtraData { amount: rem.len() });
}
Ok((res, addr))
}
Err(err) => Err(err.into()),
},
Err(err) => match err.kind() {
ErrorKind::WouldBlock | ErrorKind::TimedOut => Err(UdpRecvPostcardError::NoData),
_ => Err(err.into()),
},
}
recv_postcard_inner(self.recv_from(buffer), buffer)
}
fn send_postcard<T: Serialize + ?Sized, A: ToSocketAddrs>(
@@ -91,24 +115,9 @@ impl UdpSocketExt for UdpSocket {
buffer: &mut [u8],
addr: A,
) -> Result<(), UdpSendPostcardError> {
match postcard::to_slice(data, buffer) {
Ok(result) => {
let size_encoded = result.len();
match self.send_to(result, addr) {
Ok(size_sent) => {
if size_encoded != size_sent {
return Err(LengthMismatch {
expected: size_encoded,
actual: size_sent,
});
}
Ok(())
}
Err(e) => Err(e.into()),
}
}
Err(e) => Err(e.into()),
}
let result = postcard::to_slice(data, buffer)?;
let size_encoded = result.len();
send_inner(self.send_to(result, addr), size_encoded)
}
fn send_command<T: Command, A: ToSocketAddrs>(
@@ -120,21 +129,97 @@ impl UdpSocketExt for UdpSocket {
let mut inner_buffer = [0u8; 512];
let inner_buffer = postcard::to_slice(data, &mut inner_buffer)?;
let mut buffer = [0u8; 512];
let buffer = postcard::to_slice(
self.send_postcard(
&CommandHeader {
name,
data: inner_buffer,
},
&mut buffer,
)?;
let size_encoded = buffer.len();
let size_sent = self.send_to(buffer, addr)?;
if size_encoded != size_sent {
return Err(LengthMismatch {
expected: size_encoded,
actual: size_sent,
});
}
Ok(())
addr,
)
}
}
#[cfg(feature = "async")]
pub mod tokio {
use super::{
Command, CommandHeader, Deserialize, Serialize, SocketAddr, UdpRecvPostcardError,
UdpSendPostcardError, recv_postcard_inner, send_inner,
};
use ::tokio::net::ToSocketAddrs;
use ::tokio::net::UdpSocket;
pub trait AsyncUdpSocketExt {
/// Receive a Postcard encoded message from this UDP Socket
///
/// # Errors
/// An error that could have occurred while trying to receive this message.
/// If no data was received a `UdpRecvCborError::NoData` error would be returned.
fn recv_postcard<'de, T: Deserialize<'de>>(
&self,
buffer: &'de mut [u8],
) -> impl Future<Output = Result<(T, SocketAddr), UdpRecvPostcardError>>;
/// Send a CBOR encoded message to an address using this socket
///
/// # Errors
/// An error that could have occurred while trying to send this message
fn send_postcard<T: Serialize + ?Sized, A: ToSocketAddrs>(
&self,
data: &T,
buffer: &mut [u8],
addr: A,
) -> impl Future<Output = Result<(), UdpSendPostcardError>>;
/// Send a command message to an address using this socket
///
/// # Errors
/// An error that could have occurred while trying to send this message
fn send_command<T: Command, A: ToSocketAddrs>(
&self,
name: &str,
data: &T,
addr: A,
) -> impl Future<Output = Result<(), UdpSendPostcardError>>;
}
impl AsyncUdpSocketExt for UdpSocket {
async fn recv_postcard<'de, T: Deserialize<'de>>(
&self,
buffer: &'de mut [u8],
) -> Result<(T, SocketAddr), UdpRecvPostcardError> {
recv_postcard_inner(self.recv_from(buffer).await, buffer)
}
async fn send_postcard<T: Serialize + ?Sized, A: ToSocketAddrs>(
&self,
data: &T,
buffer: &mut [u8],
addr: A,
) -> Result<(), UdpSendPostcardError> {
let result = postcard::to_slice(data, buffer)?;
let size_encoded = result.len();
send_inner(self.send_to(result, addr).await, size_encoded)
}
async fn send_command<T: Command, A: ToSocketAddrs>(
&self,
name: &str,
data: &T,
addr: A,
) -> Result<(), UdpSendPostcardError> {
let mut inner_buffer = [0u8; 512];
let inner_buffer = postcard::to_slice(data, &mut inner_buffer)?;
let mut buffer = [0u8; 512];
self.send_postcard(
&CommandHeader {
name,
data: inner_buffer,
},
&mut buffer,
addr,
)
.await
}
}
}