Skip to content

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