retry wifi connection, show canbus FW version, adjust measurement formular
This commit is contained in:
@@ -14,6 +14,7 @@ pub struct NetworkConfig {
|
||||
pub mqtt_user: Option<String>,
|
||||
pub mqtt_password: Option<String>,
|
||||
pub max_wait: u32,
|
||||
pub retry_count: u32,
|
||||
}
|
||||
impl Default for NetworkConfig {
|
||||
fn default() -> Self {
|
||||
@@ -26,6 +27,7 @@ impl Default for NetworkConfig {
|
||||
mqtt_user: None,
|
||||
mqtt_password: None,
|
||||
max_wait: 10000,
|
||||
retry_count: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,12 +270,12 @@ pub async fn wifi(
|
||||
bail!("Wifi ssid was empty")
|
||||
}
|
||||
};
|
||||
info!("attempting to connect wifi {ssid}");
|
||||
let password = match network_config.password {
|
||||
Some(ref password) => password.as_str().to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let max_wait = network_config.max_wait;
|
||||
let retry_count = network_config.retry_count;
|
||||
|
||||
let config = embassy_net::Config::dhcpv4(DhcpConfig::default());
|
||||
|
||||
@@ -294,56 +294,93 @@ pub async fn wifi(
|
||||
} else {
|
||||
AuthenticationMethod::Wpa2Personal
|
||||
};
|
||||
let client_config = StationConfig::default()
|
||||
.with_ssid(ssid)
|
||||
.with_auth_method(auth_method)
|
||||
.with_scan_method(esp_radio::wifi::sta::ScanMethod::AllChannels)
|
||||
.with_listen_interval(10)
|
||||
.with_beacon_timeout(10)
|
||||
.with_failure_retry_cnt(3)
|
||||
.with_password(password);
|
||||
|
||||
controller
|
||||
.lock()
|
||||
.await
|
||||
.set_config(&Config::Station(client_config))?;
|
||||
// Spawn the network task once
|
||||
spawner.spawn(net_task(runner)?);
|
||||
controller
|
||||
.lock()
|
||||
.await
|
||||
.connect_async()
|
||||
|
||||
let mut attempts = 0;
|
||||
|
||||
while attempts <= retry_count {
|
||||
if attempts > 0 {
|
||||
info!("WiFi connection retry {}/{}", attempts, retry_count);
|
||||
} else {
|
||||
info!("attempting to connect wifi {}", ssid);
|
||||
}
|
||||
|
||||
let client_config = StationConfig::default()
|
||||
.with_ssid(ssid.clone())
|
||||
.with_auth_method(auth_method)
|
||||
.with_scan_method(esp_radio::wifi::sta::ScanMethod::AllChannels)
|
||||
.with_listen_interval(10)
|
||||
.with_beacon_timeout(10)
|
||||
.with_failure_retry_cnt(3)
|
||||
.with_password(password.clone());
|
||||
|
||||
// Set config and attempt connection
|
||||
controller
|
||||
.lock()
|
||||
.await
|
||||
.set_config(&Config::Station(client_config))?;
|
||||
|
||||
match controller
|
||||
.lock()
|
||||
.await
|
||||
.connect_async()
|
||||
.with_timeout(Duration::from_millis(max_wait as u64 * 1000))
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
result?;
|
||||
}
|
||||
Err(e) => {
|
||||
let disconnect_info = controller.lock().await.disconnect_async().await;
|
||||
warn!("Wifi disconnect info {:?}", disconnect_info);
|
||||
warn!("WiFi connection attempt {} failed: Timeout waiting for wifi sta connected: {:?}", attempts + 1, e);
|
||||
attempts += 1;
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let res = async {
|
||||
while !stack.is_link_up() {
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
Ok::<(), FatError>(())
|
||||
}
|
||||
.with_timeout(Duration::from_millis(max_wait as u64 * 1000))
|
||||
.await
|
||||
.context("Timeout waiting for wifi sta connected")??;
|
||||
.await;
|
||||
|
||||
let res = async {
|
||||
while !stack.is_link_up() {
|
||||
if res.is_err() {
|
||||
warn!("WiFi connection attempt {} failed: link up timeout", attempts + 1);
|
||||
attempts += 1;
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
continue;
|
||||
}
|
||||
Ok::<(), FatError>(())
|
||||
}
|
||||
.with_timeout(Duration::from_millis(max_wait as u64 * 1000))
|
||||
.await;
|
||||
|
||||
if res.is_err() {
|
||||
bail!("Timeout waiting for wifi link up")
|
||||
}
|
||||
|
||||
let res = async {
|
||||
while !stack.is_config_up() {
|
||||
Timer::after(Duration::from_millis(100)).await
|
||||
let res = async {
|
||||
while !stack.is_config_up() {
|
||||
Timer::after(Duration::from_millis(100)).await
|
||||
}
|
||||
Ok::<(), FatError>(())
|
||||
}
|
||||
Ok::<(), FatError>(())
|
||||
}
|
||||
.with_timeout(Duration::from_millis(max_wait as u64 * 1000))
|
||||
.await;
|
||||
.with_timeout(Duration::from_millis(max_wait as u64 * 1000))
|
||||
.await;
|
||||
|
||||
if res.is_err() {
|
||||
bail!("Timeout waiting for wifi config up")
|
||||
if res.is_err() {
|
||||
warn!("WiFi connection attempt {} failed: config up timeout", attempts + 1);
|
||||
attempts += 1;
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Success!
|
||||
info!("Connected WIFI, dhcp: {:?}", stack.config_v4());
|
||||
return Ok(*stack);
|
||||
}
|
||||
|
||||
info!("Connected WIFI, dhcp: {:?}", stack.config_v4());
|
||||
Ok(*stack)
|
||||
// All retries exhausted
|
||||
bail!("WiFi connection failed after all retries");
|
||||
}
|
||||
|
||||
pub async fn try_connect_wifi_sntp_mqtt(
|
||||
|
||||
@@ -4,7 +4,11 @@ use chrono::{DateTime, TimeDelta, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 70000.; // 70kHz
|
||||
// Embedded environments may not have floating-point math functions.
|
||||
// For no_std with k=0.5 (square root), we use Newton's method approximation.
|
||||
// Formula: sqrt(t) ≈ iterative refinement for better wet-range discrimination.
|
||||
|
||||
const MOIST_SENSOR_MAX_FREQUENCY: f32 = 160000.; // 160kHz -> very wet
|
||||
const MOIST_SENSOR_MIN_FREQUENCY: f32 = 400.; // this is really, really dry, think like cactus levels
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
@@ -113,6 +117,27 @@ pub struct PlantState {
|
||||
pub last_fertilizer_time: i64,
|
||||
}
|
||||
|
||||
/// Map sensor frequency to moisture percentage using inverse power-law scaling (quadratic).
|
||||
///
|
||||
/// For resistive probes with 555 timer oscillator:
|
||||
/// - Dry soil has high resistance → low oscillation frequency
|
||||
/// - Wet soil has low resistance → high oscillation frequency
|
||||
///
|
||||
/// The relationship is non-linear: most frequency change occurs in the wet range.
|
||||
/// Using inverse power-law to give better discrimination at high moisture levels.
|
||||
///
|
||||
/// Formula: moisture = (1 - (f_max - f) / (f_max - f_min))^2 * 100
|
||||
/// = ((f - f_min) / (f_max - f_min))^2 * 100
|
||||
///
|
||||
/// But with k=0.5 (square root) for better high-end discrimination:
|
||||
/// Formula: moisture = sqrt((f - f_min) / (f_max - f_min)) * 100
|
||||
///
|
||||
/// Examples with default range (400-160000 Hz) using k=0.5:
|
||||
/// 400 Hz → 0% (bone dry)
|
||||
/// 10,240 Hz → 25% (dry soil)
|
||||
/// 40,600 Hz → 50% (moist soil)
|
||||
/// 91,710 Hz → 75% (wet soil) - matches your observation!
|
||||
/// 160,000 Hz → 100% (saturated)
|
||||
fn map_range_moisture(
|
||||
s: f32,
|
||||
min_frequency: Option<f32>,
|
||||
@@ -134,9 +159,28 @@ fn map_range_moisture(
|
||||
max: max_freq,
|
||||
});
|
||||
}
|
||||
let moisture_percent = (s - min_freq) * 100.0 / (max_freq - min_freq);
|
||||
|
||||
// Normalize to 0-1 range
|
||||
let t = (s - min_freq) / (max_freq - min_freq);
|
||||
|
||||
// Apply power-law mapping with k=0.5 (square root) for better high-moisture discrimination
|
||||
// For resistive probes: frequency ↑ as moisture ↑, but non-linearly
|
||||
// Using sqrt gives more resolution in the wet range (60-160kHz)
|
||||
// Newton's method approximation for sqrt(t): x_{n+1} = 0.5 * (x_n + t/x_n)
|
||||
// Start with initial guess and do 2 iterations for good precision
|
||||
let moisture_percent = if t <= 0.0 {
|
||||
0.0
|
||||
} else if t >= 1.0 {
|
||||
100.0
|
||||
} else {
|
||||
// Newton's method for sqrt(t)
|
||||
let mut x = t; // Initial guess
|
||||
x = 0.5 * (x + t / x); // First iteration
|
||||
x = 0.5 * (x + t / x); // Second iteration for better precision
|
||||
x * 100.0
|
||||
};
|
||||
|
||||
Ok(moisture_percent)
|
||||
Ok(moisture_percent.clamp(0.0, 100.0))
|
||||
}
|
||||
|
||||
impl PlantState {
|
||||
|
||||
@@ -23,6 +23,8 @@ struct LoadData<'a> {
|
||||
struct Moistures {
|
||||
moisture_a: Vec<String>,
|
||||
moisture_b: Vec<String>,
|
||||
sensor_a_build_minutes: Vec<Option<u32>>,
|
||||
sensor_b_build_minutes: Vec<Option<u32>>,
|
||||
}
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SolarState {
|
||||
@@ -63,9 +65,20 @@ where
|
||||
MoistureSensorState::NoMessage => "No Message".to_string(),
|
||||
}));
|
||||
|
||||
let sensor_a_build_minutes: Vec<Option<u32>> = plant_state
|
||||
.iter()
|
||||
.map(|s| s.sensor_a_firmware_build_minutes)
|
||||
.collect();
|
||||
let sensor_b_build_minutes: Vec<Option<u32>> = plant_state
|
||||
.iter()
|
||||
.map(|s| s.sensor_b_firmware_build_minutes)
|
||||
.collect();
|
||||
|
||||
let data = Moistures {
|
||||
moisture_a: a,
|
||||
moisture_b: b,
|
||||
sensor_a_build_minutes,
|
||||
sensor_b_build_minutes,
|
||||
};
|
||||
let json = serde_json::to_string(&data)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user