Software
Firmware architecture, start-up behaviour, configuration and the web interface protocol. For the method-by-method API see API_REFERENCE.md.
Layout
src/main.cpp Application: setup() and loop()
lib/ModbeeMPPT/src/
ModbeeMPPT.h/.cpp Top-level controller: start-up, periodic tasks, status LED
ModbeeMpptAPI.h/.cpp Validated, unit-based interface to the BQ25798
ModbeeMpptConfig.h/.cpp Settings: defaults, validation, JSON persistence on LittleFS
ModbeeMpptWebServer.h/.cpp Wi-Fi access point, captive DNS, HTTP and WebSocket server
ModbeeMpptPowerSave.h/.cpp Wi-Fi timeout, WIFI button, CPU frequency, light sleep
ModbeeMpptLog.h/.cpp Energy and peak-power statistics persisted to LittleFS
ModbeeMpptDebug.h/.cpp Serial status and register dumps
ModbeeMpptGlobal.h Common includes
lib/bq25798/ BQ25798 register driver (derived from the Adafruit library)
data/ Web interface (LittleFS image)
Third-party libraries (vendored in lib/):
| Library | Version | License | Used for |
|---|---|---|---|
| ESP32_SoftWire | 1.0.2 | MIT | Software I2C to the BQ25798 |
| ESPAsyncWebServer | 3.9.4 | LGPL-3.0 | HTTP and WebSocket server |
| AsyncTCP | 3.4.5 | LGPL-3.0 | TCP for ESPAsyncWebServer |
| ArduinoJson | 7.4.1 | MIT | Settings file and WebSocket messages |
| FastLED | 3.9.16 | MIT | WS2812B status LED |
Application
#include "ModbeeMpptGlobal.h"
#include "ModbeeMPPT.h"
ModbeeMPPT modbeeMPPT;
void setup() {
Serial.begin(115200);
modbeeMPPT.initializeLEDs();
modbeeMPPT.begin(); // I2C, charger, settings
modbeeMPPT.initWebServer(); // Wi-Fi AP, DNS, HTTP, WebSocket
}
void loop() {
modbeeMPPT.loop(); // periodic tasks, LED, web server, power save
}
src/main.cpp also prints printQuickStatus() every 2 seconds. Swap in one of the other print*() functions listed there for more detail.
Start-up sequence
ModbeeMPPT::begin() does the following, in order:
- Radios and CPU. Turns Wi-Fi and Bluetooth off and runs the CPU at 80 MHz.
- Charger bus. Starts software I2C on GPIO3 (SDA) and GPIO2 (SCL) to the BQ25798 at address 0x6B.
- Settings. Mounts LittleFS and loads
/config/mppt_config.json. If the file is missing or fails validation, the defaults are used. - Fixed charger settings (
applyCriticalSettings()):- ADC on: continuous, 15-bit
- BQ25798 I2C watchdog disabled, so the charger keeps the host settings if the ESP32-C3 resets or stalls
- HIZ off, backup mode off
- 1.5 MHz switching
- input current optimiser (ICO) off, so it does not interfere with MPPT
- battery discharge current sensing on
- Saved settings. Writes them to the charger: cell count, charge voltage and current, minimum system voltage, termination, recharge and pre-charge, input limits, VAC OVP, safety timers, MPPT and PFM/OOA.
- Battery check. Reads the battery voltage and estimates state of charge.
- Charging enabled. This happens in all cases, so the board recovers and charges unattended after any power loss. If the battery reads missing or below 5 %, the board starts in low-power boot mode with the access point off until the battery recovers above 8 % while charging, or the WIFI button is pressed.
- Statistics and power saving. Loads the saved energy statistics and starts the power-save manager.
Note that until step 5 runs, the BQ25798 operates on its own power-on defaults, which the PROG resistor selects. See HARDWARE.md for why the battery should be connected first.
Runtime
| Task | Interval |
|---|---|
| Status LED update | 1 s |
| WebSocket data push (when a client is connected) | 1 s |
| Battery presence check | 30 s (intervals.battery_check) |
| State-of-charge check | 60 s (intervals.soc_check) |
| Re-apply fixed charger settings | 60 s |
| Re-apply all saved settings | 5 min |
| Save energy statistics | 5 min |
Interval changes made in the web interface take effect after a restart.
Power saving
- Access point lifetime. The access point starts at boot (except in low-power boot) and turns off 5 minutes after boot or after the last WIFI button press. A press turns it back on for 5 minutes.
- CPU frequency. The CPU runs at 80 MHz, and at 160 MHz while the access point is on after a button press.
- Light sleep. When the state of charge is below 20 %, the ESP32-C3 light-sleeps for 10 s per minute. It wakes on its timer or on the WIFI button. The charger keeps running while the MCU sleeps.
Status LED (WS2812B)
| Colour | Meaning |
|---|---|
| Orange | Starting up |
| Red | A BQ25798 fault or regulation flag is set |
| Yellow | Charging |
| Green | Charge complete (termination done) |
| Blue | Not charging |
Settings file
Settings are stored as JSON in /config/mppt_config.json on LittleFS. Default contents:
{
"battery": { "type": 1, "cell_count": 3, "charge_voltage": 12.6, "charge_current": 1.0, "min_system_voltage": 10.0 },
"charging": { "termination_current": 0.12, "recharge_threshold": 0.4, "precharge_current": 0.2, "precharge_voltage_threshold": 3 },
"input": { "voltage_limit": 22.0, "current_limit": 3.0, "vac_ovp_threshold": 26.0 },
"timers": { "fast_charge_enable": true, "fast_charge_timer": 2, "precharge_enable": true, "precharge_timer": 0, "topoff_timer": 1 },
"mppt": { "voc_percent": 5, "voc_delay": 1, "voc_rate": 0, "enable": true },
"power": { "pfm_forward_enable": false, "ooa_forward_enable": true },
"intervals": { "battery_check": 30000, "soc_check": 60000, "config_apply": 300000 },
"version": "1.0"
}
Enumerated fields:
| Field | Values |
|---|---|
battery.type |
0 LiFePO4, 1 Li-Po / Li-ion, 2 lead-acid, 3 custom |
charging.precharge_voltage_threshold |
0 = 15 %, 1 = 62.2 %, 2 = 66.7 %, 3 = 71.4 % of VREG |
timers.fast_charge_timer |
0 = 5 h, 1 = 8 h, 2 = 12 h, 3 = 24 h |
timers.precharge_timer |
0 = 2 h, 1 = 0.5 h |
timers.topoff_timer |
0 = off, 1 = 15 min, 2 = 30 min, 3 = 45 min |
mppt.voc_percent |
0-7 = 56.25, 62.5, 68.75, 75, 81.25, 87.5, 93.75, 100 % of VOC |
mppt.voc_delay |
0 = 50 ms, 1 = 300 ms, 2 = 2 s, 3 = 5 s |
mppt.voc_rate |
0 = 30 s, 1 = 2 min, 2 = 10 min, 3 = 30 min |
input.voltage_limit is the VINDPM setting. While MPPT is enabled, the BQ25798 overwrites VINDPM after each VOC measurement.
Validation
ModbeeMpptConfig::validateConfig() checks every value before it is saved or applied. For the battery, the charge voltage must:
- fall within the BQ25798 window for the cell count: 1S 3.0-4.99 V, 2S 5.0-9.99 V, 3S 10.0-13.99 V, 4S 14.0-18.8 V;
- not exceed 3.65 V/cell for LiFePO4 or 4.35 V/cell for Li-Po.
A save from the web interface that fails validation is rejected, and the previous settings stay in force.
Settings can also be changed in code:
modbeeMPPT.config.data.charge_current = 2.0f; // A
if (modbeeMPPT.config.validateConfig()) {
modbeeMPPT.config.saveConfig();
modbeeMPPT.config.applyToMPPT(modbeeMPPT.api);
}
Web server
| Item | Value |
|---|---|
| Wi-Fi mode | Access point only |
| SSID / password | ModbeeMPPT / none (open) |
| Address | 192.168.4.1, with captive DNS for all names |
| HTTP | GET / → index.html, GET /settings → settings.html, GET /debug → debug.html; other LittleFS files served statically; unknown paths redirect to / |
| WebSocket | ws://192.168.4.1/ws, JSON text messages |
To change the SSID or add a password, edit WIFI_SSID and WIFI_PASSWORD in lib/ModbeeMPPT/src/ModbeeMpptWebServer.h. A WPA2 password must be at least 8 characters.
WebSocket protocol
Client to device, {"command": "<name>", ...}:
| Command | Extra fields | Reply type |
|---|---|---|
getSystemData |
data |
|
getSettings |
settings |
|
getDebugData |
debug |
|
saveSettings |
settings: object with any of the keys below |
status |
resetDefaults (alias resetSettings) |
status |
|
resetStat |
domain: vin1, vin2, vbus, vbat or vsys |
status |
resetBatteryAmpStats |
status |
|
resetBatteryDischargePowerStats |
status |
saveSettings keys:
- Battery:
batteryType,cellCount,chargeVoltage,chargeCurrent,systemVoltage - Charging:
termCurrent,rechargeThreshold,prechargeCurrent,prechargeVoltageThreshold - Input:
inputVoltage,inputCurrent,vacOvp - Timers:
chargeTimerEnable,chargeTimer,prechargeTimerEnable,prechargeTimer,topoffTimer - MPPT:
mpptEnable,vocPercent,vocDelay,vocRate - Power:
pfmForwardEnable,ooaForwardEnable - Intervals:
batteryCheckInterval,socCheckInterval
status replies are {"type": "status", "success": true|false, "message": "..."}.
data messages are pushed to all clients every second. They contain voltage, current and power for VAC1, VAC2, VBUS, the battery and the system rail. They also carry:
- Charge: state of charge, charge state and
isCharging - Status: fault flag, battery and die temperature
- Statistics: energy totals and peak values
The exact key list is in ModbeeMpptWebServer::getSystemData().
Serial diagnostics
| Function | Output |
|---|---|
printQuickStatus() |
Essential status |
printStatus() |
Full status |
printPowerMeasurements() |
Input, battery and system power |
printConfiguration() |
Current charger settings |
printFaults() |
Fault status |
printRegisterDebug() |
Decoded BQ25798 status registers |
printComprehensiveBatteryStatus() |
Battery voltage and SOC detail |
Build
pio run # build
pio run -t upload # flash firmware
pio run -t uploadfs # flash web interface
pio device monitor # serial console, 115200 baud
The lolin_c3_mini board definition matches the ESP32-C3FH4 on this board (4 MB flash, native USB). The firmware uses about 92 % of the default 1.25 MB application partition.
Firmware status
| Feature | Status |
|---|---|
| BQ25798 charging, MPPT, dual input, telemetry | Implemented |
| Web dashboard, settings, debug pages | Implemented |
| Settings persistence, energy statistics | Implemented |
| WIFI button, access point timeout, light sleep | Implemented |
| Modbus RTU over RS485 | Planned (hardware fitted) |
| I2C target (slave) telemetry on the Grove port | Planned (hardware fitted) |
| Long-press WIFI button factory reset | Planned |
| Bluetooth LE | Not implemented (radio is turned off) |