A fully containerized, end-to-end IoT system: ESP32 sensor node → BLE GATT → Raspberry Pi gateway → Mosquitto MQTT with mTLS → Node-RED → InfluxDB → Grafana. Every layer is secured, auto-provisioned, and production-grade.
This project implements a complete IoT data pipeline that bridges embedded hardware and cloud-style infrastructure. The goal was to build a system that a real-world deployment could use — not just a demo — with mutual TLS authentication on every service boundary, containerized infrastructure that spins up from a single command, and auto-provisioned Grafana dashboards that appear on first boot.
The data flows from a DHT22 temperature/humidity sensor wired to an ESP32, transmitted over Bluetooth Low Energy using standard GATT Environmental Sensing profiles, received and forwarded by a Python container running on a Raspberry Pi 5, brokered through Mosquitto with TLS 1.3 and mutual certificate authentication, processed and reformatted by Node-RED, stored in InfluxDB, and finally visualised in Grafana — all without a single plaintext connection in the chain.
Sensor reading, BLE communication, and logging run as independent FreeRTOS tasks communicating via queues.
Every service presents a unique CA-signed certificate. Identity is mapped from the certificate Common Name — no passwords.
Datasources, dashboards, and service credentials are initialized on first startup from declarative YAML files.
The BLE container automatically re-establishes connections on drop without requiring a container restart.
A key focus of this project was demonstrating production-grade IoT security on commodity hardware. Rather than relying on username/password authentication, every service boundary uses certificate-based mutual TLS authentication against a self-signed root CA.
| LAYER | SECURITY MEASURE |
|---|---|
| Transport | TLS 1.3 mandatory — plaintext connections rejected at broker |
| Authentication | Certificate-based mTLS; identity from certificate CN |
| InfluxDB access | Read-only token for Grafana; write token restricted to Node-RED |
| Credentials | Grafana admin password via Podman secrets — never plaintext on disk |
| Network exposure | Backend services bound to localhost; accessible only via SSH tunneling |