WaterPulseMeter: onPulse gets called on rising AND falling !?



  • Hi,
    I build up a WaterPulseMeterSensor as the original MySensors example:
    Controller:Arduino nano
    Sensor: TCRT5000 IR Barrier Line Track sensor
    Powered with USB-powersupply.
    Sketch: Original Sensor example sketch - only changed the Interrupt mode from "FALLING" to RISING", as you can see below.

    
    #define MY_DEBUG                              
    #define MY_RADIO_NRF24                          
    #define MY_RF24_PA_LEVEL RF24_PA_MAX          
    #define MY_PARENT_NODE_ID 0                     
    #define MY_PARENT_NODE_IS_STATIC                
    
    #include <SPI.h>
    #include <MySensors.h>  
    
    #define DIGITAL_INPUT_SENSOR 3                  // The digital input you attached your sensor.  (Only 2 and 3 generates interrupt!)
    
    #define PULSE_FACTOR 1000                       // Nummber of blinks per m3 of your meter (One rotation/liter)
    
    #define SLEEP_MODE false                        // flowvalue can only be reported when sleep mode is false.
    
    #define MAX_FLOW 40                             // Max flow (l/min) value to report. This filters outliers.
    
    #define CHILD_ID 1                              // Id of the sensor child
    
    unsigned long SEND_FREQUENCY = 30000;           // Minimum time between send (in milliseconds). We don't want to spam the gateway.
    
    MyMessage flowMsg(CHILD_ID,V_FLOW);
    MyMessage volumeMsg(CHILD_ID,V_VOLUME);
    MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
    
    double ppl = ((double)PULSE_FACTOR)/1000;        // Pulses per liter
    
    volatile unsigned long pulseCount = 0;   
    volatile unsigned long lastBlink = 0;
    volatile double flow = 0;  
    boolean pcReceived = false;
    unsigned long oldPulseCount = 0;
    unsigned long newBlink = 0;   
    double oldflow = 0;
    double volume =0;                     
    double oldvolume =0;
    unsigned long lastSend =0;
    unsigned long lastPulse =0;
    
    void setup()  
    {  
      // initialize our digital pins internal pullup resistor so one pulse switches from high to low (less distortion) 
      pinMode(DIGITAL_INPUT_SENSOR, INPUT_PULLUP);
      
      pulseCount = oldPulseCount = 0;
    
      // Fetch last known pulse count value from gw
      request(CHILD_ID, V_VAR1);
    
      lastSend = lastPulse = millis();
    
      attachInterrupt(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), onPulse, RISING);
    }
    
    void presentation()  {
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("Water Meter", "1.1");
    
      // Register this device as Waterflow sensor
      present(CHILD_ID, S_WATER);       
    }
    
    void loop()     
    { 
      unsigned long currentTime = millis();
      
        // Only send values at a maximum frequency or woken up from sleep
      if (SLEEP_MODE || (currentTime - lastSend > SEND_FREQUENCY))
      {
        lastSend=currentTime;
        
        if (!pcReceived) {
          //Last Pulsecount not yet received from controller, request it again
          request(CHILD_ID, V_VAR1);
          return;
        }
    
        if (!SLEEP_MODE && flow != oldflow) {
          oldflow = flow;
    
          Serial.print("l/min:");
          Serial.println(flow);
    
          // Check that we dont get unresonable large flow value. 
          // could hapen when long wraps or false interrupt triggered
          if (flow<((unsigned long)MAX_FLOW)) {
            send(flowMsg.set(flow, 2));                   // Send flow value to gw
          }  
        }
      
        // No Pulse count received in 2min 
        if(currentTime - lastPulse > 120000){
          flow = 0;
        } 
    
        // Pulse count has changed
        if ((pulseCount != oldPulseCount)||(!SLEEP_MODE)) {
          oldPulseCount = pulseCount;
    
          Serial.print("pulsecount:");
          Serial.println(pulseCount);
    
          send(lastCounterMsg.set(pulseCount));                  // Send  pulsecount value to gw in VAR1
    
          double volume = ((double)pulseCount/((double)PULSE_FACTOR));     
          if ((volume != oldvolume)||(!SLEEP_MODE)) {
            oldvolume = volume;
    
            Serial.print("volume:");
            Serial.println(volume, 3);
            
            send(volumeMsg.set(volume, 3));               // Send volume value to gw
          } 
        }
      }
      if (SLEEP_MODE) {
        sleep(SEND_FREQUENCY);
      }
    }
    
    void receive(const MyMessage &message) {
      if (message.type==V_VAR1) {
        unsigned long gwPulseCount=message.getULong();
        pulseCount += gwPulseCount;
        flow=oldflow=0;
        Serial.print("Received last pulse count from gw:");
        Serial.println(pulseCount);
        pcReceived = true;
      }
    }
    
    void onPulse()     
    {
      if (!SLEEP_MODE)
      {
        unsigned long newBlink = micros();   
        unsigned long interval = newBlink-lastBlink;
        
        if (interval!=0)
        {
          lastPulse = millis();
          if (interval<500000L) {
            // Sometimes we get interrupt on RISING,  500000 = 0.5sek debounce ( max 120 l/min)
            return;   
          }
          flow = (60000000.0 /interval) / ppl;
        }
        lastBlink = newBlink;
      }
      pulseCount++; 
    }
    

    Here is what happens:
    When DigitalSensorPIN is RISING, the onPulse function is called as expected, but when DigitalSensorPIN is FALLING, the onPulse function is called also, which should not happen, right!??
    So this gives me a wrong consumption of 2liters per rotation, instead of 1liter.

    Has someone any idea what is going wrong?


  • Hardware Contributor

    Maybe you can add a Serial.println("pulse") in your code to see whats happening.

    Also you can try adding more time to:

    if (interval<500000L) {
            // Sometimes we get interrupt on RISING,  500000 = 0.5sek debounce ( max 120 l/min)
            return;   
    


  • thx sundberg84 for your fast response.

    meanwhile i added Serial.println("onPulse called"); before pulseCount++ in onPulse-function.
    The Serial Monitor confirms, the above problem.

    I turn on the water tap, till the position of my water counter wheel is exactly in position to trigger the RISING interrupt of the sensor, then i close the water tap. So the signal is constantly high. the serialmonitor prints, onPulse called, everything seems ok. after a while (seconds or minutes, tried both) i turn on the water tap again, till the position of the water counter wheel lets FALLING the signal from sensor, and turn of the water tap again. So signal is constantly low. In this moment the monitor prints also "onPulse called".

    Due to the described behavior I do not think it is up to the interval time, or?


  • Hardware Contributor

    @vga - ok, no you are right.
    To which input have you connected your pulsecounter? I only think D2+D3 has the possibility to detect FALLING/RISING while the other ones only can detect a CHANGE.



  • I connected the sensor to D3 pin.


  • Hardware Contributor

    @vga - I would measure the volt on the dataline for the sensor to exclude its false readings coming from the sensor then.


  • Mod

    My guess is that there is a bounce when going from high to low. Because more than 0.5s has passed since the signal went from low to high, the bounce is seen as a new transition to high.

    You could try to do one or a few digitalread inside onPulse to make sure the signal stays high long enough to really be high.


  • MySensors Evangelist

    I've seen this behaviour before. Modified the whole sketch by polling.
    Still has to be converted into 2.0.0

    /*
     * Sketch to read pulses from pulse meter; 
     * Only accepts a pulse change if the signal has been stable for more than x seconds;
     * Only sends message to gateway after more than x seconds have passed since sending last message;
     * Pulses are assumed to equal 1 liter of water
     * 
     * Author  Sandor Incze & Michel Schilthuizen
     * Created 2016-05-29
     * Version 1.0
     *
     * Based on the flow reader sketch "WaterMeterPulseSensor" by 
     * Henrik Ekblad <henrik.ekblad@mysensors.org>
     * Copyright (C) 2013-2015 Sensnology AB
     * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
     * 
     * During testing in can be handy to be able to set the values in domoticz; You can do this by using the REST request below:
     * (replace x.x.x.x and deviceid by relevant numbers
     * http://x.x.x.x:8080/json.htm?type=command&param=udevice&idx=deviceid&nvalue=0&svalue=0
     */
    
    #include <SPI.h>
    #include <MySensor.h>  
    #define IN_PIN 3                              // PIN NUMBER OF DIGITAL OUTPUT TCRT5000
    #define INTERRUPT_DEBOUNCE_DELAY 2            
    #define CHILD_ID 1                              // Id of the sensor child
    
    unsigned long SEND_FREQUENCY = 30000;           // Minimum time between send (in milliseconds). We don't want to spam the gateway.
    MySensor gw;
    MyMessage flowMsg(CHILD_ID,V_FLOW);
    MyMessage volumeMsg(CHILD_ID,V_VOLUME);
    MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
    
    volatile double flow = 0;  
    boolean gw_received = false;
    boolean seeninterrupt = false;
    
    double flowSent =0;                     
    
    volatile unsigned long globalCounter  = 0;
    volatile unsigned long old_globalCounter =0;
    unsigned long lastSent =0;
    unsigned long lastPulse =0;
    
    static volatile int last_pin_value = 0;
    static volatile int stable_pin_value = 0;
    static volatile int last_stable_pin_value = 0;
    static unsigned long lastInterrupt = 0;
    volatile unsigned long gwPulseCount = 0;
    
    void incomingMessage(const MyMessage &message) {
      if (message.type==V_VAR1) {
        gwPulseCount=message.getULong();
        Serial.print("--- Variable gwPulseCount get value from gw: ");
        Serial.println(gwPulseCount);
        Serial.print("--- Variable gwPulseCount needs a correction -1 liter: ");
        gwPulseCount--;
        Serial.println(gwPulseCount);
        
        if (globalCounter != gwPulseCount) 
        {
          globalCounter += (gwPulseCount);
          Serial.print("--- Received last pulse count from gw: ");
          Serial.println(globalCounter);
          //use line below to reset your counter in domoticz; We needed it ;-)
          // globalCounter = 301093;
          gw_received = true;
          old_globalCounter = globalCounter;
        }
      }
    } 
    
    void setup() {
      gw.begin(incomingMessage); 
    
      // Send the sketch version information to the gateway and Controller
      gw.sendSketchInfo("MiSa Water Meter", "1.0");
    
      // Register this device as Waterflow sensor
      gw.present(CHILD_ID, S_WATER);       
      
      lastInterrupt = millis();
    }
    
    void loop() {
      if (!gw_received) {
          //Last Pulsecount not yet received from controller, request it again
          Serial.println("--- Requesting as nothing was received from gateway yet!");
          gw.request(CHILD_ID, V_VAR1);
          return;
      }
      else 
      {
        int pin_value = digitalRead(IN_PIN);
      
        // Calculate time passed since last interrupt
        unsigned long timedifference = millis() - lastInterrupt;
      
        // In case millis cycled to 0 (as it goes beyong max unsigned long) reset values
        if (timedifference < 0)
        {
          lastInterrupt = 0;
          timedifference = 0;
          lastSent = 0;
        }
       
        if (pin_value != last_pin_value) //if the pin changes from high to low or low to high
        {
          Serial.println("--- Pin value different!");
          lastInterrupt=millis();
          last_pin_value = pin_value;
        }
        else if ((stable_pin_value != pin_value) && (timedifference >= (INTERRUPT_DEBOUNCE_DELAY * 1000))) //if stable pin is different from the pin and more than debounce delay has passed, switch stable pin
        {
          stable_pin_value = pin_value;
          Serial.println("--- Stable pin value different!");
        }
      
        if ((stable_pin_value != last_stable_pin_value) && (stable_pin_value == HIGH))  
        {// Only raise counter if filtered pin value goes from 0 to 1
          ++globalCounter;
          seeninterrupt = true;
          Serial.print("--- Real Interrupt NOW; Global counter is now: "); //  Debug Information will show the interrupt we will count.
          Serial.println(globalCounter);
        }
    
        //reset last_stable_pin_value also when it has changed to LOW
        if (stable_pin_value != last_stable_pin_value)
        {
          last_stable_pin_value = stable_pin_value;
        }
    
        //if we have seen an interrupt and the send frequency has passed, send message to gateway; 
        //Also send a message if last message has been sent more than x seconds ago and flow is still more than 0
        if ( (gw_received) && (millis() - lastSent > SEND_FREQUENCY) 
          && (seeninterrupt || flowSent > 0))
        {
          double liters = globalCounter - old_globalCounter;
          float minutes_passed = 1.0 * (millis()- lastSent) / 60000.0;
          double liters_per_minute = 1.0 * liters / minutes_passed; 
          flowSent = liters_per_minute;
          
          Serial.print("Minutes Passed: ");
          Serial.println(minutes_passed);
          Serial.print("Aantal Liters: ");
          Serial.println(liters);
          Serial.print("Liters per Minuut: ");
          Serial.println(liters_per_minute);
      
          // gw.send(lastCounterMsg.set(globalCounter));                  // Send  globalcounter value to gw in VAR1
          gw.send(flowMsg.set(liters_per_minute, 2));                      // Send flow value to gw
          gw.send(volumeMsg.set(1.0 * globalCounter / 1000, 3));                // Send volume value to gw and convert from dm3 to m3
    
          Serial.print("--- Globalcounter is now: "); //  Debug Information will show the interrupt we will count.
          Serial.println(globalCounter);
          Serial.print("--- Globalcounter dm3 to m3: "); //  Debug Information will show the interrupt we will count.
          Serial.println(1.0 * globalCounter / 1000);
        
              
          seeninterrupt = false;
          lastSent = millis();
          old_globalCounter = globalCounter;
        }
      }
    }```


  • thanks mfalkvidd and since!

    now i´ve added the following to the onPulse function:

    delay(150);
      if(digitalRead(DIGITAL_INPUT_SENSOR) == HIGH){
        Serial.println("onPulse called");
        pulseCount++; 
      }
    

    now it is working, thanks for the support to all of you! :)

    btw: is there a way to reset the volume value, without running the cleareeprom sketch, so that i can keep the sensors node id!? :)


  • Mod

    @vga the pulse count is stored in the controller, so it needs to be reset there.


Log in to reply
 

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.