[READ-ONLY] Mirror of https://github.com/jmrplens/kleidos. Kleidos — Hardware BLE password manager for ESP32-S3 (M5StickC Plus2, StickS3, M5Stack Gray, T-Deck, Cardputer) jmrplens.github.io/kleidos/
0

Configure Feed

Select the types of activity you want to include in your feed.

Fix the two failures the fleet power-QA sweep exposed

- Gray (IP5306): test_battery_voltage_plausible asserted a LiPo voltage
range on every DUT, but the IP5306 has no battery-voltage ADC — the
backend structurally reports 0 mV. Key the assertion on the PMIC model
and assert the structural 0 for IP5306 (HIL: model=IP5306 batt_pct=100
charging=1, batt_mv=0).
- AXP backend: the boot-time PMIC probe can transiently NACK right after
a serial-open DTR/RTS reset, leaving the whole boot with no PMIC
(model=none, 0 readings) — observed intermittently on core2_v13 and
plus1. Add a bounded lazy re-probe (one retry on first telemetry read)
so a single missed probe no longer costs the boot's telemetry. Verified
on core2_v13: 3 consecutive full power-QA runs, 5/5 each.

Claude-Session: https://claude.ai/code/session_01SV58JXhfxhhc9DC6vdjBo4

José M. Requena Plens (Jul 14, 2026, 10:25 AM +0200) 24a4efd7 0076005a

