diff --git a/.gitignore b/.gitignore index 5bb0577cc..57ea0f714 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /target -/dsp/target -vpy/ +/py/build diff --git a/CHANGELOG.md b/CHANGELOG.md index feebf7fe6..52ff12fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased](https://github.com/quartiq/stabilizer/compare/v0.9.0...main) +## [v0.10.0](https://github.com/quartiq/stabilizer/compare/v0.9.0...v0.10.0) ### Added * Serial terminal is available on USB for settings configurations @@ -29,7 +29,7 @@ console. ### Fixed * Fixed an issue where the device would sometimes not enumerate on Windows -## [0.9.0](https://github.com/quartiq/stabilizer/compare/v0.8.1...v0.9.0) +## [v0.9.0](https://github.com/quartiq/stabilizer/compare/v0.8.1...v0.9.0) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index d2e0ba919..0936a270d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "ad9959" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bit_field", "bitflags 2.6.0", @@ -1320,7 +1320,7 @@ dependencies = [ [[package]] name = "stabilizer" -version = "0.9.0" +version = "0.10.0" dependencies = [ "ad9959", "bit_field", diff --git a/Cargo.toml b/Cargo.toml index dd67841b5..6bc6f8555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "stabilizer" # Keep versions in Cargo.toml and py/setup.py synchronized. -version = "0.9.0" +version = "0.10.0" resolver = "2" authors = [ "Robert Jördens ", @@ -16,7 +16,6 @@ readme = "README.md" documentation = "https://docs.rs/stabilizer/" edition = "2021" exclude = [ - ".gitignore", "doc/", "doc/*" ] @@ -50,7 +49,7 @@ embedded-hal = "0.2.7" num_enum = { version = "0.7.3", default-features = false } paste = "1" idsp = "0.15.1" -ad9959 = { path = "ad9959", version = "0.2.1" } +ad9959 = { path = "ad9959", version = "0.3.0" } serial-settings = { version = "0.1", path = "serial-settings" } mcp230xx = "1.0" mutex-trait = "0.2" diff --git a/ad9959/Cargo.toml b/ad9959/Cargo.toml index e7cee05e5..07db8949c 100644 --- a/ad9959/Cargo.toml +++ b/ad9959/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ad9959" -version = "0.2.1" +version = "0.3.0" authors = ["Ryan Summers "] license = "MIT OR Apache-2.0" edition = "2018" diff --git a/book/src/usage.md b/book/src/usage.md index 87df521f6..add02886c 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -55,17 +55,12 @@ form `dt/sinara//`, where `` is the name of the applicati Settings have a `path` and a `value` being configured. The `value` parameter is JSON-encoded data and the `path` value is a path-like string. -As an example, for configuring `dual-iir`'s `stream_target`, the following information would be -used: -* `path` = `stream_target` -* `value` = `{"ip": [192, 168, 0, 1], "port": 4000}` - ``` -python -m miniconf --broker 10.34.16.1 dt/sinara/dual-iir/00-11-22-33-44-55 stream_target='{"ip": [10, 34, 16, 123], "port": 4000}' - -Where `10.34.16.1` is the MQTT broker address that matches the one configured in the source code and `10.34.16.123` and `4000` are the desire stream target IP and port. +python -m miniconf --broker 10.34.16.1 dt/sinara/dual-iir/00-11-22-33-44-55 stream='"10.34.16.123:4000"' ``` +Where `10.34.16.1` is the MQTT broker address that matches the one used by the application and `10.34.16.123` and `4000` are the desire stream target IP and port. + The prefix can be found for a specific device by looking at the topic on which telemetry that is being published. It can also be automatically discovered if there is only one device alive. @@ -111,13 +106,13 @@ digital input states. Refer to the respective [application documentation](overview.md#applications) for more information on telemetry. -# Livestream +# Stream -Stabilizer supports livestream capabilities for streaming real-time data over UDP. The livestream is +Stabilizer supports streaming real-time data over UDP. The stream is intended to be a high-bandwidth mechanism to transfer large amounts of data from Stabilizer to a host computer for further analysis. -Livestreamed data is sent with "best effort" - it's possible that data may be lost either due to +Streamed data is sent with "best effort" - it's possible that data may be lost either due to network congestion or by Stabilizer. Refer to the the respective [application documentation](overview.md#applications) for more information. diff --git a/hitl/loopback.py b/hitl/loopback.py index ae8df4d77..382f9afb1 100644 --- a/hitl/loopback.py +++ b/hitl/loopback.py @@ -65,7 +65,7 @@ async def test_loopback(stabilizer, telemetry_queue, set_point, gain=1, channel= await stabilizer.set(f"/iir_ch/{channel}/0", static_iir_output(set_point)) # Configure signal generators to not affect the test. - await stabilizer.set("/signal_generator/0/amplitude", 0) + await stabilizer.set("/source/0/amplitude", 0) # Wait for telemetry to update. await telemetry_queue.__anext__() diff --git a/hitl/run.sh b/hitl/run.sh index ff735d459..a8c25fdea 100755 --- a/hitl/run.sh +++ b/hitl/run.sh @@ -41,5 +41,5 @@ python3 -m stabilizer.iir_coefficients -p $PREFIX -c 0 -v pid --Ki 10 --Kp 1 # Test the ADC/DACs connected via loopback. python3 hitl/loopback.py $PREFIX -# Test the livestream capabilities +# Test the stream capabilities python3 hitl/streaming.py $PREFIX diff --git a/hitl/streaming.py b/hitl/streaming.py index 14c134e14..4d0799163 100644 --- a/hitl/streaming.py +++ b/hitl/streaming.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -"""HITL testing of Stabilizer data livestream capabilities""" +"""HITL testing of Stabilizer data stream capabilities""" import asyncio import logging @@ -58,7 +58,7 @@ async def _main(): args.ip = get_local_ip(args.broker) logger.info("Starting stream") - await conf.set("/stream_target", f"{args.ip}:{args.port}", retain=False) + await conf.set("/stream", f"{args.ip}:{args.port}", retain=False) try: logger.info("Testing stream reception") @@ -70,7 +70,7 @@ async def _main(): raise RuntimeError("High frame loss", loss) finally: logger.info("Stopping stream") - await conf.set("/stream_target", "0.0.0.0:0", retain=False) + await conf.set("/stream", "0.0.0.0:0", retain=False) logger.info("Draining queue") await asyncio.sleep(0.1) diff --git a/py/README.md b/py/README.md index 7a7cd62a8..814df7c59 100644 --- a/py/README.md +++ b/py/README.md @@ -1,6 +1,6 @@ # Stabilizer Python Utilities -This directory contains common Python utilities for Stabilizer, such as livestream data receivers. +This directory contains common Python utilities for Stabilizer, such as stream data receivers. To install this module locally (in editable mode): ``` diff --git a/py/pyproject.toml b/py/pyproject.toml index 6647bdfeb..6f2bab78b 100644 --- a/py/pyproject.toml +++ b/py/pyproject.toml @@ -8,7 +8,7 @@ text = "MIT" [project] name = "stabilizer" # Note: keep this in sync with Cargo.toml -version = "0.9.0" +version = "0.10.0" description = "Utilities for configuring Stabilizer" authors = [ { name = "Robert Jördens", email = "rj@quartiq.de" }, diff --git a/serial-settings/Cargo.toml b/serial-settings/Cargo.toml index 2a4ed61ad..ebbe068d2 100644 --- a/serial-settings/Cargo.toml +++ b/serial-settings/Cargo.toml @@ -3,11 +3,11 @@ name = "serial-settings" version = "0.1.0" edition = "2021" readme = "README.md" +description = "Embedded device settings management over serial terminal and flash" authors = [ "Robert Jördens ", "Ryan Summers ", ] -description = "Persistent settings management over serial interfaces" categories = ["embedded", "no-std", "config", "command-line-interface"] license = "MIT OR Apache-2.0" keywords = ["serial", "settings", "cli", "management", "async"] diff --git a/serial-settings/src/lib.rs b/serial-settings/src/lib.rs index ec8147a0f..bb6e51e89 100644 --- a/serial-settings/src/lib.rs +++ b/serial-settings/src/lib.rs @@ -601,8 +601,10 @@ impl<'a, P: Platform, const Y: usize> Runner<'a, P, Y> { /// # Args /// * `platform` - The platform associated with the serial settings, providing the necessary /// context and API to manage device settings. + /// /// * `line_buf` - A buffer used for maintaining the serial menu input line. It should be at /// least as long as the longest user input. + /// /// * `serialize_buf` - A buffer used for serializing and deserializing settings. This buffer /// needs to be at least as big as twice the biggest serialized setting plus its path. pub fn new( diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 5dc1c7a8e..fa28ebb4a 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -22,7 +22,7 @@ //! ## Telemetry //! Refer to [Telemetry] for information about telemetry reported by this application. //! -//! ## Livestreaming +//! ## Stream //! This application streams raw ADC and DAC data over UDP. Refer to //! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information. #![no_std] @@ -159,26 +159,26 @@ pub struct DualIir { /// Any non-zero value less than 65536. telemetry_period: u16, - /// Specifies the target for data livestreaming. + /// Specifies the target for data streaming. /// /// # Path - /// `stream_target` + /// `stream` /// /// # Value /// See [StreamTarget#miniconf] - stream_target: StreamTarget, + stream: StreamTarget, /// Specifies the config for signal generators to add on to DAC0/DAC1 outputs. /// /// # Path - /// `signal_generator/` + /// `source/` /// /// * `` specifies which channel to configure. `` := [0, 1] /// /// # Value /// See [signal_generator::BasicConfig#miniconf] #[tree(depth = 2)] - signal_generator: [signal_generator::BasicConfig; 2], + source: [signal_generator::BasicConfig; 2], } impl Default for DualIir { @@ -188,7 +188,7 @@ impl Default for DualIir { i.set_max(SCALE); Self { // Analog frontend programmable gain amplifier gains (G1, G2, G5, G10) - afe: [Gain::G1, Gain::G1], + afe: Default::default(), // IIR filter tap gains are an array `[b0, b1, b2, a1, a2]` such that the // new output is computed as `y0 = a1*y1 + a2*y2 + b0*x0 + b1*x1 + b2*x2`. // The array is `iir_state[channel-index][cascade-index][coeff-index]`. @@ -203,9 +203,9 @@ impl Default for DualIir { // The default telemetry period in seconds. telemetry_period: 10, - signal_generator: [signal_generator::BasicConfig::default(); 2], + source: Default::default(), - stream_target: StreamTarget::default(), + stream: Default::default(), } } } @@ -221,7 +221,7 @@ mod app { settings: Settings, active_settings: DualIir, telemetry: TelemetryBuffer, - signal_generator: [SignalGenerator; 2], + source: [SignalGenerator; 2], } #[local] @@ -266,14 +266,14 @@ mod app { network, active_settings: stabilizer.settings.dual_iir.clone(), telemetry: TelemetryBuffer::default(), - signal_generator: [ + source: [ SignalGenerator::new( - stabilizer.settings.dual_iir.signal_generator[0] + stabilizer.settings.dual_iir.source[0] .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) .unwrap(), ), SignalGenerator::new( - stabilizer.settings.dual_iir.signal_generator[1] + stabilizer.settings.dual_iir.source[1] .try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) .unwrap(), ), @@ -332,13 +332,13 @@ mod app { /// /// Because the ADC and DAC operate at the same rate, these two constraints actually implement /// the same time bounds, meeting one also means the other is also met. - #[task(binds=DMA1_STR4, local=[digital_inputs, adcs, dacs, iir_state, generator], shared=[active_settings, signal_generator, telemetry], priority=3)] + #[task(binds=DMA1_STR4, local=[digital_inputs, adcs, dacs, iir_state, generator], shared=[active_settings, source, telemetry], priority=3)] #[link_section = ".itcm.process"] fn process(c: process::Context) { let process::SharedResources { active_settings, telemetry, - signal_generator, + source, .. } = c.shared; @@ -351,8 +351,8 @@ mod app { .. } = c.local; - (active_settings, telemetry, signal_generator).lock( - |settings, telemetry, signal_generator| { + (active_settings, telemetry, source).lock( + |settings, telemetry, source| { let digital_inputs = [digital_inputs.0.is_high(), digital_inputs.1.is_high()]; telemetry.digital_inputs = digital_inputs; @@ -371,7 +371,7 @@ mod app { adc_samples[channel] .iter() .zip(dac_samples[channel].iter_mut()) - .zip(&mut signal_generator[channel]) + .zip(&mut source[channel]) .map(|((ai, di), signal)| { let x = f32::from(*ai as i16); let y = settings.iir_ch[channel] @@ -400,7 +400,7 @@ mod app { } // Stream the data. - const N: usize = BATCH_SIZE * core::mem::size_of::(); + const N: usize = BATCH_SIZE * size_of::(); generator.add(|buf| { for (data, buf) in adc_samples .iter() @@ -458,20 +458,18 @@ mod app { } } - #[task(priority = 1, local=[afes], shared=[network, settings, active_settings, signal_generator])] + #[task(priority = 1, local=[afes], shared=[network, settings, active_settings, source])] async fn settings_update(mut c: settings_update::Context) { c.shared.settings.lock(|settings| { c.local.afes.0.set_gain(settings.dual_iir.afe[0]); c.local.afes.1.set_gain(settings.dual_iir.afe[1]); // Update the signal generators - for (i, &config) in - settings.dual_iir.signal_generator.iter().enumerate() - { + for (i, &config) in settings.dual_iir.source.iter().enumerate() { match config.try_into_config(SAMPLE_PERIOD, DacCode::FULL_SCALE) { Ok(config) => { - c.shared.signal_generator.lock(|generator| { + c.shared.source.lock(|generator| { generator[i].update_waveform(config) }); } @@ -485,7 +483,7 @@ mod app { c.shared .network - .lock(|net| net.direct_stream(settings.dual_iir.stream_target)); + .lock(|net| net.direct_stream(settings.dual_iir.stream)); c.shared .active_settings @@ -496,8 +494,7 @@ mod app { #[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])] async fn telemetry(mut c: telemetry::Context) { loop { - let telemetry: TelemetryBuffer = - c.shared.telemetry.lock(|telemetry| *telemetry); + let telemetry = c.shared.telemetry.lock(|telemetry| *telemetry); let (gains, telemetry_period) = c.shared.settings.lock(|settings| { diff --git a/src/bin/lockin.rs b/src/bin/lockin.rs index 77924c843..75cef184a 100644 --- a/src/bin/lockin.rs +++ b/src/bin/lockin.rs @@ -21,7 +21,7 @@ //! ## Telemetry //! Refer to [Telemetry] for information about telemetry reported by this application. //! -//! ## Livestreaming +//! ## Stream //! This application streams raw ADC and DAC data over UDP. Refer to //! [stabilizer::net::data_stream](../stabilizer/net/data_stream/index.html) for more information. #![no_std] @@ -214,14 +214,14 @@ pub struct Lockin { /// Any non-zero value less than 65536. telemetry_period: u16, - /// Specifies the target for data livestreaming. + /// Specifies the target for data streaming. /// /// # Path - /// `stream_target` + /// `stream` /// /// # Value /// See [StreamTarget#miniconf] - stream_target: StreamTarget, + stream: StreamTarget, } impl Default for Lockin { @@ -241,7 +241,7 @@ impl Default for Lockin { // The default telemetry period in seconds. telemetry_period: 10, - stream_target: StreamTarget::default(), + stream: StreamTarget::default(), } } } @@ -270,7 +270,7 @@ mod app { dacs: (Dac0Output, Dac1Output), pll: RPLL, lockin: idsp::Lockin>>, - signal_generator: signal_generator::SignalGenerator, + source: signal_generator::SignalGenerator, generator: FrameGenerator, cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor, } @@ -327,9 +327,7 @@ mod app { pll: RPLL::new(SAMPLE_TICKS_LOG2 + BATCH_SIZE_LOG2), lockin: idsp::Lockin::default(), - signal_generator: signal_generator::SignalGenerator::new( - signal_config, - ), + source: signal_generator::SignalGenerator::new(signal_config), generator, cpu_temp_sensor: stabilizer.temperature_sensor, @@ -371,7 +369,7 @@ mod app { /// This is an implementation of a externally (DI0) referenced PLL lockin on the ADC0 signal. /// It outputs either I/Q or power/phase on DAC0/DAC1. Data is normalized to full scale. /// PLL bandwidth, filter bandwidth, slope, and x/y or power/phase post-filters are available. - #[task(binds=DMA1_STR4, shared=[active_settings, telemetry], local=[adcs, dacs, lockin, timestamper, pll, generator, signal_generator], priority=3)] + #[task(binds=DMA1_STR4, shared=[active_settings, telemetry], local=[adcs, dacs, lockin, timestamper, pll, generator, source], priority=3)] #[link_section = ".itcm.process"] fn process(c: process::Context) { let process::SharedResources { @@ -386,7 +384,7 @@ mod app { dacs: (dac0, dac1), pll, lockin, - signal_generator, + source, generator, .. } = c.local; @@ -450,9 +448,7 @@ mod app { Conf::InPhase => output.re >> 16, Conf::Quadrature => output.im >> 16, - Conf::Modulation => { - signal_generator.next().unwrap() as i32 - } + Conf::Modulation => source.next().unwrap() as i32, }; *sample = DacCode::from(value as i16).0; @@ -460,8 +456,8 @@ mod app { } // Stream the data. - const N: usize = BATCH_SIZE * core::mem::size_of::() - / core::mem::size_of::>(); + const N: usize = BATCH_SIZE * size_of::() + / size_of::>(); generator.add(|buf| { for (data, buf) in adc_samples .iter() @@ -523,7 +519,7 @@ mod app { c.shared .network - .lock(|net| net.direct_stream(settings.lockin.stream_target)); + .lock(|net| net.direct_stream(settings.lockin.stream)); c.shared .active_settings @@ -534,8 +530,7 @@ mod app { #[task(priority = 1, local=[digital_inputs, cpu_temp_sensor], shared=[network, settings, telemetry])] async fn telemetry(mut c: telemetry::Context) { loop { - let mut telemetry: TelemetryBuffer = - c.shared.telemetry.lock(|telemetry| *telemetry); + let mut telemetry = c.shared.telemetry.lock(|telemetry| *telemetry); telemetry.digital_inputs = [ c.local.digital_inputs.0.is_high(), diff --git a/src/hardware/afe.rs b/src/hardware/afe.rs index faea48e66..35d71ae54 100644 --- a/src/hardware/afe.rs +++ b/src/hardware/afe.rs @@ -3,9 +3,12 @@ use serde::{Deserialize, Serialize}; use core::convert::TryFrom; use num_enum::TryFromPrimitive; -#[derive(Copy, Clone, Debug, Serialize, Deserialize, TryFromPrimitive)] +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, TryFromPrimitive, Default, +)] #[repr(u8)] pub enum Gain { + #[default] G1 = 0b00, G2 = 0b01, G5 = 0b10, diff --git a/src/net/data_stream.rs b/src/net/data_stream.rs index 5ebe6fb1d..714ec57fb 100644 --- a/src/net/data_stream.rs +++ b/src/net/data_stream.rs @@ -1,10 +1,10 @@ //! Stabilizer data stream capabilities //! //! # Design -//! Data streamining utilizes UDP packets to send live data streams at high throughput. +//! Data streamining utilizes UDP packets to send data streams at high throughput. //! Packets are always sent in a best-effort fashion, and data may be dropped. //! -//! Stabilizer organizes livestreamed data into batches within a "Frame" that will be sent as a UDP +//! Stabilizer organizes streamed data into batches within a "Frame" that will be sent as a UDP //! packet. Each frame consits of a header followed by sequential batch serializations. The packet //! header is constant for all streaming capabilities, but the serialization format after the header //! is application-defined. @@ -21,7 +21,7 @@ //! //! # Example //! A sample Python script is available in `scripts/stream_throughput.py` to demonstrate reception -//! of livestreamed data. +//! of streamed data. #![allow(non_camel_case_types)] // https://github.com/rust-embedded/heapless/issues/411 @@ -116,7 +116,7 @@ pub enum StreamFormat { /// Reserved, unused format specifier. Unknown = 0, - /// Streamed data contains ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format. + /// ADC0, ADC1, DAC0, and DAC1 sequentially in little-endian format. /// /// # Example /// With a batch size of 2, the serialization would take the following form: @@ -125,9 +125,11 @@ pub enum StreamFormat { /// ``` AdcDacData = 1, - /// Streamed data in FLS (fiber length stabilization) format. See the FLS application for - /// detailed definition. + /// FLS (fiber length stabilization) format. See the FLS application. Fls = 2, + + /// Thermostat-EEM data. See `thermostat-eem` repo and application. + ThermostatEem = 3, } /// Configure streaming on a device. @@ -379,7 +381,7 @@ impl DataStream { let data = unsafe { core::slice::from_raw_parts( buf.as_ptr() as *const u8, - core::mem::size_of_val(buf), + size_of_val(buf), ) }; diff --git a/src/net/mod.rs b/src/net/mod.rs index 6a552c68e..43733fa8a 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -2,7 +2,7 @@ //! //! # Design //! The stabilizer network architecture supports numerous layers to permit transmission of -//! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data +//! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and data //! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines //! related to Stabilizer networking operations. pub use heapless; @@ -156,7 +156,7 @@ where } } - /// Enable live data streaming. + /// Enable data streaming. /// /// # Args /// * `format` - A unique u8 code indicating the format of the data.