diff --git a/.github/ISSUE_TEMPLATE/hardware_issue.md b/.github/ISSUE_TEMPLATE/hardware_issue.md
new file mode 100644
index 0000000..21d883e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/hardware_issue.md
@@ -0,0 +1,40 @@
+---
+name: Hardware Issue
+about: Report a hardware-related problem (PCB, wiring, components, assembly)
+title: "[HW] "
+labels: hardware
+---
+
+# Description
+A clear description of the hardware issue. Specify which board revision and components are affected.
+
+# Guidelines
+Before submitting, please verify:
+- [ ] You have checked the [Bill of Materials (BOM)](https://github.com/GiZano/QuakeGuard/wiki/Bill-of-Materials) and confirmed correct components
+- [ ] You have verified wiring against the [Pinout Table](firmware/PINOUT.md)
+- [ ] You have included photos or serial output below
+
+# Hardware Details
+- **PCB Revision:**
+- **Assembly Method:**
+- **Component Affected:**
+- **Power Source:**
+
+# Steps to Reproduce
+1.
+2.
+3.
+
+# Serial Monitor Output
+```
+
+```
+
+# Photos
+
+
+# Expected Behavior
+What should happen with correct hardware operation.
+
+# Actual Behavior
+What is actually happening. Include any error LEDs, missing readings, or unexpected behavior.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..e59abc4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,42 @@
+# Dependabot — Automated dependency updates for QuakeGuard
+# Docs: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
+version: 2
+updates:
+ # --- Python (Backend) ---
+ - package-ecosystem: "pip"
+ directory: "/backend"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ labels:
+ - "dependencies"
+ - "backend"
+ commit-message:
+ prefix: "chore(deps)"
+ open-pull-requests-limit: 5
+
+ # --- npm (Mobile) ---
+ - package-ecosystem: "npm"
+ directory: "/mobile"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ labels:
+ - "dependencies"
+ - "mobile"
+ commit-message:
+ prefix: "chore(deps)"
+ open-pull-requests-limit: 5
+
+ # --- GitHub Actions ---
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ labels:
+ - "dependencies"
+ - "ci"
+ commit-message:
+ prefix: "chore(ci)"
+ open-pull-requests-limit: 5
diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml
new file mode 100644
index 0000000..91f1e4b
--- /dev/null
+++ b/.github/workflows/gitleaks.yml
@@ -0,0 +1,24 @@
+name: "Gitleaks — Secret Scanning"
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ scan:
+ name: Detect Leaked Secrets
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
+ with:
+ fetch-depth: 0
+
+ - name: Run Gitleaks
+ uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196e88a9c30
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 7877068..33b0a84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,9 @@ extract.sh
research/data/
research/out/
research/*.png
-firmware/tools/detect_cli
\ No newline at end of file
+firmware/tools/detect_cli
+
+# ------------------------------------------
+# KiCad local history
+# ------------------------------------------
+.history/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f31940c..01139d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.0.2] - 2026-09-03
+### Added
+- **Dependabot Configuration:** Automated dependency updates for pip (backend), npm (mobile), and GitHub Actions (`.github/dependabot.yml`).
+- **Gitleaks Secret Scanning:** CI workflow (`.github/workflows/gitleaks.yml`) to prevent accidental credential leaks in commits — critical for a project handling ECDSA keys and MQTT credentials.
+- **Architecture Decision Records (ADRs):** Created `docs/adr/` with 4 initial ADRs documenting key architectural decisions: Redis Streams (0001), HiveMQ Cloud (0002), Hybrid Edge AI (0003), CERN-OHL licensing (0004).
+- **C4 and Sequence Diagrams:** Created `docs/diagrams/` with Mermaid diagrams: C4 Context/Container, provisioning sequence, alert delivery sequence, and triangulation sequence — versionable independently from the Typst whitepaper.
+- **Firmware Versioning:** Added `#define FIRMWARE_VERSION "2.0.2"` printed at boot via serial for field identification and remote debugging.
+- **Pinout Reference:** Created `firmware/PINOUT.md` — unified GPIO mapping, LED behavior table, connector pinout, and passive component reference.
+- **Flashing Guide:** Created `firmware/FLASHING.md` — standalone step-by-step flashing instructions for users without VS Code/PlatformIO IDE.
+- **Privacy Policy:** Created `mobile/PRIVACY_POLICY.md` — documents data collection, storage, and third-party services for App Store/Play Store compliance.
+- **SUPPORT.md:** Routing guide for all help channels (bugs, features, hardware, security, discussions).
+- **Hardware Issue Template:** `.github/ISSUE_TEMPLATE/hardware_issue.md` for PCB, wiring, and component assembly reports.
+- **Docker Compose Override Example:** `backend/docker-compose.override.yml.example` for local development customization.
+
+### Changed
+- **Dockerfile Multi-Stage Build:** Refactored `backend/Dockerfile` to a two-stage build (builder + runtime), reducing final image size by excluding build toolchain (gcc, python3-dev).
+- **BOM Enrichment:** Expanded `hardware/QuakeGuard_PCB/output/BOM.csv` with manufacturer, part number, unit cost, distributor links (LCSC/DigiKey), and socketable modules (ESP32-C3, ADXL345, NEO-6M).
+- **README Enhancements:** Added "PRs welcome" badge, prominent landing page link, CERN-OHL license reference, hardware compliance disclaimer, and v2.0.2 roadmap entry.
+- **Version artifacts bumped to v2.0.2** (CITATION.cff, firmware header, README roadmap, SECURITY.md).
+
## [2.0.1] - 2026-09-01
### Changed
- **Documentation Polish (Zenodo Sync):** Removed residual LLM tags (`[cite: 1]`) from the technical whitepaper.
diff --git a/CITATION.cff b/CITATION.cff
index a72055c..b24824b 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -10,12 +10,12 @@ authors:
given-names: "Riccardo"
alias: "riccardo0731"
title: "QuakeGuard"
-version: 2.0.1
+version: 2.0.2
identifiers:
- type: doi
value: "10.5281/zenodo.21710405"
description: "Concept DOI (Absolute - resolves to all versions)"
-date-released: 2026-09-01
+date-released: 2026-09-03
url: "https://giovanni-zanotti.is-a.dev/projects/quakeguard.html"
repository-code: "https://github.com/GiZano/QuakeGuard"
license: "AGPL-3.0"
diff --git a/README.md b/README.md
index 07910e3..e80be2d 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,8 @@
[](https://sonarcloud.io/summary/new_code?id=GiZano_QuakeGuard)
[](https://sonarcloud.io/summary/new_code?id=GiZano_QuakeGuard)
+
+
> 📚 **Technical Specification:** A comprehensive architecture whitepaper is available in the `docs/` directory, compiled via Typst. For a browsable, topic-by-topic reference, check out the [project Wiki](https://github.com/GiZano/QuakeGuard/wiki).

@@ -41,6 +43,10 @@
---
+🌐 **[Landing Page](https://giovanni-zanotti.is-a.dev/projects/quakeguard.html)** · 📚 **[Wiki](https://github.com/GiZano/QuakeGuard/wiki)** · 📄 **[Whitepaper (PDF)](docs/whitepaper/)** · 🗺️ **[ROADMAP](ROADMAP.md)**
+
+---
+
## 📖 Overview
**QuakeGuard** is a full-stack IoT architecture for real-time detection, analysis, and reporting of seismic events. The system transforms everyday household appliances — washing machines, TVs, refrigerators — into a distributed seismic sensor network, each node capable of detecting and reporting earthquake activity autonomously.
@@ -434,6 +440,7 @@ QuakeGuard/
| **v1.2.2** | ✅ Released — Zero-Trust Serial Fallback — signed telemetry over USB CDC (serial) when MQTT is unreachable |
| **v2.0.0** | ✅ Released — Triangulation (multi-node spatial correlation), Hybrid Network Architecture, Automated DevOps Orchestration (Ptyxis), Local Factory Provisioning, GNSS sync, NTP+PPS, ADXL calibration, and INGV FDSN SIL validation |
| **v2.0.1** | ✅ Released — Documentation & Zenodo Sync: PDF/Wiki architectural coherence (Cloudflare, 300s anti-replay), CERN-OHL hardware licensing, SIL vs Firmware threshold clarification, and `CITATION.cff` bump |
+| **v2.0.2** | ✅ Released — Repository Health: Dependabot, Gitleaks CI, ADRs, C4/sequence diagrams, multi-stage Dockerfile, firmware versioning, pinout table, flashing guide, privacy policy, hardware compliance disclaimer |
| **v2.1** | Data Dashboards — Grafana dashboards for real-time visualization of seismic telemetry |
| **v2.1.1** | Timeseries DB & Mobile Fix — migration to TimescaleDB/InfluxDB; per-sensor chart isolation in React Native mobile |
| **v2.2** | Heterogeneous Edge Intelligence — hybrid Tier A (STA/LTA) + Tier B (quantized CNN) decision fusion |
@@ -462,6 +469,11 @@ QuakeGuard's architecture and real-world applicability have been recognized in t
This project is licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**.
See the [LICENSE](LICENSE) file for details.
+Hardware designs (`hardware/`) are licensed under the **CERN Open Hardware Licence v2 — Strongly Reciprocal (CERN-OHL-S-2.0)**.
+See [hardware/LICENSE](hardware/LICENSE) for details.
+
+> ⚠️ **Hardware Compliance:** The QuakeGuard PCB is **not certified for CE/FCC/RoHS**. It is intended for **prototyping, educational, and research use only**. Do not deploy as a commercial safety-critical device without appropriate regulatory certification.
+
---
diff --git a/ROADMAP.md b/ROADMAP.md
index 42ad6fc..d59a7bd 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -103,6 +103,24 @@ Documentation-only patch aligning the technical whitepaper, GitHub Wiki, and pro
---
+## v2.0.2 — Repository Health (Released)
+
+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_VERSION` define, 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)
+
+---
+
## v2.1.0 — Data Dashboards
Grafana dashboards for real-time visualization of seismic telemetry.
diff --git a/SECURITY.md b/SECURITY.md
index d9c392e..efdf92b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -5,7 +5,7 @@ QuakeGuard takes the security of its IoT early warning infrastructure very serio
| Version | Supported | Notes |
| ------- | ------------------ | ----- |
-| 2.0.x | :white_check_mark: | Current stable release (Hybrid Network Architecture, GNSS Precision & SIL Validation). |
+| 2.0.x | :white_check_mark: | Current stable release (Repository Health, Hybrid Network Architecture, GNSS Precision & SIL Validation). |
| 1.3.x | :white_check_mark: | Prior stable release (GNSS Precision & SIL Validation). |
| 1.2.x | :x: | Deprecated. (Cloud MQTT + TLS + on-premise AI reports). |
| 1.1.x | :x: | Deprecated. Cloud MQTT + TLS. |
diff --git a/SUPPORT.md b/SUPPORT.md
new file mode 100644
index 0000000..7594b9c
--- /dev/null
+++ b/SUPPORT.md
@@ -0,0 +1,26 @@
+# Getting Help with QuakeGuard
+
+Thank you for using QuakeGuard! Here's how to get support depending on your situation.
+
+## 🐛 Bug Reports
+If you've found a reproducible bug, please open a [Bug Report](https://github.com/GiZano/QuakeGuard/issues/new?template=bug_report.md).
+
+## 💡 Feature Requests
+Have an idea for a new feature or improvement? Open a [Feature Request](https://github.com/GiZano/QuakeGuard/issues/new?template=feature_request.md).
+
+## 🔧 Hardware Issues
+Problems with the PCB, wiring, or component assembly? Open a [Hardware Issue](https://github.com/GiZano/QuakeGuard/issues/new?template=hardware_issue.md).
+
+## 💬 Questions & Discussion
+For general questions, architecture discussions, or community conversations, use [GitHub Discussions](https://github.com/GiZano/QuakeGuard/discussions).
+
+## 🔐 Security Vulnerabilities
+**DO NOT** open a public issue for security vulnerabilities. Please read our [Security Policy](SECURITY.md) and report privately to **gizano.dev@gmail.com**.
+
+## 📚 Documentation
+- **Technical Whitepaper:** Available in `docs/whitepaper/` (compiled via Typst)
+- **Project Wiki:** [github.com/GiZano/QuakeGuard/wiki](https://github.com/GiZano/QuakeGuard/wiki)
+- **Landing Page:** [giovanni-zanotti.is-a.dev/projects/quakeguard.html](https://giovanni-zanotti.is-a.dev/projects/quakeguard.html)
+
+## 📧 Direct Contact
+For anything not covered above, reach out at **gizano.dev@gmail.com**.
diff --git a/backend/Dockerfile b/backend/Dockerfile
index d34df7e..fbaea55 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,21 +1,31 @@
-FROM python:3.11-slim
+# =============================================================================
+# Stage 1: Build — install system deps and compile Python wheels
+# =============================================================================
+FROM python:3.11-slim AS builder
-RUN addgroup --system --gid 1001 appgroup && \
- adduser --system --uid 1001 --gid 1001 --no-create-home appuser
-
-WORKDIR /app
+WORKDIR /build
-# Install dependencies for PostgreSQL
RUN apt-get update && apt-get install --no-install-recommends -y \
gcc \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
-# Copy requirements and install Python dependencies
COPY requirements.txt .
-RUN pip install --no-cache-dir --only-binary :all: --require-hashes -r requirements.txt
+RUN pip install --no-cache-dir --only-binary :all: --require-hashes \
+ --prefix=/install -r requirements.txt
-USER appuser
+# =============================================================================
+# Stage 2: Runtime — slim image with only compiled packages and app code
+# =============================================================================
+FROM python:3.11-slim AS runtime
+
+RUN addgroup --system --gid 1001 appgroup && \
+ adduser --system --uid 1001 --gid 1001 --no-create-home appuser
+
+WORKDIR /app
+
+# Copy pre-built Python packages from builder
+COPY --from=builder /install /usr/local
# Set PYTHONPATH to include both /app and /app/src
ENV PYTHONPATH="/app:/app/src"
@@ -23,6 +33,8 @@ ENV PYTHONPATH="/app:/app/src"
# Copy application code
COPY src/ ./src/
+USER appuser
+
# Expose port
EXPOSE 8000
diff --git a/backend/docker-compose.override.yml.example b/backend/docker-compose.override.yml.example
new file mode 100644
index 0000000..7f598be
--- /dev/null
+++ b/backend/docker-compose.override.yml.example
@@ -0,0 +1,32 @@
+# docker-compose.override.yml — Local Development Overrides
+#
+# This file is automatically loaded by Docker Compose alongside docker-compose.yml.
+# Use it to customize the stack for local development without modifying the base config.
+#
+# Usage:
+# cp docker-compose.override.yml.example docker-compose.override.yml
+# # Edit as needed, then:
+# docker compose up --build -d
+
+services:
+ # --- Bind-mount source code for hot reload ---
+ fastapi-app:
+ volumes:
+ - ./src:/app/src:ro
+ environment:
+ - LOG_LEVEL=DEBUG
+
+ worker:
+ volumes:
+ - ./src:/app/src:ro
+ environment:
+ - LOG_LEVEL=DEBUG
+
+ # --- Expose additional ports for debugging ---
+ postgres:
+ ports:
+ - "5432:5432"
+
+ redis:
+ ports:
+ - "6379:6379"
diff --git a/docs/adr/0001-redis-streams-ingestion.md b/docs/adr/0001-redis-streams-ingestion.md
new file mode 100644
index 0000000..69093f5
--- /dev/null
+++ b/docs/adr/0001-redis-streams-ingestion.md
@@ -0,0 +1,33 @@
+# ADR-0001: Redis Streams as the Ingestion Transport
+
+## Status
+Accepted (v2.3.0)
+
+## Context
+QuakeGuard's original ingestion pipeline used a Redis `LPUSH`/`BRPOP` list queue with a single-consumer worker. While simple, this design has inherent scaling limitations:
+
+- **Single consumer:** Only one worker process can drain the queue, creating a bottleneck under load.
+- **No at-least-once delivery:** A crashed worker loses the popped message.
+- **No message inspection:** No way to audit pending or failed messages.
+- **No replay:** Once consumed, a message is gone.
+
+As the sensor network grows beyond ~150 nodes, the single-consumer list becomes the throughput ceiling.
+
+## Decision
+Replace the Redis list queue with **Redis Streams** (`XADD`/`XREADGROUP`):
+
+- Producers append via `XADD` to `readings:stream` (O(1)).
+- Workers form a **consumer group**, enabling horizontal scaling (`docker compose scale worker=N`).
+- `XAUTOCLAIM` recovers pending entries across worker restarts (at-least-once delivery).
+- Poisoned messages are parked on `readings:dlq` (dead-letter queue) instead of stalling the group.
+- Batched DB commits: a stream batch (default 64 messages) is written in one transaction.
+
+## Consequences
+- **Positive:** Horizontal worker scaling, at-least-once delivery, DLQ for debugging, batched writes.
+- **Negative:** Slightly more complex consumer logic vs. simple `BRPOP`.
+- **Neutral:** The `enqueue_reading` / `read_batch` / `ack` interface in `src/ingest.py` is transport-agnostic, so a future Kafka backend can slot in without touching the worker.
+
+## Alternatives Considered
+- **Kafka/Redpanda:** More scalable but adds an entirely new infrastructure dependency. Documented as the next step for millions-class ingestion (see ROADMAP.md "Future Horizon").
+- **RabbitMQ:** Feature-rich but heavier; Redis is already in the stack.
+- **Keep LPUSH/BRPOP:** Insufficient for consumer groups and recovery.
diff --git a/docs/adr/0002-hivemq-cloud-mqtt-broker.md b/docs/adr/0002-hivemq-cloud-mqtt-broker.md
new file mode 100644
index 0000000..5f7b481
--- /dev/null
+++ b/docs/adr/0002-hivemq-cloud-mqtt-broker.md
@@ -0,0 +1,32 @@
+# ADR-0002: HiveMQ Cloud as the MQTT Broker
+
+## Status
+Accepted (v1.1.0)
+
+## Context
+The ESP32-C3 firmware transmits cryptographically signed seismic telemetry via MQTT. The system requires a broker that:
+
+1. Supports TLS (port 8883) for transport-layer encryption
+2. Is reachable from any network (ESP32 nodes may be behind NAT)
+3. Has minimal operational overhead (no broker to self-host and maintain)
+4. Supports standard MQTT username/password authentication
+
+## Decision
+Use **HiveMQ Cloud (Serverless)** as the managed MQTT broker:
+
+- Managed, zero-ops TLS termination on port 8883
+- Free tier sufficient for development and small-scale deployment
+- Standard Paho-compatible client library on both ESP32 (PubSubClient) and Python (paho-mqtt)
+- No self-hosted infrastructure to maintain or secure
+
+The Python MQTT bridge (`mqtt_subscriber.py`) subscribes to `quakeguard/telemetry` and forwards payloads to the HTTP ingestion pipeline, keeping the data plane (MQTT) separate from the control plane (REST).
+
+## Consequences
+- **Positive:** Zero infrastructure overhead, TLS built-in, globally reachable.
+- **Negative:** WAN dependency — the alert path depends on internet connectivity. The "local-first resilience" claim (R2) only holds for on-premise topology with a local broker.
+- **Risk:** Vendor lock-in is minimal (standard MQTT protocol), but a self-hosted Mosquitto would eliminate the WAN dependency.
+
+## Alternatives Considered
+- **Self-hosted Mosquitto:** Full local control, enables true local-first resilience. Documented as a future step in ROADMAP.md. Currently, the added operational burden outweighs benefits for a 2-person team.
+- **EMQX Cloud:** Similar managed offering, but HiveMQ's free tier was more generous at evaluation time.
+- **AWS IoT Core:** Enterprise-grade but introduces AWS dependency and costs that are disproportionate for a research/educational project.
diff --git a/docs/adr/0003-hybrid-edge-ai-pattern.md b/docs/adr/0003-hybrid-edge-ai-pattern.md
new file mode 100644
index 0000000..acdbb1c
--- /dev/null
+++ b/docs/adr/0003-hybrid-edge-ai-pattern.md
@@ -0,0 +1,32 @@
+# ADR-0003: Hybrid Edge AI — Ollama Bare-Metal on Host
+
+## Status
+Accepted (v1.2.0)
+
+## Context
+QuakeGuard generates AI emergency reports from confirmed seismic alerts using a local LLM (Llama 3.2 1B via Ollama). The architectural decision is whether to run Ollama inside Docker alongside the other services, or bare-metal on the host OS.
+
+Key constraints:
+- The deployment target is a Linux workstation (not a cloud VM with GPU passthrough).
+- Docker on Linux does not natively support GPU passthrough without `nvidia-container-toolkit` or `--gpus` flags, which adds complexity.
+- Even CPU-only inference benefits from direct access to host memory and SIMD instructions.
+- The AI worker is a non-critical, asynchronous service — an alert is delivered regardless of whether the AI report succeeds.
+
+## Decision
+Adopt an industrial **Hybrid Edge AI** pattern (similar to NVIDIA Jetson or Tesla FSD architectures):
+
+- **Ollama runs bare-metal** on the host Linux OS (`curl -fsSL https://ollama.com/install.sh | sh`)
+- **Application services run in Docker** as before
+- The AI worker container uses `network_mode: "host"` to reach Ollama at `http://127.0.0.1:11434`
+- The Ollama Docker container definition is kept in `docker-compose.yml` (commented out) for environments where Docker-native is preferred
+
+## Consequences
+- **Positive:** Maximum hardware efficiency (direct memory, no container overhead), simpler GPU passthrough if available, model persisted natively.
+- **Positive:** Clear separation of concerns: the AI engine is a host-level capability, not a container sidecar.
+- **Negative:** The AI worker container must use `network_mode: "host"`, losing Docker network isolation for that single service.
+- **Negative:** Requires separate Ollama installation on the host (not fully containerized).
+
+## Alternatives Considered
+- **Ollama in Docker:** Fully containerized, but Docker GPU passthrough adds significant complexity on heterogeneous hardware. The commented `ollama` service block remains available for this path.
+- **Cloud LLM API (OpenAI, Anthropic):** Eliminates the privacy-first claim — telemetry would leave the host. Rejected on principle.
+- **TensorFlow Lite on ESP32:** Inference on the edge device itself. Reserved for v2.2.0 Tier B sensors (ESP32-S3 with quantized CNN). The ESP32-C3 lacks the memory for meaningful LLM inference.
diff --git a/docs/adr/0004-cern-ohl-hardware-license.md b/docs/adr/0004-cern-ohl-hardware-license.md
new file mode 100644
index 0000000..e10b7d1
--- /dev/null
+++ b/docs/adr/0004-cern-ohl-hardware-license.md
@@ -0,0 +1,31 @@
+# ADR-0004: CERN-OHL-S-2.0 for Hardware Licensing
+
+## Status
+Accepted (v2.0.1)
+
+## Context
+QuakeGuard is a multi-layer project with distinct software and hardware components:
+
+- **Software** (backend, mobile, firmware logic): Licensed under AGPL-3.0
+- **Hardware** (KiCad schematics, PCB layout, Gerber files): Needs a separate, hardware-specific open-source license
+
+The AGPL-3.0 is designed for software and its concepts (source code, compilation, linking) do not map cleanly to hardware design files. Using AGPL for hardware creates legal ambiguity.
+
+## Decision
+Apply **CERN Open Hardware Licence Version 2 — Strongly Reciprocal (CERN-OHL-S-2.0)** to all hardware design files in the `hardware/` directory:
+
+- Placed as a separate `hardware/LICENSE` file (distinct from the root `LICENSE` for software)
+- The CERN-OHL-S is the hardware equivalent of copyleft: modifications to the hardware design must be shared under the same terms
+- This aligns with the project's open-source philosophy while using a license designed specifically for hardware
+
+## Consequences
+- **Positive:** Legally precise — the license terms are designed for hardware "Source" (schematics, PCB layout, Gerber files) and "Products" (manufactured PCBs).
+- **Positive:** Strongly reciprocal — anyone modifying the PCB design must share their modifications (consistent with AGPL copyleft philosophy for software).
+- **Positive:** CERN-backed and widely recognized in the open hardware community (CERN, RISC-V, others).
+- **Negative:** Two licenses in one repo can confuse contributors. Mitigated by clear directory separation and documentation.
+
+## Alternatives Considered
+- **AGPL-3.0 for everything:** Legally unclear for hardware; AGPL concepts (linking, network interaction) have no hardware equivalent.
+- **CERN-OHL-P (Permissive):** Allows proprietary derivatives. Rejected to maintain copyleft consistency with the software license.
+- **CERN-OHL-W (Weakly Reciprocal):** Only requires sharing modifications to the original design, not the larger work. The strongly reciprocal variant better matches AGPL's intent.
+- **TAPR OHL:** Less widely adopted than CERN-OHL; CERN-OHL v2 is the current community standard.
diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md
new file mode 100644
index 0000000..60d786a
--- /dev/null
+++ b/docs/diagrams/README.md
@@ -0,0 +1,13 @@
+# QuakeGuard — Architectural Diagrams
+
+This directory contains versionable architectural diagrams in Mermaid format.
+They complement the diagrams in the Typst whitepaper (`docs/whitepaper/`) but are independently viewable on GitHub.
+
+## Contents
+
+| File | Description |
+|------|-------------|
+| `c4-context.md` | C4 Context Diagram — who interacts with QuakeGuard |
+| `sequence-provisioning.md` | Sequence diagram — device first-boot provisioning |
+| `sequence-alert-delivery.md` | Sequence diagram — earthquake detection → mobile alert |
+| `sequence-triangulation.md` | Sequence diagram — multi-node triangulation flow |
diff --git a/docs/diagrams/c4-context.md b/docs/diagrams/c4-context.md
new file mode 100644
index 0000000..9f0e1ac
--- /dev/null
+++ b/docs/diagrams/c4-context.md
@@ -0,0 +1,73 @@
+# C4 Context Diagram — QuakeGuard
+
+> Level 1 (System Context): shows the QuakeGuard system and the external actors/systems that interact with it.
+
+```mermaid
+C4Context
+ title QuakeGuard — System Context Diagram
+
+ Person(user, "End User", "Receives earthquake alerts on mobile device")
+ Person(maintainer, "Maintainer", "Deploys, configures, and monitors the system")
+
+ System(quakeguard, "QuakeGuard System", "Distributed IoT EEW platform: edge sensors, backend, mobile app")
+
+ System_Ext(hivemq, "HiveMQ Cloud", "Managed MQTT broker (TLS, port 8883)")
+ System_Ext(ollama, "Ollama (Host)", "Local LLM inference engine (Llama 3.2)")
+ System_Ext(cloudflare, "Cloudflare Tunnel", "HTTPS tunnel for remote control plane access")
+ System_Ext(expo, "Expo Push Service", "Delivers push notifications to iOS/Android")
+
+ Rel(user, quakeguard, "Receives alerts, views sensor map", "WebSocket / Push Notification")
+ Rel(maintainer, quakeguard, "Deploys stack, flashes firmware", "Docker / PlatformIO / SSH")
+ Rel(quakeguard, hivemq, "Publishes/subscribes telemetry", "MQTT over TLS")
+ Rel(quakeguard, ollama, "Requests AI emergency reports", "HTTP (localhost)")
+ Rel(quakeguard, cloudflare, "Exposes control plane", "HTTPS tunnel")
+ Rel(quakeguard, expo, "Sends push notifications", "HTTPS")
+```
+
+## Container-Level Breakdown
+
+```mermaid
+C4Container
+ title QuakeGuard — Container Diagram
+
+ Person(user, "End User")
+
+ System_Boundary(edge, "IoT Edge Layer") {
+ Container(esp32, "ESP32-C3 Node", "C++/FreeRTOS", "STA/LTA detection, ECDSA signing, MQTT publish")
+ Container(adxl, "ADXL345", "I2C Sensor", "3-axis accelerometer @ 100 Hz")
+ Container(gnss, "NEO-6M GNSS", "UART", "GPS coordinates + PPS time sync")
+ }
+
+ System_Boundary(backend, "Backend Layer (Docker)") {
+ Container(api, "FastAPI Gateway", "Python 3.11", "REST API, ECDSA verification, WebSocket")
+ Container(worker, "Background Worker", "Python", "Magnitude calc, alert engine, triangulation")
+ Container(ai_worker, "AI Report Worker", "Python", "Consumes ai_report_queue, generates reports via Ollama")
+ Container(mqtt_bridge, "MQTT Bridge", "Python/Paho", "Subscribes to HiveMQ, forwards to HTTP pipeline")
+ ContainerDb(postgres, "PostgreSQL + PostGIS", "TimescaleDB", "Sensors, readings, zones, alerts")
+ ContainerDb(redis, "Redis", "Streams + Pub/Sub", "Ingestion queue, alert broadcast, zone cache")
+ }
+
+ System_Boundary(mobile, "Mobile Layer") {
+ Container(app, "React Native App", "Expo/TypeScript", "Dashboard, sensor map, alert feed")
+ }
+
+ System_Ext(hivemq, "HiveMQ Cloud")
+ System_Ext(ollama, "Ollama (Host)")
+
+ Rel(esp32, hivemq, "MQTT publish", "TLS 8883")
+ Rel(mqtt_bridge, hivemq, "MQTT subscribe", "TLS 8883")
+ Rel(mqtt_bridge, api, "POST /readings/", "HTTP")
+ Rel(esp32, api, "POST /devices/register", "HTTP/HTTPS")
+ Rel(api, redis, "XADD readings:stream")
+ Rel(worker, redis, "XREADGROUP")
+ Rel(worker, postgres, "INSERT readings, alerts")
+ Rel(worker, redis, "PUBLISH quake_alerts")
+ Rel(ai_worker, redis, "BRPOP ai_report_queue")
+ Rel(ai_worker, ollama, "POST /api/generate", "HTTP")
+ Rel(ai_worker, redis, "PUBLISH ai_reports")
+ Rel(api, app, "WebSocket broadcast", "WSS")
+ Rel(app, api, "REST queries", "HTTPS")
+ Rel(user, app, "Views alerts")
+ Rel(adxl, esp32, "I2C data")
+ Rel(gnss, esp32, "UART + PPS")
+```
diff --git a/docs/diagrams/sequence-alert-delivery.md b/docs/diagrams/sequence-alert-delivery.md
new file mode 100644
index 0000000..4ccb0b8
--- /dev/null
+++ b/docs/diagrams/sequence-alert-delivery.md
@@ -0,0 +1,71 @@
+# Sequence Diagram — Alert Delivery (Earthquake Detection → Mobile Alert)
+
+```mermaid
+sequenceDiagram
+ participant ADXL as ADXL345 Sensor
+ participant ESP as ESP32-C3 Node
+ participant MQTT as HiveMQ Cloud
+ participant Bridge as MQTT Bridge
+ participant API as FastAPI Gateway
+ participant Redis as Redis
+ participant Worker as Background Worker
+ participant DB as PostgreSQL
+ participant AI as AI Report Worker
+ participant Ollama as Ollama LLM
+ participant WS as WebSocket
+ participant App as Mobile App
+ participant User as End User
+
+ ADXL->>ESP: Acceleration data (100 Hz, I2C)
+ ESP->>ESP: HPF → STA/LTA detection
+ Note over ESP: STA/LTA ratio > 1.8 → TRIGGER
+
+ ESP->>ESP: ECDSA sign (value:timestamp)
+ ESP->>MQTT: PUBLISH quakeguard/telemetry
{value, sensor_id, timestamp, signature}
+
+ MQTT->>Bridge: Deliver message (TLS)
+ Bridge->>API: POST /readings/
X-API-Key header
+
+ API->>API: Validate API Key
+ API->>API: Verify ECDSA signature
+ API->>API: Check anti-replay (300s window)
+ API->>API: Rate limit check (50 req/s/IP)
+
+ API->>Redis: XADD readings:stream
+
+ Worker->>Redis: XREADGROUP (batch)
+ Worker->>Worker: Calculate magnitude
M = log10(PGA/scale) × K + B
+ Worker->>DB: INSERT INTO readings
+
+ alt M ≥ 4.5 (Alert threshold)
+ Worker->>Redis: Check cooldown lock
(alert_cooldown:geohash)
+
+ alt No active cooldown
+ Worker->>DB: INSERT INTO alerts
+ Worker->>Redis: SET cooldown lock (60s TTL)
+ Worker->>Redis: PUBLISH quake_alerts
{zone, magnitude, alert_id}
+
+ Redis->>WS: Broadcast to subscribers
+ WS->>App: QUAKE_ALERT message
+
+ App->>App: SOS haptic vibration
+ App->>App: Push notification
+ App->>User: 🚨 EARTHQUAKE ALERT
+
+ opt AI Reports enabled
+ Worker->>Redis: LPUSH ai_report_queue
+ AI->>Redis: BRPOP ai_report_queue
+ AI->>Ollama: POST /api/generate
(structured prompt + telemetry)
+ Ollama-->>AI: Emergency report text
+ AI->>DB: UPDATE EmergencyReport (COMPLETED)
+ AI->>Redis: PUBLISH ai_reports
+ Redis->>WS: Broadcast report
+ WS->>App: EMERGENCY_REPORT message
+ App->>User: 📋 AI Report banner
+ end
+
+ else Cooldown active
+ Note over Worker: Duplicate suppressed (60s window)
+ end
+ end
+```
diff --git a/docs/diagrams/sequence-provisioning.md b/docs/diagrams/sequence-provisioning.md
new file mode 100644
index 0000000..7825757
--- /dev/null
+++ b/docs/diagrams/sequence-provisioning.md
@@ -0,0 +1,46 @@
+# Sequence Diagram — Device Provisioning (First Boot)
+
+```mermaid
+sequenceDiagram
+ participant ESP as ESP32-C3 Node
+ participant WM as WiFiManager
+ participant User as User (Phone/Laptop)
+ participant API as FastAPI Gateway
+ participant DB as PostgreSQL + PostGIS
+ participant NVS as ESP32 NVS Storage
+
+ Note over ESP: Power-on → LED boot test (2x blink)
+ ESP->>ESP: Generate ECDSA key pair (if first boot)
+ ESP->>NVS: Store private key in NVS
+
+ ESP->>WM: Start captive portal "QuakeGuard-Setup"
+ User->>WM: Connect to AP, enter WiFi credentials
+ WM->>ESP: WiFi connected
+
+ ESP->>ESP: Check NVS for existing sensor_id
+ Note over ESP: sensor_id == 0 → Unregistered
+
+ ESP->>API: POST /devices/register
{public_key_hex, mac_address,
enrollment_token, latitude?, longitude?}
+
+ API->>API: Validate enrollment_token
+ API->>DB: Check for existing device (MAC or public key)
+
+ alt New Device
+ API->>DB: INSERT Sensor (public_key, mac, coordinates)
+ DB->>API: sensor_id assigned
+ else Existing Device
+ API->>DB: SELECT sensor_id WHERE mac_address = ...
+ DB->>API: existing sensor_id
+ end
+
+ alt Coordinates provided
+ API->>DB: ST_Contains query → assign zone
+ else No coordinates
+ API->>DB: Assign to "Unknown Region"
+ end
+
+ API-->>ESP: 200 OK {sensor_id: N}
+ ESP->>NVS: Store sensor_id in NVS
+ Note over ESP: Blue LED solid → Fully connected
+ ESP->>ESP: Start SensorTask + NetworkTask
+```
diff --git a/docs/diagrams/sequence-triangulation.md b/docs/diagrams/sequence-triangulation.md
new file mode 100644
index 0000000..090bcdf
--- /dev/null
+++ b/docs/diagrams/sequence-triangulation.md
@@ -0,0 +1,57 @@
+# Sequence Diagram — Multi-Node Triangulation
+
+```mermaid
+sequenceDiagram
+ participant N1 as Node A (ESP32)
+ participant N2 as Node B (ESP32)
+ participant N3 as Node C (ESP32)
+ participant API as FastAPI Gateway
+ participant Redis as Redis Stream
+ participant Worker as Background Worker
+ participant Tri as Triangulation Engine
+ participant DB as PostgreSQL + PostGIS
+ participant WS as WebSocket
+ participant App as Mobile App
+
+ Note over N1,N3: Earthquake P-wave propagates
outward from epicenter
+
+ N1->>API: POST /readings/ (t₁, lat₁, lon₁, sig₁)
+ Note over N1: First node triggered (closest to epicenter)
+
+ N2->>API: POST /readings/ (t₂, lat₂, lon₂, sig₂)
+ Note over N2: Second node triggered (Δt later)
+
+ N3->>API: POST /readings/ (t₃, lat₃, lon₃, sig₃)
+ Note over N3: Third node triggered (Δt later)
+
+ API->>Redis: XADD readings:stream (×3)
+
+ Worker->>Redis: XREADGROUP batch
+ Worker->>Worker: Detect concurrent triggers
(temporal window + spatial proximity)
+
+ alt ≥ 3 nodes triggered in window
+ Worker->>Tri: correlate_events(triggers[])
+
+ Tri->>Tri: Compute TDOA from timestamps
Δt₁₂ = t₂ - t₁
Δt₁₃ = t₃ - t₁
+
+ Tri->>Tri: Solve least-squares minimization
for epicenter (lat_e, lon_e)
+
+ Tri->>Tri: Estimate origin time (t₀)
+
+ Tri-->>Worker: TriangulationResult
{epicenter, origin_time, confidence}
+
+ Worker->>DB: INSERT alert (is_triangulated=True,
epicenter_lat, epicenter_lon)
+
+ Worker->>Redis: PUBLISH quake_alerts
{type: TRIANGULATED, epicenter, ETA}
+
+ Redis->>WS: Broadcast
+ WS->>App: TRIANGULATED alert
+
+ App->>App: Calculate ETA from user GPS
to epicenter (wave speed ~6 km/s)
+
+ App->>App: Show EarlyWarningBanner
with countdown timer
+
+ else < 3 nodes (single-node detection)
+ Worker->>Worker: Standard single-node alert flow
+ end
+```
diff --git a/docs/web/assets/quakeguard/github-banner.png b/docs/web/assets/quakeguard/github-banner.png
index 4689b60..40d0f28 100644
Binary files a/docs/web/assets/quakeguard/github-banner.png and b/docs/web/assets/quakeguard/github-banner.png differ
diff --git a/firmware/FLASHING.md b/firmware/FLASHING.md
new file mode 100644
index 0000000..53a21dc
--- /dev/null
+++ b/firmware/FLASHING.md
@@ -0,0 +1,114 @@
+# 🔧 QuakeGuard — Firmware Flashing Guide
+
+> Step-by-step instructions for flashing the QuakeGuard firmware onto an ESP32-C3 SuperMini, without requiring VS Code or PlatformIO IDE.
+
+## Prerequisites
+
+- **Python 3.8+** installed on your system
+- **PlatformIO Core (CLI):** Install via `pip install platformio`
+- **USB-C cable** (data-capable, not charge-only)
+- **QuakeGuard PCB** assembled, or ESP32-C3 SuperMini with ADXL345 wired on breadboard (see [PINOUT.md](PINOUT.md))
+
+## 1. Configure Environment Variables
+
+```bash
+cd firmware
+cp esp32_config.env.example esp32_config.env
+```
+
+Edit `esp32_config.env` with your specific values:
+- **WiFi credentials** (`WIFI_SSID`, `WIFI_PASS`)
+- **Backend address** (`SERVER_HOST`, `SERVER_PORT`)
+- **MQTT broker** (`MQTT_BROKER_HOST`, credentials)
+- **Enrollment token** (`ENROLLMENT_TOKEN`) — must match the backend `.env`
+
+> ⚠️ The build will fail with `#error` if `ENROLLMENT_TOKEN`, `MQTT_BROKER_HOST`, `MQTT_USERNAME`, or `MQTT_PASSWORD` are missing. This is intentional fail-fast behavior.
+
+## 2. Connect the ESP32-C3
+
+1. Connect the ESP32-C3 SuperMini via USB-C to your computer
+2. Identify the serial port:
+ - **Linux:** `/dev/ttyACM0` (default)
+ - **macOS:** `/dev/cu.usbmodem*`
+ - **Windows:** `COM3` or similar (check Device Manager)
+
+3. If needed, update the port in `platformio.ini`:
+ ```ini
+ upload_port = /dev/ttyACM0
+ monitor_port = /dev/ttyACM0
+ ```
+
+## 3. Build and Flash
+
+```bash
+cd firmware
+
+# Build only (compile without flashing)
+pio run
+
+# Build and flash to the connected device
+pio run --target upload
+
+# Open serial monitor after flashing
+pio device monitor
+```
+
+### SuperMini Boot Mode
+
+The ESP32-C3 SuperMini may require manual boot mode entry for the first flash:
+
+1. Hold the **BOOT** button
+2. Press and release the **RESET** button
+3. Release the **BOOT** button
+4. Run `pio run --target upload`
+
+Subsequent flashes should work without manual intervention (the `--before=no_reset --after=hard_reset` flags in `platformio.ini` handle this).
+
+## 4. First Boot Sequence
+
+On first power-up after a successful flash, the device will:
+
+1. 🔵🔴 **LED boot test:** Both LEDs blink 2x to verify wiring
+2. 📡 **WiFi captive portal:** Opens `QuakeGuard-Setup` AP (180s timeout)
+3. 🌐 **Connect to WiFi** and auto-provision with the backend
+4. 🔑 **Generate ECDSA key pair** (stored in NVS, never leaves the device)
+5. 📋 **Register with backend** via `POST /devices/register`
+6. 🔵 **Solid blue LED** = fully connected and transmitting
+
+### Serial Monitor Output (Successful Boot)
+
+```
+[BOOT] QuakeGuard v2.0.2
+[SEC] Generating New ECDSA Key Pair...
+[SEC] Keys Generated.
+[BOOT] Device UNREGISTERED. Entering Provisioning Mode...
+[NET] WiFi Connected.
+[PROV] Starting Device Handshake...
+[PROV] SUCCESS! Assigned Sensor ID: 1
+[SENSOR] Task Active. Stabilizing and filling buffers...
+[SYS] System Running.
+```
+
+## 5. Troubleshooting
+
+| Symptom | Cause | Fix |
+|---------|-------|-----|
+| Build fails with `#error ENROLLMENT_TOKEN` | Missing env variable | Check `esp32_config.env` |
+| `Upload failed: No serial port` | Wrong port or cable | Try different USB-C cable (data-capable) |
+| 🔴 Solid red LED after boot | ADXL345 not detected | Check J3 wiring: SDA→GPIO7, SCL→GPIO8, VCC→3.3V |
+| 🔵 Double blink | WiFi not connecting | Reconnect via `QuakeGuard-Setup` captive portal |
+| `[PROV] Registration Failed. HTTP Code: -1` | Backend unreachable | Verify `SERVER_HOST` is correct and backend is running |
+| `errno 118` / connection refused | DNS/subnet issue | Use local IP (e.g. `192.168.1.x`), not hostname |
+
+## Optional: GNSS Module
+
+To enable the optional GNSS module (NEO-6M), uncomment in `esp32_config.env`:
+
+```env
+GNSS_ENABLED=1
+GPS_SERIAL_RX_PIN=5
+GPS_SERIAL_TX_PIN=4
+GPS_SERIAL_BAUD=9600
+```
+
+See [PINOUT.md](PINOUT.md) for connector wiring (J4).
diff --git a/firmware/PINOUT.md b/firmware/PINOUT.md
new file mode 100644
index 0000000..ff58b2e
--- /dev/null
+++ b/firmware/PINOUT.md
@@ -0,0 +1,81 @@
+# 🔌 QuakeGuard — Firmware Pinout Reference
+
+> **PCB Revision:** v2.0.0 (JLCPCB)
+> **MCU:** ESP32-C3 SuperMini
+
+## GPIO Mapping
+
+| GPIO | Function | Component | PCB Connector | Notes |
+|------|----------|-----------|---------------|-------|
+| 7 | I2C SDA | ADXL345 | J3-3 (SDA) | 100 kHz clock, internal pull-up |
+| 8 | I2C SCL | ADXL345 | J3-4 (SCL) | 100 kHz clock, internal pull-up |
+| 10 | LED Blue (PWM) | Status LED | D2 (Blue) | Connection state indicator, via R2 (330Ω) |
+| 3 | LED Red (Digital) | Alert LED | D3 (Red) | Earthquake detected indicator, via R3 (330Ω) |
+| 5 | UART1 RX | NEO-6M GNSS TX | J4-3 (GNSS TX) | Optional: compile with `GNSS_ENABLED=1` |
+| 4 | UART1 TX | NEO-6M GNSS RX | J4-4 (GNSS RX) | Optional: compile with `GNSS_ENABLED=1` |
+| 2 | PPS Interrupt | NEO-6M PPS | J4-5 (PPS) | Optional: 1PPS discipline for ms-accuracy |
+| USB | CDC Serial | Host Bridge | USB-C | Telemetry fallback + debug monitor |
+
+## I2C Bus
+
+| Address | Device | Purpose |
+|---------|--------|---------|
+| `0x53` | ADXL345 | Primary accelerometer address |
+| `0x1D` | ADXL345 | Alternate address (ALT pin HIGH) |
+
+## LED Behavior
+
+| Pattern | Meaning |
+|---------|---------|
+| 🔵 Double blink (100ms on/off) | WiFi disconnected |
+| 🔵 Single blink (200ms on/off) | WiFi OK, MQTT disconnected |
+| 🔵 Solid (PWM ~40%) | Fully connected |
+| 🔴 Solid | Sensor hardware fault (ADXL345 init failed) |
+| 🔴 3-second pulse | Earthquake event detected |
+| 🔵🔴 2x alternating blink | Boot self-test (wiring verification) |
+| 🔴 Rapid 3x blink | ADXL345 I2C retry (during init) |
+
+## Power
+
+| Pin | Source | Notes |
+|-----|--------|-------|
+| 3V3 | ESP32-C3 LDO | Powers ADXL345 (J3-1) and GNSS (J4-1) |
+| GND | Common ground | Shared across all connectors |
+| 5V (USB) | USB-C input | Powers the ESP32-C3 SuperMini |
+
+## PCB Connector Pinout
+
+### J1 — ESP32-C3 Left Header (8-pin)
+Directly soldered to the ESP32-C3 SuperMini left row.
+
+### J2 — ESP32-C3 Right Header (8-pin)
+Directly soldered to the ESP32-C3 SuperMini right row.
+
+### J3 — ADXL345 Accelerometer (8-pin)
+| Pin | Signal | Connected To |
+|-----|--------|-------------|
+| 1 | VCC (3.3V) | ESP32 3V3 |
+| 2 | GND | Common GND |
+| 3 | SDA | GPIO 7 |
+| 4 | SCL | GPIO 8 |
+| 5-8 | NC | Not connected |
+
+### J4 — GNSS Module NEO-6M (5-pin)
+| Pin | Signal | Connected To |
+|-----|--------|-------------|
+| 1 | VCC (3.3V) | ESP32 3V3 |
+| 2 | GND | Common GND |
+| 3 | TX (GNSS → ESP) | GPIO 5 (UART1 RX) |
+| 4 | RX (ESP → GNSS) | GPIO 4 (UART1 TX) |
+| 5 | PPS | GPIO 2 |
+
+## Passive Components
+
+| Ref | Value | Purpose |
+|-----|-------|---------|
+| R1, R2, R3 | 330Ω | Current limiting for LEDs D1, D2, D3 |
+| C1, C3 | 10µF | Decoupling capacitors (power supply) |
+| C2 | 0.1µF | High-frequency bypass capacitor |
+| D1 | Green LED | Reserved (future use) |
+| D2 | Blue LED | Connection status indicator |
+| D3 | Red LED | Earthquake alert indicator |
diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp
index 71e14d9..06dc3ae 100644
--- a/firmware/src/main.cpp
+++ b/firmware/src/main.cpp
@@ -1,6 +1,6 @@
/**
* Project: QuakeGuard - Professional Seismic Node
- * Version: 1.3.0-GNSS-PPS
+ * Version: 2.0.2
* Target Hardware: ESP32-C3 SuperMini + ADXL345 + NEO-6M (JLCPCB)
* Author: GiZano
*
@@ -36,6 +36,11 @@
#include
#include
+// --------------------------------------------------------------------------
+// FIRMWARE VERSION (printed at boot for field identification)
+// --------------------------------------------------------------------------
+#define FIRMWARE_VERSION "2.0.2"
+
// --------------------------------------------------------------------------
// HARDWARE & SERVER CONFIGURATION
// --------------------------------------------------------------------------
@@ -605,7 +610,7 @@ void setup() {
digitalWrite(LED_RED_PIN, LOW);
ledBootTest(); // verify wiring: 2x blink both LEDs
- Serial.println("\n\n[BOOT] QuakeGuard v2.0.0 GNSS+PPS+LED");
+ Serial.printf("\n\n[BOOT] QuakeGuard v%s\n", FIRMWARE_VERSION);
crypto().init();
diff --git a/hardware/QuakeGuard_PCB/output/BOM.csv b/hardware/QuakeGuard_PCB/output/BOM.csv
index c79b3dc..db58d22 100644
--- a/hardware/QuakeGuard_PCB/output/BOM.csv
+++ b/hardware/QuakeGuard_PCB/output/BOM.csv
@@ -1,14 +1,17 @@
-"Refs","Value","Footprint","Qty","DNP"
-"C1","10µF","Capacitor_THT:CP_Radial_D5.0mm_P2.50mm","1",""
-"C2","0.1µF","Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm","1",""
-"C3","10µF","Capacitor_THT:CP_Radial_D5.0mm_P2.50mm","1",""
-"D1","Green","LED_THT:LED_D5.0mm","1",""
-"D2","Blue","LED_THT:LED_D5.0mm","1",""
-"D3","Red","LED_THT:LED_D5.0mm","1",""
-"J1","ESP32-C3[L]","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1",""
-"J2","ESP32-C3[R]","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1",""
-"J3","ADXL345","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1",""
-"J4","NEO-6M","Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Vertical","1",""
-"R1","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1",""
-"R2","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1",""
-"R3","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1",""
\ No newline at end of file
+"Refs","Value","Footprint","Qty","DNP","Manufacturer","Part Number","Unit Cost (EUR)","Distributor","Link"
+"C1","10µF","Capacitor_THT:CP_Radial_D5.0mm_P2.50mm","1","","Panasonic / Generic","ECA-1HM100I","0.15","LCSC / DigiKey","https://www.lcsc.com/search?q=10uF+radial+electrolytic"
+"C2","0.1µF","Capacitor_THT:C_Disc_D5.0mm_W2.5mm_P5.00mm","1","","Murata / Generic","RD15N104J1HL2L","0.05","LCSC / DigiKey","https://www.lcsc.com/search?q=100nF+ceramic+disc"
+"C3","10µF","Capacitor_THT:CP_Radial_D5.0mm_P2.50mm","1","","Panasonic / Generic","ECA-1HM100I","0.15","LCSC / DigiKey","https://www.lcsc.com/search?q=10uF+radial+electrolytic"
+"D1","Green","LED_THT:LED_D5.0mm","1","","Generic","LED 5mm Green","0.05","LCSC / Amazon","https://www.lcsc.com/search?q=5mm+green+LED"
+"D2","Blue","LED_THT:LED_D5.0mm","1","","Generic","LED 5mm Blue","0.05","LCSC / Amazon","https://www.lcsc.com/search?q=5mm+blue+LED"
+"D3","Red","LED_THT:LED_D5.0mm","1","","Generic","LED 5mm Red","0.05","LCSC / Amazon","https://www.lcsc.com/search?q=5mm+red+LED"
+"J1","ESP32-C3[L]","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1","","Generic","Pin Socket 1x08 2.54mm","0.20","LCSC / Amazon","https://www.lcsc.com/search?q=pin+socket+1x08+2.54mm"
+"J2","ESP32-C3[R]","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1","","Generic","Pin Socket 1x08 2.54mm","0.20","LCSC / Amazon","https://www.lcsc.com/search?q=pin+socket+1x08+2.54mm"
+"J3","ADXL345","Connector_PinSocket_2.54mm:PinSocket_1x08_P2.54mm_Vertical","1","","Generic","Pin Socket 1x08 2.54mm","0.20","LCSC / Amazon","https://www.lcsc.com/search?q=pin+socket+1x08+2.54mm"
+"J4","NEO-6M","Connector_PinSocket_2.54mm:PinSocket_1x05_P2.54mm_Vertical","1","","Generic","Pin Socket 1x05 2.54mm","0.15","LCSC / Amazon","https://www.lcsc.com/search?q=pin+socket+1x05+2.54mm"
+"R1","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","","Generic","MF 1/4W 330R","0.02","LCSC / DigiKey","https://www.lcsc.com/search?q=330+ohm+1/4W+THT"
+"R2","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","","Generic","MF 1/4W 330R","0.02","LCSC / DigiKey","https://www.lcsc.com/search?q=330+ohm+1/4W+THT"
+"R3","330Ω","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","","Generic","MF 1/4W 330R","0.02","LCSC / DigiKey","https://www.lcsc.com/search?q=330+ohm+1/4W+THT"
+"--","ESP32-C3 SuperMini","Module (socketable)","1","","Espressif / AliExpress","ESP32-C3 SuperMini","3.50","AliExpress / Amazon","https://www.aliexpress.com/w/wholesale-esp32-c3-supermini.html"
+"--","ADXL345 Breakout","Module (socketable)","1","","Analog Devices / Generic","GY-291 ADXL345","2.00","AliExpress / Amazon","https://www.aliexpress.com/w/wholesale-ADXL345-module.html"
+"--","NEO-6M GNSS Module","Module (socketable, optional)","1","","u-blox / Generic","GY-NEO6MV2","4.50","AliExpress / Amazon","https://www.aliexpress.com/w/wholesale-neo-6m-gps.html"
\ No newline at end of file
diff --git a/mobile/PRIVACY_POLICY.md b/mobile/PRIVACY_POLICY.md
new file mode 100644
index 0000000..3e56460
--- /dev/null
+++ b/mobile/PRIVACY_POLICY.md
@@ -0,0 +1,84 @@
+# QuakeGuard — Privacy Policy
+
+**Last updated:** September 3, 2026
+**Effective for:** QuakeGuard Mobile App (iOS / Android)
+
+## Overview
+
+QuakeGuard is an open-source earthquake early warning system. This privacy policy explains how the QuakeGuard mobile application collects, uses, and protects your information.
+
+## Data We Collect
+
+### 1. Device Location (Optional)
+- **What:** Approximate GPS coordinates
+- **Why:** To determine your seismic zone and calculate earthquake wave arrival time (ETA)
+- **When:** Only when you explicitly enable "Detect my zone via GPS" in Settings
+- **Storage:** Transmitted to the QuakeGuard backend for zone assignment; not stored persistently on the server beyond the session
+
+### 2. Push Notification Token
+- **What:** Device push notification token (Expo Push Token)
+- **Why:** To deliver earthquake alerts even when the app is in the background
+- **When:** Only when you enable notifications in Settings
+- **Storage:** Used by the push notification service (Expo) to route alerts; not shared with third parties
+
+### 3. App Preferences
+- **What:** User settings (notification preferences, offline mode toggle, display preferences)
+- **Why:** To remember your configuration between app sessions
+- **Storage:** Stored locally on your device only (via Zustand persistence); never transmitted to any server
+
+## Data We Do NOT Collect
+
+- ❌ Personal identification (name, email, phone number)
+- ❌ Contacts, photos, or files
+- ❌ Browsing history or usage analytics
+- ❌ Advertising identifiers
+- ❌ Health or biometric data
+
+## Data Transmission
+
+All communication between the app and the QuakeGuard backend uses:
+- **WebSocket (WSS)** for real-time alerts — authenticated via `MOBILE_WS_TOKEN`
+- **HTTPS** for API calls — authenticated via `IOT_API_KEY`
+
+No telemetry data is sent to third-party analytics services.
+
+## Third-Party Services
+
+| Service | Purpose | Data Shared |
+|---------|---------|-------------|
+| Expo | Push notifications delivery | Device push token |
+| React Native Maps | Sensor network visualization | None (map tiles only) |
+
+## Data Retention
+
+- **Location data:** Not retained beyond the active session
+- **Alert history:** Stored in-memory only (last 10 alerts); cleared on app restart
+- **Preferences:** Stored locally on-device until the user clears app data
+
+## Open Source Transparency
+
+QuakeGuard is fully open source. You can audit exactly what data the app collects by reviewing the source code:
+- **Repository:** [github.com/GiZano/QuakeGuard](https://github.com/GiZano/QuakeGuard)
+- **Mobile source:** [`mobile/`](https://github.com/GiZano/QuakeGuard/tree/main/mobile)
+- **License:** AGPL-3.0
+
+## Children's Privacy
+
+QuakeGuard does not knowingly collect data from children under 13. The app is a safety tool and does not contain age-restricted content.
+
+## Your Rights
+
+You can:
+- **Disable location access** at any time via your device's system settings
+- **Disable notifications** via the app's Settings screen or system settings
+- **Delete all local data** by uninstalling the app
+
+## Changes to This Policy
+
+We may update this privacy policy to reflect changes in the app. Updates will be posted in this file in the repository and noted in the CHANGELOG.
+
+## Contact
+
+For privacy-related questions or concerns:
+- **Email:** gizano.dev@gmail.com
+- **Repository:** [github.com/GiZano/QuakeGuard](https://github.com/GiZano/QuakeGuard)