Use case: Water flow sensor for indoor / outdoor usage (Tasmota-flashed ESP + cheap flow meter + MQTT)

Use case:
I’m using a Gardena smart irrigation control (with 6 separate water cycles) to water my yard, fed by the tab and a cistern. After one year I realized I have no clue how much water is being consumed. In the forum here, here and here, solutions were discussed, but these sensors were either expensive or difficult to get. There are also reasonably priced sensors (e.g. this one), but the long battery life comes at the expense of a rather long measurement intervals (3 hours) and the necessity of a LoRaWAN gateway.

Goal:
Be able to place cheap “real-time” water flow sensors around the house for full transparency on the water consumption of various consumers, and for being able to send alarm messages to my mobile in case odd water consumption profiles are being detected (e.g. “more than x00 liters in y hours”, e.g. when a water hose burst).

Effort:
Two afternoons of fun, tinkering, and lots of learning on MQTT, hall sensors and ESP scripting.

Hardware used:

  • Raspi 3B+ running openHAB 3.3
  • I’m using the YF-B5 flow sensor (3/4") for ~€ 10 per piece and the YF-B10 flow sensor (1") for ~ €21 per piece. They’re probably not as precise as really expensive sensors (see below), but they get the job done.
  • One ESP-WROOM-32 (< € 10).

Working principle of the sensors:
The working principle of the sensor is important to understand. The reason why the YF-B5 and the YF-B10 are so cheap is because they’re basically just a metal pipe with a polymer / injection-moulded turbine in it. On two blades of the turbine is a small piece of metal. When the turbine turns, it triggers the hall sensor in the black box on top of the sensor.

Step 1: Flashing Tasmota on the ESP

  1. Connect a USB cable (be careful to not get a “power-only” cable, but one that also carries data) to the micro USB-port of the ESP.
  2. Since a Tasmota version with “Script” instead of “Rules” is needed, either compile it yourself or download it here (ESP32) or here (ESP8266). I did the latter, which worked fine.
  3. For the actual flashing, either use the web tool under this link (also works under Linux, but both in Linux or Windows Chrome has to be used) or download and install Tasmotizer.
  4. After you’re done flashing, use Tasmotizer to send the WiFi-details (SSID and Password) to the ESP.
  5. Afterwards you should be able to get the IP address of the ESP either via your router or via Tasmotizer (“Get IP Address”). At this point you can unplug the ESP from your computer and just use a standard USB power supply.
  6. Now you should be able to access the ESP / Tasmota GUI by entering the IP address into your browser.

If the programming of the ESP with Tasmota does not work, try…

  • using a different USB port on your computer
  • using a different USB cable (some cables only support power, not data)
  • switching from Tasmotizer to the web installer (or vice versa)

Step 2: Connect the sensor

  1. Since it can take some trial-and-error to get the sensor to work, mounting the ESP on a breadboard is a good idea.
  2. Cut off the black plastic plug of the sensor.
  3. Connect “Red” to “3V”, “Black” to “G” and Yellow (the impulse of the turbine) to an input channel of the ESP (in my case: “Input 14”).

Step 3: Configure Tasmota

  1. Enter the ESP’s IP address into your browser.
  2. Under “Configuration” → “Configure Module” give Tasmota the information where to get the counter signal from (here: on channel 14).
    image

Step 4: Import the script that converts “turbine ticks” into “water”

As mentioned above, the sensor just sends “ticks” to the ESP. The trick is to convert these ticks into “water flown through in between two ticks”.
One possibility to do so doing the conversion in openHAB. However, more “real-time” is doing the conversion on the ESP itself. This can be done with with the Tasmota Scripting language, which looks a bit crude, but appears to be very efficient.

A good version of a script that does that can be found here. I took this script as a basis and did some further editing to it (incl. more intervals, see below, and more documentation in the code itself, in case I have to edit it in a couple of months from now). My documentations might help you as well.

>D
; A valid script must start with >D in the first line
; Reference see here: https://tasmota.github.io/docs/Scripting-Language/

