Savior Faire

Zigbee waterflow meter for Clack valve using ESP32-C6

This is a personal project, based on this tutorial and Ai aid.

I am using this ESP32-C6 Development Kit and Arduino IDE.

Arduino IDE settings:

USB CDC On Boot: "Enabled"
CPU Frequency: "160MHz (WiFi)"
Core Debug Level: "None"
Erase All Flash Before Sketch Upload: "Disabled"
Flash Frequency: "80MHz"
Flash Mode: "QIO"
Flash Size: "4MB (32Mb)"
JTAG Adapter: "Disabled"
Partition Scheme: "Zigbee 4MB with spiffs"
Upload Speed: "115200"
Zigbee Mode: "Zigbee ED (end device)"

It will do the following:


Code

#include <Preferences.h>
#include "Zigbee.h"

#define WATER_PIN 10
#define PULSES_PER_LITER 17
#define FLOW_SENSOR_ENDPOINT_NUMBER 10

#define SAVE_INTERVAL 1800000  // 30 minutes in milliseconds
#define REPORT_INTERVAL 60000  // 1 minute in milliseconds
#define ZIGBEE_WATCHDOG_TIMEOUT 7200000  // 2 hours in milliseconds

#define RGB_BRIGHTNESS 50

Preferences preferences;

int pulseCount = 0;
float totalLiters = 0.0;
float lastSavedLiters = 0.0;
bool isLowVoltage = true;
bool isConnected = false;
uint32_t lastReportTime = 0;
uint32_t lastSaveTime = 0;
uint32_t lastZigbeeReportTime = 0;

ZigbeeFlowSensor zbFlowSensor(FLOW_SENSOR_ENDPOINT_NUMBER);

void setLEDColor(uint8_t red, uint8_t green, uint8_t blue) {
#ifdef RGB_BUILTIN
  rgbLedWrite(RGB_BUILTIN, red, green, blue);
#endif
}

void saveToNVS() {
  preferences.putFloat("totalLiters", totalLiters);
  lastSavedLiters = totalLiters;
  Serial.printf("💾 Saved to NVS: %.3f L\n", totalLiters);
}

void loadFromNVS() {
  totalLiters = preferences.getFloat("totalLiters", 0.0);
  lastSavedLiters = totalLiters;
  Serial.printf("🔄 Loaded from NVS: %.3f L\n", totalLiters);
}

void setup() {
  Serial.begin(115200);
  pinMode(WATER_PIN, INPUT_PULLUP);

  preferences.begin("water_meter", false);
  loadFromNVS();

  setLEDColor(RGB_BRIGHTNESS, 0, 0);  // Red during setup

  zbFlowSensor.setManufacturerAndModel("Espressif", "ZigbeeWaterMeter");
  Zigbee.addEndpoint(&zbFlowSensor);

  Serial.println("Starting Zigbee...");
  if (!Zigbee.begin()) {
    Serial.println("❌ Zigbee failed to start! Rebooting...");
    delay(100);
    ESP.restart();
  }

  while (!Zigbee.connected()) {
    delay(100);
  }

  isConnected = true;
  Serial.println("✅ Zigbee connected!");
  setLEDColor(0, RGB_BRIGHTNESS, 0);  // Green when connected

  zbFlowSensor.setReporting(0, 60, 1.0);

  lastReportTime = millis();
  lastSaveTime = millis();
  lastZigbeeReportTime = millis();
}

void loop() {
  uint32_t currentMillis = millis();

  int waterValue = digitalRead(WATER_PIN);
  if (waterValue == LOW) {
    if (isLowVoltage) {
      pulseCount++;
      isLowVoltage = false;
      totalLiters += 1.0 / PULSES_PER_LITER;

      Serial.printf("🚿 Water pulse detected! Total: %.3f L\n", totalLiters);

      if (isConnected) {
        setLEDColor(0, 0, 0);  // Blink LED off
        delay(100);
        setLEDColor(0, RGB_BRIGHTNESS, 0);  // Restore green
      }
    }
  } else {
    isLowVoltage = true;
  }

  // Force report every minute
  if (currentMillis - lastReportTime >= REPORT_INTERVAL) {
    zbFlowSensor.setFlow(totalLiters);
    zbFlowSensor.report();
    Serial.printf("📢 Forced report (every minute): %.3f L\n", totalLiters);
    lastReportTime = currentMillis;
    lastZigbeeReportTime = currentMillis;
  }

  // Save to NVS every 30 minutes if changed
  if ((currentMillis - lastSaveTime >= SAVE_INTERVAL) && (totalLiters != lastSavedLiters)) {
    saveToNVS();
    lastSaveTime = currentMillis;
  }

  // Restart if no Zigbee report for 2 hours
  if (currentMillis - lastZigbeeReportTime >= ZIGBEE_WATCHDOG_TIMEOUT) {
    Serial.println("❌ No Zigbee report for 2 hours! Restarting ESP32...");
    delay(100);
    ESP.restart();
  }
}

Add a sensor on configuration.yaml on Home Assistant:

- sensor:
      - name: "Total Water Usage"
        unique_id: total_water_usage
        state: "{{ states('sensor.espressif_zigbeewatermeter') | float(0) }}"
        unit_of_measurement: "L"
        device_class: water
        state_class: total_increasing

#Tutorials