Newbie here!
Here is my setup to monitor m3/liter continuously on the Clack WS1.
I am using the meter port (3 wires, red-black-white on the bottom right corner of the board), together with ESP32 module. I am using only the black wire (ground) and the white wire(signal). The signal has constant voltage of about 3.3v and when the water flow starts, it start pulsing (drops to 0v and then goes back up to 3.3v) depending of how fast the water flows. I figured (made measurements/calibration) that when 17 pulses has passed 1 liter of water is being pushed trough. I have a second Clack WS1 and it measures the same thing there as well. I connected the black wire to the ground of the ESP and white wire to pin 35. (powering the esp with external power supply). Here is the code for the ESP32:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#define water_pin 35
#define PULSES_PER_LITER 17
#define UPDATE_INTERVAL 60000 // 1 minute in milliseconds
int pulseCount = 0;
bool isLowVoltage = true;
float totalLiters = 0.0;
unsigned long lastUpdate = 0;
const char *ssid = "WiFi";
const char *password = "password";
const char *serverUrl = "http://10.1.1.151/water_meter.php";
void sendDataToServer(float liters) {
// Check if liters is greater than 0 before sending to the server
if (liters > 0.0) {
HTTPClient http;
// Prepare JSON payload
String jsonData = "{\"liters\":" + String(liters, 3) + ", \"entity\":\"hallway\"}";
// Send HTTP POST request to PHP script
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(jsonData);
// Check for a successful response
if (httpResponseCode == 200) {
Serial.println("Data sent successfully");
} else {
Serial.print("HTTP Error code: ");
Serial.println(httpResponseCode);
}
http.end(); // Close the connection
}
}
void setup() {
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Set totalLiters to 0 on startup
totalLiters = 0.0;
// Set the last update time
lastUpdate = millis();
// Configure OTA
ArduinoOTA.setHostname("esp32-waterflow-hallway");
ArduinoOTA.begin();
ArduinoOTA.setPassword("");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Handle OTA updates
ArduinoOTA.handle();
// Read the analog value from water_pin
int analogValue = analogRead(water_pin);
// Check if the analog value is zero
if (analogValue == 0) {
// If in a low voltage state, start a new pulse
if (isLowVoltage) {
pulseCount++;
isLowVoltage = false;
// Increment total liters for each pulse
totalLiters += 1.0 / PULSES_PER_LITER;
Serial.print("Total Liters: ");
Serial.println(totalLiters, 3);
}
} else {
// If the voltage is not zero and isLowVoltage was false, reset the flag
if (!isLowVoltage) {
isLowVoltage = true;
}
}
// Check if it's time to update the database
if (millis() - lastUpdate >= UPDATE_INTERVAL) {
// Send data to server
sendDataToServer(totalLiters);
// Reset totalLiters
totalLiters = 0.0;
// Reset pulse count or perform any other actions needed before the next update
// Set the last update time
lastUpdate = millis();
}
delay(10); // Optional delay to control the rate of analog readings
}
Let me briefly explain the code:
We are reading the pulses on the pin, collecting the data for 1 min, translating the pulses to liters, and then sending the data to a php file hosted on a local raspberry pi, after that the process starts over.
Here is how the php file looks like:
<?php
// Connect to MySQL
$host = 'localhost';
$dbname = 'db_name';
$username = 'username';
$password = 'password';
try {
$db = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
// Set the PDO error mode to exception
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Retrieve the entity variable from the GET request
$entity = isset($_GET['entity']) ? $_GET['entity'] : null;
if ($entity=='') {
http_response_code(400);
echo json_encode(array('message' => 'Entity cannot be empty'));
die();
}
// Handle GET request to retrieve data
try {
// Example SQL query to retrieve data for a specific entity
$stmt = $db->prepare("SELECT FORMAT(SUM(liters), 3) AS total_liters FROM data WHERE DATE(date) = CURDATE() AND entity = :entity");
$stmt->bindParam(':entity', $entity);
$stmt->execute();
// Fetch all rows as associative arrays
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Return the result as JSON
echo json_encode($result);
} catch (PDOException $e) {
// Log the error and send a response with an error message
http_response_code(500);
echo json_encode(array('message' => 'Internal Server Error.'));
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Handle POST request to insert data
$data = json_decode(file_get_contents("php://input"), true);
if (isset($data['liters'])&& isset($data['entity'])) {
$liters = $data['liters'];
$entity = $data['entity'];
// Begin a new transaction
$db->beginTransaction();
try {
// Example SQL query to insert data
$stmt = $db->prepare("INSERT INTO data (date, entity, liters) VALUES (NOW(), :entity, :liters)");
$stmt->bindParam(':entity', $entity);
$stmt->bindParam(':liters', $liters);
// Execute the query
$stmt->execute();
// Commit the transaction
$db->commit();
echo json_encode(array('message' => 'Data inserted successfully'));
} catch (PDOException $e) {
// Rollback the transaction on error
$db->rollBack();
http_response_code(500);
echo json_encode(array('message' => 'Internal Server Error.'));
}
} else {
http_response_code(400);
echo json_encode(array('message' => 'Bad request. Missing "liters" in the request.'));
}
} else {
http_response_code(405); // Method Not Allowed
echo json_encode(array('message' => 'Method not allowed.'));
}
} catch (PDOException $e) {
// Log the error and send a response with an error message
http_response_code(500);
echo json_encode(array('message' => 'Internal Server Error.'));
}
?>
I’ll explain briefly the php file. It gets the data from the ESP and stores it in a database. Also Serves the stored data to Home Assistant.
Here is HA setup:
In configuration.yaml I’ve added:
#Sensor for the water consumption in the hallway
- platform: rest
name: Water Consumption Hallway
resource: http://10.1.1.151/water_meter.php?entity=hallway
value_template: "{{ value_json[0].total_liters | float }}"
json_attributes:
- entity
unit_of_measurement: "L"
scan_interval: 60 # Update every 60 seconds
unique_id: sensor.water_consumption_hallway
device_class: water
state_class: total_increasing
#Sensor for the water consumption in the kitchen
- platform: rest
name: Water Consumption Hallway
resource: http://10.1.1.151/water_meter.php?entity=kitchen
value_template: "{{ value_json[0].total_liters | float }}"
json_attributes:
- entity
unit_of_measurement: "L"
scan_interval: 65 # Update every 60 seconds
unique_id: sensor.water_consumption_kitchen
device_class: water
state_class: total_increasing
#Combine the two sensors
- platform: template
sensors:
total_water_consumption:
friendly_name: "Total Water Consumption"
value_template: "{{ states('sensor.water_consumption_kitchen') | float + states('sensor.water_consumption_hallway') | float }}"
unit_of_measurement: "L"
device_class: water
total_water_cost:
friendly_name: "Total Water Cost"
value_template: "{{ ((states('sensor.water_consumption_kitchen') | float + states('sensor.water_consumption_hallway') | float) * 0.003498) | round(2) }}"
unit_of_measurement: "BGN"
device_class: water
p.s. 0.003498 is the cost for 1 liter of water where I live right now.
The sensor will also shows in the energy dashboard as well.
Here is the end result: