Water flow sensor YF-B5 shows incorrect pulses

Hello, currently I have installed water flow sensor YF-B5 at my house and I am trying to get water usage readings. Whenever I run my tap, each minute it shows somewhat good values - after taking a shower it is behind my main water meter by 2 liters.

The biggest problem: when the water is not running sometimes it displays the values that are not zero - 20, 50 or 200 millilitres. I have tried printing how many pulses it counted throughout that minute and sometimes it showed 1, 2, 31 or even 92 pulses, even though neither the water has been run, nor my main house water meter showed any water usage. I hope someone could help me with this issue.

Please see my code below (Wi-Fi and other not important configurations have been omitted):

#include <NTPClient.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <WiFiClient.h>

const char *SSID = "myssid";
const char *WIFI_PASSWORD = "mypassword";

// ESP32 configuration
#define ONE_WIRE_BUS 4 // D4 pin
#define LED 2          // LED pin

// Loop configuration
long currentMillis = 0;
long previousMillis = 0;
long previousPublish = 0;
int interval = 60000; // 60k millis = 1 minute

const String DATA = "http://192.168.1.101:8080/api/data";

volatile byte pulseCount;
float flowRate;
float calibrationFactor = 6.6;
unsigned long flowMillilitres;
unsigned long totalMillilitres;

int post(long flowMillilitres);

WiFiClient client;
HTTPClient http;

void IRAM_ATTR pulseCounter()
{
    pulseCount++;
}

void setup()
{
    Serial.begin(9600);

    WiFi.begin(SSID, WIFI_PASSWORD);

    pinMode(LED, OUTPUT);

    Serial.println("Connecting to WiFi...");

    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }

    Serial.println();

    Serial.print("Connected to WiFi with IP Address: ");
    Serial.print(WiFi.localIP());
    Serial.print("\n\n");

    // Initialize the D2 pin as an output
    pinMode(ONE_WIRE_BUS, INPUT_PULLUP);

    attachInterrupt(digitalPinToInterrupt(ONE_WIRE_BUS), pulseCounter, FALLING);
}

void loop()
{
    currentMillis = millis();

    if (currentMillis - previousMillis > 1000)
    {
        // Calculate flow rate
        flowRate = ((1000.0 / (millis() - previousMillis)) * pulseCount) / calibrationFactor;

        previousMillis = millis();

        flowMillilitres = flowRate / 60 * 1000;
        totalMillilitres += flowMillilitres;

        pulseCount = 0;
    }

    // Calculate and print flow rate every minute
    if (currentMillis - previousPublish > interval) {
        post(totalMillilitres);

        previousPublish = millis();
        totalMillilitres = 0;
    }
}

int post(long totalMillilitres)
{
  http.begin(client, DATA);
  http.addHeader("Content-Type", "application/json");

  String data = "{\"sensor_value\":" + String(totalMillilitres) + "}";

  int httpResponseCode = http.POST(data);
  return httpResponseCode;
}

Diagram:

The problem could be electrical interference being picked up long, unshielded sensor leads, sensor switch bounce, or in the code you did not post.

Does the posted snippet show the same problem?

Hi, @jremington. My omitted code just connects to Wi-Fi and send the data over HTTP. In no way it is affecting my pulses count number. My sensor is connected via USB cable which runs behind my fridge to the socket, touching fridge's back. Could it have some sort of impact? Sensor's wires are shielded. What would you recommend to check? Thanks again.

Please answer the question: does the snippet show the same problem?

BUT! how do you have the shield connected to the Arduino ground?

@jremington, please see updated code. It still shows the same problem.

Does the SNIPPET show the same problem?

Post a wiring diagram, hand drawn, not Fritzing.

Would you happen to be using an ESP32?

I'd put this millis() thingy outside of the

 // Calculate and print flow rate every minute
        if (currentMillis - previousPublish > interval) {
            post(totalMillilitres);

            previousPublish = millis();
            totalMillilitres = 0;
        }

and independent from the

