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.
|
2021-05-05 22:16:54 +08:00
|
|
|
use heapless::{consts, 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-04-20 20:12:47 +08:00
|
|
|
|
2021-05-05 22:16:54 +08:00
|
|
|
use super::NetworkReference;
|
2021-05-06 18:35:04 +08:00
|
|
|
use crate::hardware::{design_parameters::MQTT_BROKER, AfeGain};
|
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> {
|
|
|
|
mqtt: minimq::MqttClient<minimq::consts::U256, NetworkReference>,
|
|
|
|
telemetry_topic: String<consts::U128>,
|
|
|
|
_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-04-20 20:12:47 +08:00
|
|
|
pub latest_samples: [i16; 2],
|
2021-05-06 18:33:07 +08:00
|
|
|
/// The latest output code on DAC0/DAC1.
|
2021-04-29 23:39:19 +08:00
|
|
|
pub latest_outputs: [u16; 2],
|
2021-05-06 18:33:07 +08:00
|
|
|
/// The latest digital input states during processing.
|
2021-04-20 20:12:47 +08:00
|
|
|
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 {
|
|
|
|
input_levels: [f32; 2],
|
|
|
|
output_levels: [f32; 2],
|
2021-04-29 23:39:19 +08:00
|
|
|
digital_inputs: [bool; 2],
|
2021-04-29 21:55:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TelemetryBuffer {
|
2021-04-20 20:12:47 +08:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
latest_samples: [0, 0],
|
|
|
|
latest_outputs: [0, 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: AfeGain, afe1: AfeGain) -> Telemetry {
|
|
|
|
// The input voltage is measured by the ADC across a dynamic scale of +/- 4.096 V with a
|
|
|
|
// dynamic range across signed integers. Additionally, the signal is fully differential, so
|
|
|
|
// the differential voltage is measured at the ADC. Thus, the single-ended signal is
|
|
|
|
// measured at the input is half of the ADC-reported measurement. As a pre-filter, the
|
|
|
|
// input signal has a fixed gain of 1/5 through a static input active filter. Finally, at
|
|
|
|
// the very front-end of the signal, there's an analog input multiplier that is
|
|
|
|
// configurable by the user.
|
2021-04-29 23:39:19 +08:00
|
|
|
let in0_volts =
|
|
|
|
(self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
|
|
|
* 5.0
|
2021-05-06 18:33:07 +08:00
|
|
|
/ afe0.as_multiplier();
|
2021-04-29 23:39:19 +08:00
|
|
|
let in1_volts =
|
|
|
|
(self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
|
|
|
* 5.0
|
2021-05-06 18:33:07 +08:00
|
|
|
/ afe1.as_multiplier();
|
2021-04-29 21:55:36 +08:00
|
|
|
|
2021-05-06 18:33:07 +08:00
|
|
|
// The output voltage is generated by the DAC with an output range of +/- 4.096 V. This
|
|
|
|
// signal then passes through a 2.5x gain stage. Note that the DAC operates using unsigned
|
|
|
|
// integers, and u16::MAX / 2 is considered zero voltage output. Thus, the dynamic range of
|
|
|
|
// the output stage is +/- 10.24 V. At a DAC code of zero, there is an output of -10.24 V,
|
|
|
|
// and at a max DAC code, there is an output of 10.24 V.
|
2021-04-29 23:39:19 +08:00
|
|
|
let out0_volts = (10.24 * 2.0)
|
|
|
|
* (self.latest_outputs[0] as f32 / (u16::MAX as f32))
|
|
|
|
- 10.24;
|
|
|
|
let out1_volts = (10.24 * 2.0)
|
|
|
|
* (self.latest_outputs[1] as f32 / (u16::MAX as f32))
|
|
|
|
- 10.24;
|
2021-04-29 21:55:36 +08:00
|
|
|
|
|
|
|
Telemetry {
|
|
|
|
input_levels: [in0_volts, in1_volts],
|
|
|
|
output_levels: [out0_volts, out1_volts],
|
|
|
|
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.
|
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
/// A new telemetry client.
|
2021-05-05 22:16:54 +08:00
|
|
|
pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self {
|
|
|
|
let mqtt =
|
|
|
|
minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mut telemetry_topic: String<consts::U128> = String::from(prefix);
|
|
|
|
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, consts::U256> =
|
|
|
|
serde_json_core::to_vec(telemetry).unwrap();
|
|
|
|
self.mqtt
|
|
|
|
.publish(&self.telemetry_topic, &telemetry, QoS::AtMostOnce, &[])
|
|
|
|
.ok();
|
|
|
|
}
|
2021-05-05 22:46:53 +08:00
|
|
|
|
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.
|
2021-05-05 22:46:53 +08:00
|
|
|
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
|
|
|
}
|