power.adc_multiplier_override runtime changes have no immediate effect
Device Information
- Firmware version: 2.7.18.4744010
- Device: Seeed XIAO ESP32S3 (
seeed-xiao-s3) - Platform: ESP32-S3
Bug Description
When changing power.adc_multiplier_override at runtime via CLI or Admin API, the setting is saved correctly but has no visible effect on voltage readings for 1-2 minutes. Compile-time ADC_MULTIPLIER in variant.h works correctly and immediately.
Steps to Reproduce
- Device has compile-time
ADC_MULTIPLIER 2.0(default) - Battery shows ~4.08V at full charge
- Set runtime override:
meshtastic --set power.adc_multiplier_override 2.06 - Verify setting saved:
meshtastic --get power.adc_multiplier_overridereturns2.059999... - Reboot device
- Wait 2+ minutes
- Check voltage: still shows ~4.08V instead of expected ~4.20V
Expected: Voltage should show ~4.20V after multiplier change (or converge quickly) Actual: Voltage remains at ~4.08V for extended period, slowly converges over 1-2 minutes
Root Cause
Found in src/Power.cpp, method AnalogBatteryLevel::getBattVoltage().
The Low Pass Filter (LPF) uses cached last_read_value that was calculated with the old multiplier. The initial_read_done flag is set to true on first reading and never reset when multiplier changes.
// Line 315-316: Multiplier is correctly read from config
float operativeAdcMultiplier =
config.power.adc_multiplier_override > 0 ? config.power.adc_multiplier_override : ADC_MULTIPLIER;
// Lines 339-348: But LPF uses old cached value
if (!initial_read_done) {
if (scaled > last_read_value)
last_read_value = scaled;
initial_read_done = true; // Set to true FOREVER
} else {
// LPF slowly adapts with 0.5 coefficient - takes 5+ readings to converge
last_read_value += (scaled - last_read_value) * 0.5;
}With coefficient 0.5 and minimum read interval of 5 seconds, full convergence takes ~1-2 minutes.
Why compile-time works immediately
When ADC_MULTIPLIER is changed in variant.h and firmware is reflashed, device reboots with initial_read_done = false, so first reading sets last_read_value directly without LPF filtering.
Proposed Fix
Track the current multiplier and reset the filter when it changes:
// Add member variable:
float last_adc_multiplier = 0;
// In getBattVoltage(), after computing operativeAdcMultiplier:
if (last_adc_multiplier != operativeAdcMultiplier) {
initial_read_done = false;
last_adc_multiplier = operativeAdcMultiplier;
}Additional Context
The comment in AdminModule.cpp (line 702) says:
"Really just the adc override is the only thing that can change without a reboot"
This implies runtime changes should work, but the LPF filter prevents immediate effect.
Source: meshtastic/firmware