HDS 9.0.5 support - #147
Conversation
HDS 9.0.5 adds a TI BQ27427 battery fuel gauge at I2C 0x55. Probe the address and DEVICE_TYPE at boot; when absent (8.3.1 and earlier) all gauge features stay disabled and the menu looks unchanged. When present, enforce Chem ID 1202 (4.2 V cell), expose a paged Bat. Info menu with voltage/temperature/current/power/capacity/health/SOC/charging/USB, and provide a deep-sleep hook. ADS1115 battery path is kept for older boards. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Findings for now
request changes on PR #147. I found three substantive issues.
[P2] compactMainMenu() is undone after returning from any submenu. On gauge-less hardware, compactMainMenu() only decrements currentMenuSize; it does not change the compile-time size of mainMenu. When a submenu’s Back item is selected, selectMenu() restores currentMenuSize = getMenuSize(mainMenu), which includes menuBatInfo again. In the default build, Bat. Info is the last array element, so an 8.3.1 board will start with it hidden, but after entering and leaving e.g. Calibration it becomes visible as a dead menu entry. The array and initial size confirm this layout.
Fix: centralize the main-menu size calculation so returning to mainMenu uses the compacted size, or remove visibility dynamically instead of mutating the array/size once at boot.
[P2] The chemistry-change path bypasses the pinned library’s safety logic and can leave the gauge unsealed. enforceChem1202() manually unseals a sealed device, sleeps a fixed 1 second, sends CHEM_B, soft-resets, and merely prints the resulting ID. It never restores the prior sealed state and does not verify CFGUPDATE transitions or propagate failure. The exact pinned 1.0.4 library already implements setChemID: it records whether the chip was sealed, polls CFGUPMODE, checks the transition, and explicitly seals the IC again when appropriate.
Fix: preferably use bq.setChemID(CHEM_B) after the raw 0x1202 check, or reproduce its timeout/error/reseal behavior. A failed chemistry transition should also make initialization fail rather than silently enabling potentially inaccurate SOC/SOH data.
Fix: gate ADS_init() with !b_hasFuelGauge and make updateBattery() prefer fuelGaugeVoltageV() when present, or revise the PR description if retaining ADS1115 for the main battery path is intentional.
The PR is otherwise structurally straightforward, and the pinned library does verify DEVICE_TYPE == 0x0427 inside BQ27427::begin(), so the apparent lack of an explicit device-type check in fuel_gauge.cpp is not a defect.
- Add V9_0_5 board variant (CHRG_CTRL=6, GPOUT=14, no BATTERY_PIN) and esp32s3-905 env; battery voltage/percent come from the gauge on 9.0.5 while 8.3.1 keeps the legacy ADC/ADS1115 path (batteryPercent helper). - Bat. Info menu now works on both boards: full dual-column page with the gauge, limited voltage/CRG/USB page without it. Charging state reads the TP4056 CHRG pin (GPIO10) instead of gauge current. - Battery Protect menu (off by default, NVS): charge cutoff at 80% SOC via CHRG_CTRL, resume at 75%. - Boot detection retries to cover cold-start timing; CC_GAIN sign-bit fix (early batch bug) re-applied on every boot; chemistry switch uses the TRM flow with long timeouts and restores the sealed state (library setChemID has a 50 ms CFGUPMODE timeout and chemID()/CHEM_B never compares equal). - "bc" USB command queries/sets design capacity (no write at boot). - Review fixes: centralized mainMenuSize() so returning from a submenu cannot resurrect hidden items; ADS_init() gated on !b_hasFuelGauge. Co-Authored-By: Claude <noreply@anthropic.com>
PR: HDS 9.0.5 support — BQ27427 battery fuel gauge
Summary
HDS 9.0.5 hardware adds a TI BQ27427YZFR single-cell Li-ion fuel gauge
(fixed I2C address 0x55, Impedance Track algorithm) compared to 8.3.1.
This PR adds detection, initialization, battery info, and battery
protection for the new board while keeping full compatibility with 8.3.1
and earlier hardware.
Design principles
address 0x55 (with retries for cold-start timing) and verifies
DEVICE_TYPE == 0x0427. If the gauge is absent, gauge-only features stay
disabled and the 8.3.1 behavior is unchanged.
V9_0_5config block (GPIO6 repurposed frombattery ADC to CHRG_CTRL charge enable, GPOUT on GPIO14, no BATTERY_PIN)
with a dedicated
esp32s3-905PlatformIO env. On 9.0.5 the batteryvoltage and percent come straight from the gauge (
batteryPercent()helper); 8.3.1 keeps the legacy ADC/ADS1115 path.
ADS_init()is gated on!b_hasFuelGauge;boards that later drop the ADS1115 fall back to the existing internal-ADC
path.
registered with the existing mechanism.
mainMenuSize()centralizes theeffective main-menu size so returning from a submenu cannot resurrect
hidden items (review fix).
Changes
include/config.hV9_0_5block (CHRG_CTRL=6, GPOUT=14, no BATTERY_PIN)include/fuel_gauge.hsrc/fuel_gauge.cppinclude/fuel_gauge_menu.hinclude/menu.hmenuBatInfo+menuBatteryProtect;mainMenuSize();compactMainMenu()hides Battery Protect on gauge-less boardsinclude/parameter.hb_hasFuelGauge,b_batteryProtectinclude/power.hbatteryPercent(); BATTERY_PIN paths guarded for 9.0.5include/storage.hKEY_BAT_PROTECTinclude/usbcomm.hbccommand (query/set design capacity)include/decent_protocol.h,include/websocket.hbatteryPercent()src/hds.inofuelGaugeBegin()+compactMainMenu(); BATTERY_PIN guarded paths;ADS_init()gatedplatformio.iniesp32s3-905envtools/bq27427_probe/.gitignore,lib/README.mdLibrary dependency
edrean/BQ27427 Battery Fuel Gauge Arduino Library @ 1.0.4(MIT, resolvedautomatically from the PlatformIO registry; upstream:
https://github.com/edreanernst/BQ27427_Arduino_Library)
Features
1. Boot detection and initialization
CFGUPMODE timeouts and sealed-state restore (the library's setChemID
uses a 50 ms timeout and its chemID()/CHEM_B comparison never matches
the hex-nibble encoding the chip returns). Hard-coded for the 4.2 V cell.
which inverts current/power readings; the value lives in RAM and resets
on POR, so it is re-checked and fixed on every boot.
2. Bat. Info menu
power, capacity, FCC, health, battery level, charging, USB; plus a note
page. 500 ms live refresh; ENTER cycles, NEXT steps back (exit on first
page).
USB (no current/power/notes).
instead of gauge current.
3. Battery Protect (9.0.5, off by default, persisted in NVS)
Menu on/off. When enabled, SOC >= 80 % pulls CHRG_CTRL low to cut off the
charger; charging resumes at 75 % (hysteresis). The TP4056 charge-enable
input is driven by the ESP32 GPIO6 on 9.0.5.
4. Low-SOC notify
Serial notification (once per transition) when SOC drops below 10 %.
5. Design capacity command (
bc)USB serial command:
bcprints the current design capacity,bc <mAh>writes it (validated 300-2000 mAh). No write at boot — the user decides.
Testing
Both boards tested on hardware, 2026-08-14.
HDS 9.0.5 (with BQ27427)
bccommandHDS 8.3.1 (no BQ27427)
Out of scope / follow-ups
power.hsleep path (API onlyin this PR); GPOUT (GPIO14) available for SOC_INT wake-up
bconly for now)host-side counting or SOH can be used instead
References
docs/9.0.5/, Chinese translation included)CC_GAIN sign-bit issue, STOP-terminated writes) documented in
docs/9.0.5/BQ27427_中文手册.mdandBQ27427_Notes_EN.md