From e6e6ecbe5b3f84609722444127cfafc062142c07 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 2 Jun 2025 20:01:42 +0200 Subject: [PATCH 01/12] refactor sensor data handling to improve pump control logic and update documentation --- README.md | 2 -- src/domain.rs | 30 ++++++++++++++++-------------- src/sensors_task.rs | 26 ++++++++++++++++---------- src/update_task.rs | 19 +++++++++++++------ 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d875c58..f8cbe73 100644 --- a/README.md +++ b/README.md @@ -119,8 +119,6 @@ The built-in 1.9" ST7789 LCD display on the T-Display-S3 has the following pin c - [heapless](https://crates.io/crates/heapless) - [static_cell](https://crates.io/crates/static_cell) - [rand_core](https://crates.io/crates/rand_core) -- [defmt](https://crates.io/crates/defmt) -- [defmt-rtt](https://crates.io/crates/defmt-rtt) - ...and others --- diff --git a/src/domain.rs b/src/domain.rs index c48e93c..b73b248 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -29,11 +29,12 @@ impl Display for SensorData { } } +/// Represents the qualitative state of soil moisture as interpreted from sensor readings. #[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum MoistureLevel { - Wet, - Moist, - Dry, + Wet, // Soil is wet + Moist, // Soil is moist (intermediate) + Dry, // Soil is dry } impl Display for MoistureLevel { @@ -60,10 +61,11 @@ impl From for MoistureLevel { } } +/// Indicates if water is present at the base of the pot (drainage area). #[derive(Debug, Serialize, Deserialize)] pub enum WaterLevel { - Full, - Empty, + Full, // Water detected at the pot base + Empty, // No water detected at the pot base } impl Display for WaterLevel { @@ -85,16 +87,16 @@ impl From for WaterLevel { } } -/// Enum to represent different types of sensors +/// Represents all supported sensor types and their current readings. #[derive(Debug)] pub enum Sensor { - WaterLevel(WaterLevel), - AirTemperature(u8), - AirHumidity(u8), - SoilMoisture(MoistureLevel), - BatteryVoltage(u16), - SoilMoistureRaw(SoilMoistureRawLevel), - PumpTrigger(bool), + WaterLevel(WaterLevel), // Water at pot base + AirTemperature(u8), // Air temperature in °C + AirHumidity(u8), // Air humidity in % + SoilMoisture(MoistureLevel), // Soil moisture (qualitative) + BatteryVoltage(u16), // Battery voltage in mV + SoilMoistureRaw(SoilMoistureRawLevel), // Raw soil moisture sensor value + PumpTrigger(bool), // Whether pump should be triggered } #[derive(Debug)] @@ -155,7 +157,7 @@ impl Sensor { Sensor::AirTemperature(_) => "Room temperature", Sensor::AirHumidity(_) => "Room humidity", Sensor::SoilMoisture(_) => "Soil moisture", - Sensor::WaterLevel(_) => "Water level", + Sensor::WaterLevel(_) => "Drainage water level", Sensor::BatteryVoltage(_) => "Battery voltage", Sensor::SoilMoistureRaw(_) => "Soil moisture (mV)", Sensor::PumpTrigger(_) => "Pump trigger", diff --git a/src/sensors_task.rs b/src/sensors_task.rs index 71e9648..7a945b6 100644 --- a/src/sensors_task.rs +++ b/src/sensors_task.rs @@ -15,10 +15,14 @@ use heapless::Vec; use crate::{ config::AWAKE_DURATION_SECONDS, dht11::Dht11, - domain::{MoistureLevel, Sensor, SensorData, WaterLevel}, + domain::{Sensor, SensorData, WaterLevel}, + BOOT_COUNT, }; -const USB_CHARGING_VOLTAGE: u16 = 4200; +/// Number of boots between pump trigger events. +/// The pump will be enabled every Nth boot, where N is this value. +const PUMP_TRIGGER_INTERVAL: u32 = 10; +const USB_CHARGING_VOLTAGE: u16 = 4100; const DHT11_WARMUP_DELAY_MILLISECONDS: u64 = 2000; const SENSOR_WARMUP_DELAY_MILLISECONDS: u64 = 50; // in this case we keep 3 samples for averaging - first and last are ignored @@ -162,7 +166,7 @@ pub async fn sensor_task( if let Some(avg_water_level) = calculate_average(&mut water_level_samples) { let waterlevel: WaterLevel = avg_water_level.into(); - println!("Water level: {}", waterlevel); + println!("Pot base water level: {}", waterlevel); sensor_data .data .push(Sensor::WaterLevel(avg_water_level.into())) @@ -182,17 +186,19 @@ pub async fn sensor_task( .data .push(Sensor::SoilMoisture(avg_soil_moisture.into())) .expect("Too many samples"); - - // Set pump trigger to true if majority of samples indicated it should be on - let moisture_level: MoistureLevel = avg_soil_moisture.into(); - sensor_data - .data - .push(Sensor::PumpTrigger(moisture_level == MoistureLevel::Dry)) - .expect("Too many samples"); } else { println!("Unable to generate average value of soil moisture"); } + let boot_count = unsafe { BOOT_COUNT }; + + let pump_enabled = boot_count % PUMP_TRIGGER_INTERVAL == 0; + + sensor_data + .data + .push(Sensor::PumpTrigger(pump_enabled)) + .expect("Too many samples"); + if let Some(avg_battery_voltage) = calculate_average(&mut battery_voltage_samples) { println!("Battery voltage: {}mV", avg_battery_voltage); sensor_data diff --git a/src/update_task.rs b/src/update_task.rs index 43c9d8a..b02ad1f 100644 --- a/src/update_task.rs +++ b/src/update_task.rs @@ -235,14 +235,21 @@ async fn publish_sensor_data( client: &mut MqttClientImpl<'_>, sensor_data: &SensorData, ) -> Result<(), Error> { + // check if we can enable the pump + let allow_enable_pump = sensor_data + .data + .iter() + .any(|entry| matches!(entry, Sensor::WaterLevel(WaterLevel::Empty))); + sensor_data.data.iter().for_each(|entry| { - if let Sensor::WaterLevel(WaterLevel::Full) = entry { - println!("Water level is full, stopping pump"); - update_pump_state(false); - } else if let Sensor::PumpTrigger(enabled) = entry { + if let Sensor::PumpTrigger(enabled) = entry { let enabled = *enabled; - println!("Pump trigger value: {} - updating pump state", enabled); - update_pump_state(enabled); + if allow_enable_pump { + println!("Pump trigger value: {} - updating pump state", enabled); + update_pump_state(enabled); + } else { + update_pump_state(false); + } } }); From d9ccfb0e1839f9c9879d0f4789e80e3ff2f1c349 Mon Sep 17 00:00:00 2001 From: Eric Funk Date: Tue, 3 Jun 2025 11:17:56 +0200 Subject: [PATCH 02/12] refactor DHT11 sensor handling in sensor_task for improved power management and efficiency --- src/dht11.rs | 13 -------- src/sensors_task.rs | 79 ++++++++++++++++++++++----------------------- 2 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/dht11.rs b/src/dht11.rs index 74256dd..6224fbb 100644 --- a/src/dht11.rs +++ b/src/dht11.rs @@ -119,16 +119,3 @@ where Ok(u32::from(count)) } } - -impl Drop for Dht11 -where - GPIO: InputPin + OutputPin, - D: DelayNs, -{ - fn drop(&mut self) { - // Set pin high (floating with pull-up) as safe state - // Ignore errors during drop - let _ = self.gpio.set_high(); - self.delay.delay_us(40); - } -} diff --git a/src/sensors_task.rs b/src/sensors_task.rs index 7a945b6..e3085f6 100644 --- a/src/sensors_task.rs +++ b/src/sensors_task.rs @@ -70,39 +70,41 @@ pub async fn sensor_task( let mut battery_voltage_samples: Vec = Vec::new(); let mut water_level_samples: Vec = Vec::new(); - // Power on the sensors - moisture_power_pin.set_high(); - water_level_power_pin.set_high(); - - let sampling_period = Duration::from_secs(AWAKE_DURATION_SECONDS); - for i in 0..SENSOR_SAMPLE_COUNT { println!("Reading sensor data {}/{}", (i + 1), SENSOR_SAMPLE_COUNT); - { - let mut dht11_pin = Output::new( - &mut p.dht11_digital_pin, - Level::High, - OutputConfig::default() - .with_drive_mode(DriveMode::OpenDrain) - .with_pull(Pull::None), - ) - .into_flex(); - dht11_pin.enable_input(true); - - let mut dht11_sensor = Dht11::new(dht11_pin, Delay); - - // DHT11 needs a longer initial delay - Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; - if let Ok(result) = dht11_sensor.read() { - air_temperature_samples - .push(result.temperature) - .expect("Too many samples"); - air_humidity_samples - .push(result.humidity) - .expect("Too many samples"); - } - } // drop dht11_pin + let mut dht11_pin = Output::new( + &mut p.dht11_digital_pin, + Level::High, + OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::None), + ) + .into_flex(); + dht11_pin.enable_input(true); + + let mut dht11_sensor = Dht11::new(dht11_pin, Delay); + + // DHT11 needs a longer initial delay + Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; + if let Ok(result) = dht11_sensor.read() { + air_temperature_samples + .push(result.temperature) + .expect("Too many samples"); + air_humidity_samples + .push(result.humidity) + .expect("Too many samples"); + } + // Immediately put pin into low power mode after DHT11 usage + Output::new( + &mut p.dht11_digital_pin, + Level::Low, + OutputConfig::default() + .with_drive_mode(DriveMode::PushPull) + .with_pull(Pull::None), + ); + + moisture_power_pin.set_high(); if let Some(result) = sample_adc(&mut adc2, &mut moisture_pin).await { soil_moisture_samples @@ -112,12 +114,18 @@ pub async fn sensor_task( println!("Error reading soil moisture sensor"); } + moisture_power_pin.set_low(); + + water_level_power_pin.set_high(); + if let Some(value) = sample_adc(&mut adc2, &mut waterlevel_pin).await { water_level_samples.push(value).expect("Too many samples"); } else { println!("Error reading water level sensor"); } + water_level_power_pin.set_low(); + if let Some(value) = sample_adc(&mut adc1, &mut battery_pin).await { let value = value * 2; // The battery voltage divider is 2:1 if value < USB_CHARGING_VOLTAGE { @@ -212,16 +220,7 @@ pub async fn sensor_task( sender.send(sensor_data).await; - // Power off the sensors - moisture_power_pin.set_low(); - water_level_power_pin.set_low(); - // Force the pin into an explicit low-power state after the sensor is dropped - Output::new( - &mut p.dht11_digital_pin, - Level::Low, - OutputConfig::default(), - ); - + let sampling_period = Duration::from_secs(AWAKE_DURATION_SECONDS); Timer::after(sampling_period).await; } } From 8bfac1dbcc0c883c2f54946629af067ca5f0f958 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 3 Jun 2025 20:36:28 +0200 Subject: [PATCH 03/12] refactor sensor_task to enhance sensor data collection and hardware initialization --- src/sensors_task.rs | 416 +++++++++++++++++++++++++++----------------- 1 file changed, 253 insertions(+), 163 deletions(-) diff --git a/src/sensors_task.rs b/src/sensors_task.rs index e3085f6..daa1b0f 100644 --- a/src/sensors_task.rs +++ b/src/sensors_task.rs @@ -28,6 +28,18 @@ const SENSOR_WARMUP_DELAY_MILLISECONDS: u64 = 50; // in this case we keep 3 samples for averaging - first and last are ignored const SENSOR_SAMPLE_COUNT: usize = 5; +/// ADC and GPIO handles +struct SensorHardware { + adc1: Adc<'static, ADC1, Blocking>, + adc2: Adc<'static, ADC2, Blocking>, + moisture_pin: AdcPin, ADC2, AdcCalCurve>, + waterlevel_pin: AdcPin, ADC2, AdcCalCurve>, + battery_pin: AdcPin, ADC1, AdcCalLine>, + moisture_power_pin: Output<'static>, + water_level_power_pin: Output<'static>, + dht11_digital_pin: GpioPin<1>, +} + pub struct SensorPeripherals { pub dht11_digital_pin: GpioPin<1>, pub battery_pin: GpioPin<4>, @@ -42,201 +54,202 @@ pub struct SensorPeripherals { #[embassy_executor::task] pub async fn sensor_task( sender: Sender<'static, NoopRawMutex, SensorData, 3>, - mut p: SensorPeripherals, + p: SensorPeripherals, ) { - println!("Create"); + println!("Initializing sensor task"); + + let mut hardware = initialize_hardware(p).await; + + loop { + let sensor_data = collect_all_sensor_data(&mut hardware).await; + sender.send(sensor_data).await; + + let sampling_period = Duration::from_secs(AWAKE_DURATION_SECONDS); + Timer::after(sampling_period).await; + } +} +/// Initialize all sensor hardware +async fn initialize_hardware(p: SensorPeripherals) -> SensorHardware { let mut adc2_config = AdcConfig::new(); - let mut moisture_pin = adc2_config + let moisture_pin = adc2_config .enable_pin_with_cal::<_, AdcCalCurve>(p.moisture_analog_pin, Attenuation::_11dB); - let mut waterlevel_pin = adc2_config.enable_pin(p.water_level_analog_pin, Attenuation::_11dB); - let mut adc2 = Adc::new(p.adc2, adc2_config); + let waterlevel_pin = adc2_config + .enable_pin_with_cal::<_, AdcCalCurve>(p.water_level_analog_pin, Attenuation::_11dB); + let adc2 = Adc::new(p.adc2, adc2_config); let mut adc1_config = AdcConfig::new(); - let mut battery_pin = adc1_config + let battery_pin = adc1_config .enable_pin_with_cal::, AdcCalLine>(p.battery_pin, Attenuation::_11dB); - let mut adc1 = Adc::new(p.adc1, adc1_config); + let adc1 = Adc::new(p.adc1, adc1_config); - let mut moisture_power_pin = - Output::new(p.moisture_power_pin, Level::Low, OutputConfig::default()); - let mut water_level_power_pin = + let moisture_power_pin = Output::new(p.moisture_power_pin, Level::Low, OutputConfig::default()); + let water_level_power_pin = Output::new(p.water_level_power_pin, Level::Low, OutputConfig::default()); - loop { - // Collect samples for each sensor type - let mut air_humidity_samples: Vec = Vec::new(); - let mut air_temperature_samples: Vec = Vec::new(); - let mut soil_moisture_samples: Vec = Vec::new(); - let mut battery_voltage_samples: Vec = Vec::new(); - let mut water_level_samples: Vec = Vec::new(); - - for i in 0..SENSOR_SAMPLE_COUNT { - println!("Reading sensor data {}/{}", (i + 1), SENSOR_SAMPLE_COUNT); - - let mut dht11_pin = Output::new( - &mut p.dht11_digital_pin, - Level::High, - OutputConfig::default() - .with_drive_mode(DriveMode::OpenDrain) - .with_pull(Pull::None), - ) - .into_flex(); - dht11_pin.enable_input(true); - - let mut dht11_sensor = Dht11::new(dht11_pin, Delay); - - // DHT11 needs a longer initial delay - Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; - if let Ok(result) = dht11_sensor.read() { - air_temperature_samples - .push(result.temperature) - .expect("Too many samples"); - air_humidity_samples - .push(result.humidity) - .expect("Too many samples"); - } - // Immediately put pin into low power mode after DHT11 usage - Output::new( - &mut p.dht11_digital_pin, - Level::Low, - OutputConfig::default() - .with_drive_mode(DriveMode::PushPull) - .with_pull(Pull::None), - ); - - moisture_power_pin.set_high(); - - if let Some(result) = sample_adc(&mut adc2, &mut moisture_pin).await { - soil_moisture_samples - .push(result) - .expect("Too many samples"); - } else { - println!("Error reading soil moisture sensor"); - } - - moisture_power_pin.set_low(); - - water_level_power_pin.set_high(); - - if let Some(value) = sample_adc(&mut adc2, &mut waterlevel_pin).await { - water_level_samples.push(value).expect("Too many samples"); - } else { - println!("Error reading water level sensor"); - } - - water_level_power_pin.set_low(); - - if let Some(value) = sample_adc(&mut adc1, &mut battery_pin).await { - let value = value * 2; // The battery voltage divider is 2:1 - if value < USB_CHARGING_VOLTAGE { - battery_voltage_samples - .push(value) - .expect("Too many samples"); - } else { - println!( - "Battery voltage too high - looks we are charging on USB: {}mV", - value - ); - } - } else { - println!("Error reading battery voltage"); - } - } + SensorHardware { + adc1, + adc2, + moisture_pin, + waterlevel_pin, + battery_pin, + moisture_power_pin, + water_level_power_pin, + dht11_digital_pin: p.dht11_digital_pin, + } +} - // Calculate the average of the samples - let mut sensor_data = SensorData::default(); +/// Collect data from all sensors +async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { + let mut air_humidity_samples: Vec = Vec::new(); + let mut air_temperature_samples: Vec = Vec::new(); + let mut soil_moisture_samples: Vec = Vec::new(); + let mut battery_voltage_samples: Vec = Vec::new(); + let mut water_level_samples: Vec = Vec::new(); + + for i in 0..SENSOR_SAMPLE_COUNT { + println!("Reading sensor data {}/{}", (i + 1), SENSOR_SAMPLE_COUNT); + + // Read DHT11 (temperature & humidity) + read_dht11_sensor( + &mut hardware.dht11_digital_pin, + &mut air_temperature_samples, + &mut air_humidity_samples, + ) + .await; + + // Read soil moisture + read_moisture_sensor( + &mut hardware.adc2, + &mut hardware.moisture_pin, + &mut hardware.moisture_power_pin, + &mut soil_moisture_samples, + ) + .await; + + // Read water level + read_water_level_sensor( + &mut hardware.adc2, + &mut hardware.waterlevel_pin, + &mut hardware.water_level_power_pin, + &mut water_level_samples, + ) + .await; + + // Read battery voltage + read_battery_voltage( + &mut hardware.adc1, + &mut hardware.battery_pin, + &mut battery_voltage_samples, + ) + .await; + } - if let Some(avg_air_humidity) = calculate_average(&mut air_humidity_samples) { - println!("Air humidity: {}%", avg_air_humidity); - sensor_data - .data - .push(Sensor::AirHumidity(avg_air_humidity)) - .expect("Too many samples"); - } else { - println!( - "Unable to generate average value of air humidity - we had {} samples", - air_humidity_samples.len() - ); - } + build_sensor_data( + air_humidity_samples, + air_temperature_samples, + soil_moisture_samples, + battery_voltage_samples, + water_level_samples, + ) +} - if let Some(avg_air_temperature) = calculate_average(&mut air_temperature_samples) { - println!("Air temperature: {}°C", avg_air_temperature); - sensor_data - .data - .push(Sensor::AirTemperature(avg_air_temperature)) - .expect("Too many samples"); - } else { - println!( - "Unable to generate average value of air temperature, we had {} samples", - air_temperature_samples.len() - ); - } +/// Read DHT11 temperature and humidity sensor +async fn read_dht11_sensor( + dht11_pin: &mut GpioPin<1>, + temperature_samples: &mut Vec, + humidity_samples: &mut Vec, +) { + let mut pin = Output::new( + dht11_pin, + Level::High, + OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::None), + ) + .into_flex(); + pin.enable_input(true); + + let mut dht11_sensor = Dht11::new(pin, Delay); + Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; + + if let Ok(result) = dht11_sensor.read() { + let _ = temperature_samples.push(result.temperature); + let _ = humidity_samples.push(result.humidity); + } - if let Some(avg_water_level) = calculate_average(&mut water_level_samples) { - let waterlevel: WaterLevel = avg_water_level.into(); - println!("Pot base water level: {}", waterlevel); - sensor_data - .data - .push(Sensor::WaterLevel(avg_water_level.into())) - .expect("Too many samples"); - } else { - println!("Unable to generate average value of water level"); - } + // Pin is now owned by dht11_sensor and will be dropped automatically +} - if let Some(avg_soil_moisture) = calculate_average(&mut soil_moisture_samples) { - println!("Raw Moisture: {}", avg_soil_moisture); - sensor_data - .data - .push(Sensor::SoilMoistureRaw(avg_soil_moisture.into())) - .expect("Too many samples"); - - sensor_data - .data - .push(Sensor::SoilMoisture(avg_soil_moisture.into())) - .expect("Too many samples"); - } else { - println!("Unable to generate average value of soil moisture"); - } +/// Read soil moisture sensor +async fn read_moisture_sensor( + adc: &mut Adc<'_, ADC2, Blocking>, + pin: &mut AdcPin, ADC2, AdcCalCurve>, + power_pin: &mut Output<'_>, + samples: &mut Vec, +) { + power_pin.set_high(); - let boot_count = unsafe { BOOT_COUNT }; + if let Some(result) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { + let _ = samples.push(result); + } else { + println!("Error reading soil moisture sensor"); + } - let pump_enabled = boot_count % PUMP_TRIGGER_INTERVAL == 0; + power_pin.set_low(); +} - sensor_data - .data - .push(Sensor::PumpTrigger(pump_enabled)) - .expect("Too many samples"); - - if let Some(avg_battery_voltage) = calculate_average(&mut battery_voltage_samples) { - println!("Battery voltage: {}mV", avg_battery_voltage); - sensor_data - .data - .push(Sensor::BatteryVoltage(avg_battery_voltage)) - .expect("Too many samples"); - } +/// Read water level sensor +async fn read_water_level_sensor( + adc: &mut Adc<'_, ADC2, Blocking>, + pin: &mut AdcPin, ADC2, AdcCalCurve>, + power_pin: &mut Output<'_>, + samples: &mut Vec, +) { + power_pin.set_high(); - // no battery samples - no publish! - sensor_data.publish = !battery_voltage_samples.is_empty(); + if let Some(value) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { + let _ = samples.push(value); + } else { + println!("Error reading water level sensor"); + } - sender.send(sensor_data).await; + power_pin.set_low(); +} - let sampling_period = Duration::from_secs(AWAKE_DURATION_SECONDS); - Timer::after(sampling_period).await; +/// Read battery voltage +async fn read_battery_voltage( + adc: &mut Adc<'_, ADC1, Blocking>, + pin: &mut AdcPin, ADC1, AdcCalLine>, + samples: &mut Vec, +) { + if let Some(value) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { + let value = value * 2; // The battery voltage divider is 2:1 + if value < USB_CHARGING_VOLTAGE { + let _ = samples.push(value); + } else { + println!( + "Battery voltage too high - looks we are charging on USB: {}mV", + value + ); + } + } else { + println!("Error reading battery voltage"); } } -/// Sample an ADC pin and return the value -async fn sample_adc( +/// Sample ADC with configurable warmup delay +async fn sample_adc_with_warmup( adc: &mut Adc<'_, ADCI, Blocking>, pin: &mut AdcPin, + warmup_ms: u64, ) -> Option where PIN: AdcChannel, ADCI: RegisterAccess, ADCC: AdcCalScheme, { - // Wait for the sensor to warm up - Timer::after(Duration::from_millis(SENSOR_WARMUP_DELAY_MILLISECONDS)).await; + Timer::after(Duration::from_millis(warmup_ms)).await; match nb::block!(adc.read_oneshot(pin)) { Ok(value) => Some(value), Err(e) => { @@ -246,6 +259,83 @@ where } } +/// Build final sensor data structure +fn build_sensor_data( + mut air_humidity_samples: Vec, + mut air_temperature_samples: Vec, + mut soil_moisture_samples: Vec, + mut battery_voltage_samples: Vec, + mut water_level_samples: Vec, +) -> SensorData { + let mut sensor_data = SensorData::default(); + + // Process air humidity + if let Some(avg_air_humidity) = calculate_average(&mut air_humidity_samples) { + println!("Air humidity: {}%", avg_air_humidity); + let _ = sensor_data.data.push(Sensor::AirHumidity(avg_air_humidity)); + } else { + println!( + "Unable to generate average value of air humidity - we had {} samples", + air_humidity_samples.len() + ); + } + + // Process air temperature + if let Some(avg_air_temperature) = calculate_average(&mut air_temperature_samples) { + println!("Air temperature: {}°C", avg_air_temperature); + let _ = sensor_data + .data + .push(Sensor::AirTemperature(avg_air_temperature)); + } else { + println!( + "Unable to generate average value of air temperature, we had {} samples", + air_temperature_samples.len() + ); + } + + // Process water level + if let Some(avg_water_level) = calculate_average(&mut water_level_samples) { + let waterlevel: WaterLevel = avg_water_level.into(); + println!("Pot base water level: {}", waterlevel); + let _ = sensor_data + .data + .push(Sensor::WaterLevel(avg_water_level.into())); + } else { + println!("Unable to generate average value of water level"); + } + + // Process soil moisture + if let Some(avg_soil_moisture) = calculate_average(&mut soil_moisture_samples) { + println!("Raw Moisture: {}", avg_soil_moisture); + let _ = sensor_data + .data + .push(Sensor::SoilMoistureRaw(avg_soil_moisture.into())); + let _ = sensor_data + .data + .push(Sensor::SoilMoisture(avg_soil_moisture.into())); + } else { + println!("Unable to generate average value of soil moisture"); + } + + // Add pump trigger logic + let boot_count = unsafe { BOOT_COUNT }; + let pump_enabled = boot_count % PUMP_TRIGGER_INTERVAL == 0; + let _ = sensor_data.data.push(Sensor::PumpTrigger(pump_enabled)); + + // Process battery voltage + if let Some(avg_battery_voltage) = calculate_average(&mut battery_voltage_samples) { + println!("Battery voltage: {}mV", avg_battery_voltage); + let _ = sensor_data + .data + .push(Sensor::BatteryVoltage(avg_battery_voltage)); + } + + // Only publish if we have battery samples + sensor_data.publish = !battery_voltage_samples.is_empty(); + + sensor_data +} + /// Calculate the average of a slice of samples, removing the highest and lowest values fn calculate_average(samples: &mut [T]) -> Option where From 479a4291607eeb047400e269d4eecee7337d3b1c Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 3 Jun 2025 20:57:52 +0200 Subject: [PATCH 04/12] refactor sensor data collection in collect_all_sensor_data for improved error handling and clarity --- src/sensors_task.rs | 164 +++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 69 deletions(-) diff --git a/src/sensors_task.rs b/src/sensors_task.rs index daa1b0f..dbfc7d8 100644 --- a/src/sensors_task.rs +++ b/src/sensors_task.rs @@ -14,7 +14,7 @@ use heapless::Vec; use crate::{ config::AWAKE_DURATION_SECONDS, - dht11::Dht11, + dht11::{Dht11, Measurement}, domain::{Sensor, SensorData, WaterLevel}, BOOT_COUNT, }; @@ -111,38 +111,52 @@ async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { println!("Reading sensor data {}/{}", (i + 1), SENSOR_SAMPLE_COUNT); // Read DHT11 (temperature & humidity) - read_dht11_sensor( - &mut hardware.dht11_digital_pin, - &mut air_temperature_samples, - &mut air_humidity_samples, - ) - .await; + if let Some(messurement) = read_dht11_sensor(&mut hardware.dht11_digital_pin).await { + if air_temperature_samples + .push(messurement.temperature) + .is_err() + { + println!("Failed to push AirTemperature to sensor_data"); + } + if air_humidity_samples.push(messurement.humidity).is_err() { + println!("Failed to push AirHumidity to sensor_data"); + } + } // Read soil moisture - read_moisture_sensor( + if let Some(moisture) = read_moisture_sensor( &mut hardware.adc2, &mut hardware.moisture_pin, &mut hardware.moisture_power_pin, - &mut soil_moisture_samples, ) - .await; + .await + { + if soil_moisture_samples.push(moisture).is_err() { + println!("Failed to push SoilMoisture to sensor_data"); + } + } // Read water level - read_water_level_sensor( + if let Some(water_level) = read_water_level_sensor( &mut hardware.adc2, &mut hardware.waterlevel_pin, &mut hardware.water_level_power_pin, - &mut water_level_samples, ) - .await; + .await + { + if water_level_samples.push(water_level).is_err() { + println!("Failed to push WaterLevel to sensor_data"); + } + } // Read battery voltage - read_battery_voltage( - &mut hardware.adc1, - &mut hardware.battery_pin, - &mut battery_voltage_samples, - ) - .await; + if let Some(battery_voltage) = + read_battery_voltage(&mut hardware.adc1, &mut hardware.battery_pin).await + { + if battery_voltage_samples.push(battery_voltage).is_err() { + println!("Failed to push BatteryVoltage to sensor_data"); + } + } } build_sensor_data( @@ -155,11 +169,7 @@ async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { } /// Read DHT11 temperature and humidity sensor -async fn read_dht11_sensor( - dht11_pin: &mut GpioPin<1>, - temperature_samples: &mut Vec, - humidity_samples: &mut Vec, -) { +async fn read_dht11_sensor(dht11_pin: &mut GpioPin<1>) -> Option { let mut pin = Output::new( dht11_pin, Level::High, @@ -173,12 +183,7 @@ async fn read_dht11_sensor( let mut dht11_sensor = Dht11::new(pin, Delay); Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; - if let Ok(result) = dht11_sensor.read() { - let _ = temperature_samples.push(result.temperature); - let _ = humidity_samples.push(result.humidity); - } - - // Pin is now owned by dht11_sensor and will be dropped automatically + dht11_sensor.read().ok() } /// Read soil moisture sensor @@ -186,17 +191,13 @@ async fn read_moisture_sensor( adc: &mut Adc<'_, ADC2, Blocking>, pin: &mut AdcPin, ADC2, AdcCalCurve>, power_pin: &mut Output<'_>, - samples: &mut Vec, -) { +) -> Option { power_pin.set_high(); - if let Some(result) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { - let _ = samples.push(result); - } else { - println!("Error reading soil moisture sensor"); - } + let result = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await; power_pin.set_low(); + result } /// Read water level sensor @@ -204,37 +205,30 @@ async fn read_water_level_sensor( adc: &mut Adc<'_, ADC2, Blocking>, pin: &mut AdcPin, ADC2, AdcCalCurve>, power_pin: &mut Output<'_>, - samples: &mut Vec, -) { +) -> Option { power_pin.set_high(); - if let Some(value) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { - let _ = samples.push(value); - } else { - println!("Error reading water level sensor"); - } + let result = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await; power_pin.set_low(); + result } /// Read battery voltage async fn read_battery_voltage( adc: &mut Adc<'_, ADC1, Blocking>, pin: &mut AdcPin, ADC1, AdcCalLine>, - samples: &mut Vec, -) { - if let Some(value) = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await { - let value = value * 2; // The battery voltage divider is 2:1 - if value < USB_CHARGING_VOLTAGE { - let _ = samples.push(value); - } else { - println!( - "Battery voltage too high - looks we are charging on USB: {}mV", - value - ); - } +) -> Option { + let value = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await? * 2; + + if value < USB_CHARGING_VOLTAGE { + Some(value) } else { - println!("Error reading battery voltage"); + println!( + "Battery voltage too high - looks we are charging on USB: {}mV", + value + ); + None } } @@ -272,7 +266,13 @@ fn build_sensor_data( // Process air humidity if let Some(avg_air_humidity) = calculate_average(&mut air_humidity_samples) { println!("Air humidity: {}%", avg_air_humidity); - let _ = sensor_data.data.push(Sensor::AirHumidity(avg_air_humidity)); + if sensor_data + .data + .push(Sensor::AirHumidity(avg_air_humidity)) + .is_err() + { + println!("Failed to push AirHumidity to sensor_data"); + } } else { println!( "Unable to generate average value of air humidity - we had {} samples", @@ -283,9 +283,13 @@ fn build_sensor_data( // Process air temperature if let Some(avg_air_temperature) = calculate_average(&mut air_temperature_samples) { println!("Air temperature: {}°C", avg_air_temperature); - let _ = sensor_data + if sensor_data .data - .push(Sensor::AirTemperature(avg_air_temperature)); + .push(Sensor::AirTemperature(avg_air_temperature)) + .is_err() + { + println!("Failed to push AirTemperature to sensor_data"); + } } else { println!( "Unable to generate average value of air temperature, we had {} samples", @@ -297,9 +301,13 @@ fn build_sensor_data( if let Some(avg_water_level) = calculate_average(&mut water_level_samples) { let waterlevel: WaterLevel = avg_water_level.into(); println!("Pot base water level: {}", waterlevel); - let _ = sensor_data + if sensor_data .data - .push(Sensor::WaterLevel(avg_water_level.into())); + .push(Sensor::WaterLevel(avg_water_level.into())) + .is_err() + { + println!("Failed to push WaterLevel to sensor_data"); + } } else { println!("Unable to generate average value of water level"); } @@ -307,12 +315,20 @@ fn build_sensor_data( // Process soil moisture if let Some(avg_soil_moisture) = calculate_average(&mut soil_moisture_samples) { println!("Raw Moisture: {}", avg_soil_moisture); - let _ = sensor_data + if sensor_data .data - .push(Sensor::SoilMoistureRaw(avg_soil_moisture.into())); - let _ = sensor_data + .push(Sensor::SoilMoistureRaw(avg_soil_moisture.into())) + .is_err() + { + println!("Failed to push SoilMoistureRaw to sensor_data"); + } + if sensor_data .data - .push(Sensor::SoilMoisture(avg_soil_moisture.into())); + .push(Sensor::SoilMoisture(avg_soil_moisture.into())) + .is_err() + { + println!("Failed to push SoilMoisture to sensor_data"); + } } else { println!("Unable to generate average value of soil moisture"); } @@ -320,14 +336,24 @@ fn build_sensor_data( // Add pump trigger logic let boot_count = unsafe { BOOT_COUNT }; let pump_enabled = boot_count % PUMP_TRIGGER_INTERVAL == 0; - let _ = sensor_data.data.push(Sensor::PumpTrigger(pump_enabled)); + if sensor_data + .data + .push(Sensor::PumpTrigger(pump_enabled)) + .is_err() + { + println!("Failed to push PumpTrigger to sensor_data"); + } // Process battery voltage if let Some(avg_battery_voltage) = calculate_average(&mut battery_voltage_samples) { println!("Battery voltage: {}mV", avg_battery_voltage); - let _ = sensor_data + if sensor_data .data - .push(Sensor::BatteryVoltage(avg_battery_voltage)); + .push(Sensor::BatteryVoltage(avg_battery_voltage)) + .is_err() + { + println!("Failed to push BatteryVoltage to sensor_data"); + } } // Only publish if we have battery samples From 645316c7f05a1ec051ac1474b9db3caff70ce507 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 3 Jun 2025 20:58:46 +0200 Subject: [PATCH 05/12] implement Drop trait for Dht11 to ensure safe GPIO pin state on drop --- src/dht11.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dht11.rs b/src/dht11.rs index 6224fbb..74256dd 100644 --- a/src/dht11.rs +++ b/src/dht11.rs @@ -119,3 +119,16 @@ where Ok(u32::from(count)) } } + +impl Drop for Dht11 +where + GPIO: InputPin + OutputPin, + D: DelayNs, +{ + fn drop(&mut self) { + // Set pin high (floating with pull-up) as safe state + // Ignore errors during drop + let _ = self.gpio.set_high(); + self.delay.delay_us(40); + } +} From eb3abb5b76853697f2ab91bda0372d9abd43396b Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 3 Jun 2025 20:59:39 +0200 Subject: [PATCH 06/12] remove unnecessary delay in Drop implementation for Dht11 to ensure safe GPIO pin state --- src/dht11.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dht11.rs b/src/dht11.rs index 74256dd..7e1b8c2 100644 --- a/src/dht11.rs +++ b/src/dht11.rs @@ -129,6 +129,5 @@ where // Set pin high (floating with pull-up) as safe state // Ignore errors during drop let _ = self.gpio.set_high(); - self.delay.delay_us(40); } } From 40b8b193616a5586a6135fc8076e449dbbb7691a Mon Sep 17 00:00:00 2001 From: Eric Funk Date: Wed, 4 Jun 2025 13:39:22 +0200 Subject: [PATCH 07/12] update dependencies and refactor GPIO usage for improved type safety and clarity --- Cargo.lock | 222 +++++++++++++++++++++++++------------------- Cargo.toml | 20 ++-- src/display.rs | 34 ++++--- src/relay_task.rs | 7 +- src/sensors_task.rs | 102 ++++++++++---------- src/wifi.rs | 8 +- 6 files changed, 213 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12a9447..e67f288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78200ac3468a57d333cd0ea5dd398e25111194dcacd49208afca95c629a6311d" + [[package]] name = "anyhow" version = "1.0.98" @@ -31,18 +37,18 @@ dependencies = [ [[package]] name = "bitfield" -version = "0.18.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7e6caee68becd795bfd65f1a026e4d00d8f0c2bc9be5eb568e1015f9ce3c34" +checksum = "db1bcd90f88eabbf0cadbfb87a45bceeaebcd3b4bc9e43da379cd2ef0162590d" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.18.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331afbb18ce7b644c0b428726d369c5dd37ca0b815d72a459fcc2896c3c8ad32" +checksum = "3787a07661997bfc05dd3431e379c0188573f78857080cf682e1393ab8e4d64c" dependencies = [ "proc-macro2", "quote", @@ -79,21 +85,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "num-traits", -] - [[package]] name = "critical-section" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.20.11" @@ -140,6 +147,15 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "crypto-common", +] + [[package]] name = "document-features" version = "0.2.11" @@ -156,7 +172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fea5ef5bed4d3468dfd44f5c9fa4cda8f54c86d4fb4ae683eacf9d39e2ea12" dependencies = [ "embassy-futures", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embedded-hal 0.2.7", "embedded-hal 1.0.0", @@ -203,7 +219,7 @@ checksum = "940c4b9fe5c1375b09a0c6722c0100d6b2ed46a717a34f632f26e8d7327c4383" dependencies = [ "document-features", "embassy-net-driver", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embedded-io-async", "embedded-nal-async", @@ -232,6 +248,20 @@ dependencies = [ "heapless", ] +[[package]] +name = "embassy-sync" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef1a8a1ea892f9b656de0295532ac5d8067e9830d49ec75076291fd6066b136" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + [[package]] name = "embassy-time" version = "0.4.0" @@ -280,7 +310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08e753b23799329780c7ac434264026d0422044d6649ed70a73441b14a6436d7" dependencies = [ "critical-section", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-usb-driver", ] @@ -443,10 +473,11 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "esp-alloc" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78132d362cbf62ce22a1466eb9e98424f6b2d1e476e7a3cb46ca9063c5833f7" +checksum = "7e95f1de57ce5a6600368f3d3c931b0dfe00501661e96f5ab83bc5cdee031784" dependencies = [ + "allocator-api2", "cfg-if", "critical-section", "document-features", @@ -456,20 +487,24 @@ dependencies = [ [[package]] name = "esp-backtrace" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cd70abe47945c9116972781b5c05277ad855a5f5569fe2afd3e2e61a103cc0" +checksum = "7c304bbe17df32db8bc0027a9da989aa3efebbd4e7a79d58850deb29e2af577f" dependencies = [ + "cfg-if", "esp-build", + "esp-config", + "esp-metadata", "esp-println", + "heapless", "semihosting", ] [[package]] name = "esp-build" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aa1c8f9954c9506699cf1ca10a2adcc226ff10b6ae3cb9e875cf2c6a0b9a372" +checksum = "837020ff95fbf4c15c206541dda7994f1bbe6e1505e36a6a5ecb51fdb61656d7" dependencies = [ "quote", "syn", @@ -478,31 +513,33 @@ dependencies = [ [[package]] name = "esp-config" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158dba334d3a2acd8d93873c0ae723ca1037cc78eefe5d6b4c5919b0ca28e38e" +checksum = "2c8c4c95d8d6243ddb39efe1fcf2524c9becd0f86bb3e24048ed30b4f553609f" dependencies = [ "document-features", + "serde", + "serde_json", ] [[package]] name = "esp-hal" -version = "1.0.0-beta.0" +version = "1.0.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9efaa9c1324ca20a22086aba2ce47a9bdc5bd65969af8b0cd5e879603b57bef" +checksum = "0d973697621cd3eef9c3f260fa8c1af77d8547cfc92734255d8e8ddf05c7d331" dependencies = [ "basic-toml", "bitfield", "bitflags 2.9.0", "bytemuck", "cfg-if", - "chrono", "critical-section", "delegate", + "digest", "document-features", "embassy-embedded-hal", "embassy-futures", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-usb-driver", "embassy-usb-synopsys-otg", "embedded-can", @@ -520,31 +557,30 @@ dependencies = [ "esp32s3", "fugit", "instability", - "log", "nb 1.1.0", "paste", "portable-atomic", - "rand_core", + "rand_core 0.6.4", + "rand_core 0.9.3", "riscv", "serde", - "strum 0.27.1", + "strum", "ufmt-write", - "usb-device", - "void", "xtensa-lx", "xtensa-lx-rt", ] [[package]] name = "esp-hal-embassy" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27f41110117a9bf2be385b42535c686b301c8ce3b5ea0a07567e200a63a2239" +checksum = "7fd751f4b235764f6e766731e7da09d8f8d443642b829c3e6e469c38320da044" dependencies = [ + "cfg-if", "critical-section", "document-features", "embassy-executor", - "embassy-sync", + "embassy-sync 0.6.2", "embassy-time", "embassy-time-driver", "embassy-time-queue-utils", @@ -559,9 +595,9 @@ dependencies = [ [[package]] name = "esp-hal-procmacros" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd340a20a7d546570af58fd9e2aae17466a42572680d8e70d35fc7c475c4ed8" +checksum = "73164008cb2eada2ef85e6b0e459001d851f9b8e65e96e0d594bdfa8cf1b813b" dependencies = [ "darling", "document-features", @@ -576,33 +612,35 @@ dependencies = [ [[package]] name = "esp-metadata" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b4bffc22b7b1222c9467f0cb90eb49dcb63de810ecb3300e4b3bbc4ac2423e" +checksum = "0154d59933c2419ef25a01938517cc6969f47b6af53ebb34c279393aa20d9654" dependencies = [ "anyhow", "basic-toml", "serde", - "strum 0.26.3", + "strum", ] [[package]] name = "esp-println" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960703930f9f3c899ddedd122ea27a09d6a612c22323157e524af5b18876448e" +checksum = "fae8b38d5fdc1d29d823c4737f18edfb0ccf0406985cf893f87c0cfc26a6ab33" dependencies = [ "critical-section", + "document-features", "esp-build", + "esp-metadata", "log", "portable-atomic", ] [[package]] name = "esp-riscv-rt" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec69987b3d7c48b65f8fb829220832a101478d766c518ae836720d040608d5dd" +checksum = "c05c2badd16cbd6307d463090615332b77c17a6766b41ba5fe5bb783310e8af6" dependencies = [ "document-features", "riscv", @@ -624,15 +662,15 @@ dependencies = [ [[package]] name = "esp-wifi" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d7ea0e2c374343a375758861e13cf618db619436bcb386dfe5529ef31e9d5" +checksum = "4f0e7cc4d0ceed4117e0f190abba1f021d986a5b6a54ef2116541e4288b700a9" dependencies = [ + "allocator-api2", "cfg-if", "critical-section", "document-features", "embassy-net-driver", - "embassy-sync", "embedded-io", "embedded-io-async", "enumset", @@ -642,14 +680,11 @@ dependencies = [ "esp-hal", "esp-metadata", "esp-wifi-sys", - "heapless", - "libm", - "log", "num-derive", "num-traits", "portable-atomic", "portable_atomic_enum", - "rand_core", + "rand_core 0.9.3", "serde", "xtensa-lx-rt", ] @@ -661,7 +696,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6b5438361891c431970194a733415006fb3d00b6eb70b3dcb66fd58f04d9b39" dependencies = [ "anyhow", - "log", ] [[package]] @@ -671,7 +705,7 @@ dependencies = [ "embassy-executor", "embassy-futures", "embassy-net", - "embassy-sync", + "embassy-sync 0.7.0", "embassy-time", "embedded-graphics", "embedded-hal 1.0.0", @@ -693,9 +727,9 @@ dependencies = [ [[package]] name = "esp32s3" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6d20f119410092abfbc65e46f9362015a7110023528f0dbe855cab80c38ca8" +checksum = "27a4c6fd31207a297fc29d2b8f4da27facf45f8c83041f7c0f978aa65ab367c9" dependencies = [ "critical-section", "vcell", @@ -761,6 +795,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hash32" version = "0.3.1" @@ -783,8 +827,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", - "portable-atomic", - "serde", "stable_deref_trait", ] @@ -835,12 +877,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "linked_list_allocator" version = "0.10.5" @@ -1063,6 +1099,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + [[package]] name = "riscv" version = "0.12.1" @@ -1113,7 +1155,7 @@ dependencies = [ "embedded-io", "embedded-io-async", "heapless", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1209,35 +1251,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ - "strum_macros 0.27.1", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", + "strum_macros", ] [[package]] @@ -1314,6 +1334,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "ufmt-write" version = "0.1.0" @@ -1342,6 +1368,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "void" version = "1.0.2" @@ -1441,9 +1473,9 @@ dependencies = [ [[package]] name = "xtensa-lx" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51cbb46c78cfd284c9378070ab90bae9d14d38b3766cb853a97c0a137f736d5b" +checksum = "68737a6c8f32ddcd97476acf68ddc6d411697fd94f64a601af16854b74967dff" dependencies = [ "critical-section", "document-features", @@ -1451,9 +1483,9 @@ dependencies = [ [[package]] name = "xtensa-lx-rt" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "689c2ef159d9cd4fc9503603e9999968a84a30db9bde0f0f880d0cceea0190a9" +checksum = "235815f34d1bf9c2f9c07917e2b63efbcab5ca5ce9d8faddb97b7105eed1ade3" dependencies = [ "anyhow", "document-features", @@ -1461,7 +1493,7 @@ dependencies = [ "minijinja", "r0", "serde", - "strum 0.26.3", + "strum", "toml", "xtensa-lx", "xtensa-lx-rt-proc-macros", @@ -1469,9 +1501,9 @@ dependencies = [ [[package]] name = "xtensa-lx-rt-proc-macros" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11277b1e4cbb7ffe44678c668518b249c843c81df249b8f096701757bc50d7ee" +checksum = "6c1ab67b22f0576b953a25c43bdfed0ff84af2e01ced85e95c29e7bac6bf2180" dependencies = [ "darling", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 51b4f2e..7448be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -esp-backtrace = { version = "0.15.1", features = [ +esp-backtrace = { version = "0.16.0", features = [ "esp32s3", "exception-handler", "panic-handler", "println", ] } -esp-hal = { version = "1.0.0-beta.0", features = ["esp32s3", "unstable"] } -esp-alloc = { version = "0.7.0" } +esp-hal = { version = "1.0.0-beta.1", features = ["esp32s3", "unstable"] } +esp-alloc = { version = "0.8.0" } embassy-net = { version = "0.7.0", features = [ "tcp", "udp", @@ -19,19 +19,13 @@ embassy-net = { version = "0.7.0", features = [ "dhcpv4", "dhcpv4-hostname", ] } - mipidsi = { version = "0.9.0" } -esp-wifi = { version = "0.13.0", features = [ - "esp32s3", - "serde", - "wifi", - "log", -] } +esp-wifi = { version = "0.14.0", features = ["esp32s3", "serde", "wifi"] } heapless = { version = "0.8.0", default-features = false } -esp-println = { version = "0.13.1", features = ["log", "esp32s3"] } +esp-println = { version = "0.14.0", features = ["esp32s3"] } embassy-executor = { version = "0.7.0", features = ["task-arena-size-163840"] } embassy-time = { version = "0.4.0", features = [] } -esp-hal-embassy = { version = "0.7.0", features = ["esp32s3"] } +esp-hal-embassy = { version = "0.8.0", features = ["esp32s3"] } rust-mqtt = { version = "0.3.0", default-features = false, features = [] } embedded-text = "0.7.2" embedded-graphics = { version = "0.8.1", features = [] } @@ -41,7 +35,7 @@ serde_json = { version = "1.0.140", default-features = false, features = [ "alloc", ] } embassy-futures = { version = "0.1.1", features = [] } -embassy-sync = { version = "0.6.2", features = [] } +embassy-sync = { version = "0.7.0", features = [] } static_cell = "2.1.0" nb = { version = "1.1.0", features = [] } diff --git a/src/display.rs b/src/display.rs index 071fc71..366f6fd 100644 --- a/src/display.rs +++ b/src/display.rs @@ -8,7 +8,11 @@ use embedded_hal::delay::DelayNs; use embedded_text::alignment::HorizontalAlignment; use embedded_text::style::{HeightMode, TextBoxStyleBuilder}; use embedded_text::TextBox; -use esp_hal::gpio::{GpioPin, Level, Output, OutputConfig}; +use esp_hal::gpio::{Level, Output, OutputConfig}; +use esp_hal::peripherals::{ + GPIO38, GPIO39, GPIO40, GPIO41, GPIO42, GPIO45, GPIO46, GPIO47, GPIO48, GPIO5, GPIO6, GPIO7, + GPIO8, GPIO9, +}; use mipidsi::interface::{Generic8BitBus, ParallelError, ParallelInterface}; use mipidsi::models::ST7789; use mipidsi::options::{ColorInversion, Orientation, Rotation}; @@ -49,20 +53,20 @@ pub trait DisplayTrait { } pub struct DisplayPeripherals { - pub rst: GpioPin<5>, - pub cs: GpioPin<6>, - pub dc: GpioPin<7>, - pub wr: GpioPin<8>, - pub rd: GpioPin<9>, - pub backlight: GpioPin<38>, - pub d0: GpioPin<39>, - pub d1: GpioPin<40>, - pub d2: GpioPin<41>, - pub d3: GpioPin<42>, - pub d4: GpioPin<45>, - pub d5: GpioPin<46>, - pub d6: GpioPin<47>, - pub d7: GpioPin<48>, + pub rst: GPIO5<'static>, + pub cs: GPIO6<'static>, + pub dc: GPIO7<'static>, + pub wr: GPIO8<'static>, + pub rd: GPIO9<'static>, + pub backlight: GPIO38<'static>, + pub d0: GPIO39<'static>, + pub d1: GPIO40<'static>, + pub d2: GPIO41<'static>, + pub d3: GPIO42<'static>, + pub d4: GPIO45<'static>, + pub d5: GPIO46<'static>, + pub d6: GPIO47<'static>, + pub d7: GPIO48<'static>, } impl Display<'_, D> { diff --git a/src/relay_task.rs b/src/relay_task.rs index 07fbd0d..1adc5b5 100644 --- a/src/relay_task.rs +++ b/src/relay_task.rs @@ -1,5 +1,8 @@ use embassy_time::{Duration, Timer}; -use esp_hal::gpio::{GpioPin, Level, Output, OutputConfig}; +use esp_hal::{ + gpio::{Level, Output, OutputConfig}, + peripherals::GPIO2, +}; use esp_println::println; use crate::ENABLE_PUMP; @@ -7,7 +10,7 @@ use crate::ENABLE_PUMP; const PUMP_INTERVAL: Duration = Duration::from_secs(10); #[embassy_executor::task] -pub async fn relay_task(pin: GpioPin<2>) { +pub async fn relay_task(pin: GPIO2<'static>) { println!("Created a relay task"); // Configure GPIO pin for relay (using GPIO2) let mut dht_pin = Output::new(pin, Level::Low, OutputConfig::default()); diff --git a/src/sensors_task.rs b/src/sensors_task.rs index dbfc7d8..76c0c8a 100644 --- a/src/sensors_task.rs +++ b/src/sensors_task.rs @@ -5,8 +5,8 @@ use esp_hal::{ Adc, AdcCalCurve, AdcCalLine, AdcCalScheme, AdcChannel, AdcConfig, AdcPin, Attenuation, RegisterAccess, }, - gpio::{DriveMode, GpioPin, Level, Output, OutputConfig, Pull}, - peripherals::{ADC1, ADC2}, + gpio::{DriveMode, Level, Output, OutputConfig, Pull}, + peripherals::{ADC1, ADC2, GPIO1, GPIO11, GPIO12, GPIO16, GPIO21, GPIO4}, Blocking, }; use esp_println::println; @@ -29,26 +29,26 @@ const SENSOR_WARMUP_DELAY_MILLISECONDS: u64 = 50; const SENSOR_SAMPLE_COUNT: usize = 5; /// ADC and GPIO handles -struct SensorHardware { - adc1: Adc<'static, ADC1, Blocking>, - adc2: Adc<'static, ADC2, Blocking>, - moisture_pin: AdcPin, ADC2, AdcCalCurve>, - waterlevel_pin: AdcPin, ADC2, AdcCalCurve>, - battery_pin: AdcPin, ADC1, AdcCalLine>, - moisture_power_pin: Output<'static>, - water_level_power_pin: Output<'static>, - dht11_digital_pin: GpioPin<1>, +struct SensorHardware<'a> { + adc1: Adc<'a, ADC1<'a>, Blocking>, + adc2: Adc<'a, ADC2<'a>, Blocking>, + moisture_pin: AdcPin, ADC2<'a>, AdcCalCurve>>, + waterlevel_pin: AdcPin, ADC2<'a>, AdcCalCurve>>, + battery_pin: AdcPin, ADC1<'a>, AdcCalLine>>, + moisture_power_pin: Output<'a>, + water_level_power_pin: Output<'a>, + dht11_pin: esp_hal::gpio::Flex<'a>, } pub struct SensorPeripherals { - pub dht11_digital_pin: GpioPin<1>, - pub battery_pin: GpioPin<4>, - pub moisture_power_pin: GpioPin<16>, - pub moisture_analog_pin: GpioPin<11>, - pub water_level_analog_pin: GpioPin<12>, - pub water_level_power_pin: GpioPin<21>, - pub adc1: ADC1, - pub adc2: ADC2, + pub dht11_digital_pin: GPIO1<'static>, + pub battery_pin: GPIO4<'static>, + pub moisture_power_pin: GPIO16<'static>, + pub moisture_analog_pin: GPIO11<'static>, + pub water_level_analog_pin: GPIO12<'static>, + pub water_level_power_pin: GPIO21<'static>, + pub adc1: ADC1<'static>, + pub adc2: ADC2<'static>, } #[embassy_executor::task] @@ -70,7 +70,7 @@ pub async fn sensor_task( } /// Initialize all sensor hardware -async fn initialize_hardware(p: SensorPeripherals) -> SensorHardware { +async fn initialize_hardware(p: SensorPeripherals) -> SensorHardware<'static> { let mut adc2_config = AdcConfig::new(); let moisture_pin = adc2_config .enable_pin_with_cal::<_, AdcCalCurve>(p.moisture_analog_pin, Attenuation::_11dB); @@ -79,14 +79,24 @@ async fn initialize_hardware(p: SensorPeripherals) -> SensorHardware { let adc2 = Adc::new(p.adc2, adc2_config); let mut adc1_config = AdcConfig::new(); - let battery_pin = adc1_config - .enable_pin_with_cal::, AdcCalLine>(p.battery_pin, Attenuation::_11dB); + let battery_pin = adc1_config.enable_pin_with_cal(p.battery_pin, Attenuation::_11dB); let adc1 = Adc::new(p.adc1, adc1_config); let moisture_power_pin = Output::new(p.moisture_power_pin, Level::Low, OutputConfig::default()); let water_level_power_pin = Output::new(p.water_level_power_pin, Level::Low, OutputConfig::default()); + // Setup DHT11 pin once + let mut dht11_pin = Output::new( + p.dht11_digital_pin, + Level::High, + OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::None), + ) + .into_flex(); + dht11_pin.set_input_enable(true); + SensorHardware { adc1, adc2, @@ -95,12 +105,12 @@ async fn initialize_hardware(p: SensorPeripherals) -> SensorHardware { battery_pin, moisture_power_pin, water_level_power_pin, - dht11_digital_pin: p.dht11_digital_pin, + dht11_pin, } } /// Collect data from all sensors -async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { +async fn collect_all_sensor_data(hardware: &mut SensorHardware<'static>) -> SensorData { let mut air_humidity_samples: Vec = Vec::new(); let mut air_temperature_samples: Vec = Vec::new(); let mut soil_moisture_samples: Vec = Vec::new(); @@ -111,7 +121,7 @@ async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { println!("Reading sensor data {}/{}", (i + 1), SENSOR_SAMPLE_COUNT); // Read DHT11 (temperature & humidity) - if let Some(messurement) = read_dht11_sensor(&mut hardware.dht11_digital_pin).await { + if let Some(messurement) = read_dht11_sensor(&mut hardware.dht11_pin).await { if air_temperature_samples .push(messurement.temperature) .is_err() @@ -169,28 +179,18 @@ async fn collect_all_sensor_data(hardware: &mut SensorHardware) -> SensorData { } /// Read DHT11 temperature and humidity sensor -async fn read_dht11_sensor(dht11_pin: &mut GpioPin<1>) -> Option { - let mut pin = Output::new( - dht11_pin, - Level::High, - OutputConfig::default() - .with_drive_mode(DriveMode::OpenDrain) - .with_pull(Pull::None), - ) - .into_flex(); - pin.enable_input(true); - - let mut dht11_sensor = Dht11::new(pin, Delay); +async fn read_dht11_sensor(dht11_pin: &mut esp_hal::gpio::Flex<'static>) -> Option { + let mut dht11_sensor = Dht11::new(dht11_pin, Delay); Timer::after(Duration::from_millis(DHT11_WARMUP_DELAY_MILLISECONDS)).await; dht11_sensor.read().ok() } /// Read soil moisture sensor -async fn read_moisture_sensor( - adc: &mut Adc<'_, ADC2, Blocking>, - pin: &mut AdcPin, ADC2, AdcCalCurve>, - power_pin: &mut Output<'_>, +async fn read_moisture_sensor<'a>( + adc: &mut Adc<'a, ADC2<'a>, Blocking>, + pin: &mut AdcPin, ADC2<'a>, AdcCalCurve>>, + power_pin: &mut Output<'a>, ) -> Option { power_pin.set_high(); @@ -201,10 +201,10 @@ async fn read_moisture_sensor( } /// Read water level sensor -async fn read_water_level_sensor( - adc: &mut Adc<'_, ADC2, Blocking>, - pin: &mut AdcPin, ADC2, AdcCalCurve>, - power_pin: &mut Output<'_>, +async fn read_water_level_sensor<'a>( + adc: &mut Adc<'a, ADC2<'a>, Blocking>, + pin: &mut AdcPin, ADC2<'a>, AdcCalCurve>>, + power_pin: &mut Output<'a>, ) -> Option { power_pin.set_high(); @@ -215,9 +215,9 @@ async fn read_water_level_sensor( } /// Read battery voltage -async fn read_battery_voltage( - adc: &mut Adc<'_, ADC1, Blocking>, - pin: &mut AdcPin, ADC1, AdcCalLine>, +async fn read_battery_voltage<'a>( + adc: &mut Adc<'a, ADC1<'a>, Blocking>, + pin: &mut AdcPin, ADC1<'a>, AdcCalLine>>, ) -> Option { let value = sample_adc_with_warmup(adc, pin, SENSOR_WARMUP_DELAY_MILLISECONDS).await? * 2; @@ -233,14 +233,14 @@ async fn read_battery_voltage( } /// Sample ADC with configurable warmup delay -async fn sample_adc_with_warmup( - adc: &mut Adc<'_, ADCI, Blocking>, +async fn sample_adc_with_warmup<'a, PIN, ADCI, ADCC>( + adc: &mut Adc<'a, ADCI, Blocking>, pin: &mut AdcPin, warmup_ms: u64, ) -> Option where PIN: AdcChannel, - ADCI: RegisterAccess, + ADCI: RegisterAccess + 'a, ADCC: AdcCalScheme, { Timer::after(Duration::from_millis(warmup_ms)).await; diff --git a/src/wifi.rs b/src/wifi.rs index 10a61f0..72c50db 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -23,10 +23,10 @@ static STACK_RESOURCES: StaticCell> = StaticCell::new(); pub static STOP_WIFI_SIGNAL: Signal = Signal::new(); pub async fn connect_to_wifi( - wifi: peripherals::WIFI, - timer: esp_hal::timer::timg::Timer, - radio_clocks: peripherals::RADIO_CLK, - rng: RNG, + wifi: peripherals::WIFI<'static>, + timer: esp_hal::timer::timg::Timer<'static>, + radio_clocks: peripherals::RADIO_CLK<'static>, + rng: RNG<'static>, spawner: Spawner, ) -> Result, WifiError> { let mut rng = Rng::new(rng); From d1a1012210c7a115d659b16c10cbca6a9145986c Mon Sep 17 00:00:00 2001 From: Eric Funk Date: Fri, 6 Jun 2025 15:23:34 +0200 Subject: [PATCH 08/12] update dependencies and enhance logging initialization for improved debugging --- Cargo.lock | 25 ++++++++++++++----------- Cargo.toml | 14 +++++++++----- src/main.rs | 4 +++- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e67f288..0c2f193 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bytemuck" @@ -530,7 +530,7 @@ checksum = "0d973697621cd3eef9c3f260fa8c1af77d8547cfc92734255d8e8ddf05c7d331" dependencies = [ "basic-toml", "bitfield", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "cfg-if", "critical-section", @@ -557,6 +557,7 @@ dependencies = [ "esp32s3", "fugit", "instability", + "log", "nb 1.1.0", "paste", "portable-atomic", @@ -572,9 +573,9 @@ dependencies = [ [[package]] name = "esp-hal-embassy" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd751f4b235764f6e766731e7da09d8f8d443642b829c3e6e469c38320da044" +checksum = "87807cdf22124ba46dbac7d7f6d3ee2e7f06c4e461f3783443464d868101647d" dependencies = [ "cfg-if", "critical-section", @@ -589,6 +590,7 @@ dependencies = [ "esp-hal", "esp-hal-procmacros", "esp-metadata", + "log", "portable-atomic", "static_cell", ] @@ -662,9 +664,9 @@ dependencies = [ [[package]] name = "esp-wifi" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f0e7cc4d0ceed4117e0f190abba1f021d986a5b6a54ef2116541e4288b700a9" +checksum = "3700028d3d2ee57e6d2a5c5f60544711052f8d394e73a6f534b538fbfb9d058c" dependencies = [ "allocator-api2", "cfg-if", @@ -717,6 +719,7 @@ dependencies = [ "esp-println", "esp-wifi", "heapless", + "log", "mipidsi", "nb 1.1.0", "rust-mqtt", @@ -1007,9 +1010,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable_atomic_enum" @@ -1160,9 +1163,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" diff --git a/Cargo.toml b/Cargo.toml index 7448be5..aedef9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,11 @@ esp-backtrace = { version = "0.16.0", features = [ "panic-handler", "println", ] } -esp-hal = { version = "1.0.0-beta.1", features = ["esp32s3", "unstable"] } +esp-hal = { version = "1.0.0-beta.1", features = [ + "esp32s3", + "unstable", + "log-04", +] } esp-alloc = { version = "0.8.0" } embassy-net = { version = "0.7.0", features = [ "tcp", @@ -20,12 +24,12 @@ embassy-net = { version = "0.7.0", features = [ "dhcpv4-hostname", ] } mipidsi = { version = "0.9.0" } -esp-wifi = { version = "0.14.0", features = ["esp32s3", "serde", "wifi"] } +esp-wifi = { version = "0.14.1", features = ["esp32s3", "serde", "wifi"] } heapless = { version = "0.8.0", default-features = false } -esp-println = { version = "0.14.0", features = ["esp32s3"] } +esp-println = { version = "0.14.0", features = ["esp32s3", "log-04"] } embassy-executor = { version = "0.7.0", features = ["task-arena-size-163840"] } embassy-time = { version = "0.4.0", features = [] } -esp-hal-embassy = { version = "0.8.0", features = ["esp32s3"] } +esp-hal-embassy = { version = "0.8.1", features = ["esp32s3", "log-04"] } rust-mqtt = { version = "0.3.0", default-features = false, features = [] } embedded-text = "0.7.2" embedded-graphics = { version = "0.8.1", features = [] } @@ -38,7 +42,7 @@ embassy-futures = { version = "0.1.1", features = [] } embassy-sync = { version = "0.7.0", features = [] } static_cell = "2.1.0" nb = { version = "1.1.0", features = [] } - +log = "0.4.27" [profile.dev] # Rust debug is too slow. diff --git a/src/main.rs b/src/main.rs index 3e541ff..b4180ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use esp_hal::{ timer::timg::TimerGroup, }; use esp_hal_embassy::main; -use esp_println::println; +use esp_println::{logger::init_logger, println}; use esp_wifi::wifi::WifiError; use relay_task::relay_task; use sensors_task::{sensor_task, SensorPeripherals}; @@ -58,6 +58,8 @@ static mut DISCOVERY_MESSAGES_SENT: bool = false; #[main] async fn main(spawner: Spawner) { + init_logger(log::LevelFilter::Info); + let boot_count = unsafe { BOOT_COUNT }; println!("Current boot count = {}", &boot_count); unsafe { From 33ab54c7d23c99344a42c22da0e9415cf272a781 Mon Sep 17 00:00:00 2001 From: Eric Funk Date: Fri, 13 Jun 2025 10:21:08 +0200 Subject: [PATCH 09/12] add state_class to sensor discovery payload for improved MQTT integration --- Cargo.lock | 44 ++++++++++++++++++++++---------------------- src/update_task.rs | 1 + 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c2f193..7e3c096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,9 +69,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -81,9 +81,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "critical-section" @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heapless" @@ -909,9 +909,9 @@ checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "micromath" @@ -1213,9 +1213,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -1278,9 +1278,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -1298,9 +1298,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -1310,18 +1310,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -1333,9 +1333,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "typenum" @@ -1467,9 +1467,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] diff --git a/src/update_task.rs b/src/update_task.rs index b02ad1f..90895e7 100644 --- a/src/update_task.rs +++ b/src/update_task.rs @@ -326,6 +326,7 @@ fn get_sensor_discovery(s: &Sensor) -> (String, String) { let mut payload = get_common_device_info(topic, s.name()); payload["state_topic"] = json!(format!("{}/{}", DEVICE_ID, topic)); payload["value_template"] = json!("{{ value_json.value }}"); + payload["state_class"] = json!("measurement"); payload["platform"] = json!("sensor"); payload["unique_id"] = json!(format!("{}_{}", DEVICE_ID, topic)); From 443204fc2c46fda4404d6d3b7bf9d407f78706fe Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 14 Jun 2025 12:36:10 +0200 Subject: [PATCH 10/12] add support for Rust 2024 edition in cargo configuration --- .cargo/config.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index bc470c0..a6c40ad 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -10,5 +10,8 @@ rustflags = ["-C", "link-arg=-nostartfiles"] target = "xtensa-esp32s3-none-elf" +[cargo-new] +edition = "2024" + [unstable] build-std = ["alloc", "core"] From 985a4de1dd71ec137c57f6ebae7a7d6d453c4d6f Mon Sep 17 00:00:00 2001 From: Eric Funk Date: Mon, 16 Jun 2025 16:41:21 +0200 Subject: [PATCH 11/12] set state_class in sensor discovery payload conditionally based on unit presence for improved Home Assistant integration --- src/update_task.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/update_task.rs b/src/update_task.rs index 90895e7..47605a8 100644 --- a/src/update_task.rs +++ b/src/update_task.rs @@ -326,7 +326,6 @@ fn get_sensor_discovery(s: &Sensor) -> (String, String) { let mut payload = get_common_device_info(topic, s.name()); payload["state_topic"] = json!(format!("{}/{}", DEVICE_ID, topic)); payload["value_template"] = json!("{{ value_json.value }}"); - payload["state_class"] = json!("measurement"); payload["platform"] = json!("sensor"); payload["unique_id"] = json!(format!("{}_{}", DEVICE_ID, topic)); @@ -343,6 +342,8 @@ fn get_sensor_discovery(s: &Sensor) -> (String, String) { let unit = s.unit(); if let Some(unit) = unit { payload["unit_of_measurement"] = json!(unit); + // only set state_class if unit is present - enables Home Assistant to display the unit correctly and keep track of state changes + payload["state_class"] = json!("measurement"); } let discovery_topic = format!( From a65eba33101be5ca314870a6cf39ec9e1e6bd4de Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 16 Jun 2025 20:50:08 +0200 Subject: [PATCH 12/12] add strum dependency and refactor sensor discovery to use enum iteration for improved readability --- Cargo.lock | 1 + Cargo.toml | 1 + src/domain.rs | 15 +++++++++------ src/update_task.rs | 13 ++++++------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e3c096..869cbb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,6 +726,7 @@ dependencies = [ "serde", "serde_json", "static_cell", + "strum", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index aedef9e..fd085b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ embassy-sync = { version = "0.7.0", features = [] } static_cell = "2.1.0" nb = { version = "1.1.0", features = [] } log = "0.4.27" +strum = { version = "0.27.1", default-features = false } [profile.dev] # Rust debug is too slow. diff --git a/src/domain.rs b/src/domain.rs index b73b248..3f88ac5 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -2,6 +2,7 @@ use alloc::string::{String, ToString}; use core::fmt::{Display, Formatter, Result}; use heapless::Vec; use serde::{Deserialize, Serialize}; +use strum::EnumIter; const WATER_LEVEL_THRESHOLD: u16 = 3000; //soil is wet @@ -30,11 +31,12 @@ impl Display for SensorData { } /// Represents the qualitative state of soil moisture as interpreted from sensor readings. -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Default)] pub enum MoistureLevel { Wet, // Soil is wet Moist, // Soil is moist (intermediate) - Dry, // Soil is dry + #[default] + Dry, // Soil is dry } impl Display for MoistureLevel { @@ -62,9 +64,10 @@ impl From for MoistureLevel { } /// Indicates if water is present at the base of the pot (drainage area). -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Default)] pub enum WaterLevel { - Full, // Water detected at the pot base + Full, // Water detected at the pot base + #[default] Empty, // No water detected at the pot base } @@ -88,7 +91,7 @@ impl From for WaterLevel { } /// Represents all supported sensor types and their current readings. -#[derive(Debug)] +#[derive(Debug, EnumIter)] pub enum Sensor { WaterLevel(WaterLevel), // Water at pot base AirTemperature(u8), // Air temperature in °C @@ -99,7 +102,7 @@ pub enum Sensor { PumpTrigger(bool), // Whether pump should be triggered } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SoilMoistureRawLevel(u16); impl From for SoilMoistureRawLevel { diff --git a/src/update_task.rs b/src/update_task.rs index 47605a8..e0438f6 100644 --- a/src/update_task.rs +++ b/src/update_task.rs @@ -22,6 +22,7 @@ use rust_mqtt::{ }; use serde_json::{json, Value}; use static_cell::StaticCell; +use strum::IntoEnumIterator; use crate::{ config::{ @@ -181,7 +182,7 @@ async fn handle_sensor_data( display: &mut Display<'static, Delay>, sensor_data: SensorData, ) -> Result<(), Error> { - publish_discovery_topics(client, &sensor_data).await?; + publish_discovery_topics(client).await?; if sensor_data.publish { publish_sensor_data(client, &sensor_data).await?; @@ -193,15 +194,12 @@ async fn handle_sensor_data( Ok(()) } -async fn publish_discovery_topics( - client: &mut MqttClientImpl<'_>, - sensor_data: &SensorData, -) -> Result<(), Error> { +async fn publish_discovery_topics(client: &mut MqttClientImpl<'_>) -> Result<(), Error> { let discovery_messages_sent = unsafe { DISCOVERY_MESSAGES_SENT }; if !discovery_messages_sent { println!("First run, sending discovery messages"); - for s in &sensor_data.data { - let (discovery_topic, message) = get_sensor_discovery(s); + for s in Sensor::iter() { + let (discovery_topic, message) = get_sensor_discovery(&s); client .send_message( &discovery_topic, @@ -210,6 +208,7 @@ async fn publish_discovery_topics( true, ) .await?; + println!("Discovery message sent for sensor: {}", s.name()); } let (discovery_topic, message) = get_pump_discovery("pump");