; Variables are defined and initialized (p means permanent)
; Total number of counted pulses
p:TotalPulses=0
; Total number of measured liters
p:TotalLiter=0
; Pulses per liter in Flowspeed 1
p:FlowSpeed1=150
; Pulses per liter in Flowspeed 2
p:FlowSpeed2=183
; Pulses per liter in Flowspeed 3
p:FlowSpeed3=420
; Pulses per liter in Flowspeed 4
p:FlowSpeed4=275
; Pulses per liter in Flowspeed 5
p:FlowSpeed5=350
; Pulses per liter in Flowspeed 6
p:FlowSpeed6=375
; Upper threshold for Flowspeed 1 (in Pulse/second)
p:FlowSpeed1TC=2
; Upper threshold for Flowspeed 2 (in Pulse/second)
p:FlowSpeed2TC=5
; Upper threshold for Flowspeed 3 (in Pulse/second)
p:FlowSpeed3TC=10
; Upper threshold for Flowspeed 4 (in Pulse/second)
p:FlowSpeed4TC=25
; Upper threshold for Flowspeed 5 (in Pulse/second)
p:FlowSpeed5TC=75
; Variable for reset button
resetb=0
; Number of pulses in timer period (in Pulses/second)
PulseRate=0
; Flow rate in current timer period (in Liter/min)
FlowRate=0
; Temp variable for amount of water flown through in current timer period (in liter)
tmp=0
; Current water flow in current timer period (string: high, medium or low)
cflow="none"

>B
; >B is executed on BOOT time before sensors are initialized and on save script 
; After boot restore "total number of counted pulses" back to latest state of ESP pulse counter 1 (pc[1]) 
TotalPulses=pc[1]

>S
; >S is executed every second 
; If reset button resetb has been pushed (resetb has been set to 1)
if resetb==1
then
; The reset button variable resetb is set back to 0
resetb=0
; The total number of counted pulses is set back to 0
TotalPulses=0
; The total number of counted liters is set back to 0
TotalLiter=0
; ESP variable counter1 is set back to 0
=>Counter1 0 
endif

; Calculation of number of pulses in past 1 second ("PulseRate")
; PulseRate = Total # of Pulses from ESP Counter - Total # of Pulses one second ago
PulseRate=pc[1]-TotalPulses
; Afterwards, total # of pulses one second ago is set to Current total # of Pulses from ESP Counter
TotalPulses=pc[1]

; Determination of Flowspeed Cluster (1 to 6) and amount of water flown through (in liter) in past 1 second
; If "# of pulses in timer period" greater than "upper threshold for respective flowspeed cluster"
if PulseRate>FlowSpeed5TC
then
; Set current flow variable (cflow, string for text output) to the respective flowspeed
cflow="Flowspeed6"
; Calculate the current water flown through in past 1 second by dividing the "number of pulse counts" (PulseRate) by "pulses per liter for respective Flowspeed"
tmp=(PulseRate/FlowSpeed6)
endif

if PulseRate<FlowSpeed5TC
and PulseRate>FlowSpeed4TC
then
cflow="Flowspeed5"
tmp=(PulseRate/FlowSpeed5)
endif

if PulseRate<FlowSpeed4TC
and PulseRate>FlowSpeed3TC
then
cflow="Flowspeed4"
tmp=(PulseRate/FlowSpeed4)
endif

if PulseRate<FlowSpeed3TC
and PulseRate>FlowSpeed2TC
then
cflow="Flowspeed3"
tmp=(PulseRate/FlowSpeed3)
endif

if PulseRate<FlowSpeed2TC
and PulseRate>FlowSpeed1TC
then
cflow="Flowspeed2"
tmp=(PulseRate/FlowSpeed2)
endif

if PulseRate<FlowSpeed1TC
and PulseRate>0
then
cflow="Flowspeed1"
tmp=(PulseRate/FlowSpeed1)
endif

; If no pulses have been counted in past 1 second...
if PulseRate==0
then
; The current water flow-string is set to "none"
cflow="none"
; The amount of water flown through in past 1 second is set to 0 liter
tmp=0
endif

; Calculate total amount of water (in liter)
; The new total amount of water (in liter) is calculated with the previous amount (in liter) plus the new amount in the past 1 second
TotalLiter=(TotalLiter+tmp)
; The flow rate (in Liter/min) in the past 1 second is calculated by multiplying the amount of water in the timer period (tmp) * 60 seconds
FlowRate=tmp*60