if (currentMillis - previousMillis > 1000)
    {

millis() thingy.

@jremington, please see updated post. I hope this diagram will do a trick, because I followed it.

@Idahowalker, not sure If I understood you correctly, but please see updated code. Did you mean to put my second If condition outside of the first one? Thanks.

Hello cjacky475.
I did google for the data sheet and found:
•Mini. Wokring Voltage: DC 4.5V
Your schematic shows a connectoin to 3.3V . Can you check this? What shows you data sheet? Can you observe the pulses with an Oszi?

When you update code, could you just add the updated code as a new post? It makes the story line easier to follow.

Next to count pulses with an ESP32 one might consider using the ESP32's PCNT API.
Pulse Counter (PCNT) - ESP32 - — ESP-IDF Programming Guide latest documentation.

The PCNT is a programmable module included with the ESP32 that counts pulses without CPU intervention.

Here is some overly complicated ESP32 code using freeRTOS and the PCNT under the Arduino IDE.

/*
   Chappie Weather upgrade/addition
   process wind speed direction and rain fall.
*/
#include "esp32/ulp.h"
//#include "ulptool.h"
#include "driver/rtc_io.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include <driver/pcnt.h>
#include <driver/adc.h>
#include <SimpleKalmanFilter.h>
#include <ESP32Time.h>
////
ESP32Time rtc;
WiFiClient wifiClient;
PubSubClient MQTTclient(mqtt_server, mqtt_port, wifiClient);
////
float CalculatedVoltage = 0.0f;
float kph = 0.0f;
float rain  = 0.0f;
/*
   PCNT PCNT_UNIT_0, PCNT_CHANNEL_0 GPIO_NUM_15 = pulse input pin
   PCNT PCNT_UNIT_1, PCNT_CHANNEL_0 GPIO_NUM_4 = pulse input pin
*/
pcnt_unit_t pcnt_unit00 = PCNT_UNIT_0; //pcnt unit 0 channel 0
pcnt_unit_t pcnt_unit10 = PCNT_UNIT_1; //pcnt unit 1 channel 0
//
//
hw_timer_t * timer = NULL;
//
#define evtAnemometer  ( 1 << 0 )
#define evtRainFall    ( 1 << 1 )
#define evtParseMQTT   ( 1 << 2 )
EventGroupHandle_t eg;
#define OneMinuteGroup ( evtAnemometer | evtRainFall )
////
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 100;
struct stu_message
{
  char payload [payloadSize] = {'\0'};
  String topic ;
} x_message;
////
SemaphoreHandle_t sema_MQTT_KeepAlive; // used to stop all other MQTT thing do's
SemaphoreHandle_t sema_mqttOK; // protect the mqttOK variable.
SemaphoreHandle_t sema_CalculatedVoltage; // protects the CalculatedVoltage variable.
////
int mqttOK = 0; // stores a count value that is used to cause an esp reset
volatile bool TimeSet = false;
////
/*
   Topic topicOK has been subscribed to, the mqtt broker sends out "OK" messages if the client receives an OK message the mqttOK value is set back to zero.
   If the mqttOK count reaches a set point the ESP32 will reset.
*/
////
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
  int i = 0; // extract payload
  for ( i; i < length; i++)
  {
    x_message.payload[i] = (char)payload[i];
  }
  x_message.payload[i] = '\0';
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()
////
void setup()
{
  eg = xEventGroupCreate(); // get an event group handle
  x_message.topic.reserve(100);
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);// using GPIO 34 wind direction
  adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);// using GPIO 39 current
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);// using GPIO 36 battery volts

  // hardware timer 4 set for one minute alarm
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 60000000, true);
  timerAlarmEnable(timer);
  /* Initialize PCNT's counter */
  int PCNT_H_LIM_VAL         = 3000;
  int PCNT_L_LIM_VAL         = -10;
  // 1st PCNT counter
  // Anemometer
  pcnt_config_t pcnt_config  = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_15;// Set PCNT input signal and control GPIOs
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_0;
  // What to do on the positive / negative edge of pulse input?
  pcnt_config.pos_mode       = PCNT_COUNT_INC;   // Count up on the positive edge
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;   // Count down disable
  // What to do when control input is low or high?
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP; // Keep the primary counter mode if low
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;    // Keep the primary counter mode if high
  // Set the maximum and minimum limit values to watch
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config); // Initialize PCNT unit
  // 12.5ns is one APB_CLK cycle 12.5*500, debounce time
  pcnt_set_filter_value( PCNT_UNIT_0, 500); //Configure and enable the input filter, debounce
  pcnt_filter_enable( PCNT_UNIT_0 );
  pcnt_counter_pause( PCNT_UNIT_0 );
  pcnt_counter_clear( PCNT_UNIT_0 );
  pcnt_counter_resume( PCNT_UNIT_0); // start the show
  // setup 2nd PCNT
  pcnt_config = {};
  pcnt_config.pulse_gpio_num = GPIO_NUM_4;
  pcnt_config.ctrl_gpio_num  = PCNT_PIN_NOT_USED;
  pcnt_config.channel        = PCNT_CHANNEL_0;
  pcnt_config.unit           = PCNT_UNIT_1;
  pcnt_config.pos_mode       = PCNT_COUNT_INC;
  pcnt_config.neg_mode       = PCNT_COUNT_DIS;
  pcnt_config.lctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.hctrl_mode     = PCNT_MODE_KEEP;
  pcnt_config.counter_h_lim  = PCNT_H_LIM_VAL;
  pcnt_config.counter_l_lim  = PCNT_L_LIM_VAL;
  pcnt_unit_config(&pcnt_config);
  pcnt_set_filter_value( PCNT_UNIT_1, 500 );
  pcnt_filter_enable  ( PCNT_UNIT_1 );
  pcnt_counter_pause  ( PCNT_UNIT_1 );
  pcnt_counter_clear  ( PCNT_UNIT_1 );
  pcnt_counter_resume ( PCNT_UNIT_1 );
  //
  xQ_Message = xQueueCreate( 1, sizeof(stu_message) );
  //
  sema_CalculatedVoltage = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_CalculatedVoltage );
  sema_mqttOK = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
  ///
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 10000, NULL, 5, NULL, 1 );
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 10000, NULL, 5, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fReadBattery, "fReadBattery", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fReadCurrent, "fReadCurrent", 4000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fWindDirection, "fWindDirection", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fAnemometer, "fAnemometer", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fRainFall, "fRainFall", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 3000, NULL, 3, NULL, 1 ); // assign all to core 1
} //void setup()
static void init_ulp_program()
{
// not sharing this code.
}
////
void fWindDirection( void *pvParameters )
// read the wind direction sensor, return heading in degrees
{
  SimpleKalmanFilter KF_ADC( 1.0f, 1.0f, .01f );
  const TickType_t xFrequency = 100; //delay for mS
  float    adcValue = 0.0f;
  uint64_t TimePastKalman  = esp_timer_get_time();
  float    high = 0.0f;
  float    low = 2000.0f;
  float    ADscale = 3.3f / 4096.0f;
  int      count = 0;
  String   windDirection;
  String   MQTTinfo = "";
  windDirection.reserve(20);
  MQTTinfo.reserve( 150 );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  for (;;)
  {
    windDirection = "";
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_6) ); //take a raw ADC reading
    KF_ADC.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC.updateEstimate( adcValue ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    adcValue = adcValue * ADscale;
    if ( (adcValue >= 0.0f) & (adcValue <= .25f )  )
    {
      // log_i( " n" );
      windDirection.concat( "N" );
    }
    if ( (adcValue > .25f) & (adcValue <= .6f ) )
    {
      //  log_i( " e" );
      windDirection.concat( "E" );
    }
    if ( (adcValue > 2.0f) & ( adcValue < 3.3f) )
    {
      //   log_i( " s" );
      windDirection.concat( "S");
    }
    if ( (adcValue >= 1.7f) & (adcValue < 2.0f ) )
    {
      // log_i( " w" );
      windDirection.concat( "W" );
    }
    if ( count >= 30 )
    {
      MQTTinfo.concat( String(kph, 2) );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( windDirection );
      MQTTinfo.concat( ",");
      MQTTinfo.concat( String(rain, 2) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicWSWDRF, MQTTinfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      count = 0;
    }
    count++;
    MQTTinfo = "";
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
// read rainfall
void fRainFall( void *pvParemeters )
{
  int16_t click = 0; //count tipping bucket clicks
  pcnt_counter_pause( PCNT_UNIT_1 );
  pcnt_counter_clear( PCNT_UNIT_1 );
  pcnt_counter_resume( PCNT_UNIT_1 );
  for  (;;)
  {
    xEventGroupWaitBits (eg, evtRainFall, pdTRUE, pdTRUE, portMAX_DELAY);
    if ( (rtc.getHour(true) == 23) && (rtc.getMinute() == 59) )
    {
      pcnt_counter_pause( PCNT_UNIT_1 );
      rain = 0.0f;
      pcnt_counter_clear( PCNT_UNIT_1 );
      pcnt_counter_resume( PCNT_UNIT_1 );
    } else {
      pcnt_counter_pause( PCNT_UNIT_1 );
      pcnt_get_counter_value( PCNT_UNIT_1, &click );
      if ( click != 0 )
      {
        rain = rain + (0.2794f * (float)click);// 0.2794mm of rain per click
        pcnt_counter_clear( PCNT_UNIT_1 );
        log_i( "count %d, rain rain = %f mm", click, rain );
      }
      pcnt_counter_resume( PCNT_UNIT_1 );
      click = 0;
    }
  }
  vTaskDelete ( NULL );
}
////
void fAnemometer( void *pvParameters )
{
  int16_t count = 0;
  pcnt_counter_clear(PCNT_UNIT_0);
  pcnt_counter_resume(PCNT_UNIT_0);
  for (;;)
  {
    xEventGroupWaitBits (eg, evtAnemometer, pdTRUE, pdTRUE, portMAX_DELAY);
    pcnt_counter_pause( PCNT_UNIT_0 );
    pcnt_get_counter_value( PCNT_UNIT_0, &count);
    kph = 2.4f * ((float)count / 60.0f);// A wind speed of 2.4km/h causes the switch to close once per second
    //log_i( "%f", kph );
    pcnt_counter_clear( PCNT_UNIT_0 );
    pcnt_counter_resume( PCNT_UNIT_0 );
  }
  vTaskDelete ( NULL );
}
//////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 86300; // 1st time update in 100 counts
  int maxNonMQTTresponse = 60;
  for (;;)
  {
    vTaskDelay( 1000 );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
}
//////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      // parse the time from the OK message and update MCU time
      if ( String(px_message.topic) == topicOK )
      {
        if ( !TimeSet)
        {
          String temp = "";
          temp =  px_message.payload[0];
          temp += px_message.payload[1];
          temp += px_message.payload[2];
          temp += px_message.payload[3];
          int year =  temp.toInt();
          temp = "";
          temp =  px_message.payload[5];
          temp += px_message.payload[6];
          int month =  temp.toInt();
          temp =  "";
          temp =  px_message.payload[8];
          temp += px_message.payload[9];
          int day =  temp.toInt();
          temp = "";
          temp = px_message.payload[11];
          temp += px_message.payload[12];
          int hour =  temp.toInt();
          temp = "";
          temp = px_message.payload[14];
          temp += px_message.payload[15];
          int min =  temp.toInt();
          rtc.setTime( 0, min, hour, day, month, year );
          log_i( "rtc  %s ", rtc.getTime() );
          TimeSet = true;
        }
      }
      //
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK = 0;
    xSemaphoreGive( sema_mqttOK );
  }
} // void fparseMQTT( void *pvParameters )#include <ESP32Time.h>
//////
void fReadCurrent( void * parameter )
{
  const TickType_t xFrequency = 1000; //delay for mS
  const float mVperAmp        = 185.0f;
  float    ADbits             = 4096.0f;
  float    ref_voltage        = 3.3f;
  float    mA                 = 0.0f;
  float    adcValue           = 0.0f;
  float    Voltage            = 0.0f;
  float    Power              = 0.0f;
  float    offSET             = 0.0f;
  int      printCount         = 0;
  uint64_t TimePastKalman     = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_I( 1.0f, 1.0f, .01f );
  /*
     185mv/A = 5 AMP MODULE
     100mv/A = 20 amp module
     66mv/A = 30 amp module
  */
  String powerInfo = "";
  powerInfo.reserve( 150 );
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  TickType_t xLastWakeTime = xTaskGetTickCount();
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_3); // read once discard reading
    adcValue = ( (float)adc1_get_raw(ADC1_CHANNEL_3) );
    //log_i( "adcValue I = %f", adcValue );
    Voltage = ( (adcValue * ref_voltage) / ADbits ) + offSET; // Gets you mV
    mA = Voltage / mVperAmp; // get amps
    KF_I.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    mA = KF_I.updateEstimate( mA ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    printCount++;
    if ( printCount == 60 )
    {
      xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY);
      Power = CalculatedVoltage * mA;
      //log_i( "Voltage=%f mA=%f Power=%f", CalculatedVoltage, mA, Power );
      printCount = 0;
      powerInfo.concat( String(CalculatedVoltage, 2) );
      xSemaphoreGive( sema_CalculatedVoltage );
      powerInfo.concat( ",");
      powerInfo.concat( String(mA, 4) );
      powerInfo.concat( ",");
      powerInfo.concat( String(Power, 4) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicPower, powerInfo.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      powerInfo = "";
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
} //void fReadCurrent( void * parameter )
////
void fReadBattery( void * parameter )
{
  const float r1 = 50500.0f; // R1 in ohm, 50K
  const float r2 = 10000.0f; // R2 in ohm, 10k potentiometer
  const TickType_t xFrequency = 1000; //delay for mS
  float    adcValue = 0.0f;
  float    Vbatt = 0.0f;
  int      printCount = 0;
  float    vRefScale = (3.3f / 4096.0f) * ((r1 + r2) / r2);
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_0); //read and discard
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_0) ); //take a raw ADC reading
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC_b.updateEstimate( adcValue ); // apply simple Kalman filter
    Vbatt = adcValue * vRefScale;
    xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY );
    CalculatedVoltage = Vbatt;
    xSemaphoreGive( sema_CalculatedVoltage );
    
      printCount++;
      if ( printCount == 3 )
      {
      //log_i( "Vbatt %f", Vbatt );
      printCount = 0;
      }
    
    TimePastKalman = esp_timer_get_time(); // time of update complete
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( "fReadBattery %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}
////
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  // setting must be set before a mqtt connection is made
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    vTaskDelay( 250 ); //task runs approx every 250 mS
  }
  vTaskDelete ( NULL );
}
////
void connectToWiFi()
{
  int TryCount = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 4000 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
} // void connectToWiFi()
////
void connectToMQTT()
{
  MQTTclient.setKeepAlive( 90 ); // needs be made before connecting
  byte mac[5];
  WiFi.macAddress(mac);
  String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
  while ( !MQTTclient.connected() )
  {
    // boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password, NULL , 1, true, NULL );
    vTaskDelay( 250 );
  }
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( topicOK );
} // void connectToMQTT()
////
void loop() {}
1 Like

