Adding documentation
This commit is contained in:
parent
e9c3e24863
commit
4cbc826f00
|
@ -153,7 +153,6 @@ const APP: () = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update telemetry measurements.
|
// Update telemetry measurements.
|
||||||
// TODO: Should we report these as voltages?
|
|
||||||
c.resources.telemetry.latest_samples =
|
c.resources.telemetry.latest_samples =
|
||||||
[adc_samples[0][0] as i16, adc_samples[1][0] as i16];
|
[adc_samples[0][0] as i16, adc_samples[1][0] as i16];
|
||||||
|
|
||||||
|
@ -197,7 +196,7 @@ const APP: () = {
|
||||||
c.resources
|
c.resources
|
||||||
.network
|
.network
|
||||||
.telemetry
|
.telemetry
|
||||||
.publish(&telemetry.to_telemetry(gains[0], gains[1]));
|
.publish(&telemetry.finalize(gains[0], gains[1]));
|
||||||
|
|
||||||
let telemetry_period = c
|
let telemetry_period = c
|
||||||
.resources
|
.resources
|
||||||
|
|
|
@ -243,7 +243,7 @@ const APP: () = {
|
||||||
c.resources
|
c.resources
|
||||||
.network
|
.network
|
||||||
.telemetry
|
.telemetry
|
||||||
.publish(&telemetry.to_telemetry(gains[0], gains[1]));
|
.publish(&telemetry.finalize(gains[0], gains[1]));
|
||||||
|
|
||||||
let telemetry_period = c
|
let telemetry_period = c
|
||||||
.resources
|
.resources
|
||||||
|
|
|
@ -21,13 +21,13 @@ pub struct ProgrammableGainAmplifier<A0, A1> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gain {
|
impl Gain {
|
||||||
/// Get the AFE gain as a multiplying integer.
|
/// Get the AFE gain as a numerical value.
|
||||||
pub fn to_multiplier(&self) -> u8 {
|
pub fn as_multiplier (self) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
Gain::G1 => 1,
|
Gain::G1 => 1.0,
|
||||||
Gain::G2 => 2,
|
Gain::G2 => 2.0,
|
||||||
Gain::G5 => 5,
|
Gain::G5 => 5.0,
|
||||||
Gain::G10 => 10,
|
Gain::G10 => 10.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,33 @@
|
||||||
|
///! System timer used for RTIC scheduling
|
||||||
|
///!
|
||||||
|
///! # Design
|
||||||
|
///! The SystemTimer is an RTIC monotonic timer that can be used for scheduling tasks in RTIC.
|
||||||
|
///! This timer is used in place of the cycle counter to allow the timer to tick at a slower rate
|
||||||
|
///! than the CPU clock. This allows for longer scheduling periods with less resolution. This is
|
||||||
|
///! needed for infrequent (e.g. multiple second) telemetry periods.
|
||||||
|
///!
|
||||||
|
///! # Limitations
|
||||||
|
///! This implementation relies on sufficient timer polling to not miss timer counter overflows. If
|
||||||
|
///! the timer is not polled often enough, it's possible that an overflow would be missed and time
|
||||||
|
///! would "halt" for a shore period of time. This could be fixed in the future by instead
|
||||||
|
///! listening for the overflow interrupt instead of polling the overflow state.
|
||||||
use hal::prelude::*;
|
use hal::prelude::*;
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
|
// A global buffer indicating how many times the internal counter has overflowed.
|
||||||
static mut OVERFLOWS: u32 = 0;
|
static mut OVERFLOWS: u32 = 0;
|
||||||
|
|
||||||
|
/// System timer used for implementing RTIC scheduling.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// The system timer must be initialized before being used.
|
||||||
pub struct SystemTimer {}
|
pub struct SystemTimer {}
|
||||||
|
|
||||||
impl SystemTimer {
|
impl SystemTimer {
|
||||||
|
/// Initialize the system timer.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `timer` - The hardware timer used for implementing the RTIC monotonic.
|
||||||
pub fn initialize(mut timer: hal::timer::Timer<hal::device::TIM15>) {
|
pub fn initialize(mut timer: hal::timer::Timer<hal::device::TIM15>) {
|
||||||
timer.pause();
|
timer.pause();
|
||||||
// Have the system timer operate at a tick rate of 10KHz (100uS per tick). With this
|
// Have the system timer operate at a tick rate of 10KHz (100uS per tick). With this
|
||||||
|
@ -16,26 +38,45 @@ impl SystemTimer {
|
||||||
timer.resume();
|
timer.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a provided number of seconds into timer ticks.
|
||||||
pub fn ticks_from_secs(secs: u32) -> i32 {
|
pub fn ticks_from_secs(secs: u32) -> i32 {
|
||||||
(secs * 10_000) as i32
|
(secs * 10_000) as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl rtic::Monotonic for SystemTimer {
|
impl rtic::Monotonic for SystemTimer {
|
||||||
|
// Instants are stored in 32-bit signed integers. With a 10KHz tick rate, this means an
|
||||||
|
// instant can store up to ~59 hours of time before overflowing.
|
||||||
type Instant = i32;
|
type Instant = i32;
|
||||||
|
|
||||||
fn ratio() -> rtic::Fraction {
|
fn ratio() -> rtic::Fraction {
|
||||||
rtic::Fraction {
|
rtic::Fraction {
|
||||||
|
// At 10KHz with a 400MHz CPU clock, the CPU clock runs 40,000 times faster than
|
||||||
|
// the system timer.
|
||||||
numerator: 40_000,
|
numerator: 40_000,
|
||||||
denominator: 1,
|
denominator: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current time instant.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// The time will overflow into -59 hours after the first 59 hours. This time value is intended
|
||||||
|
/// for use in calculating time delta, and should not be used for timestamping purposes due to
|
||||||
|
/// roll-over.
|
||||||
fn now() -> i32 {
|
fn now() -> i32 {
|
||||||
|
// Note(unsafe): Multiple interrupt contexts have access to the underlying timer, so care
|
||||||
|
// is taken when reading and modifying register values.
|
||||||
let regs = unsafe { &*hal::device::TIM15::ptr() };
|
let regs = unsafe { &*hal::device::TIM15::ptr() };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Check for overflows
|
// Checking for overflows of the current counter must be performed atomically. Any
|
||||||
|
// other task that is accessing the current time could potentially race for the
|
||||||
|
// registers. Note that this is only required for writing to global state (e.g. timer
|
||||||
|
// registers and overflow counter)
|
||||||
|
cortex_m::interrupt::free(|_cs| {
|
||||||
|
// Check for overflows and clear the overflow bit atomically. This must be done in
|
||||||
|
// a critical section to prevent race conditions on the status register.
|
||||||
if regs.sr.read().uif().bit_is_set() {
|
if regs.sr.read().uif().bit_is_set() {
|
||||||
regs.sr.modify(|_, w| w.uif().clear_bit());
|
regs.sr.modify(|_, w| w.uif().clear_bit());
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -45,23 +86,25 @@ impl rtic::Monotonic for SystemTimer {
|
||||||
|
|
||||||
let current_value = regs.cnt.read().bits();
|
let current_value = regs.cnt.read().bits();
|
||||||
|
|
||||||
// If the overflow is still unset, return our latest count, as it indicates we weren't
|
// Check that an overflow didn't occur since we just cleared the overflow bit. If
|
||||||
// pre-empted.
|
// it did, loop around and retry.
|
||||||
if regs.sr.read().uif().bit_is_clear() {
|
if regs.sr.read().uif().bit_is_clear() {
|
||||||
unsafe {
|
return (overflows * 65535 + current_value) as i32;
|
||||||
return (OVERFLOWS * 65535 + current_value) as i32;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset the timer count.
|
||||||
unsafe fn reset() {
|
unsafe fn reset() {
|
||||||
// Note: The timer must be safely configured in `SystemTimer::initialize()`.
|
// Note: The timer must be safely configured in `SystemTimer::initialize()`.
|
||||||
let regs = &*hal::device::TIM15::ptr();
|
let regs = &*hal::device::TIM15::ptr();
|
||||||
|
|
||||||
|
OVERFLOWS = 0;
|
||||||
regs.cnt.reset();
|
regs.cnt.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a timestamp correlating to zero time.
|
||||||
fn zero() -> i32 {
|
fn zero() -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
use crate::hardware::design_parameters::MQTT_BROKER;
|
///! Stabilizer Run-time Settings Client
|
||||||
|
///!
|
||||||
|
///! # Design
|
||||||
|
///! Stabilizer allows for settings to be configured at run-time via MQTT using miniconf.
|
||||||
|
///! Settings are written in serialized JSON form to the settings path associated with the setting.
|
||||||
|
///!
|
||||||
|
///! # Limitations
|
||||||
|
///! The MQTT client logs failures to subscribe to the settings topic, but does not re-attempt to
|
||||||
|
///connect to it when errors occur.
|
||||||
|
///!
|
||||||
|
///! Respones to settings updates are sent without quality-of-service guarantees, so there's no
|
||||||
|
///! guarantee that the requestee will be informed that settings have been applied.
|
||||||
use heapless::{consts, String};
|
use heapless::{consts, String};
|
||||||
|
|
||||||
|
use crate::hardware::design_parameters::MQTT_BROKER;
|
||||||
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
use super::{MqttMessage, NetworkReference, SettingsResponse, UpdateState};
|
||||||
|
|
||||||
/// MQTT settings interface.
|
/// MQTT settings interface.
|
||||||
|
@ -144,6 +155,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current settings from miniconf.
|
||||||
pub fn settings(&self) -> &S {
|
pub fn settings(&self) -> &S {
|
||||||
&self.settings
|
&self.settings
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use core::fmt::Write;
|
|
||||||
///! Stabilizer network management module
|
///! Stabilizer network management module
|
||||||
///!
|
///!
|
||||||
///! # Design
|
///! # Design
|
||||||
|
@ -10,6 +9,8 @@ use heapless::{consts, String};
|
||||||
use miniconf::Miniconf;
|
use miniconf::Miniconf;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
mod messages;
|
mod messages;
|
||||||
mod miniconf_client;
|
mod miniconf_client;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
@ -32,6 +33,7 @@ pub enum UpdateState {
|
||||||
Updated,
|
Updated,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A structure of Stabilizer's default network users.
|
||||||
pub struct NetworkUsers<S: Default + Clone + Miniconf, T: Serialize> {
|
pub struct NetworkUsers<S: Default + Clone + Miniconf, T: Serialize> {
|
||||||
pub miniconf: MiniconfClient<S>,
|
pub miniconf: MiniconfClient<S>,
|
||||||
pub processor: NetworkProcessor,
|
pub processor: NetworkProcessor,
|
||||||
|
@ -43,6 +45,17 @@ where
|
||||||
S: Default + Clone + Miniconf,
|
S: Default + Clone + Miniconf,
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
|
/// Construct Stabilizer's default network users.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `stack` - The network stack that will be used to share with all network users.
|
||||||
|
/// * `phy` - The ethernet PHY connecting the network.
|
||||||
|
/// * `cycle_counter` - The clock used for measuring time in the network.
|
||||||
|
/// * `app` - The name of the application.
|
||||||
|
/// * `mac` - The MAC address of the network.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A new struct of network users.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
stack: NetworkStack,
|
stack: NetworkStack,
|
||||||
phy: EthernetPhy,
|
phy: EthernetPhy,
|
||||||
|
@ -81,6 +94,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update and process all of the network users state.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An indication if any of the network users indicated a state change.
|
||||||
pub fn update(&mut self) -> UpdateState {
|
pub fn update(&mut self) -> UpdateState {
|
||||||
// Poll for incoming data.
|
// Poll for incoming data.
|
||||||
let poll_result = self.processor.update();
|
let poll_result = self.processor.update();
|
||||||
|
@ -95,6 +112,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an MQTT client ID for a client.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `app` - The name of the application
|
||||||
|
/// * `client` - The unique tag of the client
|
||||||
|
/// * `mac` - The MAC address of the device.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A client ID that may be used for MQTT client identification.
|
||||||
fn get_client_id(
|
fn get_client_id(
|
||||||
app: &str,
|
app: &str,
|
||||||
client: &str,
|
client: &str,
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
///! Task to process network hardware.
|
||||||
|
///!
|
||||||
|
///! # Design
|
||||||
|
///! The network processir is a small taks to regularly process incoming data over ethernet, handle
|
||||||
|
///! the ethernet PHY state, and reset the network as appropriate.
|
||||||
use super::{NetworkReference, UpdateState};
|
use super::{NetworkReference, UpdateState};
|
||||||
|
|
||||||
use crate::hardware::{CycleCounter, EthernetPhy};
|
use crate::hardware::{CycleCounter, EthernetPhy};
|
||||||
|
|
||||||
|
/// Processor for managing network hardware.
|
||||||
pub struct NetworkProcessor {
|
pub struct NetworkProcessor {
|
||||||
stack: NetworkReference,
|
stack: NetworkReference,
|
||||||
phy: EthernetPhy,
|
phy: EthernetPhy,
|
||||||
|
@ -10,6 +15,15 @@ pub struct NetworkProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkProcessor {
|
impl NetworkProcessor {
|
||||||
|
/// Construct a new network processor.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `stack` - A reference to the shared network stack
|
||||||
|
/// * `phy` - The ethernet PHY used for the network.
|
||||||
|
/// * `clock` - The clock used for providing time to the network.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The newly constructed processor.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
stack: NetworkReference,
|
stack: NetworkReference,
|
||||||
phy: EthernetPhy,
|
phy: EthernetPhy,
|
||||||
|
@ -23,6 +37,14 @@ impl NetworkProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process and update the state of the network.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This function should be called regularly before other network tasks to update the state of
|
||||||
|
/// all relevant network sockets.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// An update state corresponding with any changes in the underlying network.
|
||||||
pub fn update(&mut self) -> UpdateState {
|
pub fn update(&mut self) -> UpdateState {
|
||||||
// Service the network stack to process any inbound and outbound traffic.
|
// Service the network stack to process any inbound and outbound traffic.
|
||||||
let now = self.clock.current_ms();
|
let now = self.clock.current_ms();
|
||||||
|
|
|
@ -1,18 +1,50 @@
|
||||||
|
///! Network Stack Sharing Utilities
|
||||||
|
///!
|
||||||
|
///! # Design
|
||||||
|
///! This module provides a mechanism for sharing a single network stack safely between drivers
|
||||||
|
///that may or may not execute in multiple contexts. The design copies that of `shared-bus`.
|
||||||
|
///!
|
||||||
|
///! Specifically, the network stack is stored in a global static singleton and proxies to the
|
||||||
|
///! underlying stack are handed out. The proxies provide an identical API for the
|
||||||
|
///! `embedded_nal::TcpStack` stack trait, so they can be provided direclty to drivers that require
|
||||||
|
///! a network stack.
|
||||||
|
///!
|
||||||
|
///! In order to ensure that pre-emption does not occur while accessing the same network stack from
|
||||||
|
///! multiple interrupt contexts, the proxy uses an atomic boolean check - if the flag indicates the
|
||||||
|
///! stack is in use, the proxy will generate a panic. The actual synchronization mechanism (mutex)
|
||||||
|
///! leverages RTIC resource allocation. All devices that use the underlying network stack must be
|
||||||
|
///! placed in a single RTIC resource, which will cause RTIC to prevent contention for the
|
||||||
|
///! underlying network stack.
|
||||||
use minimq::embedded_nal;
|
use minimq::embedded_nal;
|
||||||
use shared_bus::{AtomicCheckMutex, BusMutex};
|
use shared_bus::{AtomicCheckMutex, BusMutex};
|
||||||
|
|
||||||
use crate::hardware::NetworkStack;
|
use crate::hardware::NetworkStack;
|
||||||
|
|
||||||
|
/// A manager for a shared network stack.
|
||||||
|
pub struct NetworkManager {
|
||||||
|
mutex: AtomicCheckMutex<NetworkStack>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A basic proxy that references a shared network stack.
|
||||||
pub struct NetworkStackProxy<'a, S> {
|
pub struct NetworkStackProxy<'a, S> {
|
||||||
mutex: &'a AtomicCheckMutex<S>,
|
mutex: &'a AtomicCheckMutex<S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S> NetworkStackProxy<'a, S> {
|
impl<'a, S> NetworkStackProxy<'a, S> {
|
||||||
|
/// Using the proxy, access the underlying network stack directly.
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `f` - A closure which will be provided the network stack as an argument.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Any value returned by the provided closure
|
||||||
pub fn lock<R, F: FnOnce(&mut S) -> R>(&mut self, f: F) -> R {
|
pub fn lock<R, F: FnOnce(&mut S) -> R>(&mut self, f: F) -> R {
|
||||||
self.mutex.lock(|stack| f(stack))
|
self.mutex.lock(|stack| f(stack))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A simple forwarding macro taken from the `embedded-nal` to forward the embedded-nal API into the
|
||||||
|
// proxy structure.
|
||||||
macro_rules! forward {
|
macro_rules! forward {
|
||||||
($func:ident($($v:ident: $IT:ty),*) -> $T:ty) => {
|
($func:ident($($v:ident: $IT:ty),*) -> $T:ty) => {
|
||||||
fn $func(&self, $($v: $IT),*) -> $T {
|
fn $func(&self, $($v: $IT),*) -> $T {
|
||||||
|
@ -21,6 +53,7 @@ macro_rules! forward {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement a TCP stack for the proxy if the underlying network stack implements it.
|
||||||
impl<'a, S> embedded_nal::TcpStack for NetworkStackProxy<'a, S>
|
impl<'a, S> embedded_nal::TcpStack for NetworkStackProxy<'a, S>
|
||||||
where
|
where
|
||||||
S: embedded_nal::TcpStack,
|
S: embedded_nal::TcpStack,
|
||||||
|
@ -36,17 +69,22 @@ where
|
||||||
forward! {close(socket: S::TcpSocket) -> Result<(), S::Error>}
|
forward! {close(socket: S::TcpSocket) -> Result<(), S::Error>}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NetworkManager {
|
|
||||||
mutex: AtomicCheckMutex<NetworkStack>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkManager {
|
impl NetworkManager {
|
||||||
|
/// Construct a new manager for a shared network stack
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
/// * `stack` - The network stack that is being shared.
|
||||||
pub fn new(stack: NetworkStack) -> Self {
|
pub fn new(stack: NetworkStack) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mutex: AtomicCheckMutex::create(stack),
|
mutex: AtomicCheckMutex::create(stack),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acquire a proxy to the shared network stack.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A proxy that can be used in place of the network stack. Note the requirements of
|
||||||
|
/// concurrency listed in the description of this file for usage.
|
||||||
pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> {
|
pub fn acquire_stack<'a>(&'a self) -> NetworkStackProxy<'a, NetworkStack> {
|
||||||
NetworkStackProxy { mutex: &self.mutex }
|
NetworkStackProxy { mutex: &self.mutex }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,50 @@
|
||||||
|
///! 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::{consts, String, Vec};
|
use heapless::{consts, String, Vec};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::NetworkReference;
|
|
||||||
use crate::hardware::design_parameters::MQTT_BROKER;
|
|
||||||
use minimq::QoS;
|
use minimq::QoS;
|
||||||
|
|
||||||
use crate::hardware::AfeGain;
|
use super::NetworkReference;
|
||||||
|
use crate::hardware::{AfeGain, design_parameters::MQTT_BROKER};
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TelemetryBuffer {
|
pub struct TelemetryBuffer {
|
||||||
|
/// The latest input sample on ADC0/ADC1.
|
||||||
pub latest_samples: [i16; 2],
|
pub latest_samples: [i16; 2],
|
||||||
|
/// The latest output code on DAC0/DAC1.
|
||||||
pub latest_outputs: [u16; 2],
|
pub latest_outputs: [u16; 2],
|
||||||
|
/// The latest digital input states during processing.
|
||||||
pub digital_inputs: [bool; 2],
|
pub digital_inputs: [bool; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Telemetry {
|
pub struct Telemetry {
|
||||||
input_levels: [f32; 2],
|
input_levels: [f32; 2],
|
||||||
|
@ -32,16 +63,37 @@ impl Default for TelemetryBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TelemetryBuffer {
|
impl TelemetryBuffer {
|
||||||
pub fn to_telemetry(self, afe0: AfeGain, afe1: AfeGain) -> Telemetry {
|
/// 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.
|
||||||
let in0_volts =
|
let in0_volts =
|
||||||
(self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
(self.latest_samples[0] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
||||||
* 5.0
|
* 5.0
|
||||||
/ afe0.to_multiplier() as f32;
|
/ afe0.as_multiplier();
|
||||||
let in1_volts =
|
let in1_volts =
|
||||||
(self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
(self.latest_samples[1] as f32 / i16::MAX as f32) * 4.096 / 2.0
|
||||||
* 5.0
|
* 5.0
|
||||||
/ afe1.to_multiplier() as f32;
|
/ afe1.as_multiplier();
|
||||||
|
|
||||||
|
// 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.
|
||||||
let out0_volts = (10.24 * 2.0)
|
let out0_volts = (10.24 * 2.0)
|
||||||
* (self.latest_outputs[0] as f32 / (u16::MAX as f32))
|
* (self.latest_outputs[0] as f32 / (u16::MAX as f32))
|
||||||
- 10.24;
|
- 10.24;
|
||||||
|
@ -57,13 +109,16 @@ impl TelemetryBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TelemetryClient<T: Serialize> {
|
|
||||||
mqtt: minimq::MqttClient<minimq::consts::U256, NetworkReference>,
|
|
||||||
telemetry_topic: String<consts::U128>,
|
|
||||||
_telemetry: core::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Serialize> TelemetryClient<T> {
|
impl<T: Serialize> TelemetryClient<T> {
|
||||||
|
/// 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.
|
||||||
pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self {
|
pub fn new(stack: NetworkReference, client_id: &str, prefix: &str) -> Self {
|
||||||
let mqtt =
|
let mqtt =
|
||||||
minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack)
|
minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack)
|
||||||
|
@ -79,6 +134,14 @@ impl<T: Serialize> TelemetryClient<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
pub fn publish(&mut self, telemetry: &T) {
|
pub fn publish(&mut self, telemetry: &T) {
|
||||||
let telemetry: Vec<u8, consts::U256> =
|
let telemetry: Vec<u8, consts::U256> =
|
||||||
serde_json_core::to_vec(telemetry).unwrap();
|
serde_json_core::to_vec(telemetry).unwrap();
|
||||||
|
@ -87,6 +150,12 @@ impl<T: Serialize> TelemetryClient<T> {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
pub fn update(&mut self) {
|
||||||
match self.mqtt.poll(|_client, _topic, _message, _properties| {}) {
|
match self.mqtt.poll(|_client, _topic, _message, _properties| {}) {
|
||||||
Err(minimq::Error::Network(
|
Err(minimq::Error::Network(
|
||||||
|
|
Loading…
Reference in New Issue