diff --git a/Cargo.lock b/Cargo.lock index 9c42b6a..970506d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,12 @@ dependencies = [ "heapless 0.6.1", ] +[[package]] +name = "nanorand" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1378b66f7c93a1c0f8464a19bf47df8795083842e5090f4b7305973d5a22d0" + [[package]] name = "nb" version = "0.1.3" @@ -710,10 +716,11 @@ dependencies = [ [[package]] name = "smoltcp-nal" version = "0.1.0" -source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=56519012d7#56519012d7c6a382eaa0d7ecb26f2701771d9ce8" +source = "git+https://github.com/quartiq/smoltcp-nal.git?rev=8468f11#8468f11abacd7aba82454e6904df19c1d1ab91bb" dependencies = [ "embedded-nal", "heapless 0.6.1", + "nanorand", "smoltcp", ] diff --git a/Cargo.toml b/Cargo.toml index 8d7ae92..f4db4d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ rev = "314fa5587d" [dependencies.smoltcp-nal] git = "https://github.com/quartiq/smoltcp-nal.git" -rev = "56519012d7" +rev = "8468f11" [patch.crates-io.minimq] git = "https://github.com/quartiq/minimq.git" diff --git a/hitl/run.sh b/hitl/run.sh index 483a8a3..2af4732 100755 --- a/hitl/run.sh +++ b/hitl/run.sh @@ -17,6 +17,9 @@ python3 -m pip install -r requirements.txt cargo flash --elf target/thumbv7em-none-eabihf/release/dual-iir --chip STM32H743ZITx +# Before attempting to ping the device, sleep to allow Stabilizer to boot. +sleep 30 + # Test pinging Stabilizer. This exercises that: # * DHCP is functional and an IP has been acquired # * Stabilizer's network is functioning as intended @@ -24,6 +27,6 @@ cargo flash --elf target/thumbv7em-none-eabihf/release/dual-iir --chip STM32H743 ping -c 5 -w 20 stabilizer-hitl # Test the MQTT interface. -python3 miniconf.py dt/sinara/stabilizer afe/0='"G2"' -python3 miniconf.py dt/sinara/stabilizer afe/0='"G1"' iir_ch/0/0=\ +python3 miniconf.py dt/sinara/dual-iir/04-91-62-d9-7e-5f afe/0='"G2"' +python3 miniconf.py dt/sinara/dual-iir/04-91-62-d9-7e-5f afe/0='"G1"' iir_ch/0/0=\ '{"y_min": -32767, "y_max": 32767, "y_offset": 0, "ba": [1.0, 0, 0, 0, 0]}' diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs index 1de0ff0..83cf764 100644 --- a/src/bin/dual-iir.rs +++ b/src/bin/dual-iir.rs @@ -2,17 +2,19 @@ #![no_std] #![no_main] -use stabilizer::hardware; +use stabilizer::{hardware, net}; -use miniconf::{minimq, Miniconf, MqttInterface}; +use miniconf::{minimq, Miniconf}; use serde::{Deserialize, Serialize}; use dsp::iir; use hardware::{ - Adc0Input, Adc1Input, AfeGain, CycleCounter, Dac0Output, Dac1Output, - DigitalInput0, DigitalInput1, SystemTimer, InputPin, NetworkStack, AFE0, AFE1, + Adc0Input, Adc1Input, AfeGain, Dac0Output, Dac1Output, DigitalInput0, + DigitalInput1, InputPin, SystemTimer, AFE0, AFE1, }; +use net::{Action, MiniconfInterface}; + const SCALE: f32 = i16::MAX as _; // The number of cascaded IIR biquads per channel. Select 1 or 2! @@ -63,11 +65,9 @@ const APP: () = { digital_inputs: (DigitalInput0, DigitalInput1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - mqtt_interface: - MqttInterface, + mqtt_config: MiniconfInterface, telemetry: Telemetry, settings: Settings, - clock: CycleCounter, // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -79,23 +79,16 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - let mqtt_interface = { - let mqtt_client = { - minimq::MqttClient::new( - hardware::design_parameters::MQTT_BROKER.into(), - "", - stabilizer.net.stack, - ) - .unwrap() - }; - - MqttInterface::new( - mqtt_client, - "dt/sinara/stabilizer", - Settings::default(), - ) - .unwrap() - }; + let mqtt_config = MiniconfInterface::new( + stabilizer.net.stack, + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); // Spawn a settings update for default settings. c.spawn.settings_update().unwrap(); @@ -111,13 +104,12 @@ const APP: () = { stabilizer.adc_dac_timer.start(); init::LateResources { - mqtt_interface, afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, - clock: stabilizer.cycle_counter, telemetry: Telemetry::default(), digital_inputs: stabilizer.digital_inputs, + mqtt_config, settings: Settings::default(), } } @@ -186,44 +178,26 @@ const APP: () = { ]; } - #[idle(resources=[mqtt_interface, clock], spawn=[settings_update])] + #[idle(resources=[mqtt_config], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { - let clock = c.resources.clock; - loop { - let sleep = c.resources.mqtt_interface.lock(|interface| { - match interface.network_stack().poll(clock.current_ms()) { - Ok(updated) => !updated, - Err(err) => { - log::info!("Network error: {:?}", err); - false - } - } - }); - match c .resources - .mqtt_interface - .lock(|interface| interface.update()) + .mqtt_config + .lock(|config_interface| config_interface.update()) { - Ok(update) => { - if update { - c.spawn.settings_update().unwrap(); - } else if sleep { - //cortex_m::asm::wfi(); - } + Some(Action::Sleep) => cortex_m::asm::wfi(), + Some(Action::UpdateSettings) => { + c.spawn.settings_update().unwrap() } - Err(miniconf::MqttError::Network( - smoltcp_nal::NetworkError::NoIpAddress, - )) => {} - Err(error) => log::info!("Unexpected error: {:?}", error), + _ => {} } } } - #[task(priority = 1, resources=[mqtt_interface, afes, settings])] + #[task(priority = 1, resources=[mqtt_config, afes, settings])] fn settings_update(mut c: settings_update::Context) { - let settings = &c.resources.mqtt_interface.settings; + let settings = &c.resources.mqtt_config.mqtt.settings; // Update the IIR channels. c.resources.settings.lock(|current| *current = *settings); @@ -233,9 +207,10 @@ const APP: () = { c.resources.afes.1.set_gain(settings.afe[1]); } - #[task(priority = 1, resources=[mqtt_interface, settings, telemetry], schedule=[telemetry])] + #[task(priority = 1, resources=[mqtt_config, settings, telemetry], schedule=[telemetry])] fn telemetry(mut c: telemetry::Context) { - let telemetry = c.resources.telemetry.lock(|telemetry| telemetry.clone()); + let telemetry = + c.resources.telemetry.lock(|telemetry| telemetry.clone()); // Serialize telemetry outside of a critical section to prevent blocking the processing // task. @@ -245,15 +220,29 @@ const APP: () = { >(&telemetry) .unwrap(); - c.resources.mqtt_interface.client(|client| { + c.resources.mqtt_config.mqtt.client(|client| { // TODO: Incorporate current MQTT prefix instead of hard-coded value. - client.publish("dt/sinara/dual-iir/telemetry", telemetry.as_bytes(), minimq::QoS::AtMostOnce, &[]).ok() + client + .publish( + "dt/sinara/dual-iir/telemetry", + telemetry.as_bytes(), + minimq::QoS::AtMostOnce, + &[], + ) + .ok() }); - let telemetry_period = c.resources.settings.lock(|settings| settings.telemetry_period_secs); + let telemetry_period = c + .resources + .settings + .lock(|settings| settings.telemetry_period_secs); // Schedule the telemetry task in the future. - c.schedule.telemetry( c.scheduled + SystemTimer::ticks_from_secs(telemetry_period as u32)) + c.schedule + .telemetry( + c.scheduled + + SystemTimer::ticks_from_secs(telemetry_period as u32), + ) .unwrap(); } diff --git a/src/bin/lockin-external.rs b/src/bin/lockin-external.rs index 7d2db0e..082b0d2 100644 --- a/src/bin/lockin-external.rs +++ b/src/bin/lockin-external.rs @@ -4,16 +4,20 @@ use generic_array::typenum::U4; -use miniconf::{minimq, Miniconf, MqttInterface}; use serde::Deserialize; use dsp::{Accu, Complex, ComplexExt, Lockin, RPLL}; +use stabilizer::net; + use stabilizer::hardware::{ - design_parameters, setup, Adc0Input, Adc1Input, AfeGain, CycleCounter, - Dac0Output, Dac1Output, InputStamper, NetworkStack, AFE0, AFE1, + design_parameters, setup, Adc0Input, Adc1Input, AfeGain, Dac0Output, + Dac1Output, InputStamper, AFE0, AFE1, }; +use miniconf::Miniconf; +use stabilizer::net::{Action, MiniconfInterface}; + #[derive(Copy, Clone, Debug, Deserialize, Miniconf)] enum Conf { PowerPhase, @@ -56,11 +60,7 @@ const APP: () = { afes: (AFE0, AFE1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - clock: CycleCounter, - - mqtt_interface: - MqttInterface, - + mqtt_config: MiniconfInterface, settings: Settings, timestamper: InputStamper, @@ -73,21 +73,16 @@ const APP: () = { // Configure the microcontroller let (mut stabilizer, _pounder) = setup(c.core, c.device); - let mqtt_interface = { - let mqtt_client = minimq::MqttClient::new( - design_parameters::MQTT_BROKER.into(), - "", - stabilizer.net.stack, - ) - .unwrap(); - - MqttInterface::new( - mqtt_client, - "dt/sinara/lockin", - Settings::default(), - ) - .unwrap() - }; + let mqtt_config = MiniconfInterface::new( + stabilizer.net.stack, + "", + &net::get_device_prefix( + env!("CARGO_BIN_NAME"), + stabilizer.net.mac_address, + ), + stabilizer.net.phy, + stabilizer.cycle_counter, + ); let settings = Settings::default(); @@ -118,10 +113,8 @@ const APP: () = { afes: stabilizer.afes, adcs: stabilizer.adcs, dacs: stabilizer.dacs, + mqtt_config, timestamper: stabilizer.timestamper, - clock: stabilizer.cycle_counter, - - mqtt_interface, settings, @@ -202,44 +195,26 @@ const APP: () = { } } - #[idle(resources=[mqtt_interface, clock], spawn=[settings_update])] + #[idle(resources=[mqtt_config], spawn=[settings_update])] fn idle(mut c: idle::Context) -> ! { - let clock = c.resources.clock; - loop { - let sleep = c.resources.mqtt_interface.lock(|interface| { - match interface.network_stack().poll(clock.current_ms()) { - Ok(updated) => !updated, - Err(err) => { - log::info!("Network error: {:?}", err); - false - } - } - }); - match c .resources - .mqtt_interface - .lock(|interface| interface.update()) + .mqtt_config + .lock(|config_interface| config_interface.update()) { - Ok(update) => { - if update { - c.spawn.settings_update().unwrap(); - } else if sleep { - cortex_m::asm::wfi(); - } + Some(Action::Sleep) => cortex_m::asm::wfi(), + Some(Action::UpdateSettings) => { + c.spawn.settings_update().unwrap() } - Err(miniconf::MqttError::Network( - smoltcp_nal::NetworkError::NoIpAddress, - )) => {} - Err(error) => log::info!("Unexpected error: {:?}", error), + _ => {} } } } - #[task(priority = 1, resources=[mqtt_interface, settings, afes])] + #[task(priority = 1, resources=[mqtt_config, settings, afes])] fn settings_update(mut c: settings_update::Context) { - let settings = &c.resources.mqtt_interface.settings; + let settings = &c.resources.mqtt_config.mqtt.settings; c.resources.afes.0.set_gain(settings.afe[0]); c.resources.afes.1.set_gain(settings.afe[1]); diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index b612bf7..1fb7ab8 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -13,8 +13,8 @@ use embedded_hal::digital::v2::{InputPin, OutputPin}; use super::{ adc, afe, cycle_counter::CycleCounter, dac, design_parameters, - digital_input_stamper, eeprom, pounder, timers, system_timer, DdsOutput, DigitalInput0, - DigitalInput1, NetworkStack, AFE0, AFE1, + digital_input_stamper, eeprom, pounder, system_timer, timers, DdsOutput, + DigitalInput0, DigitalInput1, EthernetPhy, NetworkStack, AFE0, AFE1, }; pub struct NetStorage { @@ -56,7 +56,8 @@ impl NetStorage { /// The available networking devices on Stabilizer. pub struct NetworkDevices { pub stack: NetworkStack, - pub phy: ethernet::phy::LAN8742A, + pub phy: EthernetPhy, + pub mac_address: smoltcp::wire::EthernetAddress, } /// The available hardware interfaces on Stabilizer. @@ -608,14 +609,27 @@ pub fn setup( ) }; + let random_seed = { + let mut rng = + device.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks); + let mut data = [0u8; 4]; + rng.fill(&mut data).unwrap(); + data + }; + + let mut stack = smoltcp_nal::NetworkStack::new( + interface, + sockets, + &handles, + Some(dhcp_client), + ); + + stack.seed_random_port(&random_seed); + NetworkDevices { - stack: smoltcp_nal::NetworkStack::new( - interface, - sockets, - &handles, - Some(dhcp_client), - ), + stack, phy: lan8742a, + mac_address: mac_addr, } }; diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index c24b5e3..a439642 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -53,6 +53,8 @@ pub type NetworkStack = smoltcp_nal::NetworkStack< hal::ethernet::EthernetDMA<'static>, >; +pub type EthernetPhy = hal::ethernet::phy::LAN8742A; + pub use configuration::{setup, PounderDevices, StabilizerDevices}; #[inline(never)] diff --git a/src/lib.rs b/src/lib.rs index 0c9bf2a..975b08f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ extern crate log; pub mod hardware; +pub mod net; diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..13b514c --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,148 @@ +use crate::hardware::{ + design_parameters::MQTT_BROKER, CycleCounter, EthernetPhy, NetworkStack, +}; + +use core::fmt::Write; + +use heapless::{consts, String}; +use miniconf::minimq; + +/// Potential actions for firmware to take. +pub enum Action { + /// Indicates that firmware can sleep for the next event. + Sleep, + + /// Indicates that settings have updated and firmware needs to propogate changes. + UpdateSettings, +} + +/// MQTT settings interface. +pub struct MiniconfInterface +where + S: miniconf::Miniconf + Default, +{ + pub mqtt: miniconf::MqttInterface, + clock: CycleCounter, + phy: EthernetPhy, + network_was_reset: bool, +} + +impl MiniconfInterface +where + S: miniconf::Miniconf + Default, +{ + /// Construct a new MQTT settings interface. + /// + /// # Args + /// * `stack` - The network stack to use for communication. + /// * `client_id` - The ID of the MQTT client. May be an empty string for auto-assigning. + /// * `prefix` - The MQTT device prefix to use for this device. + /// * `phy` - The PHY driver for querying the link state. + /// * `clock` - The clock to utilize for querying the current system time. + pub fn new( + stack: NetworkStack, + client_id: &str, + prefix: &str, + phy: EthernetPhy, + clock: CycleCounter, + ) -> Self { + let mqtt = { + let mqtt_client = { + minimq::MqttClient::new(MQTT_BROKER.into(), client_id, stack) + .unwrap() + }; + + miniconf::MqttInterface::new(mqtt_client, prefix, S::default()) + .unwrap() + }; + + Self { + mqtt, + clock, + phy, + network_was_reset: false, + } + } + + /// Update the MQTT interface and service the network + /// + /// # Returns + /// An option containing an action that should be completed as a result of network servicing. + pub fn update(&mut self) -> Option { + let now = self.clock.current_ms(); + + // First, service the network stack to process and inbound and outbound traffic. + let sleep = match self.mqtt.network_stack().poll(now) { + Ok(updated) => !updated, + Err(err) => { + log::info!("Network error: {:?}", err); + false + } + }; + + // If the PHY indicates there's no more ethernet link, reset the DHCP server in the network + // stack. + if self.phy.poll_link() == false { + // Only reset the network stack once per link reconnection. This prevents us from + // sending an excessive number of DHCP requests. + if !self.network_was_reset { + self.network_was_reset = true; + self.mqtt.network_stack().handle_link_reset(); + } + } else { + self.network_was_reset = false; + } + + // Finally, service the MQTT interface and handle any necessary messages. + match self.mqtt.update() { + Ok(true) => Some(Action::UpdateSettings), + Ok(false) if sleep => Some(Action::Sleep), + Ok(_) => None, + + Err(miniconf::MqttError::Network( + smoltcp_nal::NetworkError::NoIpAddress, + )) => None, + + Err(error) => { + log::info!("Unexpected error: {:?}", error); + None + } + } + } +} + +/// Get the MQTT prefix of a device. +/// +/// # Args +/// * `app` - The name of the application that is executing. +/// * `mac` - The ethernet MAC address of the device. +/// +/// # Returns +/// The MQTT prefix used for this device. +pub fn get_device_prefix( + app: &str, + mac: smoltcp_nal::smoltcp::wire::EthernetAddress, +) -> String { + let mac_string = { + let mut mac_string: String = String::new(); + let mac = mac.as_bytes(); + + // Note(unwrap): 32-bytes is guaranteed to be valid for any mac address, as the address has + // a fixed length. + write!( + &mut mac_string, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ) + .unwrap(); + + mac_string + }; + + // Note(unwrap): The mac address + binary name must be short enough to fit into this string. If + // they are defined too long, this will panic and the device will fail to boot. + let mut prefix: String = String::new(); + write!(&mut prefix, "dt/sinara/{}/{}", app, mac_string).unwrap(); + + prefix +}