Semantic Versioning progression.
Edge seismic detection on ESP32 with local alerts.
- ADXL345 acquisition at 100 Hz with STA/LTA triggering
- ECDSA signature on every payload
- Local alert via LED / serial
Data Plane migration to MQTT Cloud (HiveMQ), REST Control Plane (HTTPS) and TLS security.
- Data Plane: ESP32 → HiveMQ Cloud (port 8883, TLS + username/password)
- Control Plane: HTTPS tunnel for REST provisioning — Cloudflare quick tunnel in dev (the ngrok free-tier edge is unusable by ESP32 IoT clients: it terminates ESP-IDF/mbedTLS TLS handshakes via JA3 fingerprinting)
setInsecure()for TLS handshake on ESP32- MQTT Bridge (Python Paho) with TLS
- Working mobile dashboard with live data
- Active CI/CD
On-premise AI integration (LLM in the backend) to generate textual emergency reports from MQTT data.
- ✅ On-premise LLM via Ollama (
llama3.2:1b) consuming confirmed alerts — telemetry never exposed - ✅ Automatic emergency report generation: magnitude, zone, timestamp, recommendations (deterministic, anti-hallucination)
- ✅ WebSocket push of the AI report to the mobile app (banner + history card)
- ✅
PENDING → COMPLETED | FAILEDstate machine with DLQ and explicit fallback - ✅ REST endpoint
GET /reports/{alert_id}
Geographic zone division designed so the system is ready for the GNSS upgrade (v1.3). The alert cooldown is fragmented from the coarse macro-region level down to a per-area geohash granularity, and the zone-assignment hot path is offloaded from PostGIS queries to a Redis geohash index — with PostGIS kept as the single source of truth.
Blocking prerequisite for v1.3 (GNSS): real GNSS coordinates replace the hardcoded Rome fix in the firmware (
main.cpp), so the geo layer must already resolve zones and fragment cooldowns from raw coordinates — not from a fixed registration-time zone.
- ✅ Geohash zone index (Redis fast path) —
backend/src/geo.py: at seed time every zone polygon is decomposed into the set of geohash cells (prec 3, ~156 km) it intersects;resolve_zone()(inmain.py) resolves a coordinate from the Redis SET without a DB round-trip. Redis miss or ambiguous multi-zone match falls back to the authoritative PostGISST_Contains+ST_Area ASCquery, so the cache can never assign a wrong zone - ✅ Pure-Python geohash encoder matching PostGIS
ST_GeoHash— zero new dependencies, deterministic keys shared by seed-time index and runtime lookup - ✅ Fragmented cooldown lock —
alert_cooldown:<geohash>(prec 4, ~50 km) instead ofalert_cooldown:<zone_id>: two independent events inside the same overlapping macro-polygon no longer silence each other. Legacyalert_cooldown:zone:<id>retained for coordinates-less sensors - ✅ GNSS-ready data model —
Sensor.last_fix_at,Reading.latitude/longitude; the ingestion payload carries the sensor's fix + geohash;/devices/registerre-resolves the zone if a relocated node reports a changed fix - ✅ Real spatial tests — integration tests assert resolution against the seeded PostGIS polygons (Milan, Madrid, Tokyo, unknown point), and
point_to_geohashis cross-validated against PostGISST_GeoHash
Future hardening (for administrative polygons):
ST_SimplifyPreserveTopology+ GiST tuning, sizing zones so an event cannot physically reach the adjacent zone (~50–100 km for destructive surface-wave propagation).
Signed telemetry over a serial link (USB CDC) when MQTT/WiFi connectivity is lost, so the host still receives data during offline simulations.
- ✅ Second consumer of the existing event queue —
networkTaskdispatches each event to the first available path: MQTT publish (unchanged data plane), USB serial fallback, or in-memory retention ring when no path exists - ✅ ECDSA-signed payloads, identical signing to the MQTT data plane —
SerialFallback.hbuilds[QG:FB]{...}frames with the exact MQTT JSON; retained events are re-signed with the current wall time at drain so the backend ±300 s replay window accepts them - ✅ USB-host-aware retention —
Serial.isConnected()(HWCDC) distinguishes a real host from a power-only USB charger: with no host, events are retained in the ring instead of being written to a dead port; drained FIFO when a path becomes available - ✅ Offline wall clock — software clock anchored at the first NTP sync (
epoch_at_sync+millis()), so timestamps stay valid even after WiFi drops; no frames emitted before time is valid - ✅ Host-side bridge collecting serial output and forwarding to the ingestion pipeline —
firmware/tools/serial_bridge.pyreads/dev/ttyACM0, filters[QG:FB]frames, and POSTs them to/readings/withX-API-Key(same forwarding as the MQTT bridge) - ✅ Automatic first-boot provisioning — compile-time
SENSOR_IDshortcut removed; the node POSTs/devices/register(public key + MAC + enrollment token + GNSS-ready coords) and the backend assigns thesensor_idand zone. Verified live on hardware. Backend accepts NULL geometry when a node has no GNSS fix yet
Advanced GNSS synchronization of nodes for exact timestamps.
- ✅ Optional GPS/GNSS module on ESP32
- ✅ Correct NTP + PPS timestamps for all nodes
- ✅ Replace hardcoded GPS (Rome coordinates) with real coordinates
- ✅ ADXL345 offset calibration on boot
Triangulation algorithm. Multi-node spatial correlation combined with AI reports to compute the internal epicenter.
- ✅ Triangulation algorithm from ≥3 nodes
- ✅ Multi-node spatial and temporal correlation
- ✅ Internal epicenter computation
- ✅ AI + triangulation data fusion for precise alerts
- ✅ KiCad schematics and Gerber files of the node PCB (hardware blueprints for the triangulation node) — fabricated and assembled, ready for deployment
Documentation-only patch aligning the technical whitepaper, GitHub Wiki, and project website.
- ✅ Typst PDF compilation polished (LLM citation removal, version updates)
- ✅ Architectural Coherence: tunnel architecture (Cloudflare) and threshold/anti-replay values (300s) synchronized across all assets
- ✅ Hardware Documentation: clarified
1.8ffirmware threshold vs2.4SIL offline threshold; ROC curve contextualized - ✅ Project Licensing Health: testing pipelines, AGPL-3.0 software licensing, and CERN-OHL hardware licensing explicitly documented
- ✅
CITATION.cffbumped for Zenodo archival
- ✅ Demo Plausibility: Modified the
/demo/trigger-earthquakeendpoint (magnitude 5.0, PostGIS dynamic centroid, 1.6 calibration factor). - ✅ Hollywood Simulator: Added Pisa to local orchestration.
- ✅ Versioning Fix: Cleaned up ghost roadmap entries.
Comprehensive repository maturity improvements: CI hardening, architectural documentation, developer experience, and compliance foundations.
-
✅ Dependabot for automated dependency updates (pip, npm, GitHub Actions)
-
✅ Gitleaks secret scanning in CI (prevents credential leaks)
-
✅ Architecture Decision Records (ADRs): 4 initial ADRs (Redis Streams, HiveMQ, Hybrid Edge AI, CERN-OHL)
-
✅ C4 and Sequence Diagrams in Mermaid (versionable, GitHub-renderable)
-
✅ Multi-stage Dockerfile (builder + runtime stages, reduced image size)
-
✅ Firmware versioning (
FIRMWARE_VERSIONdefine, printed at boot) -
✅ Pinout reference (
firmware/PINOUT.md) and Flashing guide (firmware/FLASHING.md) -
✅ Privacy Policy (
mobile/PRIVACY_POLICY.md) for App Store/Play Store readiness -
✅ SUPPORT.md, Hardware Issue Template, Docker Compose override example
-
✅ BOM enrichment (manufacturer, cost, distributor links, socketable modules)
-
✅ README enhancements (landing page link, PRs welcome badge, hardware disclaimer)
-
✅ Grafana Dashboards: natively connected to TimescaleDB via automated JSON provisioning
-
✅ System Telemetry: (End-to-End Latency, ESP32 Free Heap, RSSI, GNSS Fix) appended to payload
-
✅ Captive Portal Onboarding:
WiFiManagerembedded HTML to serve the mobile APK and display the ECDSA Public Key -
✅ Dynamic Demo Onboarding: terminal QR Code (
qrencode) and mobileAsyncStorageoverride for zero-recompile tunnel routing -
✅ Over-the-Air (OTA) Updates: GitHub-based React Native automatic in-app updater
-
✅ Real-time visualization of multi-node network activity
-
✅ Automated zero-config deployment via
docker-compose.yml -
✅ Hollywood Simulator:
scripts/hollywood.shdemo orchestrator for live exhibitions with automated zone seeding
Geo-zoning & cooldown-lock design (relevant for the paper's System-Engineering claim):
- Implemented in v1.2.1: fragmentation to Geohash keys (
alert_cooldown:<geohash>), which offloads zone assignment from PostGISST_Containsto fast Redis lookups and stops overlapping macro-regions from silencing independent earthquakes.- Future: H3 hex-grid reindexing is re-evaluated when the v2.1 triangulation clustering is designed; daily H3 resolution can replace the coarsen geohash grid with no zone-model change. For real administrative polygons: apply
ST_SimplifyPreserveTopology+ a GiST index; size zones so an event cannot physically reach the adjacent zone (~50–100 km for destructive surface-wave propagation).
Crowning of the engineering phase. Two-tier edge cluster where TinyML is not a simple STA/LTA replacement, but a hierarchical Decision Fusion between cheap ubiquitous sensors and intelligent confirmation gates.
Blocking prerequisite: the STA/LTA parameter calibration from R1 must be completed before drafting/training the v2.2.0 models. Calibration is urgent and runs in parallel with v1.3.
Tier A — Ubiquitous sensors (ESP32-C3):
- Low-cost, installable anywhere; STA/LTA + ECDSA signing, unchanged from v1.x
- Produce the proprietary MEMS dataset (fills the domain gap vs INGV professional seismometers)
Tier B — Intelligent confirmation gates (ESP32-S3):
- Hybrid quantized CNN (INT8) via ESP-DL / TensorFlow Lite Micro
- Activated only on STA/LTA triggers to compute the local event probability
- Emits a confidence score that confirms or discards Tier A triggers (Decision Fusion)
Backend ingestion redesigned so the control plane sustains tens of thousands of sensors on a small footprint instead of degrading into a single-queue toy.
- ✅ Redis Streams replaces the single-consumer list queue — producers XADD to
readings:stream(O(1) append); N worker processes drain via consumer groups (docker compose scale worker=N);XAUTOCLAIMrecovers pending entries across worker restarts (at-least-once delivery); poisoned heartbeats park on thereadings:dlqstream so they never stall the group —backend/src/ingest.py - ✅ Batched DB commits — a stream batch (default 64) is written in one transaction instead of one commit per heartbeat
- ✅ TimescaleDB hypertable on
readings— chunked onrecorded_at; continuous aggregatereadings_minuteserves the dashboard rollups; compression + retention policies. Migration (backend/src/timescale.py) is idempotent and fails closed on plain PostGIS (dev/CI) - ✅ Statistics fast-path —
/sensors/{id}/statisticsreads the continuous aggregate when present, falls back to a COUNT otherwise - ✅ Single TimescaleDB+PostGIS image —
backend/docker/postgres-timescale.Dockerfile, wired intodocker-compose.yml - ✅ Real migration coverage — dedicated CI job runs the hypertable + aggregate tests against the actual deployment image
- ✅ Load generator
backend/scripts/load_test.py— N sensors at H Hz (default matches the 150-sensor CI requirement), stream or HTTP transport
Scale math (design target): 150 sensors @ 1/5s ≈ 30 msg/s (trivial today); 10k sensors @ 1 Hz ≈ 2k msg/s (bounded by worker count + hypertable inserts, still 1 Postgres node). The MQTT transport already exists (firmware → broker → bridge → API); the bridge stays HTTP-proxying by design — direct MQTT→stream is the documented next step only if the broker becomes the bottleneck.
Parallel node (non-semantic). R1 is the Foundation: it starts immediately, in parallel with v1.3 (GNSS), and its calibration is urgent because it is the blocking prerequisite for v2.2.0 and the paper.
Software-in-the-Loop (SIL) cross-validation: no logic duplication. It uses 100% of the production C++ code on both the firmware and the host, guaranteeing numerical equivalence for the IEEE paper.
Status: completed. The STA/LTA core is isolated in pure C++, compiled natively in CI, and driven by the Python orchestrator with zero logic duplication.
Scheduling: completed in parallel with v1.3 (GNSS). The calibration of the trigger parameters is completed and unblocks v2.2.0 model drafting/training.
- Isolation of the STA/LTA algorithmic core in pure C++, fully decoupled from the ESP32 hardware (no I2C/WiFi/FreeRTOS calls in the algorithmic core) —
firmware/src/DetectionCore.h - Native host compilation of the C++ core (same source as the firmware) —
detect_cli.cpp+ CI build - Python as the sole orchestrator: reading the public INGV dataset (accelerograms), passing data to the C++ binary via
subprocess, collecting trigger points and tracing ROC curves —research/ - Metrics: Sensitivity/Recall, False-Alarm Rate, response latency —
research/metrics.py - Calibration of the trigger parameters (
TRIGGER_RATIO,NOISE_FLOOR,HPF_ALPHA) against ground-truth —research/calibrate.py; real ground-truth download via INGV FDSN (public API + ObsPy, CC-BY-4.0 flatfiles) — seeresearch/README.md
Completed full R1 closure: real ESM/INGV ground-truth validation via ObsPy and ROC curve generated.
- P50/P99 latency benchmark of the local async AI worker (Ollama)
- Measurement of the hallucination rate of the generated reports
- Quantification of the privacy/latency advantage vs Cloud baseline
Two distinct claims — do not conflate them in the paper:
- Self-hosted, data-sovereign LLM. Inference runs inside the Docker network (
http://ollama:11434), uses no third-party inference API, and telemetry never leaves the deployment. True regardless of deployment target (defensible).- Local-first resilience is a deployment property, not a software property: it holds only when the full alert path (local MQTT broker → bridge → AI worker) runs on an on-premise host co-located with the community. Today the alert path uses HiveMQ Cloud, so the alert path still depends on the WAN. Claim this only for the on-premise topology.
Determinism is a hypothesis, not a guarantee:
temperature=0,top_k=1(greedy decoding) remove stochastic variance, but the model can be deterministically wrong. The hallucination rate must be measured empirically in this benchmark before any quantitative claim is reported.
R1 + R2 converge here: results are published as artifacts separate from the software.
- Publish open validation dataset (Zenodo DOI, separate from software)
- Draft technical paper / preprint (arXiv)
Open data strategy & license gate:
- Publish the derived ESM parametric dataset (CC-BY-4.0) as a re-distributable Zenodo artifact with its own DOI, separate from the software.
- License re-verification step before publishing ANY derived data: re-check the current ITACA/ESM license terms and update
CITATION.cffat release time (ITACA is CC-BY-NC-ND-4.0 and forbids redistribution of derived waveforms).
Production-grade cloud platform behind the alert pipeline: the MQTT/REST/AI stack of v1.x–v2.2 runs as containerized workloads on Kubernetes, fully provisioned as Infrastructure-as-Code with Terraform. The control plane elastically scales with the number of deployed sensors and with real-time alert bursts.
Post-research horizon (after v2.2.0 / paper). Not blocking for the thesis; it targets the operational release of the system.
Infrastructure-as-Code (Terraform):
- Declarative provisioning of the cloud provider resources (managed Kubernetes cluster, VPC, node pools, networking) in versioned modules
- State management and drift detection for reproducible, auditable deployments
Orchestration (Kubernetes):
- Containerized deployment of the MQTT broker, AI report worker, REST control plane and dashboard
- Native autoscaling (Horizontal Pod Autoscaler / cluster autoscaler) driven by MQTT ingestion rate and CPU/memory
- Real-time elastic burst handling: alert spikes scale up workers (AI reports) and event queues; quiet periods scale to zero
- Rolling updates, health probes and self-healing for continuous availability
Delivery & observability:
- GitOps / CI/CD pipeline applying Terraform and Helm charts
- Monitoring and alerting for the cluster itself (resource saturation, autoscaling events)
- Kafka / Redpanda as the central ingestion buffer (millions-class) — replaces Redis Streams as the durable, replayable backbone once sustained ingestion exceeds what a single Redis node can buffer. The consumer interface (
src/ingest.py) is deliberately transport-agnostic:enqueue_reading/read_batch/ack/recover_pendingare re-pointable so a Kafka-backed implementation can slot in without touching the worker. Also unlocks partitions-per-sensor ordering and backfill reprocessing for the triangulation engine. - ClickHouse for cold-path analytics — move long-range dashboards / multi-node correlation queries (epicenter triangulation, swarm clustering) off the operational Postgres node onto a columnar store with a Kafka connector. Cold reads never contend with the ingestion hot path; TimescaleDB continuous aggregates keep serving the real-time dashboard.
- Non-blocking MQTT-Bridge refactor —
aiomqtt+ async push to Redis (orhttpx/aiohttp) to make the bridge relay fully non-blocking. Partially superseded: the ingestion endpoint is now an O(1) stream append, so the HTTP-proxying bridge is no longer the DB bottleneck; direct MQTT→stream still removes the HTTP hop and is the documented next step. - Rust ingestion microservice (Axum) + ECDSA verification via PyO3 — the hybrid path:
keep FastAPI/PostGIS/Ollama, move only the CPU-bound signature verification (P-256/SHA-256)
to native speed; a dedicated Axum ingestion endpoint can later absorb
POST /readings/. - Local MQTT broker (
mosquitto) as the default alert path — makes the R2 "local-first resilience" claim real; explicitly note HiveMQ Cloud as the current WAN dependency. - Load-test on rented infrastructure (e.g. AWS), one-off — not GitHub Actions (hardware limits); results published as static charts in the paper.