Skip to content

Hardware and Edge Computing

GiZano edited this page Sep 3, 2026 · 3 revisions

Hardware & Edge Computing (ESP32-C3)

The edge layer operates on resource-constrained microcontrollers, specifically the ESP32-C3 SuperMini (RISC-V architecture).

Hardware Configuration

Due to the specific physical layout, the I2C bus is software-mapped to non-standard GPIO pins:

  • SDA (Data): GPIO 7 (requires internal pull-up).
  • SCL (Clock): GPIO 8 (requires internal pull-up).
  • Power: The ADXL345 is powered strictly via the 3.3V rail.

The sensor operates at a 100Hz sampling rate (ADXL345_DATARATE_100_HZ) with a measurement range of ±16G.

Digital Signal Processing (DSP) Pipeline

  • High-Pass Filter (HPF): A digital filter (HPF_ALPHA = 0.9f) isolates dynamic vibration data by subtracting the static DC component (Earth's gravity).
  • Noise Gate: Micro-vibrations below the empirical threshold of 0.04G are clamped to zero to prevent false positives from electrical noise.
  • Dropout Protection: The firmware automatically drops frames reporting near-zero absolute acceleration (< 2.0 $m/s^2$ prior to filtering), mitigating corrupted readings from I2C disconnects.

STA/LTA Seismic Detection Algorithm

The implementation utilizes a custom, memory-efficient RingBuffer template class in C++ to maintain rolling sums for $O(1)$ average calculations:

  • Short-Term Window (STA): 100 samples (1 second).
  • Long-Term Window (LTA): 1000 samples (10 seconds).
  • Trigger Condition: An earthquake is registered when the STA/LTA ratio exceeds 1.8f, provided the STA absolute value is above the noise floor. (Note: The firmware deployed on edge nodes uses this more sensitive threshold of 1.8f to ensure no P-waves are missed in the field, whereas a stricter ratio of 2.4 is used specifically in the offline SIL calibration to certify a theoretical 0% False Alarm Rate bound.)

FreeRTOS Task Architecture

The firmware decouples acquisition from network latency via FreeRTOS:

  • sensorTask (priority 5): Wakes every 10 ms to poll ADXL345, runs SeismicDetector (pure C++ DetectionCore.h shared with host SIL validation), pushes SeismicEvent to eventQueue on trigger.
  • networkTask (priority 1): Consumes eventQueue, syncs time via NTP (pool.ntp.org), signs payload, dispatches via MQTT or serial fallback. Never blocks sensorTask.
  • gnssTask (optional, priority 2): When GNSS_ENABLED=1, continuously feeds NMEA frames.

Zero-Trust Serial Fallback (v1.2.2)

Since v1.2.2 the node tolerates complete MQTT/WiFi loss:

  • First-available-path dispatcher: MQTT → USB CDC Serial → Retention Ring via decidePath(mqttReachable, usbHostPresent, timeValid) (pure, shared with test_serial_fallback.cpp).
  • Framing: SerialFallback.h builds [QG:FB]{"value":..,"sensor_id":..,"device_timestamp":..,"signature_hex":".."} — byte-identical to MQTT JSON; marker filters CDC boot noise.
  • Host-aware routing: Serial.isConnected() (HWCDC, ARDUINO_USB_CDC_ON_BOOT=1) distinguishes a real USB host from a charger; with no host, events are retained, not written to a dead port.
  • Retention ring: Bounded RetentionRing<100> FIFO (oldest overwritten on overflow). On path recovery, events are drained in order and re-signed with current wall time so backend ±300 s replay window accepts them.
  • Offline wall clock: NTP is opportunistic (configTime); at first sync epochAtSync+millis() anchors a software clock. No frame is emitted before timeValid.
  • Host bridge: firmware/tools/serial_bridge.py tails /dev/ttyACM0, filters [QG:FB], SSRF-validates URL, POSTs to /readings/ with X-API-Key — same gate as MQTT bridge. Smoke-tested in iot-ci.yml.

Optional GNSS Subsystem (v1.3 Readiness, GNSS-ready since v1.2.1)

An optional u-blox / NEO-6M / M8N on secondary UART (RX GPIO 0, TX GPIO 1, 9600 baud), compiled only with GNSS_ENABLED=1:

  • Last-known fix in NVS (quake-gnss, ≤1 write/60 s): provisioning reports real coords even after cold boot.
  • Staleness: Fix >10 s old → fallback to last-known; if none, coordinates omitted → backend assigns Unknown Region.
  • Scope: NTP+PPS discipline deferred to v1.3; this module only pipes coordinates.

Clone this wiki locally