Utility Functions¶
Helpers that don't fit any of the other software pages — file I/O wrappers, password hashing, the message queue, mDNS, debug dumps, and a handful of small math primitives. Listed here so they're greppable when you need one of them, but the implementations are short enough to read directly.
LittleFS Wrappers (2_functions.ino)¶
| Function | What |
|---|---|
readFile(fs, path) |
Returns full file as String. Empty string on miss. |
writeFile(fs, path, message) |
Overwrites. Creates parent dir if needed. |
fsExists(path) |
Wraps LittleFS.exists() with mutex guard |
fsRemove(path) |
Wraps LittleFS.remove() |
fsMkdir(path) |
Wraps LittleFS.mkdir() — silent no-op if directory already exists |
fsTakeLock() / fsReleaseLock() |
FreeRTOS recursive-mutex on fsMutex |
All file I/O on either the user partition (LittleFS) or the web partition (webFS) flows through these so the fsMutex is uniformly acquired and the operations are atomic with respect to other tasks. httpsTask and TempTask both touch LittleFS occasionally — without the mutex, simultaneous writes corrupt the file system.
ensureLittleFS() (called once in setup()) mounts the user partition with auto-format on first boot. ensureWebFS() mounts whichever of factory_fs / prod_fs matches the active partition state and validates the presence of the five required web files (index.html.gz, script.js.gz, styles.css.gz, uPlot.iife.min.js.gz, uPlot.min.css.gz).
Password Hash (5_functions.ino)¶
mbedTLS SHA-256 over a fixed salt prefix + plaintext, hex-encoded, written to /password_hash.txt. No bcrypt — keeps the dependency surface small. Stored hash is compared on every authenticated request.
| Function | What |
|---|---|
loadPasswordHash() |
Read hash from LittleFS into cachedPasswordHash at boot |
savePasswordHash() |
Write cachedPasswordHash to LittleFS |
savePasswordPlaintext(p) |
Hash p and call savePasswordHash() |
validatePassword(p) |
Hash p and compare against cachedPasswordHash. Returns bool |
The default password is exposed in the captive portal info box; the user is expected to set their own via the dashboard. Plaintext is never stored.
Console Message Queue (5_functions.ino)¶
PSRAM-resident ConsoleMessage circular buffer, CONSOLE_QUEUE_SIZE = 10. Producer/consumer model — any task can produce; only trySendConsoleSSE() on Core 1 consumes.
struct ConsoleMessage {
char text[128]; // truncated if longer
unsigned long ts; // millis() at queue time
};
| Function | What |
|---|---|
queueConsoleMessage(const char *) |
Plain string |
queueConsoleMessage(const String &) |
String overload |
queueConsoleMessageF(const char *fmt, …) |
printf-style; 128-byte buffer |
popConsoleMessages(out[][128], outTs[], maxPop) |
Drain up to maxPop messages atomically; returns count |
trySendConsoleSSE(sentSomething, now) |
Pulls from queue, sends console SSE event, 1 s throttle, max 5 per dispatch |
Lossy by design — a flood that overflows the queue silently overwrites the oldest entries. The tempCoreBusySkipCount-style counters elsewhere are the diagnostic mechanism for "what got dropped"; the console queue itself is not a reliable log.
Math Primitives (6_functions.ino)¶
| Function | What |
|---|---|
clamp_f(x, lo, hi) |
Float clamp |
clamp_i(x, lo, hi) |
Int clamp |
slew_limit_f(prev, target, rise_per_s, fall_per_s, dt_sec) |
Asymmetric slew limiter. Returns target if Δ within budget, otherwise prev ± max_delta_for_dt |
interpolateRPMTable(rpm, table) |
Linear interp into a 10-entry RPM-indexed float table |
findRPMSegment(rpm) |
Returns segment index 0..8 for the breakpoints |
clamp_f / clamp_i / slew_limit_f are used heavily in the field-control path; they're inlined liberally |
I²C Helpers (4_functions.ino)¶
| Function | What |
|---|---|
i2cProbe8bit(addr8) |
Wire.beginTransmission(addr8 >> 1) + endTransmission(); returns true on ACK. Used as a presence check before driver init for sensors that crash on a non-existent address |
writeINA228Register(i2cAddress, reg, value) |
Raw write — bypasses the INA228 library when configuring the ALERT pin behavior |
readINA228AlertRegister(i2cAddress) |
Raw read of the INA228 DIAG_ALRT register; used by CheckAlarms() to poll the OV latch |
clearINA228AlertLatch(i2cAddress) |
Acknowledge the ALERT latch so the chip can re-arm |
Logging and Diagnostics¶
| Function | Source | What |
|---|---|---|
printPartitionInfo() |
4_functions.ino |
Dumps esp_partition_* results to Serial at boot — shows running partition, factory location, OTA slots, data partitions |
getSubtypeString(type, subtype) |
5_functions.ino |
Human-readable partition type/subtype names |
checkExpectedPartition(name, type, subtype, expectedSize) |
5_functions.ino |
Hard-asserts that the running partition layout matches partitions.csv |
printTempDebugStatus() / printEffDiagnostics() |
4_functions.ino / 7_functions.ino |
Periodic dumps of subsystem-specific counters |
debugStackBeforeHTTPS(functionName) |
3_functions.ino |
Prints current uxTaskGetStackHighWaterMark before entering HTTPS code paths — used to debug stack overflows during TLS handshakes |
validateHeapIntegrity() |
4_functions.ino |
heap_caps_check_integrity_all() — called pre-OTA to catch corruption before flashing |
Time Utilities¶
| Function | Source | What |
|---|---|---|
getCurrentTimestamp() |
Implicit / 2_functions.ino |
Unix epoch seconds, derived from NTP + millis offset |
syncTimeFromNTP() |
2_functions.ino |
One-shot NTP sync; fills syncEpoch and syncMillisAt |
syncTimeFromGPS(daysSince1970, secondsSinceMidnight) |
4_functions.ino |
Fallback time source from NMEA2000 SystemTime PGN |
checkTimeSync() |
4_functions.ino |
12-hour re-sync gate (only at fieldOffSettled(0)) |
loadTimeSyncState() / saveTimeSyncState() |
4_functions.ino |
NVS-persisted sync offset across reboots — survives a power cycle without re-NTPing |
NTP is preferred when available; GPS is the fallback for offline deployments. syncEpoch of 0 means "no time sync yet"; consumers treat that as "no timestamping" rather than "epoch 0".
mDNS¶
MDNS.begin("alternator") runs in both connectToWiFi() and setupAccessPoint(). Registers HTTP service so alternator.local resolves on most networks (works reliably from macOS / iOS / modern Android; less reliable from some Windows configurations and older routers). MDNS.end() precedes each MDNS.begin() to guarantee a clean state when the WiFi mode flips.
Reset Reason Capture (5_functions.ino)¶
void captureResetReason() {
esp_reset_reason_t r = esp_reset_reason();
// map to readable string and persist
}
Runs at boot, before loadNVSData(). Saves the reason (POWERON_RESET, SW_RESET, WATCHDOG, BROWNOUT, etc.) to LittleFS so the dashboard can show "last reset reason" on the Stats panel. Crucial for diagnosing "did the watchdog trip?" without a serial console attached.
Device Identity (4_functions.ino)¶
initializeDeviceId() reads the ESP32 efuse MAC and either uses it directly or applies a UID-spoofing override (used during development to test multiple "different" devices from the same physical board). getDeviceId() returns the cached string. checkDeviceUIDChange() compares against the last-boot value stored in LittleFS; on mismatch, the user has to re-register with Supabase. This handler is intentionally commented out during routine testing to avoid forcing re-registration on every UID-spoof change.
NMEA2000 Verbose Print¶
PrintLabelValWithConversionCheckUnDef(label, val, ConvFunc, AddLf, Desim)
Template helper in Xregulator.ino (the only top-level template in the codebase — must stay in the main file because of how Arduino's auto-prototype generator handles templates). Used by every NMEA2K PGN handler when NMEA2KVerbose == 1 to print received PGN field values with optional radians-to-degrees / m/s-to-knots conversion. Otherwise no-op.
Cross-references¶
- Main control loop, field-control helpers → Field Control
- Sensor drivers (ADS, INA, DS18B20, BMP388, IMU) → Sensor Systems
- Network/web/OTA functions → Network & Web System
- CSV plumbing helpers (
SafeInt,TIMED_CALL,MARK_FRESH) → Important Functions