Water softener (CLACK WS1) consumed m3/liter monitoring

I hooked up a logic analyzer… there is nothing on those pins… no talk at all.

I also tried to put the valve into controller mode but 1 Minute after setting controller mode it gave me an error code of 106. Had to power off the valve completely and had to set mode back to Alt off.

1 Like

I’m pretty happy with water flow metering only. Though regen would be nice, but it can be calculated on hass side

Not the brightest idea but. Maybe we should just send a letter to Clack Corp.? Not all businesses are a…les like mazda or myq ) There is not that much $ that can be made from enthusiasts of HA

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:

i wrote them an email and asked for support and maybe some documentation about communication on that port… i’m not super optimistic about sucess but hey… i cant be more than a no :wink:

2 Likes

What is the easiest way to sense the relays state? Looking at the docs contacts are at 12VDC? Logic level conferter? Optocoupler? Or really put the relays inside?

I use two optocouplers on my clack reader esp32 PCB’s

Any plans to convert this to ESPHome? :slight_smile:

Which optocouplers do you use in the version 2 of your board and the resistance values? Will be hard to get the shipping of your boards to my place. My question on esphome can be discarded as i’ve finally read a disclaimer. Maybe make it bigger? Excellent work

I have made now a newer design of my Clack Reader with some improvements (V4)

  • connect to the clack flowmeter and count the pulses (pulse per liter can be set)
  • L/min and a waterflow leakage alarm after delay
  • determination of regeneration steps by measuring the power of the drivemotor
  • history of the last regeneration cycle times and used water and days in service
  • salt measuring, only measure lower levels (so no water will be measured during fill
  • Date of salt fill, last regeneration, resin cleaner date (count down timer) etc.

Suitable for Clack WS1 (branch ws1 on github) and Clack WS PI (Disc valve) (branch main on github)
Only for 15 VDC clack power supply (not 12 VAC)
fonske/clack-reader-v4 (github.com)
More pictures in the readme directory on github

2 Likes

Which Clack is the preferred model? WS1 or WS PI

I and going to replace my Fleck BINRUN head, it doesn’t store the programming so it is lost after power outages…

@Brad_Ford
I asked Aqmos if the WS PI can be delivered only as head, but thats not possible.
also some internal parts are difficult to get. (like the ceramic discs and wheels)
So the Clack WS1 CI is fine also. (downflow brining, wet or dry salt tank possible)
And parts can be ordered… Look at [sinegroup]( WS1 CI, TC Clack residential valve (sinergroup.net)) if you have a business account. Although shipment from itally is also expensive.
I ordered recently a clack WS1 (injector C for 8" vessel), with DLFC from clack (017) and a riser tube.
15010301-019 (7e) Clack WS1CI / WS1TC DLFC 017 (1.7 gpm) for 3/4" - vessel 8" softener
Powersupply is 15 VDC (and not 12 VAC as mentioned)

Look at the materials.
I am not sure if you need to order seperately:
15010301-033 (2e) Clack WS1CI / WS1TC Drain line insert tube
15010301-034 (3e) Clack WS1CI / WS1TC Drain line elbow nut 3/4"

1 Like

thanks for that, I am looking to see if I can source them at the moment (Thailand)… Please nobody buy the BINRUN it needs to be in the BIN and then RUN…

1 Like

This is definitely the best option for Clack softener I could find. Now can monitor salt level, regen stage and even block / force regen remotely. Watermeter is a bonus. For sure can recommend @Fonske kit / ESPhome integration.
https://github.com/fonske/clack-reader-v4

If you choose to get premade kit - it’s installation is absolutely strait-forward and all needed wires/connectors are included. Just use manual for wiring and add device to HA. Thats almost all you need to do.


1 Like

Where do you get the kit?

Pm @Fonske
or there is contacts on Git

1 Like

@Fonske, great work. I’m interested on the clack-reader but I’m unsure which connections between the reader and the clack are required. Only the flow meter? I have the WS1 and only one relay is populated on the board. Lastly, will this v4 board fit inside the WS1?

Hi @FPSkywalker

The flowmeter is read with a extra 3pin molex to molex cable
Also the power is read (to see the motor current for the steps simulation) with an extra 4 pin molex to molex cable
There is also a 2 pin molex connection on the back to be used if you have a DP-SW connection on your clack WS1 PCB
An overview (exept the dp sw) can be found on github in readme directory
clack-reader-v4/readme/Clack_reader_v4_connections_on_clack_ws1_EN.pdf at ws1_usa · fonske/clack-reader-v4

The power supply adapter need to be 15 VDC (not 12 VAC) but most WS1 boards accept 15 VDC also

It should fit inside the WS1 cabinet.

Thanks @Fonske. I went on github and looked on the manuals folder before asking but I must’ve missed the document. Thanks for the quick reply. Will be ordering soon.

1 Like

I would add, that I’ve received all the cables ready to connect, so just needed to setup/connect the kit only

1 Like