+47 -10
+7 -1
qa/tests/hardware/test_power.py
··· 32 32 def test_battery_voltage_plausible(dut): 33 33 hal = dut.hal() 34 34 mv = int(hal.get("batt_mv", "0")) 35 - assert 2500 <= mv <= 5500, f"batt_mv {mv} implausible for a LiPo/USB rail: {hal}" 35 + if dut.pmic().get("model") == "IP5306": 36 + # The IP5306 (Gray) exposes no battery-voltage ADC — only a coarse 37 + # 4-step gauge and charge state — so the backend structurally reports 38 + # 0 mV. Assert that structural value rather than a LiPo range. 39 + assert mv == 0, f"IP5306 has no voltage ADC -- expected batt_mv=0, got {mv}: {hal}" 40 + else: 41 + assert 2500 <= mv <= 5500, f"batt_mv {mv} implausible for a LiPo/USB rail: {hal}" 36 42 37 43 38 44 def test_pmic_model_and_battery(dut):
+27 -8
src/hal/common/power_manager_axp2101.cpp
··· 53 53 54 54 } // namespace 55 55 56 + kleidos::drivers::power::IPmic* PowerManagerAXP2101::pmic() { 57 + if ( pmic_ == nullptr && !pmicRetryDone_ ) { 58 + pmicRetryDone_ = true; 59 + pmic_ = kleidos::drivers::power::createPmic( device::kPmicI2cAddr ); 60 + if ( pmic_ != nullptr ) { 61 + KLEIDOS_LOGI( kTag, "PMIC probe recovered: %s", pmic_->name() ); 62 + } 63 + } 64 + return pmic_; 65 + } 66 + 56 67 bool PowerManagerAXP2101::init() { 57 68 pmic_ = kleidos::drivers::power::createPmic( device::kPmicI2cAddr ); 58 69 if ( pmic_ == nullptr ) { ··· 193 204 // Battery 194 205 // --------------------------------------------------------------------------- 195 206 int PowerManagerAXP2101::getBatteryPercent() { 196 - if ( int percent = 0; pmic_ != nullptr && pmic_->readBatteryPercent( percent ) ) { 207 + auto* chip = pmic(); 208 + if ( int percent = 0; chip != nullptr && chip->readBatteryPercent( percent ) ) { 197 209 return percent; 198 210 } 199 211 return 0; 200 212 } 201 213 202 214 int PowerManagerAXP2101::getBatteryMillivolts() { 203 - if ( int millivolts = 0; pmic_ != nullptr && pmic_->readBatteryMillivolts( millivolts ) ) { 215 + auto* chip = pmic(); 216 + if ( int millivolts = 0; chip != nullptr && chip->readBatteryMillivolts( millivolts ) ) { 204 217 return millivolts; 205 218 } 206 219 return 0; 207 220 } 208 221 209 222 bool PowerManagerAXP2101::isCharging() { 210 - if ( bool charging = false; pmic_ != nullptr && pmic_->readCharging( charging ) ) { 223 + auto* chip = pmic(); 224 + if ( bool charging = false; chip != nullptr && chip->readCharging( charging ) ) { 211 225 return charging; 212 226 } 213 227 return false; ··· 216 230 bool PowerManagerAXP2101::getBatteryCurrentMilliamps( float& milliamps ) { 217 231 // Forwards to the detected chip driver: real on AXP192 boards (Plus1), 218 232 // unsupported (false) on the AXP2101 fleet, which has no battery-current ADC. 219 - return pmic_ != nullptr && pmic_->readBatteryCurrentMilliamps( milliamps ); 233 + auto* chip = pmic(); 234 + return chip != nullptr && chip->readBatteryCurrentMilliamps( milliamps ); 220 235 } 221 236 222 237 bool PowerManagerAXP2101::getVbusMilliamps( float& milliamps ) { 223 238 // VBUS (USB) input current: real on AXP192, unsupported on AXP2101. 224 - return pmic_ != nullptr && pmic_->readVbusMilliamps( milliamps ); 239 + auto* chip = pmic(); 240 + return chip != nullptr && chip->readVbusMilliamps( milliamps ); 225 241 } 226 242 227 243 bool PowerManagerAXP2101::getVbusMillivolts( int& millivolts ) { 228 - return pmic_ != nullptr && pmic_->readVbusMillivolts( millivolts ); 244 + auto* chip = pmic(); 245 + return chip != nullptr && chip->readVbusMillivolts( millivolts ); 229 246 } 230 247 231 248 bool PowerManagerAXP2101::getPmicTemperatureCelsius( float& celsius ) { 232 249 // PMIC die temperature: real on AXP192 and AXP2101; unsupported elsewhere. 233 - return pmic_ != nullptr && pmic_->readPmicTemperatureCelsius( celsius ); 250 + auto* chip = pmic(); 251 + return chip != nullptr && chip->readPmicTemperatureCelsius( celsius ); 234 252 } 235 253 236 254 bool PowerManagerAXP2101::getBatteryCoulombMilliampHours( float& milliampHours ) { 237 255 // Coulomb gauge: real on AXP192 only; the AXP2101 fleet has no counter. 238 - return pmic_ != nullptr && pmic_->readBatteryCoulombMilliampHours( milliampHours ); 256 + auto* chip = pmic(); 257 + return chip != nullptr && chip->readBatteryCoulombMilliampHours( milliampHours ); 239 258 } 240 259 241 260 void PowerManagerAXP2101::logBatteryStatus() {
+13 -1
src/hal/common/power_manager_axp2101.h
··· 54 54 bool wasPowerButtonClicked() override; 55 55 56 56 private: 57 + /** 58 + * @brief Return the PMIC driver, retrying the probe once if init() missed. 59 + * 60 + * The boot-time probe can transiently NACK right after a serial-open 61 + * DTR/RTS reset (esptool-style reset artifact); without a retry the whole 62 + * boot would report no PMIC (model "none", 0 readings). Bounded to a 63 + * single re-probe so an absent chip does not cost I2C traffic per read. 64 + * @return The detected IPmic driver, or nullptr when no PMIC responds. 65 + */ 66 + kleidos::drivers::power::IPmic* pmic(); 67 + 57 68 #if PIN_POWER_BUTTON >= 0 58 69 void initPowerButton(); 59 70 bool readPowerButtonRawPressed() const; ··· 72 83 bool pmicPowerButtonClick_ = false; 73 84 #endif // PIN_POWER_BUTTON >= 0 74 85 75 - kleidos::drivers::power::IPmic* pmic_ = nullptr; 86 + kleidos::drivers::power::IPmic* pmic_ = nullptr; 87 + bool pmicRetryDone_ = false; 76 88 };