This project itself, it is not completed, but more a prof of concept for using Prometheus and Grafana to store and visualize data coming from any ESP32 device.
Why Prometheus and Grafana?Over the last 6 years, Prometheus and Grafana become the standard for monitoring any cloud solution, from small K3S clusters with a single node to the biggest platforms with millions of nodes to monitor.Also, it's very simple to use.At home, I have a small K3S cluster running Raspberry on 6 nodes, and there I have already a Prometheus and Grafana running. Only thing I need to do is to send to Prometheus every data coming from sensors
HardwareStill work in progress, I will make a 3d printed box and will use a M5stamp powered by batteries, however at the moment it's made by:
- M5Stack as core.
- Light sensor, to see the amount of ambient light on the room, connected to Port B
- Dht11 temperature and humidity sensor, connected to Port A
- MH-Z19 Co2 sensor, connected to Port C
Core of this project is the PrometheusArduino Library make by the Grafana team.In my code, I initialize 6 Time series (ts1…ts6) dedicated to 6 different metrics:
// Create a write request for 6 series.
WriteRequest req(6,1024);
TimeSeries ts1(5, "uptime_milliseconds_total", "{job=\"esp32-test\",host=\"esp32\"}"); // Default in the example provided
TimeSeries ts2(5, "heap_free_bytes", "{job=\"esp32-test\",host=\"esp32\"}"); // Default in the example provided
TimeSeries ts3(5, "room_temp", "{job=\"esp32-test\"}"); // from dht11
TimeSeries ts4(5, "room_hum", "{job=\"esp32-test\"}"); // from dht11
TimeSeries ts5(5, "co2_value", "{job=\"esp32-test\"}"); // from mhz19
TimeSeries ts6(5, "light_value", "{job=\"esp32-test\"}"); // from Light sensor
from Documentation:
TimeSeries(batchSize, metricName, labelset)
batchSize = default 5, allow sending sample in batches
metricName = name used in Prometheus to store and retrieve the metrics
labelset = set of labels added to the metrics. In Prometheus, labels are very helpfully to give more details on the sample. Example: if I have several sensors of the same kind sending same metric (room_temperatur), I can map the different samples with a specific labels, like {room = "bedroom"} or {room = "living_room"}
On the loop, I just populate the time series by reading values from different sensors and I send via Wi-Fi to my Prometheus server.
There is not yet any energy saving measure and the loop happens every 15 seconds, but this is just for testing: I don't really need to have this frequency of samplings, every 5 minutes is enough.
Full codeTemp.ino
#include "config.h"
#include <PromLokiTransport.h>
#include <PrometheusArduino.h>
#include "DHTesp.h"
#include "MHZ19.h"
DHTesp dht;
ComfortState cf;
int dhtPin = 22;
MHZ19 myMHZ19;
PromLokiTransport transport;
PromClient client(transport);
// Create a write request for 6 series.
WriteRequest req(6,1024);
// Define a TimeSeries which can hold up to 5 samples, has a name of `uptime_milliseconds`
// Using job="esp32-test" as label. Different hosts requires different job name to keep separation
TimeSeries ts1(5, "uptime_milliseconds_total", "{job=\"esp32-test\",host=\"esp32\"}");
// Define a TimeSeries which can hold up to 5 samples, has a name of `heap_free_bytes`
TimeSeries ts2(5, "heap_free_bytes", "{job=\"esp32-test\",host=\"esp32\"}");
TimeSeries ts3(5, "room_temp", "{job=\"esp32-test\"}");
TimeSeries ts4(5, "room_hum", "{job=\"esp32-test\"}");
TimeSeries ts5(5, "co2_value", "{job=\"esp32-test\"}");
TimeSeries ts6(5, "light_value", "{job=\"esp32-test\"}");
int loopCounter = 0;
uint16_t analogRead_value = 0;
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, 16, 17);
myMHZ19.begin(Serial2);
myMHZ19.autoCalibration();
dht.setup(dhtPin, DHTesp::DHT11);
pinMode(26, INPUT);
// Wait 5s for serial connection or continue without it
// some boards like the esp32 will run whether or not the
// serial port is connected, others like the MKR boards will wait
// for ever if you don't break the loop.
uint8_t serialTimeout;
while (!Serial && serialTimeout < 50) {
delay(100);
serialTimeout++;
}
Serial.println("Starting");
// Configure and start the transport layer
transport.setWifiSsid(WIFI_SSID);
transport.setWifiPass(WIFI_PASSWORD);
// transport.setDebug(Serial); // Remove this line to disable debug logging of the client.
if (!transport.begin()) {
Serial.println(transport.errmsg);
while (true) {};
}
// Configure the client
client.setUrl(URL);
client.setPath((char*)PATH);
client.setPort(PORT);
client.setDebug(Serial); // Remove this line to disable debug logging of the client.
if (!client.begin()) {
Serial.println(client.errmsg);
while (true) {};
}
// Add our TimeSeries to the WriteRequest
req.addTimeSeries(ts1);
req.addTimeSeries(ts2);
req.addTimeSeries(ts3);
req.addTimeSeries(ts4);
req.addTimeSeries(ts5);
req.addTimeSeries(ts6);
// req.setDebug(Serial); // Remove this line to disable debug logging of the write request serialization and compression.
Serial.print("Free Mem After Setup: ");
Serial.println(freeMemory());
};
void loop() {
int64_t time;
int CO2;
time = transport.getTimeMillis();
Serial.println(time);
CO2 = myMHZ19.getCO2();
analogRead_value = analogRead(36);
Serial.print("CO2 (ppm): ");
Serial.println(CO2);
TempAndHumidity newValues = dht.getTempAndHumidity();
if (!ts1.addSample(time, millis())) {
Serial.println(ts1.errmsg);
}
if (!ts2.addSample(time, freeMemory())) {
Serial.println(ts2.errmsg);
}
ts3.addSample(time,newValues.temperature);
ts4.addSample(time,newValues.humidity);
ts5.addSample(time,CO2);
ts6.addSample(time,analogRead_value);
Serial.println(newValues.temperature);
loopCounter++;
if (loopCounter >= 1) {
// Send data
loopCounter = 0;
PromClient::SendResult res = client.send(req);
if (!res == PromClient::SendResult::SUCCESS) {
Serial.println(client.errmsg);
}
ts1.resetSamples();
ts2.resetSamples();
ts3.resetSamples();
ts4.resetSamples();
ts5.resetSamples();
ts6.resetSamples();
}
delay(15000);
};
config.h
#define WIFI_SSID "<YOUR SSID>"
#define WIFI_PASSWORD "<YOUR PASSWORD>"
#define URL "<YOUR PTOMETHEUS HOST>" // "192.168.0.1" or "prometheus.yourdomain.local" No http or https, No port or path
#define PATH "/api/v1/write" // should not change
#define PORT 80 //usually is 9090
Comments