; Every 60 seconds: Print status, save permanent variables and publish via MQTT 
if upsecs%60==0
then
; Print a summary in the tasmota log
print Status: Flowrate (in liter/min): %FlowRate%, Total (in liter): %TotalLiter%, Total (in pulses): %TotalPulses%, Pulsesrate (in pulses/sec): %PulseRate% 
; save permanent vars (see p: above)
svars
=>publish stat/%topic%/Flow {"FlowRate":%FlowRate%,"TotalLiter":%TotalLiter%,"TotalPulses":"%TotalPulses%","PulseRate":%PulseRate%}
endif

>W
; The lines in the >W section are displayed in the web UI main page.

; Total number of measured liters (in liter)
Total{m}%TotalLiter% Liter
; Flow rate in current timer period (in Liter/min)
Flow Rate{m}%FlowRate% Liter/min
; Pulse rate in current timer period (in Pulse/sec)
Pulse Rate{m}%PulseRate% Pulse/sec
; Current water flow in current timer period (high, medium or low)
Flowspeed Cluster{m}%cflow% 

; Upper threshold (in pulses per second) for different flowspeed clusters
; Number Inputs (min, max, step, variable name, label text, horizontal size)
nm(0 400 1 FlowSpeed1TC "Upper threshold for Flowspeed 1 (in Pulse/Sec) " 80)
nm(0 400 1 FlowSpeed2TC "Upper threshold for Flowspeed 2 (in Pulse/Sec) " 80)
nm(0 400 1 FlowSpeed3TC "Upper threshold for Flowspeed 3 (in Pulse/Sec) " 80)
nm(0 400 1 FlowSpeed4TC "Upper threshold for Flowspeed 4 (in Pulse/Sec) " 80)
nm(0 400 1 FlowSpeed5TC "Upper threshold for Flowspeed 5 (in Pulse/Sec) " 80)

; Flowspeed (in pulses per liter) for different flowspeed clusters
; Number Inputs (min, max, step, variable name, label text, horizontal size)
nm(0 1000 1 FlowSpeed1 "Flowspeed 1 (in Pulse/liter) " 80)
nm(0 1000 1 FlowSpeed2 "Flowspeed 2 (in Pulse/liter) " 80)
nm(0 1000 1 FlowSpeed3 "Flowspeed 3 (in Pulse/liter) " 80)
nm(0 1000 1 FlowSpeed4 "Flowspeed 4 (in Pulse/liter) " 80)
nm(0 1000 1 FlowSpeed5 "Flowspeed 5 (in Pulse/liter) " 80)
nm(0 1000 1 FlowSpeed6 "Flowspeed 6 (in Pulse/liter) " 80)

; Button (variable name, text of ON state of button, text of OFF state of button)
bu(resetb "Resetting" "Reset Total")

The script basically converts “# of ticks in a timeframe” into “water flown through in the same timeframe”. Unfortunately, the “# of ticks per amount of water” is not constant over the “flowspeed”, probably because it is a really cheap sensor. To compensate for that, the script above distinguishes six different flow speeds (precise enough for me), ranging from “2 pulses per second” (water only dripping through) up to “75 pulses per second” (full speed).

Copy your version of script into the window “Console” → “Edit Script” (not “Berry Scripting Console”) and activate the checkbox “Script enable”.

Afterwards the Tasmota main menu should look like this:

Step 5: Calibrate the sensor (optional, if using the YF-B5)

This part can be tricky / take some time. You can of course take my calibration values, but I assume that even two sensors of the same production batch can have variations, let alone the bigger YF-B10 sensor. So I’d recommend to at least cross-check my values.

To calibrate the sensor, let one liter of water flow through the sensor at the respective speed, and enter the number of pulses counted into the script under the respective flowspeed. The reset-button helps resetting the counters. For comfort reasons, the numbers can also be entered in the field in the main menu, but to be safe (after restart) it is better to include the values in the code itself.
image

Step 6: Enable openHAB to receive the flow information

