Skip to content

Instantly share code, notes, and snippets.

@Tugzrida
Last active January 1, 2024 16:46
Show Gist options
  • Save Tugzrida/2c9fcf52571909cf60cbafc32413daf0 to your computer and use it in GitHub Desktop.
Save Tugzrida/2c9fcf52571909cf60cbafc32413daf0 to your computer and use it in GitHub Desktop.
Prometheus Smart Water Meter project for the ESP32

Prometheus Water Meter

This is a fairly involved process as it seems that no one currently makes a pre-made product that measures water usage from a household meter.

It took quite a while to set up due to WiFi issues and the flow rate code still needs some work as the output is semi-random at the moment.

If you get stuck anywhere I'm happy to answer questions provided this doesn't go viral 😂

Components:

Hardware:

Basically all that needs to happen is that the output of the pulse probe is connected between A GND and D7 on the ESP32.

I did this by cutting off the probe wire from the plug as supplied by Aquatrip, then soldering on an extension wire and sealing the joint in heatshrink.

The wire can then be run from the water meter to inside and into the ESP32.

Software:

The attached Arduino sketch can be uploaded to the board with the Arduino IDE, after following the DFRobot instructions to install the Beetle board.

Before uploading the sketch, you'll need to substitute your WiFi details, and also the timezone if you aren't in Sydney. The required format for the timezone is a bit convoluted, but there's a reference here.

Upon powering up, the board will briefly turn on the green led, then turn it back off until a WiFi connection is made. This specific board seems to have fairly poor reception so close proximity to an access point is necessary.

Once working, the board will be waiting for http connections on port 80 and will serve the litres used today and current flowrate in Prometheus text exposition format, along with a couple of debug headers.

Depending on how your water meter sends pulses, you may also need to change the volPerPulse variable to ensure an accurate reading. You can do this by checking the change in the reported water usage before and after filling up a bucket with a known capacity.

Once it's working, you can add a job to prometheus with something like this:

scrape_configs:
  - job_name: 'water'
    scrape_interval: 30s
    static_configs:
      - targets: ['ESP32_IP_ADDRESS']

Then onto Grafana dashboard design.

#include <WiFi.h>
const int pulseDebounce = 250; //50
const int flowRateTimeout = 20000;
const float volPerPulse = 0.5;
const float flowRatePerMs = 60000;
volatile unsigned int pulses = 0;
volatile unsigned long secondLastPulse = 0;
volatile unsigned long lastPulse = 0;
int lastResetDate = 0;
int newLineCount = 0;
WiFiServer promSrv(80);
void setup() {
pinMode(D9, OUTPUT);
digitalWrite(D9, LOW);
Serial.begin(9600);
Serial.print("Connecting to WiFi");
WiFi.begin("WIFI_SSID", "WIFI_PASSWORD");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("Connected as ");
Serial.println(WiFi.localIP());
configTzTime("AEST-10:00:00AEDT-11:00:00,M10.1.0/02:00:00,M4.1.0/03:00:00", "au.pool.ntp.org");
struct tm localTime;
getLocalTime(&localTime);
lastResetDate = localTime.tm_mday;
Serial.println(&localTime, "It's currently %A %d %B %Y %H:%M:%S %Z");
promSrv.begin();
pinMode(D7, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(D7), pulse, RISING);
Serial.println("Ready!");
digitalWrite(D9, HIGH);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Reconnecting to WiFi");
WiFi.begin();
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("Connected as ");
Serial.println(WiFi.localIP());
}
WiFiClient client = promSrv.available();
if (client) {
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n') {
newLineCount++;
if (newLineCount == 2) {
// Client has finished request, send response
struct tm localTime;
getLocalTime(&localTime);
if (localTime.tm_mday != lastResetDate) {
pulses = 0;
lastResetDate = localTime.tm_mday;
}
client.print("HTTP/1.1 200 OK\n");
client.print("Content-Type: text/plain; charset=UTF-8; version=0.0.4\n");
client.print("Access-Control-Allow-Origin: *\n");
client.print(&localTime, "X-DateTime: %A %d %B %Y %H:%M:%S %Z\n");
client.print("X-WiFi: ");
client.print(WiFi.SSID());
client.print(" (");
client.print(WiFi.BSSIDstr());
client.print(") at ");
client.print(WiFi.RSSI());
client.print("dBm\n");
client.print("Connection: close\n\n");
client.print("# HELP water_today_litres Total litres of water used since midnight.\n");
client.print("# TYPE water_today_litres counter\n");
client.print("water_today_litres ");
client.print(pulses * volPerPulse);
client.print("\n\n");
client.print("# HELP water_flowrate_lpm Current flowrate in litres per minute.\n");
client.print("# TYPE water_flowrate_lpm gauge\n");
client.print("water_flowrate_lpm ");
if ((millis() - lastPulse < flowRateTimeout) && secondLastPulse != lastPulse) {
client.print(volPerPulse / ((lastPulse - secondLastPulse) / flowRatePerMs));
} else {
client.print(0.0);
}
client.print("\n");
client.stop();
}
} else if (c != '\r') {
newLineCount = 0;
}
}
}
}
}
void pulse() {
if (millis() - lastPulse > pulseDebounce) {
pulses++;
secondLastPulse = lastPulse;
lastPulse = millis();
}
}
@Tugzrida
Copy link
Author

@tumharaashish I'm not familiar with Grafana cloud so all I can suggest is looking in the prometheus interface to see if the series are present there, or maybe doing a packet capture on your device to ensure it's pushing the metrics correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment