Wireless water flow meter


Topic was discussed several times but maybe someone have any new solution for installing water flow meter. Ideally based on zwave or zigbee.

I 'm also interested if someone use water meters not on the main valve but i.e under sink ( I have big problem with one person that don’t close valve…)

Not exactly what you’re after…

But if just closing the tap after use is intended, an automatic tap might be an idea. E.g.:

I just implemented a water flow meter in my shower.
It’s based on an ESP8366 with battery and uses ESP-Now for communication (uses way less battery) to MQTT. Still optimizing the code as i only get about 3 days out of an 7000mah lifepo4 which is way off, but the logic behind works.
It updates OH with water flow and total liter measures.
Shout if you want to see the code.

Unfortunatelly automatic tap is not a solution for me :frowning:

I have made a good experience with digitizing a standard analogue water meter:

Costs are reasonable and the solution fits quite good into OH (writes MQTT messages)

seems nice but I need something small to put under sink

Do you have also equipment list with some manual how to build everything?

I followed some online how-to’s and github codes to come up with an implementation.
The equipment I am using for now:

  • Wemos D1 Mini
  • LifePo4 7000mAh battery connected to 3.3v and GND (I don’t have a power outlet close by)
  • Waterflow Sensor YF201 linked to GND, D7 (data) and D2 (VCC)
  • deepSleep set via RST and D0 short

My initial non-ESP-Now code is here on reddit.

Since then I changed to ESP-Now though which requires 2 devices (a receiver to connect to WiFi and MQTT and one sender which has the sensor connected).

My code for both is below, take into account that it might not be fully optimzied:

//Include libraries and other includes
#include <Arduino.h>
#include <ArduinoOTA.h>
#include <ESP8266WiFi.h>
#include <espnow.h>
#include <PubSubClient.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

#include "network.h"
#include "waterflow.h"
#include "ntp.h"

//Init WiFi and MQTT
WiFiClient espClient;
PubSubClient client(espClient);
NTPClient timeClient(ntpUDP, "asia.pool.ntp.org", utcOffsetInSeconds);

//Structure to receive data and create myData
typedef struct struct_message {
  int status;
  int totalLiters;
  float flowRate;  
  } struct_message;

struct_message myData;

//Callback function that will be executed when data is received
void OnDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len) {
  memcpy(&myData, incomingData, sizeof(myData));

  if (int(myData.flowRate) >= 1) {
    //Calculate flowRate as string for MQTT
    flow_str = String(myData.flowRate);
    flow_str.toCharArray(flow, total_str.length() + 5);
    client.publish("masterbathroom/shower/flowrate", flow);
  } else {
    //Calculate totalliters as string for MQTT
    total_str = String(myData.totalLiters);
    total_str.toCharArray(total, total_str.length() + 5);

    //Calculate timestamp - epochTime for MQTT last_seen
    unsigned long epochTime = timeClient.getEpochTime();
    epochTime_str = String(epochTime);
    epochTime_str.toCharArray(epochTime_mqtt, epochTime_str.length() + 1);
    //Serial.print("Total Liters: ");
    client.publish("masterbathroom/shower/last_seen", epochTime_mqtt);
    client.publish("masterbathroom/shower/totalliters", total);
    client.publish("masterbathroom/shower/state", "OFF");

void setup() {
  //Initialize Serial Monitor

  //Set device as WiFi station - loop till connected
  WiFi.config(staticIP, dns, gateway, subnet);
  WiFi.begin(ssid, password);
  //, channel, bssid);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("Setting as a Wi-Fi Station..");

  //Connect to MQTT - loop till connected
  client.setServer(mqttServer, mqttPort);

  while (!client.connected()) {
    if (client.connect("WEMOS", mqttUser, mqttPassword)) {
    } else {
      Serial.println("Connecting to MQTT..");

  //Init ESP-Now
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");

  //Once ESPNow is successfully init, set ESP-Now role and register recv
  client.publish("masterbathroom/shower/state_coordinator", "ON");

  //Start OTA mode
  ArduinoOTA.onStart([]() {
  ArduinoOTA.onEnd([]() {
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR)
      Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR)
      Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR)
      Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR)
      Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR)
      Serial.println("End Failed");


void loop()

//Include libraries and other includes
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <espnow.h>
#include <SPI.h>

#include "waterflow.h"

//Declare GPIO variables - D7 for pulse measure; D2 for VCC
#define SENSOR D7
const int pingPin = D2;
int status = 0;

//Receiver MAC-Address
uint8_t broadcastAddress[] = {0x84, 0xCC, 0xA8, 0xA6, 0x1D, 0x7D};

//Structure example to send data - must match coordinator
typedef struct struct_message {
  int status;
  float flowRate;
} struct_message;

// Create a struct_message called myData
struct_message myData;

//Declare functions
//Count pulses of the waterflow sensor
void IRAM_ATTR pulseCounter()

//Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  if (sendStatus == 0){
  } else {
  if (int(flowRate) == 0) {
void setup() {
  //Init Serial Monitor

  //Deactive build-in LED and power up GPIO D2
  digitalWrite(LED_BUILTIN, HIGH);
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, HIGH);

  //Set warterflow variables
  pulseCount = 0;
  flowRate = 0.0;
  flowMilliLitres = 0;
  totalMilliLitres = 0;
  previousMillis = 0;
  totalLiters = 0;
  attachInterrupt(digitalPinToInterrupt(SENSOR), pulseCounter, FALLING);
  //Set device as a Wi-Fi Station
  int32_t channel = 1;

  //Init ESP-NOW
  if (esp_now_init() != 0) {

  //Once ESP-Now is successfully init, set role, regirster onDataSent and register with Coordinator
  esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
void loop() {

  //Init struct_messege
  myData.status = status;
  myData.flowRate = flowRate;
  currentMillis = millis();
  if (currentMillis - previousMillis > interval) {
    pulse1Sec = pulseCount;
    pulseCount = 0;
    flowRate = ((1000.0 / (millis() - previousMillis)) * pulse1Sec) / calibrationFactor;
    previousMillis = millis();

    if (int(flowRate) < 5) {
      status = 0;
      esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(struct_message));
      digitalWrite(pingPin, LOW);

    } while (int(flowRate) > 5) {
      status = 1;
      esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(struct_message));

Open to code improvements if someone has further ideas.