Assuming that MQTT is already working on your openHAB installation (if not, here under #3 are some hints), the goal is to create an openHAB item which receives the information from the ESP.

  1. Create a thing for the ESP and various channels that capture the water flow and other telemetry information from the ESP. In my case this finally looked like this. Please note that the ID of the MQTT-bridge (7519737e0f) needs to be replaced by the openHAB ID of your MQTT-bridge.
UID: mqtt:topic:7519737e0f:flow_meter_1
label: Flow Meter 1
thingTypeUID: mqtt:topic
configuration: {}
bridgeUID: mqtt:broker:7519737e0f
location: Garten
channels:
  - id: counter
    channelTypeUID: mqtt:number
    label: Total number of pulses
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/SENSOR
      transformationPattern: JSONPATH:$.COUNTER.C1
  - id: temperature
    channelTypeUID: mqtt:number
    label: ESP32 core temperature
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/SENSOR
      transformationPattern: JSONPATH:$.ESP32.Temperature
  - id: time
    channelTypeUID: mqtt:string
    label: Local ESP32 time
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/SENSOR
      transformationPattern: JSONPATH:$.Time
  - id: uptime
    channelTypeUID: mqtt:string
    label: Uptime
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/STATE
      transformationPattern: JSONPATH:$.Uptime
  - id: UptimeSec
    channelTypeUID: mqtt:number
    label: Uptime (in seconds)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/STATE
      transformationPattern: JSONPATH:$.UptimeSec
  - id: RSSI
    channelTypeUID: mqtt:number
    label: RSSI (in %)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/STATE
      transformationPattern: JSONPATH:$.Wifi.RSSI
  - id: Signal
    channelTypeUID: mqtt:number
    label: Signal Strength (in db)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: tele/flow_meter_1/STATE
      transformationPattern: JSONPATH:$.Wifi.Signal
  - id: FlowRate
    channelTypeUID: mqtt:number
    label: Flow Rate (in liter/min)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: stat/flow_meter_1/Flow
      transformationPattern: JSONPATH:$.FlowRate
  - id: TotalLiter
    channelTypeUID: mqtt:number
    label: Total amount (in liter)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: stat/flow_meter_1/Flow
      transformationPattern: JSONPATH:$.TotalLiter
  - id: TotalPulses
    channelTypeUID: mqtt:number
    label: Total amount of pulses
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: stat/flow_meter_1/Flow
      transformationPattern: JSONPATH:$.TotalPulses
  - id: PulseRate
    channelTypeUID: mqtt:number
    label: Pulse rate (in pulses/second)
    description: null
    configuration:
      postCommand: false
      retained: false
      step: 1
      formatBeforePublish: "%s"
      stateTopic: stat/flow_meter_1/Flow
      transformationPattern: JSONPATH:$.PulseRate

Please also note that the topic of your Tasmota device (in my case flow_meter_1) has to match whatever you have configured in Tasmota under “Configuration” → “MQTT Parameters” → “Topic”.

Afterwards you can use openHAB to visualize the information being recorded in the respective items.

Step 7: Install the flow sensor

Installing the sensor is not particularly difficult, but in the end harder than expected. With a simple “single-piece fitting” (as visible on the pic below) I could not get it to be tight.

What’s crucial is having a good fitting, e.g. one made from two parts. This one here worked great for me.

Closing remarks

As mentioned, the flow sensor used is not super precise. But for my purpose (get an impression on the amount of water consumed at various points around the house) good enough.

There’s another iteration which is rather neat: A colleague of mine used an ESP-01, which is small enough to be place in a 3D-printed housing that can be mounted on top of the flow sensor (the screws can be removed really easy).

9 Likes

I will definitely try this, thanks a lot for taking the time to write this. :slight_smile:

1 Like

Hi,
I just wanted to post a big “thank you” here. I usually have a lot of fun figuring out stuff like this by myself but with limited time lately I am very happy to find such well documented DIY projects where I don’t have to spend days of fiddling to get it work. I got the YF-B5 and the rest I already had lying around the house, so it took me 15 minutes to get it all up and running.

One hint for others:

  • The tasmota web installer Install Tasmota is very neat and it took me a while to find the unofficial tasmota scripting binary in the dropdown, you have to scroll all the way down for it.

I use the flow meter for my irrigation system to measure the water amount. One problem I had is that after a while the counter started counding up without any flow - either it had a faulty connection or the esp nodemcu board had problems (resistor). Anyways I have swapped the board and connection and so far it’s working very well.

Thanks again and all the best,

Bob

Glad you like it.

For me, after one year still running perfectly. Managed to build a proper case around it for protection in the meantime.

I appreciated so much this effort of yours that I decided to give it a “little mod” and add a second flow sensor. By doing so, we avoid the need to have two ESP32 to measure two flow sensors that are close to each other (as it’s my case).

I also added Cubic Meter information, because usually invoices have prices per cubic meter and like so we can establish a direct comparison.

Here’s the updated script:

>D
blinkpin=2
blink=0
blinkt=4
blinkc=0
p:total=0
p:liter=0
p:m3litter=0
p:total1=0
p:liter1=0
p:m3litter1=0
p:total2=0
p:liter2=0
p:m3litter2=0
highflow=450
midflow=420
lowflow=390
highflowTC=150
midflowTC=80
lowflowTC=30
resetb=0
fastblink=2
midblink=4
slowblink=8
delta=0
delta1=0
delta2=0
flow=0
tmp=0
tmp1=0
tmp2=0
cflow="none"
lastval=0

>F
if blinkc>blinkt
then
if blink==0 
then
blink=1
else
blink=0
endif
spinm(blinkpin blink) 
blinkc=0
endif
if cflow!="none"
then
blinkc=blinkc+1
endif

>B
total=pc[1]+pc[2]
total1=pc[1]
total2=pc[2]


>S
if resetb==1
then
resetb=0
total=0
total1=0
total2=0
liter=0
liter1=0
liter2=0
m3litter=0
m3litter1=0
m3litter2=0
=>Counter1 0 
=>Counter2 0 
endif

delta=(pc[1]+pc[2])-total
if pc[1]>0
then
delta1=pc[1]-total1
else
delta1=0
endif
if pc[2]>0
then
delta2=pc[2]-total2
else
delta2=0
endif
total=pc[1]+pc[2]
total1=pc[1]
total2=pc[2]

if delta>highflowTC 
then
cflow="high"
blinkt=fastblink
tmp=(delta/highflow)
tmp1=(delta1/highflow)
tmp2=(delta2/highflow)
endif

if delta>midflowTC
and delta<highflowTC
then
cflow="mid"
blinkt=midblink
tmp=(delta/midflow)
tmp1=(delta1/midflow)
tmp2=(delta2/midflow)
endif

if delta<midflowTC
then
cflow="low"
blinkt=slowblink
tmp=(delta/lowflow)
tmp1=(delta1/lowflow)
tmp2=(delta2/lowflow)
endif

if delta==0
then
cflow="none"
blinkt=100000
spinm(blinkpin 0) 
tmp=0
tmp1=0
tmp2=0
endif


liter=(liter+tmp)
liter1=(liter1+tmp1)
liter2=(liter2+tmp2)
m3litter=liter/1000
m3litter1=liter1/1000
m3litter2=liter2/1000
flow=tmp*60
print total:%total% total2:%total2% total2:%total2% delta:%delta% liter:%liter% liter1:%liter1% liter2:%liter2% flowrate:%flow% m3litter:%m3litter% m3litter1:%m3litter1% m3litter2:%m3litter2%
svars

=>publish stat/%topic%/Flow {"FlowRate":%flow%,"PulseRate":%delta%,"WaterFlow":"%cflow%","Total":%liter%,"Total1":%liter1%,"Total2":%liter2%,"Counter":%total%,"Counter1":%total1%,"Counter2":%total2%, "M3L":%m3litter%, "M3L1":%m3litter1%, "M3L2":%m3litter2%}


>W

Total{m}%liter% Liter
Total1{m}%liter1% Liter
Total2{m}%liter2% Liter
M3L{m}%m3litter% Cubic Meter
M3L1{m}%m3litter1% Cubic Meter
M3L2{m}%m3litter2% Cubic Meter
FlowRate{m}%flow% Liter/min
PulseRate{m}%delta% Pulse/sec
PulseRate1{m}%delta1% Pulse/sec
PulseRate2{m}%delta2% Pulse/sec
WaterFlow{m}%cflow% 

bu(resetb "Resetting" "Reset Total")

I though in doing this dynamic for “unlimited” flow sensors, but it’s very late now and I’m very sleepy (sorry if there’s bugs in the code, I tested and it seemed all fine, but this late, we just never know).

Bravo for this work which inspired me to configure my water consumption sensor.
For the Mqtt part, I’m using Home Assistant and was wondering if anyone had a configuration suggestion.
For the moment, I can’t do it.
THANKS.

1 Like

Glad you like it.

As for the configuration, though unfamiliar with HA, shouldn’t the MQTT part be similar?

No. Is different in HA.
The config Mqtt no is the same.
I search …
Thank you for reponse.