stream/
lib.rs

1//! Stabilizer data stream capabilities
2//!
3//! # Design
4//! Data streamining utilizes UDP packets to send data streams at high throughput.
5//! Packets are always sent in a best-effort fashion, and data may be dropped.
6//!
7//! Stabilizer organizes streamed data into batches within a "Frame" that will be sent as a UDP
8//! packet. Each frame consits of a header followed by sequential batch serializations. The packet
9//! header is constant for all streaming capabilities, but the serialization format after the header
10//! is application-defined.
11//!
12//! ## Frame Header
13//! The header consists of the following, all in little-endian.
14//!
15//! * **Magic word 0x057B** (u16): a constant to identify Stabilizer streaming data.
16//! * **Format Code** (u8): a unique ID that indicates the serialization format of each batch of data
17//!   in the frame. Refer to [Format] for further information.
18//! * **Batch Count** (u8): the number of batches of data.
19//! * **Sequence Number** (u32): an the sequence number of the first batch in the frame.
20//!   This can be used to determine if and how many stream batches are lost.
21//!
22//! # Example
23//! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception
24//! of streamed data.
25
26#![no_std]
27
28use core::{fmt::Write, net::SocketAddr};
29use heapless::String;
30use num_enum::IntoPrimitive;
31use serde::Serialize;
32use serde_with::DeserializeFromStr;
33
34/// Represents the destination for the UDP stream to send data to.
35///
36/// # Miniconf
37/// `<addr>:<port>`
38///
39/// * `<addr>` is an IPv4 address. E.g. `192.168.0.1`
40/// * `<port>` is any unsigned 16-bit value.
41///
42/// ## Example
43/// `192.168.0.1:1234`
44#[derive(Copy, Clone, Debug, DeserializeFromStr, PartialEq, Eq)]
45pub struct Target(pub SocketAddr);
46
47impl Default for Target {
48    fn default() -> Self {
49        Self("0.0.0.0:0".parse().unwrap())
50    }
51}
52
53impl Serialize for Target {
54    fn serialize<S: serde::Serializer>(
55        &self,
56        serializer: S,
57    ) -> Result<S::Ok, S::Error> {
58        let mut display: String<30> = String::new();
59        write!(&mut display, "{}", self.0).unwrap();
60        serializer.serialize_str(&display)
61    }
62}
63
64impl core::str::FromStr for Target {
65    type Err = &'static str;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        let addr = SocketAddr::from_str(s)
69            .map_err(|_| "Invalid socket address format")?;
70        Ok(Self(addr))
71    }
72}
73
74/// Specifies the format of streamed data
75#[repr(u8)]
76#[derive(Debug, Copy, Clone, PartialEq, Eq, IntoPrimitive)]
77pub enum Format {
78    /// Reserved, unused format specifier.
79    Unknown = 0,
80
81    /// ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format.
82    ///
83    /// # Example
84    /// With a batch size of 2, the serialization would take the following form:
85    /// ```
86    /// <ADC0[0]> <ADC0[1]> <ADC1[0]> <ADC1[1]> <DAC0[0]> <DAC0[1]> <DAC1[0]> <DAC1[1]>
87    /// ```
88    AdcDacData = 1,
89
90    /// FLS (fiber length stabilization) format. See the FLS application.
91    Fls = 2,
92
93    /// Thermostat-EEM data. See `thermostat-eem` repo and application.
94    ThermostatEem = 3,
95}
96
97#[cfg(target_arch = "arm")]
98mod stream;
99#[cfg(target_arch = "arm")]
100pub use stream::*;