pounder_test/src/net/telemetry.rs

155 lines
5.3 KiB
Rust
Raw Normal View History

2021-05-06 18:33:07 +08:00
///! Stabilizer Telemetry Capabilities
///!
///! # Design
///! Telemetry is reported regularly using an MQTT client. All telemetry is reported in SI units
///! using standard JSON format.
///!
///! In order to report ADC/DAC codes generated during the DSP routines, a telemetry buffer is
///! employed to track the latest codes. Converting these codes to SI units would result in
///! repetitive and unnecessary calculations within the DSP routine, slowing it down and limiting
///! sampling frequency. Instead, the raw codes are stored and the telemetry is generated as
///! required immediately before transmission. This ensures that any slower computation required
///! for unit conversion can be off-loaded to lower priority tasks.
use heapless::{String, Vec};
2021-05-06 18:33:07 +08:00
use minimq::QoS;
2021-05-06 18:35:04 +08:00
use serde::Serialize;
2021-05-05 22:16:54 +08:00
use super::NetworkReference;
2021-07-15 19:28:19 +08:00
use crate::hardware::{adc::AdcCode, afe::Gain, dac::DacCode};
use minimq::embedded_nal::IpAddr;
2021-05-05 22:16:54 +08:00
2021-05-06 18:33:07 +08:00
/// The telemetry client for reporting telemetry data over MQTT.
pub struct TelemetryClient<T: Serialize> {
2021-05-27 19:42:52 +08:00
mqtt: minimq::Minimq<NetworkReference, 256>,
telemetry_topic: String<128>,
2021-05-06 18:33:07 +08:00
_telemetry: core::marker::PhantomData<T>,
}
2021-04-29 21:55:36 +08:00
2021-05-06 18:33:07 +08:00
/// The telemetry buffer is used for storing sample values during execution.
///
/// # Note
/// These values can be converted to SI units immediately before reporting to save processing time.
/// This allows for the DSP process to continually update the values without incurring significant
/// run-time overhead during conversion to SI units.
2021-04-29 21:55:36 +08:00
#[derive(Copy, Clone)]
pub struct TelemetryBuffer {
2021-05-06 18:33:07 +08:00
/// The latest input sample on ADC0/ADC1.
2021-05-07 20:11:25 +08:00
pub adcs: [AdcCode; 2],
2021-05-06 18:33:07 +08:00
/// The latest output code on DAC0/DAC1.
pub dacs: [DacCode; 2],
2021-05-06 18:33:07 +08:00
/// The latest digital input states during processing.
pub digital_inputs: [bool; 2],
}
2021-05-06 18:33:07 +08:00
/// The telemetry structure is data that is ultimately reported as telemetry over MQTT.
///
/// # Note
/// This structure should be generated on-demand by the buffer when required to minimize conversion
/// overhead.
2021-04-29 21:55:36 +08:00
#[derive(Serialize)]
pub struct Telemetry {
2021-07-15 19:28:19 +08:00
/// Most recent input voltage measurement.
2021-07-16 20:25:37 +08:00
pub adcs: [f32; 2],
2021-07-15 19:28:19 +08:00
/// Most recent output voltage.
2021-07-16 20:25:37 +08:00
pub dacs: [f32; 2],
2021-07-15 19:28:19 +08:00
/// Most recent digital input assertion state.
2021-07-16 20:25:37 +08:00
pub digital_inputs: [bool; 2],
2021-04-29 21:55:36 +08:00
}
impl Default for TelemetryBuffer {
fn default() -> Self {
Self {
2021-05-07 20:11:25 +08:00
adcs: [AdcCode(0), AdcCode(0)],
dacs: [DacCode(0), DacCode(0)],
digital_inputs: [false, false],
}
}
}
2021-04-29 21:55:36 +08:00
impl TelemetryBuffer {
2021-05-06 18:33:07 +08:00
/// Convert the telemetry buffer to finalized, SI-unit telemetry for reporting.
///
/// # Args
/// * `afe0` - The current AFE configuration for channel 0.
/// * `afe1` - The current AFE configuration for channel 1.
///
/// # Returns
/// The finalized telemetry structure that can be serialized and reported.
pub fn finalize(self, afe0: Gain, afe1: Gain) -> Telemetry {
let in0_volts = Into::<f32>::into(self.adcs[0]) / afe0.as_multiplier();
let in1_volts = Into::<f32>::into(self.adcs[1]) / afe1.as_multiplier();
2021-04-29 21:55:36 +08:00
Telemetry {
2021-05-07 19:02:14 +08:00
adcs: [in0_volts, in1_volts],
dacs: [self.dacs[0].into(), self.dacs[1].into()],
2021-04-29 21:55:36 +08:00
digital_inputs: self.digital_inputs,
}
}
}
2021-05-05 22:16:54 +08:00
impl<T: Serialize> TelemetryClient<T> {
2021-05-06 18:33:07 +08:00
/// Construct a new telemetry client.
///
/// # Args
/// * `stack` - A reference to the (shared) underlying network stack.
/// * `client_id` - The MQTT client ID of the telemetry client.
/// * `prefix` - The device prefix to use for MQTT telemetry reporting.
/// * `broker` - The IP address of the MQTT broker to use.
2021-05-06 18:33:07 +08:00
///
/// # Returns
/// A new telemetry client.
pub fn new(
stack: NetworkReference,
client_id: &str,
prefix: &str,
broker: IpAddr,
) -> Self {
let mqtt = minimq::Minimq::new(broker, client_id, stack).unwrap();
2021-05-05 22:16:54 +08:00
let mut telemetry_topic: String<128> = String::from(prefix);
2021-05-05 22:16:54 +08:00
telemetry_topic.push_str("/telemetry").unwrap();
Self {
mqtt,
telemetry_topic,
_telemetry: core::marker::PhantomData::default(),
}
}
2021-05-06 18:33:07 +08:00
/// Publish telemetry over MQTT
///
/// # Note
/// Telemetry is reported in a "best-effort" fashion. Failure to transmit telemetry will cause
/// it to be silently dropped.
///
/// # Args
/// * `telemetry` - The telemetry to report
2021-05-05 22:16:54 +08:00
pub fn publish(&mut self, telemetry: &T) {
let telemetry: Vec<u8, 256> =
2021-05-05 22:16:54 +08:00
serde_json_core::to_vec(telemetry).unwrap();
self.mqtt
2021-05-27 19:42:52 +08:00
.client
2021-05-05 22:16:54 +08:00
.publish(&self.telemetry_topic, &telemetry, QoS::AtMostOnce, &[])
.ok();
}
2021-05-06 18:33:07 +08:00
/// Update the telemetry client
///
/// # Note
/// This function is provided to force the underlying MQTT state machine to process incoming
/// and outgoing messages. Without this, the client will never connect to the broker. This
/// should be called regularly.
pub fn update(&mut self) {
match self.mqtt.poll(|_client, _topic, _message, _properties| {}) {
Err(minimq::Error::Network(
smoltcp_nal::NetworkError::NoIpAddress,
)) => {}
Err(error) => log::info!("Unexpected error: {:?}", error),
_ => {}
}
}
2021-05-05 22:16:54 +08:00
}