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::*;