Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This repository contains the firmware source code and configuration needed to fl
- [Build and Flash](#build-and-flash-platformio)
- [Configuration](#configuration)
- [MQTT + Home Assistant](#mqtt--home-assistant)
- [Prometheus Metrics](#prometheus-metrics)
- [Contributing](#contributing)
- [AI Assistance](#ai-assistance)
- [License and Commercial Use](#license-and-commercial-use)
Expand Down Expand Up @@ -207,6 +208,22 @@ MQTT stays idle until configured and enabled.

![Home Assistant dashboard](docs/assets/ha-dashboard.jpg)

## Prometheus Metrics
The device exposes a Prometheus-compatible endpoint at `GET /metrics` with ~60 metrics
covering all sensors, derived values, fan control, network status, and ESP32 system
diagnostics (heap, PSRAM, chip temperature, flash, FreeRTOS).

Add to your `prometheus.yml`:
```yaml
scrape_configs:
- job_name: "project_aura"
scrape_interval: 30s
static_configs:
- targets: ["aura.local:80"]
```

Full metric reference and Grafana tips: [`docs/prometheus/README.md`](docs/prometheus/README.md).

## Contributing
Contributions are welcome! Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) for details on the process for submitting pull requests and the Contributor License Agreement (CLA).

Expand Down
200 changes: 200 additions & 0 deletions docs/prometheus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Prometheus Metrics Exporter

Project Aura exposes a Prometheus-compatible metrics endpoint at `GET /metrics`.
This allows scraping by Prometheus, VictoriaMetrics, Grafana Alloy, or any
OpenMetrics-compatible collector.

## Quick Start

Once your device is connected to Wi-Fi, metrics are available at:

```
http://aura.local/metrics
```

No configuration is needed on the device side. The endpoint is always active
when the web server is running.

## Prometheus Scrape Configuration

Add a scrape job to your `prometheus.yml`:

```yaml
scrape_configs:
- job_name: "project_aura"
scrape_interval: 30s
static_configs:
- targets: ["aura.local:80"]
labels:
location: "living_room"
```

If you have multiple Aura devices with custom hostnames, list them as separate
targets:

```yaml
scrape_configs:
- job_name: "project_aura"
scrape_interval: 30s
static_configs:
- targets:
- "aura-living.local:80"
- "aura-bedroom.local:80"
- "aura-office.local:80"
```

You can also use IP addresses instead of mDNS hostnames:

```yaml
- targets: ["192.168.1.42:80"]
```

### Recommended Scrape Interval

30 seconds is a good default. The endpoint responds in under 10 ms and uses
chunked transfer to keep memory usage low (~1.5 KB peak). Intervals as low as
15 seconds work fine without impacting device performance.

## Available Metrics

All metrics use the `aura_` prefix and follow
[Prometheus naming conventions](https://prometheus.io/docs/practices/naming/).

### Environmental Sensors

| Metric | Unit | Description |
|:---|:---|:---|
| `aura_temperature_celsius` | C | Ambient temperature |
| `aura_humidity_percent` | % | Relative humidity |
| `aura_pressure_hpa` | hPa | Atmospheric pressure |
| `aura_pm05_count_per_cm3` | #/cm3 | PM0.5 particle count |
| `aura_pm1_ugm3` | ug/m3 | PM1.0 mass concentration |
| `aura_pm25_ugm3` | ug/m3 | PM2.5 mass concentration |
| `aura_pm4_ugm3` | ug/m3 | PM4.0 mass concentration |
| `aura_pm10_ugm3` | ug/m3 | PM10 mass concentration |
| `aura_co2_ppm` | ppm | CO2 concentration |
| `aura_voc_index` | index | VOC index (Sensirion, 1-500) |
| `aura_nox_index` | index | NOx index (Sensirion, 1-500) |
| `aura_hcho_ppb` | ppb | Formaldehyde concentration |
| `aura_co_ppm` | ppm | Carbon monoxide (if sensor installed) |
| `aura_co_sensor_present` | bool | CO sensor module installed (0/1) |
| `aura_co_sensor_warmup` | bool | CO sensor in warmup phase (0/1) |

Metrics for sensors that are not connected or not yet valid are **omitted**
(not exported as zero). Prometheus treats absent metrics as stale.

### Derived Metrics

| Metric | Unit | Description |
|:---|:---|:---|
| `aura_dew_point_celsius` | C | Dew point (from temp + humidity) |
| `aura_absolute_humidity_gm3` | g/m3 | Absolute humidity |
| `aura_mold_risk_index` | index | Mold risk (0-10) |
| `aura_pressure_delta_3h_hpa` | hPa | Pressure change over 3 hours |
| `aura_pressure_delta_24h_hpa` | hPa | Pressure change over 24 hours |

### DAC / Fan Control

| Metric | Unit | Description |
|:---|:---|:---|
| `aura_dac_available` | bool | DAC hardware detected (0/1) |
| `aura_dac_running` | bool | Fan output active (0/1) |
| `aura_dac_faulted` | bool | Hardware fault detected (0/1) |
| `aura_dac_output_known` | bool | Output level valid (0/1) |
| `aura_dac_manual_override` | bool | Manual override active (0/1) |
| `aura_dac_auto_resume_blocked` | bool | Auto resume blocked (0/1) |
| `aura_dac_mode` | enum | 0 = manual, 1 = auto |
| `aura_dac_manual_step` | step | Manual speed step (1-100) |
| `aura_dac_timer_selected_seconds` | s | Timer duration |
| `aura_dac_timer_remaining_seconds` | s | Timer remaining |
| `aura_dac_output_millivolts` | mV | DAC output (0-10000) |
| `aura_dac_output_percent` | % | DAC output (0-100) |

### Network

| Metric | Unit | Description |
|:---|:---|:---|
| `aura_wifi_enabled` | bool | WiFi enabled (0/1) |
| `aura_wifi_rssi_dbm` | dBm | WiFi signal strength |
| `aura_mqtt_enabled` | bool | MQTT enabled (0/1) |
| `aura_mqtt_connected` | bool | MQTT connected (0/1) |

### System

| Metric | Unit | Description |
|:---|:---|:---|
| `aura_uptime_seconds` | s | Device uptime |
| `aura_heap_free_bytes` | bytes | Free heap memory |
| `aura_heap_min_free_bytes` | bytes | Minimum free heap since boot |
| `aura_heap_max_alloc_bytes` | bytes | Largest contiguous free block |
| `aura_heap_8bit_free_bytes` | bytes | Free 8-bit capable heap |
| `aura_heap_8bit_min_free_bytes` | bytes | Minimum free 8-bit heap |
| `aura_heap_8bit_largest_block_bytes` | bytes | Largest 8-bit free block |
| `aura_heap_internal_free_bytes` | bytes | Free internal SRAM |
| `aura_heap_internal_min_free_bytes` | bytes | Minimum free internal SRAM |
| `aura_heap_internal_largest_block_bytes` | bytes | Largest internal free block |
| `aura_psram_free_bytes` | bytes | Free PSRAM |
| `aura_psram_min_free_bytes` | bytes | Minimum free PSRAM |
| `aura_psram_max_alloc_bytes` | bytes | Largest PSRAM free block |
| `aura_cpu_frequency_mhz` | MHz | CPU clock frequency |
| `aura_chip_temperature_celsius` | C | Internal chip temperature |
| `aura_flash_size_bytes` | bytes | Total flash size |
| `aura_sketch_size_bytes` | bytes | Firmware image size |
| `aura_sketch_free_bytes` | bytes | Free space for OTA update |
| `aura_boot_count` | count | Boot counter |
| `aura_reset_reason` | enum | ESP reset reason code |
| `aura_freertos_task_count` | count | Number of FreeRTOS tasks |

### Build Info

```
aura_build_info{firmware="1.1.0",build_date="...",build_time="...",hostname="aura",device_name="Project Aura"} 1
```

## Example PromQL Queries

```promql
# Current CO2 level
aura_co2_ppm

# Average temperature over the last hour
avg_over_time(aura_temperature_celsius[1h])

# Free heap memory trend
aura_heap_free_bytes

# PM2.5 above WHO guideline (15 ug/m3)
aura_pm25_ugm3 > 15

# WiFi signal quality
aura_wifi_rssi_dbm

# Alert on low memory (below 20 KB)
aura_heap_internal_free_bytes < 20000
```

## Grafana

Add your Prometheus instance as a data source in Grafana. All `aura_*` metrics
will be available in the metric browser. A recommended dashboard layout:

- **Row 1:** Temperature, Humidity, Pressure, Dew Point
- **Row 2:** CO2, VOC, NOx, HCHO
- **Row 3:** PM0.5, PM1.0, PM2.5, PM4.0, PM10
- **Row 4:** Heap free, PSRAM free, Uptime, WiFi RSSI

## Validating the Output

You can validate the endpoint output with the Prometheus `promtool`:

```bash
curl -s http://aura.local/metrics | promtool check metrics
```

## Notes

- The endpoint returns HTTP 503 during OTA firmware uploads.
- Flash and sketch size metrics are cached at boot (they never change at runtime).
- The internal chip temperature reflects silicon temperature, not ambient. It
typically reads 4-6 C higher than ambient due to self-heating.
- RSSI is only exported when connected in station mode with a valid signal.
1 change: 1 addition & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ build_src_filter =
+<core/InitConfig.cpp>
+<core/Logger.cpp>
+<web/OtaDeferredRestart.cpp>
+<web/PrometheusExporter.cpp>
extra_scripts =
pre:test/prepend_mocks.py
3 changes: 3 additions & 0 deletions src/modules/NetworkManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "core/Logger.h"
#include "config/AppConfig.h"
#include "ui/ThemeManager.h"
#include "web/PrometheusExporter.h"

namespace {

Expand Down Expand Up @@ -107,6 +108,7 @@ void AuraNetworkManager::begin(StorageManager &storage) {
web_ctx_.mqtt_ui_open = &mqtt_ui_open_;
web_ctx_.theme_ui_open = &theme_ui_open_;
WebHandlersInit(&web_ctx_);
PrometheusExporterInit(&web_ctx_);
registerServerRoutes();

storage_->loadWiFiSettings(wifi_ssid_, wifi_pass_, wifi_enabled_);
Expand Down Expand Up @@ -212,6 +214,7 @@ void AuraNetworkManager::registerServerRoutes() {
server_.on("/api/events", HTTP_GET, events_handle_data);
server_.on("/api/settings", HTTP_POST, settings_handle_update);
server_.on("/api/ota", HTTP_POST, ota_handle_update, ota_handle_upload);
server_.on("/metrics", HTTP_GET, prometheus_handle_metrics);
server_.onNotFound(wifi_handle_not_found);
server_routes_registered_ = true;
}
Expand Down
Loading
Loading