@anon64005695, yes, minimum working voltage is DC 4.5V, as described in the datasheet. Sorry, I do not have that device to observe pulses.

@Idahowalker, here is the updated part:

void loop()
{
    currentMillis = millis();

    if (currentMillis - previousMillis > 1000)
    {
        // Calculate flow rate
        flowRate = ((1000.0 / (millis() - previousMillis)) * pulseCount) / calibrationFactor;

        previousMillis = millis();

        flowMillilitres = flowRate / 60 * 1000;
        totalMillilitres += flowMillilitres;

        pulseCount = 0;
    }

    // Calculate and print flow rate every minute
    if (currentMillis - previousPublish > interval) {
        post(totalMillilitres);

        previousPublish = millis();
        totalMillilitres = 0;
    }
}

For some troubleshooting.

volatile bool triggered = false;
void IRAM_ATTR pulseCounter()
{
    pulseCount++;
triggered=true;
}

void loop()
{
if ( triggered )
{
triggered = !triggered;
Serial.println( "triggered" );
}

Add to loop().

Just thinking the sensor might need a debounce thingy like a switch and printing out triggered may show or not.

Does the sensor only trigger in one flow direction or bi-directional flow triggers?

Yes and I see the code was modified.

When you post new or changed code, could you post all your code and not just a snippet that causes me to refer to several postings blah, blah, blah?

@Idahowalker, I've updated the my code according to your provided code (using PCNT). Still, sometimes (could from every 3rd minute to 11th minute) it shows some random water usage, like 3, 7, 20 or 300 millilitres used.

#include <NTPClient.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <WiFiClient.h>

#include <driver/pcnt.h>

const char *SSID = "My-Wifi-Name";
const char *WIFI_PASSWORD = "My-Wifi-Password";

// PCNT PCNT_UNIT_0, PCNT_CHANNEL_0 GPIO_NUM_4 = pulse input pin
pcnt_unit_t pcnt_unit00 = PCNT_UNIT_0;

// Loop configuration
long currentMillis = 0;
long previousMillis = 0;
long previousPublish = 0;
int interval = 60000; // 60k millis = 1 minute

// Data endpoint
const String DATA = "http://192.168.1.101:8080/api/data";

// Values for calculating flow rate
float flowRate;
float calibrationFactor = 6.6;
unsigned long flowMillilitres;
unsigned long totalMillilitres;

// Post method
int post(long flowMillilitres);

// Wifi and Http clients
WiFiClient client;
HTTPClient http;

// Declared functions
void connectToWiFi();

// Water flow pulses count task
void fWaterFlow(void *pvParameters)
{
    int16_t pulseCount = 0;
    pcnt_counter_pause(PCNT_UNIT_0);
    pcnt_counter_clear(PCNT_UNIT_0);
    pcnt_counter_resume(PCNT_UNIT_0);

    for (;;)
    {
        currentMillis = millis();

        if (currentMillis - previousMillis > 1000)
        {
            pcnt_counter_pause(PCNT_UNIT_0);
            pcnt_get_counter_value(PCNT_UNIT_0, &pulseCount);
            pcnt_counter_clear(PCNT_UNIT_0);
            pcnt_counter_resume(PCNT_UNIT_0);

            // Calculate flow rate
            flowRate = ((1000.0 / (millis() - previousMillis)) * pulseCount) / calibrationFactor;

            previousMillis = millis();

            flowMillilitres = flowRate / 60 * 1000;
            totalMillilitres += flowMillilitres;
        }

        // Calculate and print flow rate every minute
        if (currentMillis - previousPublish > interval)
        {
            post(totalMillilitres);

            previousPublish = millis();
            totalMillilitres = 0;
        }
    }
    vTaskDelete(NULL);
}

void setup()
{
    // PCNT counter
    pcnt_config_t pcnt_config = {};
    pcnt_config.pulse_gpio_num = GPIO_NUM_4;
    pcnt_config.ctrl_gpio_num = PCNT_PIN_NOT_USED;
    pcnt_config.channel = PCNT_CHANNEL_0;
    pcnt_config.unit = PCNT_UNIT_0;
    pcnt_config.pos_mode = PCNT_COUNT_INC;
    pcnt_config.neg_mode = PCNT_COUNT_DIS;
    pcnt_config.lctrl_mode = PCNT_MODE_KEEP;
    pcnt_config.hctrl_mode = PCNT_MODE_KEEP;
    pcnt_unit_config(&pcnt_config);
    pcnt_set_filter_value(PCNT_UNIT_0, 1);
    pcnt_filter_enable(PCNT_UNIT_0);
    pcnt_counter_pause(PCNT_UNIT_0);
    pcnt_counter_clear(PCNT_UNIT_0);
    pcnt_counter_resume(PCNT_UNIT_0);

    connectToWiFi();
    xTaskCreatePinnedToCore(fWaterFlow, "fWaterFlow", 4000, NULL, 3, NULL, 1);
}

void connectToWiFi()
{
    Serial.begin(9600);

    WiFi.begin(SSID, WIFI_PASSWORD);

    Serial.println("Connecting to WiFi...");
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }

    Serial.println();

    Serial.print("Connected to WiFi with IP Address: ");
    Serial.print(WiFi.localIP());
    Serial.print("\n\n");
}

void loop()
{
}

int post(long totalMillilitres)
{
    http.begin(client, DATA);
    http.addHeader("Content-Type", "application/json");

    String data = "{\"sensor_value\":" + String(totalMillilitres) + "}";

    Serial.println("Data sent...");

    int httpResponseCode = http.POST(data);
    return httpResponseCode;
}

Without wanting to derail the debugging that's happened so far, I have a couple of suggestions that may help.

Minor point, but In you fWaterFlow task, I would set previousMillis = previousMillis + 1000, where 1000 is the interval from your if statement a few lines above.

It may also be worth briefly commenting out all the flow rate maths and simply reporting back the value of pulseCount just to see if the problem may lie in overflowing data types etc.

1 Like

The issue may not be with the code.