Rain Guage



  • @BulldogLowell I also busy building an rain gauge. How is your one performing? Now did you set this up in the arduino? How did you do the calculation?

    Regards

    Francois


  • Contest Winner

    @Francois

    yes is working still, I mentioned how I did the math above...



  • @BulldogLowell do you mind sharing the sketch I not seeing in this forum or on the Build pages of MySensor.org


  • Contest Winner

    @Francois

    Sure, but I have that running at my 'other' house and it is on older version of MySensors (haven't upgraded there yet 😉

    I'll take a whack at updating if you like... it needs to get done anyway... here it is:

    /*
     Arduino Tipping Bucket Rain Gauge
     
     June 15, 2014
     
     Version 1.00b   
     
     Arduino Tipping Bucket Rain Gauge
     
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org 
     gateway you can measure and sense local rain.  This sketch will create two devices on your 
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours) 
     above a threshold.  Both these settings are user definable. 
     
     This sketch features the following:
     
     * Allows you to set the rain threshold in mm  
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 24, 48, 72, 96 and 120 hours total rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every 3 hours.
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power outage, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEBPROM
     * There is a unique setup requirement necessary in order to properly present the Vera device 
       variables.  The details are outlined in the sketch below.
     * LED status indicator
     
     by BulldogLowell@gmail.com for free public use
     
     */
    #include <SPI.h>
    #include <EEPROM.h>  
    #include <RF24.h>
    #include <Sensor.h>
    //
    #define STATE_LOCATION 513 // stay away from EEPROM used with Sensor.h
    #define EEPROM_BUFFER 514  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121
    //
    Sensor gw;
    boolean metric = false;
    //
    int eepromIndex;
    int tipSensorPin = 3;
    int ledPin = 5; //PWM capable required
    unsigned long dataMillis;
    unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 60000UL;
    unsigned long lastTipTime;
    unsigned long lastBucketInterval;
    unsigned long startMillis;
    int dayBuckets [5] [2] = {
     { V_VAR1, 24 },
     { V_VAR2, 48 },
     { V_VAR3, 72 },
     { V_VAR4, 96 },
     { V_VAR5, 120},
     };
    volatile byte rainBucket [120] ; // 5 days of data
    const unsigned long calibrateFactor = 1UL; //Calibration in milimeters of rain per single tip
    unsigned long rainRate = 0;
    float currentRain = 0;
    boolean wasTipped = false;
    boolean updateVera;
    int rainCount;
    volatile int tipBuffer = 0;
    byte rainWindow = 72;//default rain window in hours
    int rainSensorThreshold = 15;//default rain sensor sensitivity in mm
    byte hourCount = 24;
    byte state;
    byte oldState = -1;
    unsigned long ledPulseTime = 500UL;
    unsigned long pulseStart;
    //
    void setup()  
    { 
      Serial.begin(115200);
      gw.begin();
      //
      pinMode(tipSensorPin, OUTPUT);
      attachInterrupt (1, sensorTipped, CHANGE);
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      gw.sendSketchInfo("Rain Guage", "1.00b"); 
      gw.sendSensorPresentation(1, S_RAIN);
      gw.sendSensorPresentation(2, S_MOTION);
      Serial.println(F("Sensor Presentation Complete"));
      pinMode(tipSensorPin, INPUT);
      //
      state = EEPROM.read(STATE_LOCATION);
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
        byte locator = EEPROM.read(EEPROM_BUFFER + i);
        if (locator == 0xFF)
        {
          eepromIndex = EEPROM_BUFFER + i;
          loadRainArray(eepromIndex);
          Serial.println(eepromIndex);
          break;
        }
      }
      //
      gw.sendVariable(2, V_TRIPPED, state);
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;
      // uncomment the following block the first time you include this device.  The delays
      // are necessary in order to create the five Variable buckets in the correct order.
      // Once you create them, you can comment out these lines and re-upload to your arduino
      //------------------------------------------------------
      /*
      for (int j = 0; j < 5; j++)
       {
       delay(2000);
       gw.sendVariable(1, dayBuckets[j] [0], 1);
       }
       delay(2000);
       gw.sendVariable(2,V_VAR1, 1);
       delay(2000);
       gw.sendVariable(2,V_VAR2, 1);
       */
      //------------------------------------------------------  
      rainWindow = atoi(gw.getStatus(2, V_VAR1));
      delay(2000);
      rainSensorThreshold = atoi(gw.getStatus(2, V_VAR2));
      gw.sendVariable(1, V_RAINRATE, 0);
      Serial.print(F("Radio Done"));  
      analogWrite(ledPin, 20);
    }
    //
    void loop()     
    { 
      unsigned long measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure = measure + rainBucket [i];
      }
      measure >= rainSensorThreshold ? state = 1: state = 0;
      if (millis() - pulseStart < ledPulseTime)
      {
        analogWrite(ledPin, 255);
      }
      else 
      {
        analogWrite(ledPin, 20);
      }
      if (state != oldState)
      {
        gw.sendVariable(2, V_TRIPPED, state);
        EEPROM.write(STATE_LOCATION, state);
        oldState = state;
      }
      //
      if (millis() - lastTipTime >= oneHour)// timeout for rain rate
      {
        if (rainRate != 0)
        {
          rainRate = 0;
          gw.sendVariable(1, V_RAINRATE, rainRate);
        }
      }
      if (updateVera)
      {
        gw.sendVariable(1, V_RAINRATE, rainRate);
        updateVera = false;
      }
      //
      if ( (millis() - dataMillis) >= serialInterval)//Comment back in this block to enable Serial prints
      {
        for (int i = 24; i <= 120; i=i+24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
      //
      if (tipBuffer > 0)
      {
        Serial.println(F("Sensor Tipped"));
        rainBucket [0] ++;
        pulseStart = millis();
        if (rainBucket [0] > 253) rainBucket[0] = 253; // odd occurance but prevent overflow
        int dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        gw.sendVariable(1, V_RAIN, dayTotal);
        unsigned long tipDelay = millis() - lastTipTime;
        if (tipDelay <= oneHour) 
        {
          rainRate = ((oneHour) / tipDelay);
          gw.sendVariable(1, V_RAINRATE, rainRate);
          Serial.print(F("RainRate= "));
          Serial.println(rainRate);
        }
        lastTipTime = millis();
        updateVera = true;
        tipBuffer--;
      }
      if ( (millis()- startMillis) >= oneHour)
      {
        Serial.println("one hour elapsed");
        //EEPROM write last value
        EEPROM.write(eepromIndex, rainBucket[0]);
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER;
        Serial.println(eepromIndex);
        EEPROM.write(eepromIndex, 0xFF);
        //
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.sendVariable(1, V_RAIN, tipCounter(24));// send 24hr tips
        startMillis = millis();
        for (int j = 0; j < 5; j++)
        {
          int total = 0;
          for (int i = 0; i < dayBuckets[j][1]; i++)
          {
            total = total + rainBucket [i];
          }
          gw.sendVariable( 1, dayBuckets[j] [0], total);
        }
        hourCount++;
        if (hourCount >=3)//8 times daily update the Sensor variables
        {
          rainWindow = atoi(gw.getStatus(2, V_VAR1));
          if (rainWindow < 6) 
          {
            rainWindow = 6;
            gw.sendVariable( 2, V_VAR1, rainWindow);
          }
          if (rainWindow > 120) 
          {
            rainWindow = 120;
            gw.sendVariable( 2, V_VAR2, rainWindow);
          }
          rainSensorThreshold = atoi(gw.getStatus(2, V_VAR2));
          delay(2000);
          //
          if (rainSensorThreshold < 1) 
          {
            rainSensorThreshold = 1;
            gw.sendVariable( 2, V_VAR2, rainSensorThreshold);
          }
          if (rainSensorThreshold > 1000) 
          {
            rainSensorThreshold = 1000;
            gw.sendVariable( 2, V_VAR2, rainSensorThreshold);
          }
          //
          hourCount = 0;
        }
      }
    }
    //
    void sensorTipped()
    {
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 200)
      {
        tipBuffer++;
      }
      last_interrupt_time = interrupt_time;
    }
    //
    unsigned long tipCounter(int x)
    {
      int tipCount = 0;
      for ( int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      return tipCount;
    }
    //
    void updateSerialData(int x)
    {
      Serial.print(F("Tips last "));
      Serial.print(x);
      Serial.print(F("hours: "));
      int tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      Serial.println(tipCount);
    }
    void loadRainArray(int value)
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
       {
         value--;
         Serial.println(value);
         if (value < EEPROM_BUFFER) 
           {
             value = EEPROM_BUFFER + BUFFER_LENGTH;
           }
         byte rainValue = EEPROM.read(value);
         Serial.println(rainValue);
         if (rainValue < 255)
         {
           rainBucket[i] = rainValue;
         }
         else
         {
           rainBucket [i] = 0;
         }
       }
    }


  • Hey Francois

    Thanks for a nice projekt. I am thinking on building one myself. But is it possible to log the rain for everyday and writing the data to a csv file so I can use the data in Excel on Windows.

    Hope that you can help my.

    Tom


  • Contest Winner

    @tomrask

    Tom,

    Are you wishing to

    1. log to an SD card
    2. collect data controller side (e.g. Vera or other controller)
    3. transmit data directly to a server
    4. something else....?

  • Contest Winner

    @BulldogLowell said:

    #define STATE_LOCATION 513 // stay away from EEPROM used with Sensor.h
    #define EEPROM_BUFFER 514  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121
    

    @hek

    are we still required to steer clear of MySensor's EEPROM in addresses 0-512?



  • I use a vera light with a USB key where the data can be stored. It vill be better if I the date was on my dropbox og sendt to my NAS.

    But I have not fount a way to do it.

    Tom


  • Admin

    @hek

    are we still required to steer clear of MySensor's EEPROM in addresses 0-512?

    Use the saveState()/loadState() function in the MySensors library



  • @BulldogLowell

    Hi!!

    I was searching for a long time in internet any place where someone offers a design of this mechanism of rain gauge. Finally I've found you!. I really interested in having one, but I don't have a 3D (not jet!). Could you provide me one print? Let me know how is the process to get one, please ! (I am in Spain!)

    Thank you


  • Contest Winner

    @nostradamux

    I actually used www.3dhubs.com which I believe is a Dutch company. I looked, they have service in Spain!

    the service connects you with owners of 3D printers who make money using their personal machines.



  • @nostradamux I had one printed using @BulldogLowell design and thru 3Dhubs.com the build was good and I used abs also for material. My cost in USA was approx. $17 usd and took a day to print and receive, looks good.
    However I have not gotten far with project due to other issues unrealated just with I had more time and experience.

    But this group is most helpful for newbies!
    Good luck


  • Hero Member

    @BulldogLowell could you convert your 123D file to STL so that the 3DHub could be used please ?

    this is how to do it:
    http://sitesupport.123dapp.com/entries/20540436-How-to-export-an-STL-file-for-3D-printing-in-123D

    thanks,

    😉


  • Contest Winner


  • Admin

    @BulldogLowell Did you ever get around to updating the sketch to work with version 1.4.1? I'm finally getting around to making a rain gauge and I didn't want to recreate the wheel if you already made it and I missed it somewhere.

    Thanks,

    Pete


  • Contest Winner

    @petewill

    Sorry for the lag time, I've been on Easter vacation with the family!

    Yes, I did update it, but I don't have my laptop with me here. I'll post it when I return home, early next week.


  • Admin

    @BulldogLowell Great, thanks! You are a way better coder than me and I was struggling to understand the code. I did learn quite a bit about interrupts when I was trying to figure it out though.


  • Admin

    @BulldogLowell Any luck finding the sketch? Sorry to bug you, I was just hoping to get this project completed soon so I can work on the irrigation controller next. It's almost time to start watering the lawn/plants... Thanks!


  • Contest Winner

    @petewill
    I am embarrassed to tell you that after updating my arduino software a while back, I cannot locate a compilable sketch!

    I have Time Machine, but still can't find it... and it is running on my rain guage!!!

    Here is a version that I have (which is titled RainGuage_V1.4.1.ino in my directory) which i found but cannot compile, it looks like a first save without edit?

    I need to completely update Arduino, the version and mySensors but please look at this:

    /*
     Arduino Tipping Bucket Rain Gauge
     
     June 15, 2014
     
     Version 1.00b   
     
     Arduino Tipping Bucket Rain Gauge
     
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org 
     gateway you can measure and sense local rain.  This sketch will create two devices on your 
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours) 
     above a threshold.  Both these settings are user definable. 
     
     This sketch features the following:
     
     * Allows you to set the rain threshold in mm  
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 24, 48, 72, 96 and 120 hours total rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every 3 hours.
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power outage, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEBPROM
     * There is a unique setup requirement necessary in order to properly present the Vera device 
       variables.  The details are outlined in the sketch below.
     * LED status indicator
     
     by BulldogLowell@gmail.com for free public use
     
     */
    #include <SPI.h>
    #include <EEPROM.h>  
    #include <RF24.h>
    #include <Sensor.h>
    //
    #define STATE_LOCATION 513 // stay away from EEPROM used with Sensor.h
    #define EEPROM_BUFFER 514  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121
    //
    Sensor gw;
    boolean metric = false;
    //
    int eepromIndex;
    int tipSensorPin = 3;
    int ledPin = 5; //PWM capable required
    unsigned long dataMillis;
    unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 60000UL;
    unsigned long lastTipTime;
    unsigned long lastBucketInterval;
    unsigned long startMillis;
    int dayBuckets [5] [2] = {
     { V_VAR1, 24 },
     { V_VAR2, 48 },
     { V_VAR3, 72 },
     { V_VAR4, 96 },
     { V_VAR5, 120},
     };
    volatile byte rainBucket [120] ; // 5 days of data
    const unsigned long calibrateFactor = 1UL; //Calibration in milimeters of rain per single tip
    unsigned long rainRate = 0;
    float currentRain = 0;
    boolean wasTipped = false;
    boolean updateVera;
    int rainCount;
    volatile int tipBuffer = 0;
    byte rainWindow = 72;//default rain window in hours
    int rainSensorThreshold = 15;//default rain sensor sensitivity in mm
    byte hourCount = 24;
    byte state;
    byte oldState = -1;
    unsigned long ledPulseTime = 500UL;
    unsigned long pulseStart;
    //
    void setup()  
    { 
      Serial.begin(115200);
      gw.begin();
      //
      pinMode(tipSensorPin, OUTPUT);
      attachInterrupt (1, sensorTipped, CHANGE);
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      gw.sendSketchInfo("Rain Guage", "1.00b"); 
      gw.sendSensorPresentation(1, S_RAIN);
      gw.sendSensorPresentation(2, S_MOTION);
      Serial.println(F("Sensor Presentation Complete"));
      pinMode(tipSensorPin, INPUT);
      //
      state = EEPROM.read(STATE_LOCATION);
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
        byte locator = EEPROM.read(EEPROM_BUFFER + i);
        if (locator == 0xFF)
        {
          eepromIndex = EEPROM_BUFFER + i;
          loadRainArray(eepromIndex);
          Serial.println(eepromIndex);
          break;
        }
      }
      //
      gw.sendVariable(2, V_TRIPPED, state);
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;
      // uncomment the following block the first time you include this device.  The delays
      // are necessary in order to create the five Variable buckets in the correct order.
      // Once you create them, you can comment out these lines and re-upload to your arduino
      //------------------------------------------------------
      /*
      for (int j = 0; j < 5; j++)
       {
       delay(2000);
       gw.sendVariable(1, dayBuckets[j] [0], 1);
       }
       delay(2000);
       gw.sendVariable(2,V_VAR1, 1);
       delay(2000);
       gw.sendVariable(2,V_VAR2, 1);
       */
      //------------------------------------------------------  
      rainWindow = atoi(gw.getStatus(2, V_VAR1));
      delay(2000);
      rainSensorThreshold = atoi(gw.getStatus(2, V_VAR2));
      gw.sendVariable(1, V_RAINRATE, 0);
      Serial.print(F("Radio Done"));  
      analogWrite(ledPin, 20);
    }
    //
    void loop()     
    { 
      unsigned long measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure = measure + rainBucket [i];
      }
      measure >= rainSensorThreshold ? state = 1: state = 0;
      if (millis() - pulseStart < ledPulseTime)
      {
        analogWrite(ledPin, 255);
      }
      else 
      {
        analogWrite(ledPin, 20);
      }
      if (state != oldState)
      {
        gw.sendVariable(2, V_TRIPPED, state);
        EEPROM.write(STATE_LOCATION, state);
        oldState = state;
      }
      //
      if (millis() - lastTipTime >= oneHour)// timeout for rain rate
      {
        if (rainRate != 0)
        {
          rainRate = 0;
          gw.sendVariable(1, V_RAINRATE, rainRate);
        }
      }
      if (updateVera)
      {
        gw.sendVariable(1, V_RAINRATE, rainRate);
        updateVera = false;
      }
      //
      if ( (millis() - dataMillis) >= serialInterval)//Comment back in this block to enable Serial prints
      {
        for (int i = 24; i <= 120; i=i+24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
      //
      if (tipBuffer > 0)
      {
        Serial.println(F("Sensor Tipped"));
        rainBucket [0] ++;
        pulseStart = millis();
        if (rainBucket [0] > 253) rainBucket[0] = 253; // odd occurance but prevent overflow
        int dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        gw.sendVariable(1, V_RAIN, dayTotal);
        unsigned long tipDelay = millis() - lastTipTime;
        if (tipDelay <= oneHour) 
        {
          rainRate = ((oneHour) / tipDelay);
          gw.sendVariable(1, V_RAINRATE, rainRate);
          Serial.print(F("RainRate= "));
          Serial.println(rainRate);
        }
        lastTipTime = millis();
        updateVera = true;
        tipBuffer--;
      }
      if ( (millis()- startMillis) >= oneHour)
      {
        Serial.println("one hour elapsed");
        //EEPROM write last value
        EEPROM.write(eepromIndex, rainBucket[0]);
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER;
        Serial.println(eepromIndex);
        EEPROM.write(eepromIndex, 0xFF);
        //
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.sendVariable(1, V_RAIN, tipCounter(24));// send 24hr tips
        startMillis = millis();
        for (int j = 0; j < 5; j++)
        {
          int total = 0;
          for (int i = 0; i < dayBuckets[j][1]; i++)
          {
            total = total + rainBucket [i];
          }
          gw.sendVariable( 1, dayBuckets[j] [0], total);
        }
        hourCount++;
        if (hourCount >=3)//8 times daily update the Sensor variables
        {
          rainWindow = atoi(gw.getStatus(2, V_VAR1));
          if (rainWindow < 6) 
          {
            rainWindow = 6;
            gw.sendVariable( 2, V_VAR1, rainWindow);
          }
          if (rainWindow > 120) 
          {
            rainWindow = 120;
            gw.sendVariable( 2, V_VAR2, rainWindow);
          }
          rainSensorThreshold = atoi(gw.getStatus(2, V_VAR2));
          delay(2000);
          //
          if (rainSensorThreshold < 1) 
          {
            rainSensorThreshold = 1;
            gw.sendVariable( 2, V_VAR2, rainSensorThreshold);
          }
          if (rainSensorThreshold > 1000) 
          {
            rainSensorThreshold = 1000;
            gw.sendVariable( 2, V_VAR2, rainSensorThreshold);
          }
          //
          hourCount = 0;
        }
      }
    }
    //
    void sensorTipped()
    {
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 200)
      {
        tipBuffer++;
      }
      last_interrupt_time = interrupt_time;
    }
    //
    unsigned long tipCounter(int x)
    {
      int tipCount = 0;
      for ( int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      return tipCount;
    }
    //
    void updateSerialData(int x)
    {
      Serial.print(F("Tips last "));
      Serial.print(x);
      Serial.print(F("hours: "));
      int tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      Serial.println(tipCount);
    }
    void loadRainArray(int value)
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
       {
         value--;
         Serial.println(value);
         if (value < EEPROM_BUFFER) 
           {
             value = EEPROM_BUFFER + BUFFER_LENGTH;
           }
         byte rainValue = EEPROM.read(value);
         Serial.println(rainValue);
         if (rainValue < 255)
         {
           rainBucket[i] = rainValue;
         }
         else
         {
           rainBucket [i] = 0;
         }
       }
    }
    
    
    
    

    @petewill

    I will convert this this weekend.


  • Contest Winner

    @petewill

    Her is an untested go at it, and a very down and dirty update... I will test it probably Wednesday night, when I am back home. Let me know if you can!

    /*
     Arduino Tipping Bucket Rain Gauge
    
     April 26, 2015
    
     Version 1.01b for MySensors version 1.4.1
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours)
     above a threshold.  Both these settings are user definable.
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 24, 48, 72, 96 and 120 hours total rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every 3 hours.
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power outage, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * There is a unique setup requirement necessary in order to properly present the Vera device
       variables.  The details are outlined in the sketch below.
     * LED status indicator
    
     by BulldogLowell@gmail.com for free public use
    
     */
    #include <SPI.h>
    #include <MySensor.h>
    #include <EEPROM.h>
    
    #define RADIO_ID 99
    
    #define STATE_LOCATION 513 // stay away from EEPROM used with Sensor.h
    #define EEPROM_BUFFER 514  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121  // buffer plus the current hour
    //
    MySensor gw;
    //
    MyMessage msgRainRate(1, V_RAINRATE);
    MyMessage msgRain(1, V_RAIN);
    MyMessage msgRainVAR1(1, V_VAR1);
    MyMessage msgRainVAR2(1, V_VAR2);
    MyMessage msgRainVAR3(1, V_VAR3);
    MyMessage msgRainVAR4(1, V_VAR4);
    MyMessage msgRainVAR5(1, V_VAR5);
    
    MyMessage msgTripped(2, S_MOTION);
    MyMessage msgTrippedVar1(2, V_VAR1);
    MyMessage msgTrippedVar2(2, V_VAR2);
    //
    boolean metric = false;
    //
    int eepromIndex;
    int tipSensorPin = 3;
    int ledPin = 5; //PWM capable required
    unsigned long dataMillis;
    unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 60000UL;
    unsigned long lastTipTime;
    unsigned long lastBucketInterval;
    unsigned long startMillis;
    int dayBuckets [5] [2] = {
      { V_VAR1, 24 },
      { V_VAR2, 48 },
      { V_VAR3, 72 },
      { V_VAR4, 96 },
      { V_VAR5, 120},
    };
    volatile byte rainBucket [120] ; // 5 days of data
    const unsigned long calibrateFactor = 1UL; //Calibration in milimeters of rain per single tip
    unsigned long rainRate = 0;
    float currentRain = 0;
    boolean wasTipped = false;
    boolean updateVera;
    int rainCount;
    volatile int tipBuffer = 0;
    byte rainWindow = 72;         //default rain window in hours
    int rainSensorThreshold = 15; //default rain sensor sensitivity in mm
    byte hourCount = 24;
    byte state;
    byte oldState = -1;
    //
    void setup()
    {
      Serial.begin(115200);
      gw.begin(getVariables, RADIO_ID);
      //
      pinMode(tipSensorPin, OUTPUT);
      attachInterrupt (1, sensorTipped, CHANGE);
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      gw.sendSketchInfo("Rain Guage", "1.01b");
      gw.present(1, S_RAIN);
      gw.present(2, S_MOTION);
      Serial.println(F("Sensor Presentation Complete"));
      pinMode(tipSensorPin, INPUT);
      //
      state = EEPROM.read(STATE_LOCATION);    //retreive prior state from EEPROM
      gw.send(msgTripped.set(state), false);  //send state to Vera
      delay(250);                             // recharge the capacitor
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
        byte locator = EEPROM.read(EEPROM_BUFFER + i);
        if (locator == 0xFF)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER + i;
          loadRainArray(eepromIndex);
          isDataOnEeprom = true;
          Serial.println(eepromIndex);
          break;
        }
      }
      //
      if (!isDataOnEeprom)
      {
        // load all zeroes to EEPROM?
        // I gotta check this out!!!
      }
      //
      // reset the timers
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;
      gw.request(2, V_VAR1);
      delay(250);
      gw.request(2, V_VAR2);
      delay(250);
      Serial.print(F("Radio Done"));
      analogWrite(ledPin, 20);
    }
    //
    void loop()
    {
      gw.process();
      pulseLED();
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
      int measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
      }
      state = (measure >= rainSensorThreshold);
      if (state != oldState)
      {
        gw.send(msgTripped.set(state), false);
        delay(250);
        EEPROM.write(STATE_LOCATION, state);
        oldState = state;
      }
      //
      // Now lets reset the rainRate to zero if no tips in the last hour
      //
      if (millis() - lastTipTime >= oneHour)// timeout for rain rate
      {
        if (rainRate != 0)
        {
          rainRate = 0;
          gw.send(msgRainRate.set(0));
          delay(250);
        }
      }
      // if tipped,send updates to VERA
      //
      if (updateVera)
      {
        gw.send(msgRainRate.set(rainRate));
        delay(250);
        updateVera = false;
      }
      /*
      if ( (millis() - dataMillis) >= serialInterval)//Comment back in this block to enable Serial prints
      {
        for (int i = 24; i <= 120; i = i + 24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
      */
      if (tipBuffer > 0)
      {
        Serial.println(F("Sensor Tipped"));
        rainBucket [0] ++;
        //
        if (rainBucket [0] > 253) rainBucket[0] = 253; // odd occurance but prevent overflow
        int dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        gw.send(msgRain.set(dayTotal));
        delay(250);
        unsigned long tipDelay = millis() - lastTipTime;
        if (tipDelay <= oneHour)
        {
          rainRate = ((oneHour) / tipDelay);
          gw.send(msgRainRate.set(rainRate));
          Serial.print(F("RainRate= "));
          Serial.println(rainRate);
        }
        lastTipTime = millis();
        updateVera = true;
        tipBuffer--;
      }
      if (millis() - startMillis >= oneHour)
      {
        Serial.println("one hour elapsed");
        //EEPROM write last value
        EEPROM.write(eepromIndex, rainBucket[0]);
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER;
        Serial.println(eepromIndex);
        EEPROM.write(eepromIndex, 0xFF);
        //
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.send(msgRain.set(tipCounter(24)));// send 24hr tips
        delay(250);
        transmitRainData(); // send all of the 5 buckets of data to controller
        startMillis = millis();
      }
    }
    //
    void sensorTipped()
    {
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 200)
      {
        tipBuffer++;
      }
      last_interrupt_time = interrupt_time;
    }
    //
    int tipCounter(int hours)
    {
      int tipCount = 0;
      for ( int i = 0; i < hours; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      return tipCount;
    }
    //
    void updateSerialData(int x)
    {
      Serial.print(F("Tips last "));
      Serial.print(x);
      Serial.print(F("hours: "));
      int tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      Serial.println(tipCount);
    }
    void loadRainArray(int value)
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
      {
        value--;
        Serial.println(value);
        if (value < EEPROM_BUFFER)
        {
          value = EEPROM_BUFFER + BUFFER_LENGTH;
        }
        byte rainValue = EEPROM.read(value);
        Serial.println(rainValue);
        if (rainValue < 255)
        {
          rainBucket[i] = rainValue;
        }
        else
        {
          rainBucket [i] = 0;
        }
      }
    }
    
    void transmitRainData(void)
    {
      int rainUpdateTotal = 0;
      for (int i = 0; i < 24; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR1.set(rainUpdateTotal));
      delay(250);
      for (int i = 24; i < 48; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR2.set(rainUpdateTotal));
      delay(250);
      for (int i = 48; i < 72; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR3.set(rainUpdateTotal));
      delay(250);
      for (int i = 72; i < 96; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR4.set(rainUpdateTotal));
      delay(250);
      for (int i = 96; i < 120; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR5.set(rainUpdateTotal));
      delay(250);
    }
    
    void getVariables(const MyMessage &message)
    {
      if (message.sensor == 1)
      {
        // nothing to do here
      }
      else if (message.sensor == 2)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 6)
          {
            rainWindow = 6;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 1000)
          {
            rainSensorThreshold = 1000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    void pulseLED(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 500UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }
    
    
    

  • Admin

    @BulldogLowell Awesome, thank you very much! I know what it's like to lose code that way. I have done the same thing myself...

    Yes, I will test this. I'll do my best to get it tested tonight. I'm combining it with some other sensors (light and temp/humidity) so once I get everything merged I'll be able to upload it.

    I'll let you know what I find.

    Thanks again!

    Pete


  • Admin

    @BulldogLowell

    Ok, I made the necessary changes for my application and I had a couple of questions along the way.

    • I have never worked with eeprom so I don't pretend to know anything about it (it's now on my list of things to research though). Anyway, I noticed above hek recommended using the "saveState()/loadState() function in the MySensors library". Is that necessary in your code or are we ok as is? It seems to be functioning ok for me when I compile it so maybe not...

    • I would like to trigger my rain sensor as soon as the first bucket has tipped (I have scenes that tell me to close the windows etc. when it's raining). Is changing the rainSensorThreshold value to 1 an appropriate way to do that or should I create another child node for my purposes? Maybe I should use rainRate in my PLEG instead?

    • My tipping bucket was pulled from an existing rain gauge and according to my calculations (which may be off) each bucket is .7mm of rain. I don't know much about unsigned long variables but I'm wondering if I need to change "const unsigned long calibrateFactor" to a float or something?

    Thanks for all your help!!

    Pete


  • Admin

    Jim,

    I uploaded the code but it didn't seem to work for me. Do I need to upload some additional files to Vera? When I added the nodes a lot of my devices stopped displaying correctly (not just the newly added ones). They look like this:
    upload-f009544f-e06e-4001-abba-63de772a744f

    When I deleted the rain gauge nodes everything came back:
    upload-2a04de4c-b861-4e84-892d-2ae37591ad0e

    As I was looking at the code I came up with some additional questions:

    • There is a dayBuckets array but it doesn't seem to do anything in the code?

    • I also couldn't find "const unsigned long calibrateFactor" used anywhere.

    I fully admit I'm not a very good with code and I could be missing something. I was just trying to find how the variables were working together and being calculated.

    Thanks for all your help with this! I could not do it without you.

    Pete


  • Hero Member

    @petewill The reason your MySensor plugin brakes when you add the rain gauge is that the device_files are missing. I don't know if there are any device files for the rain gauge since they are not included in the github branch for the vera plugin.

    You could check under your rain gauge which device_file it is trying to use maybe you can search for it online and hope it exists.
    Worst case is that you need to create them your self.


  • Contest Winner

    yeah, you need the implemention files:

    D_RainSensor1.xml:

    <?xml version="1.0"?>
    <root xmlns="urn:schemas-upnp-org:device-1-0">
      <specVersion>
        <major>1</major>
        <minor>0</minor>
      </specVersion>
      <device>
        <deviceType>urn:schemas-micasaverde-com:device:RainSensor:1</deviceType>
        <Category_Num>12</Category_Num>
    	<staticJson>D_RainSensor1.json</staticJson>
        <serviceList>
          <service>
            <serviceType>urn:schemas-upnp-org:service:RainSensor:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:RainSensor1</serviceId>
            <SCPDURL>S_RainSensor1.xml</SCPDURL>
          </service>
          <service>
            <serviceType>urn:schemas-micasaverde-com:service:HaDevice:1</serviceType>
            <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
            <SCPDURL>S_HaDevice1.xml</SCPDURL>
          </service>
        </serviceList>
      </device>
    </root>
    

    and

    D_RainSensor1.json

    {
        "flashicon": "icons/Humidity_Sensor.swf",
        "imgIconBody": "",
        "imgIconDimmable": "",
        "imgIconTurnable": "",
        "imgIconMin": "",
        "imgIconMax": "",
        "halloIconsDir": "pics\/hallo",
        "inScene": "0",
        "DisplayStatus": {},
        "doc_url": {
            "doc_language": 1,
            "doc_manual": 1,
            "doc_version": 1,
            "doc_platform": 0,
            "doc_page": "sensors" 
        },
    	
        "Tabs": [
            {
                "Label": {
                    "lang_tag": "tabname_control",
                    "text": "Information" 
                },
                "Position": "0",
                "TabType": "flash",
                "SceneGroup": [
                    {
                        "id": "1",
                        "top": "1.5",
                        "left": "0",
                        "x": "2",
                        "y": "2"
                    }
                ],
                "ControlGroup": [
                    {
                        "id": "1",
                        "scenegroup": "1",
                        "type": "info"
                    },
                    {
                        "id": "2",
                        "scenegroup": "1",
                        "type": "info"
                    }
                ],
                "Control": [
                  {
                    "ControlGroup":"1",
                    "ControlPair": "1",
                    "ControlHeader": "1",
                    "top": "1",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_rate",
                        "text": "Rate of Rain" 
                    },
                    "Display": {
                        "Top": 20,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"1",
                    "ControlPair": "1",
                    "ControlHeader": "1",
                    "top": "1",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:RainSensor1",
                        "Variable": "CurrentRain",
                        "Top": 20,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "total_rain_unit",
                        "text": "mm/hr" 
                    },
                    "Display": {
                        "Top": 20,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlGroup":"2",
                    "ControlPair": "2",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "total_rain",
                        "text": "24hrs Total:",
                    },
                    "Display": {
                        "Top": 55,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"2",
                    "ControlPair": "2",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:RainSensor1",
                        "Variable": "CurrentTRain",
                        "Top": 55,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_rate_unit",
                        "text": "mm" 
                    },
                    "Display": {
                        "Top": 55,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlGroup":"3",
                    "ControlPair": "3",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_forty",
                        "text": "48hrs Total:" 
                    },
                    "Display": {
                        "Top": 80,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"3",
                    "ControlPair": "3",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:VContainer1",
                        "Variable": "Variable2",
                        "Top": 80,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_unit",
                        "text": "mm" 
                    },
                    "Display": {
                        "Top": 80,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlGroup":"4",
                    "ControlPair": "4",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_seventy",
                        "text": "72hrs Total:" 
                    },
                    "Display": {
                        "Top": 105,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"4",
                    "ControlPair": "4",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:VContainer1",
                        "Variable": "Variable3",
                        "Top": 105,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_unit1",
                        "text": "mm" 
                    },
                    "Display": {
                        "Top": 105,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlGroup":"5",
                    "ControlPair": "5",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_forty",
                        "text": "96hrs Total:" 
                    },
                    "Display": {
                        "Top": 130,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"5",
                    "ControlPair": "5",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:VContainer1",
                        "Variable": "Variable4",
                        "Top": 130,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_unit2",
                        "text": "mm" 
                    },
                    "Display": {
                        "Top": 130,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlGroup":"6",
                    "ControlPair": "6",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "0",
                    "x": "1.25",
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_hundred",
                        "text": "120hrs Total:" 
                    },
                    "Display": {
                        "Top": 155,
                        "Left": 50,
                        "Width": 75,
                        "Height": 20 
                    } 
                  },             
                  {
                    "ControlGroup":"6",
                    "ControlPair": "6",
                    "ControlHeader": "1",
                    "top": "0",
                    "left": "1.25",
                    "x": "0.75",
       		     "ControlType": "variable",
                    "Display": {
                        "Service": "urn:upnp-org:serviceId:VContainer1",
                        "Variable": "Variable5",
                        "Top": 155,
                        "Left": 150,
                        "Width": 25,
                        "Height": 20 
                    } 
                  },
                  {
                    "ControlType": "label",
                    "Label": {
                        "lang_tag": "rain_unit3",
                        "text": "mm" 
                    },
                    "Display": {
                        "Top": 155,
                        "Left": 200,
                        "Width": 25,
                        "Height": 20 
                    } 
                  }
              ]
             },
            {
                "Label": {
                    "lang_tag": "advanced",
                    "text": "Advanced" 
                },
                "Position": "1",
                "TabType": "javascript",
                "ScriptName": "shared.js",
                "Function": "advanced_device" 
            },
            {
                "Label": {
                    "lang_tag": "logs",
                    "text": "Logs"
                },
                "Position": "2",
                "TabType": "javascript",
                "ScriptName": "shared.js",
                "Function": "device_logs"
            },
            {
                "Label": {
                    "lang_tag": "notifications",
                    "text": "Notifications"
                },
                "Position": "3",
                "TabType": "javascript",
                "ScriptName": "shared.js",
                "Function": "device_notifications"
            }
        ],
        "eventList2": [
            {
                "id": 1,
                "label": {
                    "lang_tag": "total_rain_goes_above",
                    "text": "Total rain goes above"
                },
                "serviceId": "urn:upnp-org:serviceId:RainSensor1",
                "norepeat": "1",
                "argumentList": [
                    {
                        "id": 1,
                        "dataType": "r8",
                        "name": "CurrentTRain",
                        "comparisson": ">",
                        "prefix": {
                            "lang_tag": "total_rain",
                            "text": "Total rain"
                        },
                        "suffix": {
                            "lang_tag": "total_rain_unit",
                            "text": "mm"
                        },
                        "HumanFriendlyText": {
                            "lang_tag": "hft_total_rain_goes_above",
                            "text": "Total rain for _DEVICE_NAME_ goes above _ARGUMENT_VALUE_"
                        }
                    }
                ]
            },
            {
                "id": 2,
                "label": {
                    "lang_tag": "total_rain_goes_below",
                    "text": "Total rain goes below"
                },
                "serviceId": "urn:upnp-org:serviceId:RainSensor1",
                "norepeat": "1",
                "argumentList": [
                    {
                        "id": 1,
                        "dataType": "r8",
                        "name": "CurrentTRain",
                        "comparisson": "<",
                        "prefix": {
                            "lang_tag": "total_rain",
                            "text": "Total rain"
                        },
                        "suffix": {
                            "lang_tag": "total_rain_unit",
                            "text": "mm"
                        },
                        "HumanFriendlyText": {
                            "lang_tag": "hft_total_rain_goes_below",
                            "text": "Total rain for _DEVICE_NAME_ goes below _ARGUMENT_VALUE_"
                        }
                    }
                ]
            },
            {
                "id": 3,
                "label": {
                    "lang_tag": "rain_rate_goes_above",
                    "text": "Current rain goes above"
                },
                "serviceId": "urn:upnp-org:serviceId:RainSensor1",
                "norepeat": "1",
                "argumentList": [
                    {
                        "id": 1,
                        "dataType": "r8",
                        "name": "CurrentRain",
                        "comparisson": ">",
                        "prefix": {
                            "lang_tag": "rain_rate",
                            "text": "Current rain"
                        },
                        "suffix": {
                            "lang_tag": "rain_rate_unit",
                            "text": "mm/hr"
                        },
                        "HumanFriendlyText": {
                            "lang_tag": "hft_rain_rate_goes_above",
                            "text": "Current rain for _DEVICE_NAME_ goes above _ARGUMENT_VALUE_"
                        }
                    }
                ]
            },
            {
                "id": 4,
                "label": {
                    "lang_tag": "rain_rate_goes_below",
                    "text": "Current rain goes below"
                },
                "serviceId": "urn:upnp-org:serviceId:RainSensor1",
                "norepeat": "1",
                "argumentList": [
                    {
                        "id": 1,
                        "dataType": "r8",
                        "name": "CurrentRain",
                        "comparisson": "<",
                        "prefix": {
                            "lang_tag": "rain_rate",
                            "text": "Current rain"
                        },
                        "suffix": {
                            "lang_tag": "rain_rate_unit",
                            "text": "mm/hr"
                        },
                        "HumanFriendlyText": {
                            "lang_tag": "hft_rain_rate_goes_below",
                            "text": "Current rain for _DEVICE_NAME_ goes below _ARGUMENT_VALUE_"
                        }
                    }
                ]
            },
            {
                "id": 5,
                "label": {
                    "lang_tag": "battery_level_goes_below",
                    "text": "Battery level goes below"
                },
                "serviceId": "urn:micasaverde-com:serviceId:HaDevice1",
                "argumentList": [
                    {
                        "id": 1,
                        "prefix": {
                            "lang_tag": "Level",
                            "text": "Level"
                        },
                        "dataType": "i4",
                        "name": "BatteryLevel",
                        "comparisson": "<",
                        "suffix": {
                            "lang_tag": "percent_sign",
                            "text": "%"
                        },
                        "HumanFriendlyText": {
                            "lang_tag": "hft_battery_level_goes_below",
                            "text": "Battery level for _DEVICE_NAME_ go below _ARGUMENT_VALUE_%"
                        }
                    }
                ]
            }
        ],
        "DeviceType": "urn:schemas-micasaverde-com:device:RainSensor:1"
    }
    

    S_Rainsensor1.xml:

    <?xml version="1.0"?>
    <scpd xmlns="urn:schemas-upnp-org:service-1-0">
      <specVersion>
        <major>1</major>
        <minor>0</minor>
      </specVersion>
      <serviceStateTable>
        <stateVariable>
          <name>CurrentTRain</name>
          <sendEventsAttribute>yes</sendEventsAttribute>
          <dataType>r8</dataType>
          <shortCode>total_rain</shortCode>
        </stateVariable>
        <stateVariable>
          <name>CurrentRain</name>
          <sendEventsAttribute>yes</sendEventsAttribute>
          <dataType>r8</dataType>
          <shortCode>rain_rate</shortCode>
        </stateVariable>
      </serviceStateTable>
      <actionList>
        <action>
          <name>GetCurrentTRain</name>
          <argumentList>
            <argument>
              <name>RainValue</name>
              <direction>out</direction>
              <relatedStateVariable>CurrentTRain</relatedStateVariable>
            </argument>
          </argumentList>
        </action>
    	<action>
          <name>GetCurrentRain</name>
          <argumentList>
            <argument>
              <name>RainValue</name>
              <direction>out</direction>
              <relatedStateVariable>CurrentRain</relatedStateVariable>
            </argument>
          </argumentList>
        </action>
      </actionList>
    </scpd>
    
    

  • Contest Winner

    @petewill said:

    As I was looking at the code I came up with some additional questions:

    There is a dayBuckets array but it doesn't seem to do anything in the code?

    I also couldn't find "const unsigned long calibrateFactor" used anywhere.

    dayBuckets[] was used in the old code and since I designed 1mm of rain per tip, I never had to implement calibration.


  • Contest Winner

    @petewill said:

    I have never worked with eeprom so I don't pretend to know anything about it (it's now on my list of things to research though). Anyway, I noticed above hek recommended using the "saveState()/loadState() function in the MySensors library". Is that necessary in your code or are we ok as is? It seems to be functioning ok for me when I compile it so maybe not...

    I developed the code before hek integrated EEPROM in his libraries. This uses a circular buffer, so it would take me some time to reprogram for hek's library, so I just kept using the arduino EEPROM.h library I used to develop the original code.

    @petewill said:

    changing the rainSensorThreshold value to 1 an appropriate way to do that

    yes, I hardcoded boundaries, which you will will allow a minimum of 1 (one)

    @petewill said:

    My tipping bucket was pulled from an existing rain gauge and according to my calculations (which may be off) each bucket is .7mm of rain. I don't know much about unsigned long variables but I'm wondering if I need to change "const unsigned long calibrateFactor" to a float or something?

    Sorry, I never put a bucket calibration into my code, I lucked out with the 1mm per tip working.


  • Admin

    @BulldogLowell Thanks for the responses! Ok, I suspected I was missing some implementation files. Thanks for them. Did you create them from scratch? If so, nice!

    I will take a look at the code to see if I can implement the calebrateFactor. I will most likely be back with more questions. 🙂 Thanks again.


  • Admin

    @BulldogLowell
    I have been doing lots of research and working to understand the code so it will work with the calibrateFactor and I have another question... Why is the "tipSensorPin" set to OUTPUT then INPUT? Does that have to do with the interrupt?

    pinMode(tipSensorPin, OUTPUT);
    attachInterrupt (1, sensorTipped, CHANGE); //* should this be RISING instead??
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    gw.sendSketchInfo(SN, SV);
    gw.present(CHILD_ID_RAIN, S_RAIN);
    gw.present(CHILD_ID_TRIPPED_RAIN, S_MOTION);
    Serial.println(F("Sensor Presentation Complete"));
    pinMode(tipSensorPin, INPUT);
    

    Thanks again for your help! I'll post the full code back here when I have it working in case anyone else wants to use a different size tipping bucket.


  • Contest Winner

    @petewill said:

    Why is the "tipSensorPin" set to OUTPUT then INPUT? Does that have to do with the interrupt?

    it looks a lot like a bonehead error in the code!!

    funny how the brain works, I never noticed that...


  • Admin

    @BulldogLowell said:

    it looks a lot like a bonehead error in the code!!

    Ok, thanks. I make those constantly...

    I have been testing the code for the last couple of days and I think this is working but another set of eyes would be good. I added functionality to set the "calibrateFactor" to a decimal. I also updated the save/load EEPROM to work with the MySensors standard. It seems to be working in my testing but I'm no programmer.

    I also have two questions:

    1. Is it necessary to save/load "state" to/from EEPROM? It seems that from my testing the state is set when "measure" is calculated in this line: state = (measure >= rainSensorThreshold);. It seems that measure is evaluated when the sensor first loads and rainBucket [] is loaded from EEPROM. Hopefully that makes sense what I'm asking.

    2. I'm still trying to understand interrupts but I'm wondering if changing to "FALLING" would be more appropriate? Maybe it doesn't matter but I thought I'd check. Here is the line I'm referring to: attachInterrupt (1, sensorTipped, FALLING);

    Here is the code if you wanted to take a look:

     /*
     Arduino Tipping Bucket Rain Gauge
     
     April 26, 2015
    
     Version 1.01b for MySensors version 1.4.1
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours)
     above a threshold.  Both these settings are user definable.
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 24, 48, 72, 96 and 120 hours total rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every 3 hours.
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power outage, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * There is a unique setup requirement necessary in order to properly present the Vera device
       variables.  The details are outlined in the sketch below.
     * LED status indicator
    
     by BulldogLowell@gmail.com for free public use
    
     */
    #include <SPI.h>
    #include <MySensor.h>
    
    //*No longer need the EEPROM.h?
    //#include <EEPROM.h>
    
    #define NODE_ID 24
    #define SN "Rain Gauge"
    #define SV "1.01b"
    
    #define CHILD_ID_RAIN 3 
    #define CHILD_ID_TRIPPED_RAIN 4
    
    #define STATE_LOCATION 0 // location to save state to EEPROM
    #define EEPROM_BUFFER 1  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121  // buffer plus the current hour
    //
    MySensor gw;
    //
    MyMessage msgRainRate(CHILD_ID_RAIN, V_RAINRATE);
    MyMessage msgRain(CHILD_ID_RAIN, V_RAIN);
    MyMessage msgRainVAR1(CHILD_ID_RAIN, V_VAR1);
    MyMessage msgRainVAR2(CHILD_ID_RAIN, V_VAR2);
    MyMessage msgRainVAR3(CHILD_ID_RAIN, V_VAR3);
    MyMessage msgRainVAR4(CHILD_ID_RAIN, V_VAR4);
    MyMessage msgRainVAR5(CHILD_ID_RAIN, V_VAR5);
    
    MyMessage msgTripped(CHILD_ID_TRIPPED_RAIN, V_TRIPPED);
    MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_RAIN, V_VAR1);
    MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_RAIN, V_VAR2);
    //
    boolean metric = false;
    //
    int eepromIndex;
    int tipSensorPin = 3; //Do not change (needed for interrupt)
    int ledPin = 5; //PWM capable required
    unsigned long dataMillis;
    unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 3600000UL;
    unsigned long lastTipTime;
    unsigned long lastBucketInterval;
    unsigned long startMillis;
    
    float rainBucket [120] ; // 5 days of data  //*120 hours = 5 days
    
    float calibrateFactor = .7; //Calibration in milimeters of rain per single tip.  Note: Limit to one decimal place or data may be truncated when saving to eeprom. 
    
    float rainRate = 0;
    volatile int tipBuffer = 0;
    byte rainWindow = 72;         //default rain window in hours
    int rainSensorThreshold = 10; //default rain sensor sensitivity in mm.  Will be overwritten with msgTrippedVar2. 
    byte state = 0;
    byte oldState = -1;
    //
    void setup()
    {
      gw.begin(getVariables, NODE_ID);
    
      pinMode(tipSensorPin, INPUT);
    
    //  attachInterrupt (1, sensorTipped, CHANGE); //* should this be FALLING instead??
      attachInterrupt (1, sensorTipped, FALLING);
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      digitalWrite(tipSensorPin, HIGH); //ADDED. Activate internal pull-up 
      gw.sendSketchInfo(SN, SV);
      gw.present(CHILD_ID_RAIN, S_RAIN);
      gw.present(CHILD_ID_TRIPPED_RAIN, S_MOTION);
    //  Serial.println(F("Sensor Presentation Complete"));
      
    
      state = gw.loadState(STATE_LOCATION); //retreive prior state from EEPROM
    //  Serial.print("Tripped State (from EEPROM): ");
    //  Serial.println(state);
      
      gw.send(msgTripped.set(state==1?"1":"0"));
      delay(250);                             // recharge the capacitor
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
    
        byte locator = gw.loadState(EEPROM_BUFFER + i); //New code
    
        if (locator == 0xFF)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER + i;
          //Now that we have the buffer index let's populate the rainBucket with data from eeprom
          loadRainArray(eepromIndex); 
          isDataOnEeprom = true;
    //      Serial.print("EEPROM Index ");
    //      Serial.println(eepromIndex);
          break;
        }
      }
    
      // reset the timers
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;  //will this work if millis() starts a 0??
      gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR1); //Get rainWindow from controller (Vera)
      delay(250);
      gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR2); //Get rainSensorThreshold from controller (Vera)
      delay(250);
    //  Serial.println("Radio Done");
    //  analogWrite(ledPin, 20);
    
    }
    //
    void loop()
    {
      gw.process();
      pulseLED();
    
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
    
      float measure = 0; // Check to see if we need to show sensor tripped in this block
    
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
    //    Serial.print("measure value (total rainBucket within rainWindow): ");
    //    Serial.println(measure);
      }
      state = (measure >= rainSensorThreshold);
    
      if (state != oldState)
      {
        gw.send(msgTripped.set(state==1?"1":"0"));
        delay(250);
        gw.saveState(STATE_LOCATION, state); //New Code
    //    Serial.print("State Changed. Tripped State: ");
    //    Serial.println(state);
        
        oldState = state;
      }
      //
      // Now lets reset the rainRate to zero if no tips in the last hour
      //
      if (millis() - lastTipTime >= oneHour)// timeout for rain rate
      {
        if (rainRate != 0)
        {
          rainRate = 0;
          gw.send(msgRainRate.set(0));
          delay(250);
        }
      }
    
    ////////////////////////////
    ////Comment back in this block to enable Serial prints
    //  
    //  if ( (millis() - dataMillis) >= serialInterval)
    //  {
    //    for (int i = 24; i <= 120; i = i + 24)
    //    {
    //      updateSerialData(i);
    //    }
    //    dataMillis = millis();
    //  }
    //  
    ////////////////////////////
      
      
      if (tipBuffer > 0)
      {
    //     Serial.println("Sensor Tipped");
        
    
        //******Added calibrateFactor calculations here to account for calibrateFactor being different than 1
        rainBucket [0] += calibrateFactor;
    //    Serial.print("rainBucket [0] value: ");
    //    Serial.println(rainBucket [0]);
        
        if (rainBucket [0] * calibrateFactor > 253) rainBucket[0] = 253; // odd occurance but prevent overflow}}
    
        float dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        
    //    Serial.print("dayTotal value: ");
    //    Serial.println(dayTotal);
        
        gw.send(msgRain.set(dayTotal,1));
        delay(250);
        unsigned long tipDelay = millis() - lastTipTime;
        if (tipDelay <= oneHour)
        {
    
          rainRate = ((oneHour) / tipDelay) * calibrateFactor; //Is my math/logic correct here??
      
          gw.send(msgRainRate.set(rainRate, 1));
    
    //      Serial.print("RainRate= ");
    //      Serial.println(rainRate);
        }
    
        //If this is the first trip in an hour send .1
        else
        {
          gw.send(msgRainRate.set(0.1, 1));
        }
        lastTipTime = millis();
        tipBuffer--;
      }
      if (millis() - startMillis >= oneHour)
      {
    //    Serial.println("One hour elapsed.");
        //EEPROM write last value
        //Converting rainBucket to byte.  Note: limited to one decimal place.
        //Can this math be simplified?? 
        float convertRainBucket = rainBucket[0] * 10;
        if (convertRainBucket  > 253) convertRainBucket = 253; // odd occurance but prevent overflow
        byte eepromRainBucket = (byte)convertRainBucket;
        gw.saveState(eepromIndex, eepromRainBucket);
    
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER;
    //    Serial.print("Writing to EEPROM.  Index: ");
    //    Serial.println(eepromIndex);
        gw.saveState(eepromIndex, 0xFF);
        
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.send(msgRain.set(tipCounter(24),1));// send 24hr tips
        delay(250);
        transmitRainData(); // send all of the 5 buckets of data to controller
        startMillis = millis();
      }
    }
    //
    void sensorTipped()
    {
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 200)
      {
        tipBuffer++;
      }
      last_interrupt_time = interrupt_time;
    }
    //
    float tipCounter(int hours)
    {
      float tipCount = 0;
      for ( int i = 0; i < hours; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      return tipCount;
    }
    //
    void updateSerialData(int x)
    {
      Serial.print(F("Tips last "));
      Serial.print(x);
      Serial.print(F(" hours: "));
      float tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      Serial.println(tipCount);
    }
    void loadRainArray(int value)
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
      {
        value--;
        Serial.print("EEPROM location: ");
        Serial.println(value);
        if (value < EEPROM_BUFFER)
        {
          value = EEPROM_BUFFER + BUFFER_LENGTH;
        }
        float rainValue = gw.loadState(value);
    
        if (rainValue < 255)
        {
          //Convert back to decimal value
          float decimalRainValue = rainValue/10;
          rainBucket[i] = decimalRainValue;
        }
        else
        {
          rainBucket [i] = 0;
        }
    //    Serial.print("rainBucket[ value: ");
    //    Serial.print(i);
    //    Serial.print("] value: ");
    //    Serial.println(rainBucket[i]);
      }
    }
    
    void transmitRainData(void)
    {
      float rainUpdateTotal = 0;
      for (int i = 0; i < 24; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR1.set(rainUpdateTotal,1));
      delay(250);
      for (int i = 24; i < 48; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR2.set(rainUpdateTotal,1));
      delay(250);
      for (int i = 48; i < 72; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR3.set(rainUpdateTotal,1));
    
      delay(250);
      for (int i = 72; i < 96; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR4.set(rainUpdateTotal,1));
    
      delay(250);
      for (int i = 96; i < 120; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR5.set(rainUpdateTotal,1));
      delay(250);
    }
    
    void getVariables(const MyMessage &message)
    {
      if (message.sensor == CHILD_ID_RAIN)
      {
        // nothing to do here
      }
      else if (message.sensor == CHILD_ID_TRIPPED_RAIN)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 6)
          {
            rainWindow = 6;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 1000)
          {
            rainSensorThreshold = 1000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    void pulseLED(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 500UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }

  • Admin

    This starts to look really good. Maybe we should lift this into the examples on github?

    Just wish someone could sell the tipping bucket hardware. 🙂


  • Contest Winner

    @petewill

    Pete, let me look it all over again and see if we can get the calibration working. I think the EEPROM part can be updated for @hek 's addition into the mySensors library.

    @hek maybe we can do a 100% printed design for the collector/funnel and mechanism. and offer that on github as well.


  • Admin

    @BulldogLowell said:

    @hek maybe we can do a 100% printed design for the collector/funnel and mechanism. and offer that on github as well.

    Good idea. We could probably just place the stl-file in the folder together with ino-file. Let me know when you feel ready.


  • Admin

    @BulldogLowell said:

    Pete, let me look it all over again and see if we can get the calibration working. I think the EEPROM part can be updated for @hek 's addition into the mySensors library.

    Great! I think it should be working now (it did in my testing) but it could probably be cleaned up to work more efficiently.

    @hek maybe we can do a 100% printed design for the collector/funnel and mechanism. and offer that on github as well.

    That would be great! I was also hoping to make a step by step video of how I got mine all set up (with Jim's permission of course). That way pretty much any collector could be used when the calibration setting is changed. I was able to find one really cheap at a local store. The 3d print option would be really cool though.

    @BulldogLowell one thing I forgot to mention in my post above is that I don't think the rainWindow and rainSensorThreshold are pulled from Vera except for when it's initially powered on. I thought I remember reading somewhere that it was designed to check every 3 hours. Maybe I'm remembering incorrectly though. I can make the change if you want but I wanted to check with you first.


  • Contest Winner

    @petewill said:

    @BulldogLowell one thing I forgot to mention in my post above is that I don't think the rainWindow and rainSensorThreshold are pulled from Vera except for when it's initially powered on. I thought I remember reading somewhere that it was designed to check every 3 hours. Maybe I'm remembering incorrectly though. I can make the change if you want but I wanted to check with you first.

    yeah, we need to add the:

    gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR1);
    gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR2);
    

    into a millis( ) timer, once an hour works, I'd think.

    Try that out in here:

    if (millis() - startMillis >= oneHour)
    

    Once you get it done, let's take @hek 's idea and put it into github. I can clean it up, get Serial printing on a toggle and we can straighten out all the questions you had in the code.

    Thanks for pushing this forward, it is good to get more eyes on it (remember, I may need to reinstall in my working sensor someday!)

    @hek,

    i think @korttoma has NetAtmo, maybe he will give us some rough dimensions for that size (the one I make was the standard eight inch diameter used by the US weather services, but a more portable version may be better). @korttoma?

    Once I get that, I will work on a 3D printable design (for many to contribute I hope).

    @petewill

    we also need to create image files for the Sensor, but for now, if you want to do the tutorial, I say go for it!


  • Contest Winner

    @petewill said:

    I have been testing the code for the last couple of days and I think this is working but another set of eyes would be good. I added functionality to set the "calibrateFactor" to a decimal. I also updated the save/load EEPROM to work with the MySensors standard. It seems to be working in my testing but I'm no programmer.

    Pete,

    I had a whack of combining your desire to use floats, and my desire not to use floats!

    Essentially we are storing in hundredths of a mm or hundredths of an inch, and dividing the values down when we transmit to Vera.

    you set the calibration in hundredths of a unit per tip here:

    #define CALIBRATE_FACTOR 5 // e.g. 5 is .05mm (or 5 hundredths of an inch if imperial) per tip
    

    Take a look at this and let me know if you like it, note it is untested and I still have to test the circular buffer storing ints instead of bytes.

    /*
     Arduino Tipping Bucket Rain Gauge
    
     April 26, 2015
    
     Version 1.02b for MySensors version 1.4.1
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours)
     above a threshold.  Both these settings are user definable.
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 24, 48, 72, 96 and 120 hours total rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every hour
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power outage, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * There is a unique setup requirement necessary in order to properly present the Vera device
       variables.  The details are outlined in the sketch below.
     * LED status indicator
    
     by @BulldogLowell and @PeteWill for free public use
    
     */
    #include <SPI.h>
    #include <MySensor.h>
    #include <math.h>
    
    #define NODE_ID 24
    #define SKETCH_NAME "Rain Gauge"
    #define SKETCH_VERSION "1.02b"
    
    #define DEBUG_ON  // comment out this line to disable serial debug
    
    #define CHILD_ID_RAIN 3
    #define CHILD_ID_TRIPPED_RAIN 4
    
    #define STATE_LOCATION 0 // location to save state to EEPROM
    #define EEPROM_BUFFER 1  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121  // buffer plus the current hour
    
    #define CALIBRATE_FACTOR 100 // e.g. 5 is .05mm (or 5 hundredths of an inch if imperial) per tip
    
    #ifdef  DEBUG_ON
    #define DEBUG_PRINT(x)   Serial.print(x)
    #define DEBUG_PRINTLN(x) Serial.println(x)
    #define SERIAL_START(x)  Serial.begin(x)
    #else
    #define DEBUG_PRINT(x)
    #define DEBUG_PRINTLN(x)
    #define SERIAL_START(x)
    #endif
    
    //
    MySensor gw;
    //
    MyMessage msgRainRate(CHILD_ID_RAIN, V_RAINRATE);
    MyMessage msgRain(CHILD_ID_RAIN, V_RAIN);
    MyMessage msgRainVAR1(CHILD_ID_RAIN, V_VAR1);
    MyMessage msgRainVAR2(CHILD_ID_RAIN, V_VAR2);
    MyMessage msgRainVAR3(CHILD_ID_RAIN, V_VAR3);
    MyMessage msgRainVAR4(CHILD_ID_RAIN, V_VAR4);
    MyMessage msgRainVAR5(CHILD_ID_RAIN, V_VAR5);
    //
    MyMessage msgTripped(CHILD_ID_TRIPPED_RAIN, V_TRIPPED);
    MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_RAIN, V_VAR1);
    MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_RAIN, V_VAR2);
    //
    boolean metric = true;
    //
    int eepromIndex;
    int tipSensorPin = 3; // Must be interrupt capable pin
    int ledPin = 5; // PWM capable pin required
    unsigned long dataMillis;
    unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 3600000UL;
    unsigned long lastTipTime;
    unsigned long lastBucketInterval;
    unsigned long startMillis;
    
    unsigned int rainBucket [120] ; /* 120 hours = 5 days of data */
    unsigned int rainRate = 0;
    volatile int tipBuffer = 0;
    byte rainWindow = 72;         //default rain window in hours
    int rainSensorThreshold = 50; //default rain sensor sensitivity in hundredths.  Will be overwritten with msgTrippedVar2.
    byte state = 0;
    byte oldState = -1;
    //
    void setup()
    {
      SERIAL_START(115200);
      gw.begin(getVariables, NODE_ID);
      pinMode(tipSensorPin, INPUT_PULLUP);
      attachInterrupt (1, sensorTipped, FALLING);  // depending on location of the hall effect sensor may need CHANGE
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      gw.sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      gw.present(CHILD_ID_RAIN, S_RAIN);
      gw.present(CHILD_ID_TRIPPED_RAIN, S_MOTION);
      DEBUG_PRINTLN(F("Sensor Presentation Complete"));
      state = gw.loadState(STATE_LOCATION); //retreive prior state from EEPROM
      DEBUG_PRINT(F("Previous Tripped State (from EEPROM): "));
      DEBUG_PRINTLN(state ? "Tripped" : "Not Tripped");
      //
      gw.send(msgTripped.set(state));
      delay(250);  // recharge the capacitor
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
        byte locator = gw.loadState(EEPROM_BUFFER + 2 * i); //<<<<<<<<<<<
        if (locator == 0xFF)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER + 2 * i;
          //Now that we have the buffer index let's populate the rainBucket with data from eeprom
          loadRainArray(eepromIndex);
          isDataOnEeprom = true;
          DEBUG_PRINT("EEPROM Index ");
          DEBUG_PRINTLN(eepromIndex);
          isDataOnEeprom = true;
          break;
        }
      }
      if (!isDataOnEeprom) // Added for the first time it is run on a new arduino
      {
        eepromIndex = 1;
        gw.saveState(eepromIndex, 0xFF);
        gw.saveState(eepromIndex + 1, 0x00);
      }
      // reset the timers
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;
      gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR1); //Get rainWindow from controller (Vera)
      delay(250);
      gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR2); //Get rainSensorThreshold from controller (Vera)
      delay(250);
      DEBUG_PRINTLN(F("Radio Setup Complete!"));
    }
    //
    void loop()
    {
      gw.process();
      if (state)
      {
        prettyFade();  // breath if tripped
      }
      else
      {
        slowFlash();   // blink if not tripped
      }
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
      int measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
        DEBUG_PRINT(F("measure value (total rainBucket within rainWindow): "));
        DEBUG_PRINTLN(measure);
      }
      //
      state = (measure >= rainSensorThreshold);
      if (state != oldState)
      {
        gw.send(msgTripped.set(state));
        delay(250);
        gw.saveState(STATE_LOCATION, state); //New Code
        DEBUG_PRINT(F("State Changed. Tripped State: "));
        DEBUG_PRINTLN(state);
        oldState = state;
      }
      //
      // Now lets reset the rainRate to zero if no tips in the last hour
      //
      if (millis() - lastTipTime >= oneHour)// timeout for rain rate
      {
        if (rainRate != 0)
        {
          rainRate = 0;
          gw.send(msgRainRate.set(0));
          delay(250);
        }
      }
    
    #ifdef DEBUG_ON
      if ( (millis() - dataMillis) >= serialInterval)
      {
        for (int i = 24; i <= 120; i = i + 24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
    #endif
      //
      if (tipBuffer > 0)
      {
        DEBUG_PRINTLN(F("Sensor Tipped"));
        DEBUG_PRINT(F("rainBucket [0] value: "));
        DEBUG_PRINTLN(rainBucket [0]);
        //
        int dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        //
        DEBUG_PRINT(F("dayTotal value: "));
        DEBUG_PRINTLN(dayTotal);
        gw.send(msgRain.set(dayTotal, 1));
        delay(250);
        //
        unsigned long tipDelay = millis() - lastTipTime;
        if (tipDelay <= oneHour)
        {
          rainRate = ((oneHour) / tipDelay);
          gw.send(msgRainRate.set(rainRate, 1));
          DEBUG_PRINT(F("RainRate= "));
          DEBUG_PRINTLN(rainRate);
        }
        //
        //If this is the first trip in an hour send .1
        //
        else
        {
          gw.send(msgRainRate.set( (float) CALIBRATE_FACTOR / 100.0, 1));//<<<<<<<<<<<<<<calibrate?
        }
        lastTipTime = millis();
        tipBuffer--;
      }
      //
      if (millis() - startMillis >= oneHour)
      {
        DEBUG_PRINTLN(F("One hour elapsed."));
        //EEPROM write last value
        gw.saveState(eepromIndex, highByte(rainBucket[0]));
        gw.saveState(eepromIndex + 1, lowByte(rainBucket[0]));
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER;
        DEBUG_PRINT(F("Writing to EEPROM.  Index: "));
        DEBUG_PRINTLN(eepromIndex);
        gw.saveState(eepromIndex, 0xFF);
        gw.saveState(eepromIndex + 1, 0x00);
        //
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.send(msgRain.set(tipCounter(24), 1)); // send 24hr tips
        delay(250);
        transmitRainData(); // send all of the 5 buckets of data to controller
        delay(250);
        gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR1);
        delay(250);
        gw.request(CHILD_ID_TRIPPED_RAIN, V_VAR2);
        startMillis = millis();
      }
    }
    //
    void sensorTipped()
    {
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 200)
      {
        rainBucket[0] += CALIBRATE_FACTOR; // adds CALIBRATE_FACTOR hundredths of unit each tip
      }
      last_interrupt_time = interrupt_time;
    }
    //
    float tipCounter(int hours)
    {
      float tipCount = 0;
      for ( int i = 0; i < hours; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      return tipCount;
    }
    //
    void updateSerialData(int x)
    {
      DEBUG_PRINT(F("Tips last "));
      DEBUG_PRINT(x);
      DEBUG_PRINTLN(F(" hours: "));
      int tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      DEBUG_PRINTLN(tipCount);
    }
    void loadRainArray(int value)
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
      {
        value--;
        DEBUG_PRINT("EEPROM location: ");
        DEBUG_PRINTLN(value);
        if (value < EEPROM_BUFFER)
        {
          value = EEPROM_BUFFER + BUFFER_LENGTH;
        }
        byte rainValueHigh = gw.loadState(value);
        byte rainValueLow = gw.loadState(value + 1);
        int rainValue = (rainValueHigh << 8) & rainValueLow;
        rainBucket[i] = rainValue;
        //
        DEBUG_PRINT(F("rainBucket[ value: "));
        DEBUG_PRINT(i);
        DEBUG_PRINT(F("] value: "));
        DEBUG_PRINTLN(rainBucket[i]);
      }
    }
    
    void transmitRainData(void)
    {
      int rainUpdateTotal = 0;
      for (int i = 0; i < 24; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR1.set(( float) rainUpdateTotal / 100.0 , 1));
      delay(250);
      for (int i = 24; i < 48; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR2.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(250);
      for (int i = 48; i < 72; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR3.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(250);
      for (int i = 72; i < 96; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR4.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(250);
      for (int i = 96; i < 120; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR5.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(250);
    }
    
    void getVariables(const MyMessage &message)
    {
      if (message.sensor == CHILD_ID_RAIN)
      {
        // nothing to do here
      }
      else if (message.sensor == CHILD_ID_TRIPPED_RAIN)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 1)
          {
            rainWindow = 1;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 10000)
          {
            rainSensorThreshold = 10000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    //
    void prettyFade(void)
    {
      float val = (exp(sin(millis() / 2000.0 * PI)) - 0.36787944) * 108.0;
      analogWrite(ledPin, val);
    }
    
    void slowFlash(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 100UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }
    

    EDIT, modified some serial.print()s that needed editing for the debug toggle


  • Admin

    @BulldogLowell
    Thanks for the comments and sorry for the delayed response. It was a busy weekend and I didn't get much time in front of the computer.

    I had a whack of combining your desire to use floats, and my desire not to use floats!

    Haha! I only used floats because I don't know any better. I'm curious, why the dislike of floats?

    Once you get it done, let's take @hek 's idea and put it into github.

    Is this something you would like me to do since you made it way better 🙂 I'm happy to do it but I don't want to overstep. I consider this your handy work that I'm just happy to use.

    Take a look at this and let me know if you like it, note it is untested and I still have to test the circular buffer storing ints instead of bytes.

    Great! I will take a look some time this week. I mounted my gauge on the house today because we were supposed to get some rain and I wanted to see how it worked in real life. Unfortunately we didn't get any... Hopefully we will get some in the next couple of days before I pull it off to do some more testing.

    we also need to create image files for the Sensor, but for now, if you want to do the tutorial, I say go for it!

    I was thinking of going through how I made my gauge by using an existing rain gauge. I'd go over how to do the calculations for the tipping bucket calibration, uploading the files to Vera and how I added the MySensors stuff to the existing gauge. I will definitely mention the 3d printing option as well

    Thanks for your help!.


  • Contest Winner

    @petewill said:

    why the dislike of floats?

    their four-byte size on arduino make for a huge eeprom storage buffer that we really don't need, we will never have negative rain and we'd never be interested in precision below 0.01 inches or milimeters.

    @petewill said:

    I mounted my gauge on the house today

    terrific, glad to see it make its way into the real world!


  • Admin

    @BulldogLowell said:

    their four-byte size on arduino make for a huge eeprom storage buffer that we really don't need

    Good to know! I still have a lot to learn about all this stuff. It's definitely fun.

    terrific, glad to see it make its way into the real world!

    Yes, me too! I'm happy to report it's working. It rained 6.4mm last night. SO COOL!


  • Contest Winner

    @petewill

    👍

    big thumbs up!


  • Contest Winner

    how about adding windspeed to the device?


  • Admin

    @BulldogLowell said:

    how about adding windspeed to the device?

    Cool device. Haven't seen that one before.


  • Admin

    @BulldogLowell Cool! Do you think it detects wind from all directions? Would this be better than the traditional spinning cup design?


  • Contest Winner

    @hek
    @petewill

    A little progress on the device...

    Screen Shot 2015-05-09 at 11.07.41 PM.png

    Screen Shot 2015-05-09 at 11.17.11 PM.png

    Screen Shot 2015-05-09 at 11.07.28 PM.png

    Screen Shot 2015-05-09 at 11.11.29 PM.png


  • Contest Winner

    electronics box:

    Screen Shot 2015-05-10 at 10.08.29 AM.png

    Screen Shot 2015-05-10 at 10.09.57 AM.png



  • @BulldogLowell this looks great once you complete it I will print it. Only problem our raining session is over till Dec. But I can start the build so long :).


  • Admin

    Nice. But I'm a bit worried about water finding its way into the electronics. Would it be possible to make a cavity for the electronics box in the bottom plate?

    1431266959242-screen-shot-2015-05-10-at-10.08.29-am.png


  • Contest Winner

    @hek
    recessing the electronics into the bottom would be tough in this design. I was taking advantage on the two parts to use the smooth plate of the printer, and then make a gasket or even use silicone. lemme think about it. I could add a couple of deflectors on the bottom plate to glue on around where the weep holes are...

    PS enough room for 2 C sized cells

    bottom view:

    Screen Shot 2015-05-10 at 11.32.50 AM.png

    Screen Shot 2015-05-10 at 11.44.44 AM.png


  • Admin

    @BulldogLowell Awesome!

    I kind of wish I didn't have mine built already 🙂

    I may have missed it but did you have any mounting holes (for the side of a building or pole) What is your vision for that?

    It would also be cool to see some place to add a DHT22 in there. On mine I added one of those as well as a BH1750 for light. I'm not sure how long that will hold up though.


  • Contest Winner

    @petewill said:

    It would also be cool to see some place to add a DHT22 in there. On mine I added one of those as well as a BH1750 for light. I'm not sure how long that will hold up though.

    I'll try to fit that in, if I can, but I'm really focusing on sealing up the electronics. A skirt around the electronics housing may be enough...

    I was thinking of a female 'flange-like' option for the unit to sit at the top of some galvanized pipe. It could be mounted that way on the side of a house, a fence or just a pole (back of your basketball backboard!). So, the yellow-brown part will be of two options to print.


  • Admin

    @BulldogLowell said:

    I was taking advantage on the two parts to use the smooth plate of the printer

    Of course, didn't think that long.


  • Contest Winner



  • If you add a little edge around the holes, the water cannt flow into the electronic box.


  • Admin

    @BulldogLowell Awesome. I would love to get a 3d printer. Someday.


  • Contest Winner

    @petewill

    As it happens, I am sort of turning the code on its head for this update. Do you want to use accumulated rain over X days as the trigger?

    I was thinking about storing the time on EEPROM and retrieving the stored rain values from VERA for each of the last 5 days after a power-up/restart. This saves from having to create too much EEPROM management, plus we can observe how much time has passed in the event of a power-off. That is, if the unit starts up, we test EEPROM to see what was the last day of rain recorded. If it hasn't been more than one, then we retrieve the values from VERA that were previously sent up. Sound OK to you?

    I would save an epoch timestamp once each day at midnight when we total today's rain and cascade the values back a day.


  • Admin

    @BulldogLowell said:

    Do you want to use accumulated rain over X days as the trigger?

    As opposed to hours? I don't have too strong of an opinion either way. I think whatever is easier works. If they are both the same then maybe hours so we have a little more granular control?

    I was thinking about storing the time on EEPROM and retrieving the stored rain values from VERA for each of the last 5 days after a power-up/restart.

    I like that it will simplify the EEPROM more and potentially prolong the life of the device (since it's not using as much EEPROM). One thought though, if there is a communication error between Vera and the rain gauge when it is first started is there a way to prevent the rain gauge from subsequently overwriting the values in Vera? I don't know too much about the "ack" portion of the communication but maybe that would make it so it's not an issue?

    I'm excited to see the new code. Keep up the good work!


  • Contest Winner

    @petewill said:

    As opposed to hours? I don't have too strong of an opinion either way. I think whatever is easier works. If they are both the same then maybe hours so we have a little more granular control?

    that's where we were with the 120 hour framework, so let's leave that. I'm not worried about EEPROM life, you get 100,000 writes and that is your lifetime++ using the circular buffer. I did the math on it but can't recall the exact number.

    So, we won't change EEPROM data, actually may need to add another 24hours. We just send to VERA the total of each of the past 5 days instead of the total accumulated rainfall over the past 24, 48, 72, 96 and 120 hours. This will be calendar days, we will verify by getting the time from VERA, and synchronize that a few times daily. the days will rollover at midnight, and the daily accumulation will be cascaded back accordingly.

    trigger for the sensor will still be total accumulation over so many hours.

    Yes?


  • Admin

    @BulldogLowell That sounds GREAT to me! This is your baby though (and you have much more experience with this stuff) so I trust what direction you go.


  • Contest Winner

    @petewill

    here is some (untested) mods to update the 5 day rainfall history once per day at midnight. Take a look, let me know what you think (any problems issues). It turns out I was overthinking the problem and it really only needed minor mods...

    /*
     Arduino Tipping Bucket Rain Gauge
    
     April 26, 2015
    
     Version 1.4.1 alpha
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 24, 48, 72, 96 and 120
     hours.  The other, a sensor that changes state if there is recent rain (up to last 120 hours)
     above a threshold.  Both these settings are user definable.
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the interval window up to 120 hours.
     * Displays the last 5 days of rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every hour
     * SHould run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power interruption, saving small ammount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * LED status indicator
    
     by @BulldogLowell and @PeteWill for free public use
    
     */
    #include <SPI.h>
    #include <MySensor.h>
    #include <math.h>
    #include <Time.h>
    
    #define NODE_ID 24
    #define SKETCH_NAME "Rain Gauge"
    #define SKETCH_VERSION "1.4.1a"
    
    #define DWELL_TIME 125  // this allows for radio to come back to power after a transmission, ideally 0 
    
    #define DEBUG_ON  // comment out this line to disable serial debug
    
    #define CHILD_ID_RAIN_LOG 3  // Keeps track of accumulated rainfall
    #define CHILD_ID_TRIPPED_INDICATOR 4  // Indicates Tripped when rain detected
    
    #define EEPROM_STATE_LOCATION 0 // location to save state to EEPROM
    #define EEPROM_BUFFER_LOCATION 1  // location of the EEPROM circular buffer
    #define BUFFER_LENGTH 121
    
    #define CALIBRATE_FACTOR 100 // e.g. 5 is .05mm (or 5 hundredths of an inch if imperial) per tip
    
    #ifdef  DEBUG_ON
    #define DEBUG_PRINT(x)   Serial.print(x)
    #define DEBUG_PRINTLN(x) Serial.println(x)
    #define SERIAL_START(x)  Serial.begin(x)
    #else
    #define DEBUG_PRINT(x)
    #define DEBUG_PRINTLN(x)
    #define SERIAL_START(x)
    #endif
    //
    MySensor gw;
    //
    MyMessage msgRainRate(CHILD_ID_RAIN_LOG, V_RAINRATE);
    MyMessage msgRain(CHILD_ID_RAIN_LOG, V_RAIN);
    //
    MyMessage msgRainVAR1(CHILD_ID_RAIN_LOG, V_VAR1);
    MyMessage msgRainVAR2(CHILD_ID_RAIN_LOG, V_VAR2);
    MyMessage msgRainVAR3(CHILD_ID_RAIN_LOG, V_VAR3);
    MyMessage msgRainVAR4(CHILD_ID_RAIN_LOG, V_VAR4);
    MyMessage msgRainVAR5(CHILD_ID_RAIN_LOG, V_VAR5);
    //
    MyMessage msgTripped(CHILD_ID_TRIPPED_INDICATOR, V_TRIPPED);
    MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
    MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
    //
    boolean metric = true;
    int eepromIndex;
    int tipSensorPin = 3; // Must be interrupt capable pin
    int ledPin = 5; // PWM capable pin required
    unsigned long dataMillis;
    const unsigned long serialInterval = 10000UL;
    const unsigned long oneHour = 3600000UL;
    unsigned long lastTipTime;
    unsigned long startMillis;
    unsigned int rainBucket [24] ; /* 24 hours = 1 day of data */
    unsigned int rainRate = 0;
    volatile int wasTippedBuffer = 0;
    byte rainWindow = 72;         //default rain window in hours
    int rainSensorThreshold = 50; //default rain sensor sensitivity in hundredths.  Will be overwritten with msgTrippedVar2.
    byte state = 0;
    byte oldState = -1;
    int lastRainRate = 0;
    int lastMeasure = 0;
    boolean gotTime = false;
    byte lastHour;
    
    void setup()
    {
      SERIAL_START(115200);
      //
      // Set up the IO
      pinMode(tipSensorPin, INPUT_PULLUP);
      attachInterrupt (1, sensorTipped, FALLING);  // depending on location of the hall effect sensor may need CHANGE
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      //
      //Let's get the controller talking to the Arduino
      gw.begin(getVariables, NODE_ID);
      gw.sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      delay(DWELL_TIME);
      gw.present(CHILD_ID_RAIN_LOG, S_RAIN);
      delay(DWELL_TIME);
      gw.present(CHILD_ID_TRIPPED_INDICATOR, S_MOTION);
      delay(DWELL_TIME);
      DEBUG_PRINTLN(F("Sensor Presentation Complete"));
      state = gw.loadState(EEPROM_STATE_LOCATION); //retreive prior state from EEPROM
      DEBUG_PRINT(F("Previous Tripped State (from EEPROM): "));
      DEBUG_PRINTLN(state ? "Tripped" : "Not Tripped");
      //
      gw.send(msgTripped.set(state));
      delay(DWELL_TIME);
      //
      //Sync time with the server, this will be called hourly in order to keep time from creeping with the crystal
      //
      while(timeStatus() == timeNotSet)
      {
        gw.process();
        gw.requestTime(receiveTime);
        Serial.println("getting Time");
        delay(1000); // call once per second
        Serial.print(".");
      }
      //
      //retrieve from EEPROM stored values on a power cycle.
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < BUFFER_LENGTH; i++)
      {
        byte locator = gw.loadState(EEPROM_BUFFER_LOCATION + 2 * i); //<<<<<<<<<<<
        if (locator == 0xFF)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER_LOCATION + 2 * i;
          //Now that we have the buffer index let's populate the rainBucket[] with data from eeprom
          loadRainArray(eepromIndex);
          isDataOnEeprom = true;
          DEBUG_PRINT("EEPROM Index ");
          DEBUG_PRINTLN(eepromIndex);
          isDataOnEeprom = true;
          break;
        }
      }
      if (!isDataOnEeprom) // Added for the first time it is run on a new arduino
      {
        eepromIndex = 1;
        gw.saveState(eepromIndex, 0xFF); // store the EEPROM index marker...
        gw.saveState(eepromIndex + 1, 0xFF);
      }
      dataMillis = millis();
      startMillis = millis();
      lastTipTime = millis() - oneHour;
      //
      //Get sensor time window and threshold from controller
      gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1); 
      delay(DWELL_TIME);
      gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2); 
      delay(DWELL_TIME);
      DEBUG_PRINTLN(F("Radio Setup Complete!"));
    }
    
    void loop()
    {
      gw.process();
      if (state)
      {
        prettyFade();  // breathe if tripped
      }
      else
      {
        slowFlash();   // blink if not tripped
      }
    #ifdef DEBUG_ON  // Serial Debug Block
      if ( (millis() - dataMillis) >= serialInterval)
      {
        for (int i = 24; i <= 120; i = i + 24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
    #endif
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
      int measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
        if (measure != lastMeasure)
        {
          DEBUG_PRINT(F("measure value (total rainBucket within rainWindow): "));
          DEBUG_PRINTLN(measure);
          lastMeasure = measure;
        }
      }
      //
      state = (measure >= rainSensorThreshold);
      if (state != oldState)
      {
        gw.send(msgTripped.set(state));
        delay(DWELL_TIME);
        gw.saveState(EEPROM_STATE_LOCATION, state); //New Code
        DEBUG_PRINT(F("New Sensor State... Sensor: "));
        DEBUG_PRINTLN(state? "Tripped" : "Not Tripped");
        oldState = state;
      }
      //
      unsigned long tipDelay = millis() - lastTipTime;
      if (wasTippedBuffer) // if was tipped, then update the 24hour total and transmit to Vera
      {
        DEBUG_PRINTLN(F("Sensor Tipped"));
        DEBUG_PRINT(F("rainBucket [0] value: "));
        DEBUG_PRINTLN(rainBucket [0]);
        //
        int dayTotal = 0;
        for (int i = 0; i < 24; i++)
        {
          dayTotal = dayTotal + rainBucket [i];
        }
        //
        DEBUG_PRINT(F("dayTotal value: "));
        DEBUG_PRINTLN(dayTotal);
        gw.send(msgRain.set(dayTotal, 1));
        delay(DWELL_TIME);
        wasTippedBuffer--;
        rainRate = ((oneHour) / tipDelay);
        if (rainRate != lastRainRate)
        {
          gw.send(msgRainRate.set(rainRate, 1));
          delay(DWELL_TIME);
          DEBUG_PRINT(F("RainRate= "));
          DEBUG_PRINTLN(rainRate);
          lastRainRate = rainRate;
        }
      }
      if (tipDelay > oneHour)
      {
        rainRate = 0;
        gw.send(msgRainRate.set(rainRate, 1));
      }
      //
      if (millis() - startMillis >= oneHour)
      {
        DEBUG_PRINTLN(F("One hour elapsed."));
        for (int i = BUFFER_LENGTH - 1; i >= 0; i--)//cascade an hour of values back into the array
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        rainBucket[0] = 0;
        gw.send(msgRain.set(rainTotal(24), 1)); // send 24hr tips
        delay(DWELL_TIME);
        gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
        delay(DWELL_TIME);
        gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
        delay(DWELL_TIME);
        gw.saveState(eepromIndex, highByte(rainBucket[0]));
        gw.saveState(eepromIndex + 1, lowByte(rainBucket[0]));
        eepromIndex++;
        if (eepromIndex > EEPROM_BUFFER_LOCATION + BUFFER_LENGTH) eepromIndex = EEPROM_BUFFER_LOCATION;
        DEBUG_PRINT(F("Writing to EEPROM.  Index: "));
        DEBUG_PRINTLN(eepromIndex);
        gw.saveState(eepromIndex, 0xFF);
        gw.saveState(eepromIndex + 1, 0xFF);
        gw.requestTime(receiveTime); // sync the time every hour
        delay(DWELL_TIME);
        startMillis = millis();
      }
      if (hour() == 0 and lastHour == 23)
      {
        transmitRainData();
      }
      lastHour = hour();
    }
    
    void sensorTipped()
    {
      unsigned long thisTipTime = millis();
      if (thisTipTime - lastTipTime > 100UL)
      {
        rainBucket[0] += CALIBRATE_FACTOR; // adds CALIBRATE_FACTOR hundredths of unit each tip
        wasTippedBuffer++;
      }
      lastTipTime = thisTipTime;
    }
    //
    int rainTotal(int hours)
    {
      int total = 0;
      for ( int i = 0; i < hours; i++)
      {
        total += rainBucket [i];
      }
      return total;
    }
    
    void updateSerialData(int x)
    {
      DEBUG_PRINT(F("Tips last "));
      DEBUG_PRINT(x);
      DEBUG_PRINTLN(F(" hours: "));
      int tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      DEBUG_PRINTLN(tipCount);
    }
    
    void loadRainArray(int value) // load stored rain array from EEPROM on powerup
    {
      for (int i = 0; i < BUFFER_LENGTH - 1; i++)
      {
        value--;
        DEBUG_PRINT("EEPROM location: ");
        DEBUG_PRINTLN(value);
        if (value < EEPROM_BUFFER_LOCATION)
        {
          value = EEPROM_BUFFER_LOCATION + BUFFER_LENGTH;
        }
        byte rainValueHigh = gw.loadState(value);
        byte rainValueLow = gw.loadState(value + 1);
        int rainValue = (rainValueHigh << 8) & rainValueLow;
        rainBucket[i] = rainValue;
        //
        DEBUG_PRINT(F("rainBucket[ value: "));
        DEBUG_PRINT(i);
        DEBUG_PRINT(F("] value: "));
        DEBUG_PRINTLN(rainBucket[i]);
      }
    }
    
    void transmitRainData(void)
    {
      int rainUpdateTotal = 0;
      for (int i = 0; i < 24; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR1.set(( float) rainUpdateTotal / 100.0 , 1));
      delay(DWELL_TIME);
      rainUpdateTotal = 0;
      for (int i = 24; i < 48; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR2.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(DWELL_TIME);
      rainUpdateTotal = 0;
      for (int i = 48; i < 72; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR3.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(DWELL_TIME);
      rainUpdateTotal = 0;
      for (int i = 72; i < 96; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR4.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(DWELL_TIME);
      rainUpdateTotal = 0;
      for (int i = 96; i < 120; i++)
      {
        rainUpdateTotal += rainBucket[i];
      }
      gw.send(msgRainVAR5.set( (float) rainUpdateTotal / 100.0 , 1));
      delay(DWELL_TIME);
    }
    
    void getVariables(const MyMessage &message)
    {
      if (message.sensor == CHILD_ID_RAIN_LOG)
      {
        // nothing to do here
      }
      else if (message.sensor == CHILD_ID_TRIPPED_INDICATOR)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 1)
          {
            rainWindow = 1;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 10000)
          {
            rainSensorThreshold = 10000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            gw.send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    
    void prettyFade(void)
    {
      float val = (exp(sin(millis() / 2000.0 * PI)) - 0.36787944) * 108.0;
      analogWrite(ledPin, val);
    }
    
    void slowFlash(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 100UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }
    
    void receiveTime(unsigned long time)
    {
      DEBUG_PRINTLN(F("Time received from controller..."));
      setTime(time);
      char theTime[26];
      sprintf(theTime, "The current time is %d:%2d", hour(), minute());
      DEBUG_PRINTLN(theTime);
    }
    

  • Hero Member

    @BulldogLowell

    Wow, just wow man.... that is a thing of beauty!!


  • Contest Winner

    @ServiceXp

    waiting for the 3D prints from China, I'll post when I get them!


  • Mod

    I would love to see the .stl files for this.
    It has turned into something really amazing imho.


  • Contest Winner

    @marceltrapman said:

    I would love to see the .stl files for this.
    It has turned into something really amazing imho.

    I'll post, but I wanted to print and check first...

    @marceltrapman here you go...

    Posted to GoogleDrive



  • @BulldogLowell this has been quite a project to watch unfold and develop into just the greatest project. I am learning more just following you programing wizards, than I learn surfing . Thanks for the project and the inside tracks on programming etc.
    I have got my tripping gage back from my local 3d hub shop and the job looks good I hope to have it running hardware and soft before I return to and take it to Florida this fall.


  • Admin

    @BulldogLowell , @petewill

    A page for your new creation has been deployed here:
    http://www.mysensors.org/build/rain

    Please let me know if I missed something or spelling needs to be corrected.


  • Admin

    @hek Looks great! Thanks!


  • Admin

    @petewill - another outstanding tutorial video.


  • Admin

    @blacey Thanks! Couldn't have done it without @BulldogLowell and @hek


  • Admin

    @petewill, @BulldogLowell

    Received my off the shelf tipping bucket sensor today. http://www.ebay.com/itm/331525977548?rmvSB=true

    To my surprise it contained a dummy 2xAAA battery compartment. It might be possible to squeeze in a sensebender and nrf-radio (with some slight modification).

    20150729_142145.jpg


  • Admin

    @hek Cool! Looks like you may even have some room on the other side if you can figure out a way to waterproof it?



  • @hek said:

    @petewill, @BulldogLowell

    Received my off the shelf tipping bucket sensor today. http://www.ebay.com/itm/331525977548?rmvSB=true

    To my surprise it contained a dummy 2xAAA battery compartment. It might be possible to squeeze in a sensebender and nrf-radio (with some slight modification).

    @hek
    Did you ever wire this up with sensebender & radio squeezed in?
    I have this same guage I want to use with sensebender


  • Admin

    No, never had time to finish my rain gauge this summer.

    But it should be doable. But the big pcb in there has to be modified, or even better is probably to move the reed switch directly to the sensebender.



  • @hek said:

    No, never had time to finish my rain gauge this summer.

    But it should be doable. But the big pcb in there has to be modified, or even better is probably to move the reed switch directly to the sensebender.

    ok thanks



  • Hi.
    i'm having some trouble whit my rain gauge...

    My rain gauge is a Aercus KW9015, using the code provided i'm getting rain count every hour whit no rain at all.
    I don't know what to do to fix this.

    Over the rain gauge i have 4 connections.
    1 - GND
    2 - TX1 --- Temperature sensor
    3 - TX2 --- Rain sensor
    4 - VCC

    Why i'm i getting reads of rain ever 1h when there is no rain ?
    Can anyone help me whit this problem ?


  • Admin

    @mrc-core
    Are you actually getting a rain value or is it just sending 0? The code is designed to send an update to your gateway every hour with the total rain for the day whether there is rain or not.



  • Some times i do get value 0 but other times i get rain value a big rain value for example values above 10mm of rain.
    The update is made every hour. Yesterday the rain value was 148mm when there was no rain at all. It doesn't rain for the last month.

    Ill try today reassembling the arduino, clear cd rom and flash again the rain gauge code.

    But i don't understand why i get this values.
    I even beleaved that the sensor was sendind data because off the wind... but it's not the problem.


  • Contest Winner

    @mrc-core

    how is it connected electronically?



  • At the rain gauge i have an arduino nano conneting PIN "d3" to TX2 over the rain gauge circuit, the arduino is powered at pins VIN and GND.
    The rain gauge gets its power from the 3.3V over the arduino.

    im posting two images from the rain gauge circuit:
    IMG_1
    IMG_2

    Starting to believe i had connect someting rong like for example had switched TX1 and TX2 at the rain gauge



  • Looking over the IMG_2 should i connect arduino pin d3 to T1 just above the reed switch?
    Or i'm i correct using the TX2 connection.


  • Contest Winner

    @mrc-core

    No PULLUP or PULLDOWN resistor?



  • no i cant find any pullup or pulldown resistor.
    You can see the images from de interior of the rain gauge hi have.



  • Which resistor do i pu between the 5v and the D3 pin ???


  • Contest Winner

    @mrc-core

    if you don't pull up/down the signal, you may get floating voltages and spurious interrupts occurring.

    try to use the internal pullup

    pinMode(yourInterruptPin, INPUT_PULLUP);
    

    if that does not work try an external 10kOhm resistor to pull it up/down for your switch.



  • Going to put the 10kOhm resistor. the internall pullup did not work.
    One question. the resistor i put it between de 5v and the D3 pin


  • Contest Winner

    @mrc-core

    to PULL UP, yes.



  • Thanks going to try it now. And today is a good day since its raining.

    No luck... now i'm getting only "0"

    What i have done until now:
    Put a resistor 10kOhm between the GND and D3 Pin from arduino and connect it to TX2 over rthe rain gauge.
    And connected the 5V from arduino to the VCC over the rain gauge.

    But i'm only getting 0 ...

    send: 4-4-0-0 s=3,c=1,t=28,pt=7,l=5,sg=0,st=fail:-0.2
    read: 5-5-255 s=255,c=3,t=7,pt=0,l=0,sg=0:
    send: 4-4-0-0 s=4,c=1,t=16,pt=1,l=1,sg=0,st=ok:0
    New Sensor State... Sensor: Not Tripped
    read: 3-3-0 s=0,c=1,t=38,pt=7,l=5,sg=0:4.748
    read: 0-0-4 s=4,c=2,t=24,pt=0,l=1,sg=0:1
    read: 5-5-255 s=255,c=3,t=7,pt=0,l=0,sg=0:
    read: 5-5-255 s=255,c=3,t=7,pt=0,l=0,sg=0:


  • Contest Winner

    @mrc-core

    I guess I'd need to see a schematic or a good photo.

    can you generate a simple interrupt with a pushbutton and a resistor?



  • Hi. yesterday i disassembled the rain gauge circuit removed from it the reed switch and created a new circuit.
    I have connnected the reed switch to GND and PIN3 whit 10kOhm resistor.
    I'll post an image tonight.

    Up until now the values i'me getting from this new circuit is:
    rain gauge log

    Values i'm getting from serial port:
    Rain last 24 hours:
    1.80
    Rain last 48 hours:
    12481.94
    Rain last 72 hours:
    28210.34
    Rain last 96 hours:
    43938.74
    Rain last 120 hours:
    59667.14
    read and drop: 6-6-255 s=255,c=3,t=7,pt=0,l=0,sg=0:
    read and drop: 6-6-255 s=255,c=3,t=7,pt=0,l=0,sg=0:
    read and drop: 5-5-255 s=255,c=3,t=7,pt=0,l=0,sg=0:
    read and drop: 6-6-255 s=255,c=3,t=7,pt=0,l=0,sg=0:

    Seeing the graphic and this values it seems the problem i was having is fixed...
    But tonight i'm going do spread some water over the bucket to see if i get any values and not just 0


  • Hardware Contributor

    MI-SOL Rain Guage has CALIBRATE_FACTOR = 36


  • Hardware Contributor

    Sensor manufacturer has sent information about the sensor. 0.3 mm to 1 tick


  • Admin

    @Ivan-Z 👍 Thanks for the info.



  • Hey guys,
    Sorry if this is a stupid question but how does the Arduino interact with the tipping bucket to detect rain?
    Thanks!


  • Admin

    @Theekshana There is a magnet on the tipping bucket and a reed switch that detects the magnet each time it passes by. So each time the bucket tips it will trigger to the Arduino and it can count from there.



  • Has anyone converted this sketch to Mysensors 2.0 already? I've tried but received following error when compiling:

    Rain-gauge.ino: In function 'void setup()':
    Rain-gauge.ino:151:28: error: too many arguments to function 'void requestTime()'
    In file included from D:\Programs\Arduino\Sketchbook\libraries\libraries\MySensors/MySensors.h:293:0,
                 from Rain-gauge.ino:71:
    D:\Programs\Arduino\Sketchbook\libraries\libraries\MySensors/core/MySensorsCore.cpp:296:6: note: declared here
     void requestTime() {
       ^
    Rain-gauge.ino: In function 'void loop()':
    Rain-gauge.ino:323:28: error: too many arguments to function 'void requestTime()'
    In file included from D:\Programs\Arduino\Sketchbook\libraries\libraries\MySensors/MySensors.h:293:0,
                 from Rain-gauge.ino:71:
    D:\Programs\Arduino\Sketchbook\libraries\libraries\MySensors/core/MySensorsCore.cpp:296:6: note: declared here
     void requestTime() {
      ^
    too many arguments to function 'void requestTime()'
    

    Modified sketch below:

    /*
     Arduino Tipping Bucket Rain Gauge
    
     April 26, 2015
    
     Version 1.4.1 alpha
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 5 days.  The other, 
     a sensor that changes state if there is recent rain (up to last 120 hours)  above a threshold.  
     Both these settings are user definable.
    
     There is a build overview video here: https://youtu.be/1eMfKQaLROo
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the tripped indicator window up to 120 hours.
     * Displays the last 5 days of rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every hour
     * Should run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power interruption, saving small amount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * LED status indicator
     * Optional Temp/Humidity (DHT-22 or DHT-11) and Light LUX (BH1750) sensors. To use, uncomment
       #define DHT_ON  and/or #define LUX_ON
     * Optionally send total accumulation of each day's rainfall or send only individual days rainfall totals.
       Comment out #define USE_DAILY to display individual daily rainfall.
    
     by @BulldogLowell and @PeteWill for free public use
    
     */
    #include <SPI.h>
    #include <math.h>
    #include <Time.h>
    
    //#define NODE_ID AUTO //or AUTO to let controller assign
    #define SKETCH_NAME "Rain Gauge"
    #define SKETCH_VERSION "1.4.1a"
    #define MY_RADIO_NRF24
    
    #define DWELL_TIME 125  // this allows for radio to come back to power after a transmission, ideally 0 
    
    //#define DEBUG_ON  // comment out this line to disable serial debug
    //#define DHT_ON // uncomment out this line to enable DHT sensor
    //#define LUX_ON // uncomment out this line to enable BH1750 sensor
    //#define USE_DAILY // displays each time segment as an accumulation of prior periods inclusive.  Comment out to display individual daily rainfall totals in the variables sent to your controller.
    
    #define CALIBRATE_FACTOR 60 // amount of rain per rain bucket tip e.g. 5 is .05mm
    #define DHT_LUX_DELAY 300000  //Delay in milliseconds that the DHT and LUX sensors will wait before sending data
    
    #define CHILD_ID_RAIN_LOG 3  // Keeps track of accumulated rainfall
    #define CHILD_ID_TRIPPED_INDICATOR 4  // Indicates Tripped when rain detected
    #define EEPROM_BUFFER_LOCATION 0  // location of the EEPROM circular buffer
    #define E_BUFFER_LENGTH 240
    #define RAIN_BUCKET_SIZE 120
    
    #ifdef  DEBUG_ON
      #define DEBUG_PRINT(x)   Serial.print(x)
      #define DEBUG_PRINTLN(x) Serial.println(x)
      #define SERIAL_START(x)  Serial.begin(x)
      #else
      #define DEBUG_PRINT(x)
      #define DEBUG_PRINTLN(x)
      #define SERIAL_START(x)
    #endif
    #include <MySensors.h>
    //
    MyMessage msgRainRate(CHILD_ID_RAIN_LOG, V_RAINRATE);
    MyMessage msgRain(CHILD_ID_RAIN_LOG, V_RAIN);
    //
    MyMessage msgRainVAR1(CHILD_ID_RAIN_LOG, V_VAR1);
    MyMessage msgRainVAR2(CHILD_ID_RAIN_LOG, V_VAR2);
    MyMessage msgRainVAR3(CHILD_ID_RAIN_LOG, V_VAR3);
    MyMessage msgRainVAR4(CHILD_ID_RAIN_LOG, V_VAR4);
    MyMessage msgRainVAR5(CHILD_ID_RAIN_LOG, V_VAR5);
    //
    MyMessage msgTripped(CHILD_ID_TRIPPED_INDICATOR, V_TRIPPED);
    MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
    MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
    //
    #ifdef DHT_ON
      #include <DHT.h>
      #define CHILD_ID_HUM 0
      #define CHILD_ID_TEMP 1
      #define HUMIDITY_SENSOR_DIGITAL_PIN 8
      DHT dht;
      float lastTemp;
      float lastHum;
      boolean metric = true;
      MyMessage msgHum(CHILD_ID_HUM, V_HUM);
      MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
    #endif
    //
    #ifdef LUX_ON
      //BH1750 is connected to SCL (analog input A5) and SDA (analog input A4)
      #include <BH1750.h>
      #include <Wire.h>
      #define CHILD_ID_LIGHT 2
      BH1750 lightSensor;
      MyMessage msg(CHILD_ID_LIGHT, V_LIGHT_LEVEL);
      unsigned int lastlux;
      byte heartbeat = 10; //Used to send the light lux to gateway as soon as the device is restarted and after the DHT_LUX_DELAY has happened 10 times
    #endif
    unsigned long sensorPreviousMillis;
    int eepromIndex;
    int tipSensorPin = 3; // Pin the tipping bucket is connected to. Must be interrupt capable pin
    int ledPin = 5; // Pin the LED is connected to.  PWM capable pin required
    unsigned long dataMillis;
    unsigned long serialInterval = 600000UL;
    const unsigned long oneHour = 3600000UL;
    unsigned long lastTipTime;
    unsigned long lastRainTime; //Used for rainRate calculation
    unsigned int rainBucket [RAIN_BUCKET_SIZE] ; /* 24 hours x 5 Days = 120 hours */
    unsigned int rainRate = 0;
    byte rainWindow = 72;         //default rain window in hours.  Will be overwritten with msgTrippedVar1.
    volatile int wasTippedBuffer = 0;
    int rainSensorThreshold = 50; //default rain sensor sensitivity in hundredths.  Will be overwritten with msgTrippedVar2.
    byte state = 0;
    byte oldState = -1;
    int lastRainRate = 0;
    int lastMeasure = 0;
    boolean gotTime = false;
    byte lastHour;
    byte currentHour;
    //
    void setup()
    {
      SERIAL_START(115200);
      //
      // Set up the IO
      pinMode(tipSensorPin, INPUT_PULLUP);
      attachInterrupt (1, sensorTipped, FALLING);  // depending on location of the hall effect sensor may need CHANGE
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      //
      //Let's get the controller talking to the Arduino
      //begin(getVariables, NODE_ID);
    
      //
      //Sync time with the server, this will be called hourly in order to keep time from creeping with the crystal
      //
      unsigned long functionTimeout = millis();
      while (timeStatus() == timeNotSet && millis() - functionTimeout < 30000UL)
      {
    //    process();
        requestTime(receiveTime);
        DEBUG_PRINTLN(F("Getting Time"));
        wait(1000); // call once per second
        DEBUG_PRINTLN(F("."));
      }
      currentHour = hour();
      lastHour = hour();
      //
      //retrieve from EEPROM stored values on a power cycle.
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < E_BUFFER_LENGTH; i++)
      {
        byte locator = loadState(EEPROM_BUFFER_LOCATION + i);
        if (locator == 0xFE)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER_LOCATION + i;
          DEBUG_PRINT(F("EEPROM Index "));
          DEBUG_PRINTLN(eepromIndex);
          //Now that we have the buffer index let's populate the rainBucket[] with data from eeprom
          loadRainArray(eepromIndex);
          isDataOnEeprom = true;
          break;
        }
      }
      //
      if (!isDataOnEeprom) // Added for the first time it is run on a new Arduino
      {
        DEBUG_PRINTLN(F("I didn't find valid EEPROM Index, so I'm writing one to location 0"));
        eepromIndex = EEPROM_BUFFER_LOCATION;
        saveState(eepromIndex, 0xFE);
        saveState(eepromIndex + 1, 0xFE);
        //then I will clear out any bad data
        for (int i = 2; i <= E_BUFFER_LENGTH; i++)
        {
          saveState(i, 0x00);
        }
      }
      dataMillis = millis();
      lastTipTime = millis() - oneHour; //why is this -oneHour?? Doesn't millis() start at 0 when first powered on?
      //
      request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
      wait(DWELL_TIME);
      request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
      wait(DWELL_TIME);
      //
    #ifdef DHT_ON
      dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN);
      // Register all sensors to gw (they will be created as child devices)
      present(CHILD_ID_HUM, S_HUM);
      wait(DWELL_TIME);
      present(CHILD_ID_TEMP, S_TEMP);
      wait(DWELL_TIME);
      metric = getConfig().isMetric;
      .wait(DWELL_TIME);
    #endif
      //
    #ifdef LUX_ON
      present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
      wait(DWELL_TIME);
      lightSensor.begin();
    #endif
      //
      DEBUG_PRINTLN(F("Radio Setup Complete!"));
      transmitRainData();
    }
    void presentation()
    {
      sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      wait(DWELL_TIME);
      present(CHILD_ID_RAIN_LOG, S_RAIN);
      wait(DWELL_TIME);
      present(CHILD_ID_TRIPPED_INDICATOR, S_MOTION);
      wait(DWELL_TIME);
      DEBUG_PRINTLN(F("Sensor Presentation Complete"));
    }
    //
    void loop()
    {
    //  process();
      if (state)
      {
        prettyFade();  // breathe if tripped
      }
      else
      {
        slowFlash();   // blink if not tripped
      }
    #ifdef DEBUG_ON  // Serial Debug Block
      if ( (millis() - dataMillis) >= serialInterval)
      {
        for (int i = 24; i <= 120; i = i + 24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
    #endif
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
      int measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
        if (measure != lastMeasure)
        {
          //      DEBUG_PRINT(F("measure value (total rainBucket within rainWindow): "));
          //      DEBUG_PRINTLN(measure);
          lastMeasure = measure;
        }
      }
      //
      state = (measure >= (rainSensorThreshold * 100));
      if (state != oldState)
      {
        send(msgTripped.set(state));
        wait(DWELL_TIME);
        DEBUG_PRINT(F("New Sensor State... Sensor: "));
        DEBUG_PRINTLN(state ? "Tripped" : "Not Tripped");
        oldState = state;
      }
      //
      unsigned long tipDelay = millis() - lastRainTime;
      if (wasTippedBuffer) // if was tipped, then update the 24hour total and transmit to Vera
      {
        DEBUG_PRINTLN(F("Sensor Tipped"));
        DEBUG_PRINT(F("rainBucket [0] value: "));
        DEBUG_PRINTLN(rainBucket [0]);
        send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); //Calculate the total rain for the day
        wait(DWELL_TIME);
        wasTippedBuffer--;
        rainRate = ((oneHour) / tipDelay);
        if (rainRate != lastRainRate)
        {
          send(msgRainRate.set(rainRate, 1));
          wait(DWELL_TIME);
          DEBUG_PRINT(F("RainRate= "));
          DEBUG_PRINTLN(rainRate);
          lastRainRate = rainRate;
        }
        lastRainTime = lastTipTime;
      }
      //
      currentHour = hour();
      if (currentHour != lastHour)
      {
        DEBUG_PRINTLN(F("One hour elapsed."));
        send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); // send today's rainfall
        wait(DWELL_TIME);
        saveState(eepromIndex, highByte(rainBucket[0]));
        saveState(eepromIndex + 1, lowByte(rainBucket[0]));
        DEBUG_PRINT(F("Saving rainBucket[0] to eeprom. rainBucket[0] = "));
        DEBUG_PRINTLN(rainBucket[0]);
        for (int i = RAIN_BUCKET_SIZE - 1; i >= 0; i--)//cascade an hour of values back into the array
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
        wait(DWELL_TIME);
        request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
        wait(DWELL_TIME);
        rainBucket[0] = 0;
        eepromIndex = eepromIndex + 2;
        if (eepromIndex > EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH)
        {
          eepromIndex = EEPROM_BUFFER_LOCATION;
        }
        DEBUG_PRINT(F("Writing to EEPROM.  Index: "));
        DEBUG_PRINTLN(eepromIndex);
        saveState(eepromIndex, 0xFE);
        saveState(eepromIndex + 1, 0xFE);
        requestTime(receiveTime); // sync the time every hour
        wait(DWELL_TIME);
        transmitRainData();
        rainRate = 0;
        send(msgRainRate.set(rainRate, 1));
        wait(DWELL_TIME);
        DEBUG_PRINTLN(F("Sending rainRate is 0 to controller"));
        lastHour = currentHour;
      }
      if (millis() - sensorPreviousMillis > DHT_LUX_DELAY)
      {
        #ifdef DHT_ON  //DHT Code
          doDHT();
        #endif
        #ifdef LUX_ON
          doLUX();
        #endif
        sensorPreviousMillis = millis();
      }
    }
    //
    #ifdef DHT_ON
    void doDHT(void)
    {
      float temperature = dht.getTemperature();
        if (isnan(temperature)) 
        {
          DEBUG_PRINTLN(F("Failed reading temperature from DHT"));
        } else if (temperature != lastTemp) 
        {
          lastTemp = temperature;
          if (!metric) 
          {
            temperature = dht.toFahrenheit(temperature);
          }
          gw.send(msgTemp.set(temperature, 1));
          gw.wait(DWELL_TIME);
          DEBUG_PRINT(F("Temperature is: "));
          DEBUG_PRINTLN(temperature);
        }
        float humidity = dht.getHumidity();;
        if (isnan(humidity)) 
        {
          DEBUG_PRINTLN(F("Failed reading humidity from DHT"));
        } else if (humidity != lastHum) 
        {
          lastHum = humidity;
          gw.send(msgHum.set(humidity, 1));
          gw.wait(DWELL_TIME);
          DEBUG_PRINT(F("Humidity is: "));
          DEBUG_PRINTLN(humidity);
        }
    }
    #endif
    //
    #ifdef LUX_ON
    void doLUX(void)
    {
      unsigned int lux = lightSensor.readLightLevel();// Get Lux value
      DEBUG_PRINT(F("Current LUX Level: "));
      DEBUG_PRINTLN(lux);
      heartbeat++;
      if (lux != lastlux || heartbeat > 10) 
      {
        gw.send(msg.set(lux));
        lastlux = lux;
      }
      if (heartbeat > 10) 
      {
        heartbeat = 0;
      }
    }
    #endif
    //
    void sensorTipped()
    {
      unsigned long thisTipTime = millis();
      if (thisTipTime - lastTipTime > 100UL)
      {
        rainBucket[0] += CALIBRATE_FACTOR; // adds CALIBRATE_FACTOR hundredths of unit each tip
        wasTippedBuffer++;
      }
      lastTipTime = thisTipTime;
    }
    //
    int rainTotal(int hours)
    {
      int total = 0;
      for ( int i = 0; i <= hours; i++)
      {
        total += rainBucket [i];
      }
      return total;
    }
    
    void updateSerialData(int x)
    {
      DEBUG_PRINT(F("Rain last "));
      DEBUG_PRINT(x);
      DEBUG_PRINTLN(F(" hours: "));
      float tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      tipCount = tipCount / 100;
      DEBUG_PRINTLN(tipCount);
    }
    
    void loadRainArray(int value) // retrieve stored rain array from EEPROM on powerup
    {
      for (int i = 0; i < RAIN_BUCKET_SIZE; i++)
      {
        value = value - 2;
        if (value < EEPROM_BUFFER_LOCATION)
        {
          value = EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH;
        }
        DEBUG_PRINT(F("EEPROM location: "));
        DEBUG_PRINTLN(value);
        byte rainValueHigh = loadState(value);
        byte rainValueLow = loadState(value + 1);
        unsigned int rainValue = rainValueHigh << 8;
        rainValue |= rainValueLow;
        rainBucket[i + 1] = rainValue;
        //
        DEBUG_PRINT(F("rainBucket[ value: "));
        DEBUG_PRINT(i + 1);
        DEBUG_PRINT(F("] value: "));
        DEBUG_PRINTLN(rainBucket[i + 1]);
      }
    }
    
    void transmitRainData(void)
    {
      DEBUG_PRINT(F("In transmitRainData. currentHour = "));
      DEBUG_PRINTLN(currentHour);
      int rainUpdateTotal = 0;
      for (int i = currentHour; i >= 0; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 1: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR1.set((float)rainUpdateTotal / 100.0, 1)); //Send current day rain totals (resets at midnight)
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 24; i > currentHour; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 2: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR2.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 48; i > currentHour + 24; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 3: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR3.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 72; i > currentHour + 48; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 4: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR4.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 96; i > currentHour + 72; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 5: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR5.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    }
    
    void getVariables(const MyMessage &message)
    {
      if (message.sensor == CHILD_ID_RAIN_LOG)
      {
        // nothing to do here
      }
      else if (message.sensor == CHILD_ID_TRIPPED_INDICATOR)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 1)
          {
            rainWindow = 1;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 10000)
          {
            rainSensorThreshold = 10000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    
    void prettyFade(void)
    {
      float val = (exp(sin(millis() / 2000.0 * PI)) - 0.36787944) * 108.0;
      analogWrite(ledPin, val);
    }
    
    void slowFlash(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 100UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }
    
    void receiveTime(unsigned long time)
    {
      DEBUG_PRINTLN(F("Time received..."));
      setTime(time);
      char theTime[6];
      sprintf(theTime, "%d:%2d", hour(), minute());
      DEBUG_PRINTLN(theTime);
    }
    

  • Hero Member

    @niccodemi syntax for time functions is:

    requestTime();						// Request latest time from controller
    
    // This is called when a new time value was received
    void receiveTime(unsigned long controllerTime) {
    }
    


  • @AWI thank you. Sketch compiles now but after uploading it still doesn't work - serial monitor is blank (debug on).



  • Using the new code for version 2.0.0 i'm getting this errors:

    C:\Users\marco\Documents\Arduino\Nova pasta\RainGauge\RainGauge.ino: In function 'void setup()':

    RainGauge:145: error: 'timeStatus' was not declared in this scope

    while (timeStatus() == timeNotSet && millis() - functionTimeout < 30000UL)

    RainGauge:145: error: 'timeNotSet' was not declared in this scope

    while (timeStatus() == timeNotSet && millis() - functionTimeout < 30000UL)

    RainGauge:152: error: 'hour' was not declared in this scope

    currentHour = hour();

    C:\Users\marco\Documents\Arduino\Nova pasta\RainGauge\RainGauge.ino: In function 'void loop()':

    RainGauge:299: error: 'hour' was not declared in this scope

    currentHour = hour();

    C:\Users\marco\Documents\Arduino\Nova pasta\RainGauge\RainGauge.ino: In function 'void receiveTime(long unsigned int)':

    RainGauge:597: error: 'setTime' was not declared in this scope

    setTime(time);

    RainGauge:599: error: 'hour' was not declared in this scope

    sprintf(theTime, "%d:%2d", hour(), minute());

    RainGauge:599: error: 'minute' was not declared in this scope

    sprintf(theTime, "%d:%2d", hour(), minute());

    Using library MySensors at version 2.0.0 in folder: C:\Users\marco\Documents\Arduino\libraries\MySensors
    Using library SPI at version 1.0 in folder: C:\Users\marco\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.13\libraries\SPI
    Using library Wire at version 1.0 in folder: C:\Users\marco\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.13\libraries\Wire
    exit status 1
    'timeStatus' was not declared in this scope

    Does anyone now how to fix this ??



  • @mrc-core is this the sketch you are using?

    /*
     Arduino Tipping Bucket Rain Gauge
    
     April 26, 2015
    
     Version 1.4.1 alpha
    
     Arduino Tipping Bucket Rain Gauge
    
     Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
     gateway you can measure and sense local rain.  This sketch will create two devices on your
     Vera controller.  One will display your total precipitation for the last 5 days.  The other, 
     a sensor that changes state if there is recent rain (up to last 120 hours)  above a threshold.  
     Both these settings are user definable.
    
     There is a build overview video here: https://youtu.be/1eMfKQaLROo
    
     This sketch features the following:
    
     * Allows you to set the rain threshold in mm
     * Allows you to determine the tripped indicator window up to 120 hours.
     * Displays the last 5 days of rain in Variable1 through Variable5
       of the Rain Sensor device
     * Configuration changes to Sensor device updated every hour
     * Should run on any Arduino
     * Will retain Tripped/Not Tripped status and data in a power interruption, saving small amount
       of data to EEPROM (Circular Buffer to maximize life of EEPROM)
     * LED status indicator
     * Optional Temp/Humidity (DHT-22 or DHT-11) and Light LUX (BH1750) sensors. To use, uncomment
       #define DHT_ON  and/or #define LUX_ON
     * Optionally send total accumulation of each day's rainfall or send only individual days rainfall totals.
       Comment out #define USE_DAILY to display individual daily rainfall.
    
     by @BulldogLowell and @PeteWill for free public use
    
     */
    
    // Enable debug prints to serial monitor
    #define MY_DEBUG 
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    //#define MY_NODE_ID 7
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <math.h>
    #include <Time.h>
    
    #define SKETCH_NAME "Rain Gauge"
    #define SKETCH_VERSION "1.4.1a"
    
    #define DWELL_TIME 125  // this allows for radio to come back to power after a transmission, ideally 0 
    
    //#define DEBUG_ON  // comment out this line to disable serial debug
    #define DHT_ON // uncomment out this line to enable DHT sensor
    #define LUX_ON // uncomment out this line to enable BH1750 sensor
    //#define USE_DAILY // displays each time segment as an accumulation of prior periods inclusive.  Comment out to display individual daily rainfall totals in the variables sent to your controller.
    
    #define TIP_SENSOR_PIN 3
    
    #define CALIBRATE_FACTOR 60 // amount of rain per rain bucket tip e.g. 5 is .05mm
    #define DHT_LUX_DELAY 300000  //Delay in milliseconds that the DHT and LUX sensors will wait before sending data
    
    #define CHILD_ID_RAIN_LOG 3  // Keeps track of accumulated rainfall
    #define CHILD_ID_TRIPPED_INDICATOR 4  // Indicates Tripped when rain detected
    #define EEPROM_BUFFER_LOCATION 0  // location of the EEPROM circular buffer
    #define E_BUFFER_LENGTH 240
    #define RAIN_BUCKET_SIZE 120
    
    #ifdef  DEBUG_ON
      #define DEBUG_PRINT(x)   Serial.print(x)
      #define DEBUG_PRINTLN(x) Serial.println(x)
      #define SERIAL_START(x)  Serial.begin(x)
      #else
      #define DEBUG_PRINT(x)
      #define DEBUG_PRINTLN(x)
      #define SERIAL_START(x)
    #endif
    //
    MyMessage msgRainRate(CHILD_ID_RAIN_LOG, V_RAINRATE);
    MyMessage msgRain(CHILD_ID_RAIN_LOG, V_RAIN);
    //
    MyMessage msgRainVAR1(CHILD_ID_RAIN_LOG, V_VAR1);
    MyMessage msgRainVAR2(CHILD_ID_RAIN_LOG, V_VAR2);
    MyMessage msgRainVAR3(CHILD_ID_RAIN_LOG, V_VAR3);
    MyMessage msgRainVAR4(CHILD_ID_RAIN_LOG, V_VAR4);
    MyMessage msgRainVAR5(CHILD_ID_RAIN_LOG, V_VAR5);
    //
    MyMessage msgTripped(CHILD_ID_TRIPPED_INDICATOR, V_TRIPPED);
    MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
    MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
    //
    #ifdef DHT_ON
      #include <DHT.h>
      #define CHILD_ID_HUM 0
      #define CHILD_ID_TEMP 1
      #define HUMIDITY_SENSOR_DIGITAL_PIN 8
      DHT dht;
      float lastTemp;
      float lastHum;
      boolean metric = true;
      MyMessage msgHum(CHILD_ID_HUM, V_HUM);
      MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
    #endif
    //
    #ifdef LUX_ON
      //BH1750 is connected to SCL (analog input A5) and SDA (analog input A4)
      #include <BH1750.h>
      #include <Wire.h>
      #define CHILD_ID_LIGHT 2
      BH1750 lightSensor;
      MyMessage msg(CHILD_ID_LIGHT, V_LIGHT_LEVEL);
      unsigned int lastlux;
      byte heartbeat = 10; //Used to send the light lux to gateway as soon as the device is restarted and after the DHT_LUX_DELAY has happened 10 times
    #endif
    unsigned long sensorPreviousMillis;
    int eepromIndex;
    int ledPin = 5; // Pin the LED is connected to.  PWM capable pin required
    unsigned long dataMillis;
    unsigned long serialInterval = 600000UL;
    const unsigned long oneHour = 3600000UL;
    unsigned long lastTipTime;
    unsigned long lastRainTime; //Used for rainRate calculation
    unsigned int rainBucket [RAIN_BUCKET_SIZE] ; /* 24 hours x 5 Days = 120 hours */
    unsigned int rainRate = 0;
    byte rainWindow = 72;         //default rain window in hours.  Will be overwritten with msgTrippedVar1.
    volatile int wasTippedBuffer = 0;
    int rainSensorThreshold = 50; //default rain sensor sensitivity in hundredths.  Will be overwritten with msgTrippedVar2.
    byte state = 0;
    byte oldState = -1;
    unsigned int lastRainRate = 0;
    int lastMeasure = 0;
    boolean gotTime = false;
    byte lastHour;
    byte currentHour;
    //
    void setup()
    {
      SERIAL_START(115200);
      //
      // Set up the IO
      pinMode(TIP_SENSOR_PIN, INPUT_PULLUP);
      attachInterrupt (digitalPinToInterrupt(TIP_SENSOR_PIN), sensorTipped, FALLING);  // depending on location of the hall effect sensor may need CHANGE
      pinMode(ledPin, OUTPUT);
      digitalWrite(ledPin, HIGH);
      //
      //Sync time with the server, this will be called hourly in order to keep time from creeping with the crystal
      //
      unsigned long functionTimeout = millis();
      while (timeStatus() == timeNotSet && millis() - functionTimeout < 30000UL)
      {
        requestTime();
        DEBUG_PRINTLN(F("Getting Time"));
        wait(1000); // call once per second
        DEBUG_PRINTLN(F("."));
      }
      currentHour = hour();
      lastHour = hour();
      //
      //retrieve from EEPROM stored values on a power cycle.
      //
      boolean isDataOnEeprom = false;
      for (int i = 0; i < E_BUFFER_LENGTH; i++)
      {
        byte locator = loadState(EEPROM_BUFFER_LOCATION + i);
        if (locator == 0xFE)  // found the EEPROM circular buffer index
        {
          eepromIndex = EEPROM_BUFFER_LOCATION + i;
          DEBUG_PRINT(F("EEPROM Index "));
          DEBUG_PRINTLN(eepromIndex);
          //Now that we have the buffer index let's populate the rainBucket[] with data from eeprom
          loadRainArray(eepromIndex);
          isDataOnEeprom = true;
          break;
        }
      }
      //
      if (!isDataOnEeprom) // Added for the first time it is run on a new Arduino
      {
        DEBUG_PRINTLN(F("I didn't find valid EEPROM Index, so I'm writing one to location 0"));
        eepromIndex = EEPROM_BUFFER_LOCATION;
        saveState(eepromIndex, 0xFE);
        saveState(eepromIndex + 1, 0xFE);
        //then I will clear out any bad data
        for (int i = 2; i <= E_BUFFER_LENGTH; i++)
        {
          saveState(i, 0x00);
        }
      }
      dataMillis = millis();
      lastTipTime = millis() - oneHour; //why is this -oneHour?? Doesn't millis() start at 0 when first powered on?
      //
      request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
      wait(DWELL_TIME);
      request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
      wait(DWELL_TIME);
      //
    #ifdef DHT_ON
      dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN);
      wait(DWELL_TIME);
      metric = getConfig().isMetric;
    #endif
      //
    #ifdef LUX_ON
      wait(DWELL_TIME);
      lightSensor.begin();
    #endif
      //
      DEBUG_PRINTLN(F("Radio Setup Complete!"));
      transmitRainData();
    }
    
    
    void presentation()  {
      // Register all sensors to gw (they will be created as child devices)
      sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      wait(DWELL_TIME);
      present(CHILD_ID_RAIN_LOG, S_RAIN);
      wait(DWELL_TIME);
      present(CHILD_ID_TRIPPED_INDICATOR, S_MOTION);
      wait(DWELL_TIME);
    
    #ifdef DHT_ON
      present(CHILD_ID_HUM, S_HUM);
      wait(DWELL_TIME);
      present(CHILD_ID_TEMP, S_TEMP);
      wait(DWELL_TIME);
    #endif
    
    
    #ifdef LUX_ON
      present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
    #endif
    
      DEBUG_PRINTLN(F("Sensor Presentation Complete"));
    }
    
    void loop()
    {
      if (state)
      {
        prettyFade();  // breathe if tripped
      }
      else
      {
        slowFlash();   // blink if not tripped
      }
    #ifdef DEBUG_ON  // Serial Debug Block
      if ( (millis() - dataMillis) >= serialInterval)
      {
        for (int i = 24; i <= 120; i = i + 24)
        {
          updateSerialData(i);
        }
        dataMillis = millis();
      }
    #endif
      //
      // let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
      //
      int measure = 0; // Check to see if we need to show sensor tripped in this block
      for (int i = 0; i < rainWindow; i++)
      {
        measure += rainBucket [i];
        if (measure != lastMeasure)
        {
          //      DEBUG_PRINT(F("measure value (total rainBucket within rainWindow): "));
          //      DEBUG_PRINTLN(measure);
          lastMeasure = measure;
        }
      }
      //
      state = (measure >= (rainSensorThreshold * 100));
      if (state != oldState)
      {
        send(msgTripped.set(state));
        wait(DWELL_TIME);
        DEBUG_PRINT(F("New Sensor State... Sensor: "));
        DEBUG_PRINTLN(state ? "Tripped" : "Not Tripped");
        oldState = state;
      }
      //
      unsigned long tipDelay = millis() - lastRainTime;
      if (wasTippedBuffer) // if was tipped, then update the 24hour total and transmit to Vera
      {
        DEBUG_PRINTLN(F("Sensor Tipped"));
        DEBUG_PRINT(F("rainBucket [0] value: "));
        DEBUG_PRINTLN(rainBucket [0]);
        send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); //Calculate the total rain for the day
        wait(DWELL_TIME);
        wasTippedBuffer--;
        rainRate = ((oneHour) / tipDelay);
        if (rainRate != lastRainRate)
        {
          send(msgRainRate.set(rainRate, 1));
          wait(DWELL_TIME);
          DEBUG_PRINT(F("RainRate= "));
          DEBUG_PRINTLN(rainRate);
          lastRainRate = rainRate;
        }
        lastRainTime = lastTipTime;
      }
      //
      currentHour = hour();
      if (currentHour != lastHour)
      {
        DEBUG_PRINTLN(F("One hour elapsed."));
        send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); // send today's rainfall
        wait(DWELL_TIME);
        saveState(eepromIndex, highByte(rainBucket[0]));
        saveState(eepromIndex + 1, lowByte(rainBucket[0]));
        DEBUG_PRINT(F("Saving rainBucket[0] to eeprom. rainBucket[0] = "));
        DEBUG_PRINTLN(rainBucket[0]);
        for (int i = RAIN_BUCKET_SIZE - 1; i >= 0; i--)//cascade an hour of values back into the array
        {
          rainBucket [i + 1] = rainBucket [i];
        }
        request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
        wait(DWELL_TIME);
        request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
        wait(DWELL_TIME);
        rainBucket[0] = 0;
        eepromIndex = eepromIndex + 2;
        if (eepromIndex > EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH)
        {
          eepromIndex = EEPROM_BUFFER_LOCATION;
        }
        DEBUG_PRINT(F("Writing to EEPROM.  Index: "));
        DEBUG_PRINTLN(eepromIndex);
        saveState(eepromIndex, 0xFE);
        saveState(eepromIndex + 1, 0xFE);
        requestTime(); // sync the time every hour
        wait(DWELL_TIME);
        transmitRainData();
        rainRate = 0;
        send(msgRainRate.set(rainRate, 1));
        wait(DWELL_TIME);
        DEBUG_PRINTLN(F("Sending rainRate is 0 to controller"));
        lastHour = currentHour;
      }
      if (millis() - sensorPreviousMillis > DHT_LUX_DELAY)
      {
        #ifdef DHT_ON  //DHT Code
          doDHT();
        #endif
        #ifdef LUX_ON
          doLUX();
        #endif
        sensorPreviousMillis = millis();
      }
    }
    //
    #ifdef DHT_ON
    void doDHT(void)
    {
      float temperature = dht.getTemperature();
        if (isnan(temperature)) 
        {
          DEBUG_PRINTLN(F("Failed reading temperature from DHT"));
        } else if (temperature != lastTemp) 
        {
          lastTemp = temperature;
          if (!metric) 
          {
            temperature = dht.toFahrenheit(temperature);
          }
          send(msgTemp.set(temperature, 1));
          wait(DWELL_TIME);
          DEBUG_PRINT(F("Temperature is: "));
          DEBUG_PRINTLN(temperature);
        }
        float humidity = dht.getHumidity();;
        if (isnan(humidity)) 
        {
          DEBUG_PRINTLN(F("Failed reading humidity from DHT"));
        } else if (humidity != lastHum) 
        {
          lastHum = humidity;
          send(msgHum.set(humidity, 1));
          wait(DWELL_TIME);
          DEBUG_PRINT(F("Humidity is: "));
          DEBUG_PRINTLN(humidity);
        }
    }
    #endif
    //
    #ifdef LUX_ON
    void doLUX(void)
    {
      unsigned int lux = lightSensor.readLightLevel();// Get Lux value
      DEBUG_PRINT(F("Current LUX Level: "));
      DEBUG_PRINTLN(lux);
      heartbeat++;
      if (lux != lastlux || heartbeat > 10) 
      {
        send(msg.set(lux));
        lastlux = lux;
      }
      if (heartbeat > 10) 
      {
        heartbeat = 0;
      }
    }
    #endif
    //
    void sensorTipped()
    {
      unsigned long thisTipTime = millis();
      if (thisTipTime - lastTipTime > 100UL)
      {
        rainBucket[0] += CALIBRATE_FACTOR; // adds CALIBRATE_FACTOR hundredths of unit each tip
        wasTippedBuffer++;
      }
      lastTipTime = thisTipTime;
    }
    //
    int rainTotal(int hours)
    {
      int total = 0;
      for ( int i = 0; i <= hours; i++)
      {
        total += rainBucket [i];
      }
      return total;
    }
    
    void updateSerialData(int x)
    {
      DEBUG_PRINT(F("Rain last "));
      DEBUG_PRINT(x);
      DEBUG_PRINTLN(F(" hours: "));
      float tipCount = 0;
      for (int i = 0; i < x; i++)
      {
        tipCount = tipCount + rainBucket [i];
      }
      tipCount = tipCount / 100;
      DEBUG_PRINTLN(tipCount);
    }
    
    void loadRainArray(int value) // retrieve stored rain array from EEPROM on powerup
    {
      for (int i = 0; i < RAIN_BUCKET_SIZE; i++)
      {
        value = value - 2;
        if (value < EEPROM_BUFFER_LOCATION)
        {
          value = EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH;
        }
        DEBUG_PRINT(F("EEPROM location: "));
        DEBUG_PRINTLN(value);
        byte rainValueHigh = loadState(value);
        byte rainValueLow = loadState(value + 1);
        unsigned int rainValue = rainValueHigh << 8;
        rainValue |= rainValueLow;
        rainBucket[i + 1] = rainValue;
        //
        DEBUG_PRINT(F("rainBucket[ value: "));
        DEBUG_PRINT(i + 1);
        DEBUG_PRINT(F("] value: "));
        DEBUG_PRINTLN(rainBucket[i + 1]);
      }
    }
    
    void transmitRainData(void)
    {
      DEBUG_PRINT(F("In transmitRainData. currentHour = "));
      DEBUG_PRINTLN(currentHour);
      int rainUpdateTotal = 0;
      for (int i = currentHour; i >= 0; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 1: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR1.set((float)rainUpdateTotal / 100.0, 1)); //Send current day rain totals (resets at midnight)
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 24; i > currentHour; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 2: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR2.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 48; i > currentHour + 24; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 3: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR3.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 72; i > currentHour + 48; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 4: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR4.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    #ifdef USE_DAILY
      rainUpdateTotal = 0;
    #endif
      for (int i = currentHour + 96; i > currentHour + 72; i--)
      {
        rainUpdateTotal += rainBucket[i];
        DEBUG_PRINT(F("Adding rainBucket["));
        DEBUG_PRINT(i);
        DEBUG_PRINTLN(F("] to rainUpdateTotal."));
      }
      DEBUG_PRINT(F("TX Day 5: rainUpdateTotal = "));
      DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
      send(msgRainVAR5.set((float)rainUpdateTotal / 100.0, 1));
      wait(DWELL_TIME);
    }
    
    void receive(const MyMessage &message)
    {
      if (message.sensor == CHILD_ID_RAIN_LOG)
      {
        // nothing to do here
      }
      else if (message.sensor == CHILD_ID_TRIPPED_INDICATOR)
      {
        if (message.type == V_VAR1)
        {
          rainWindow = atoi(message.data);
          if (rainWindow > 120)
          {
            rainWindow = 120;
          }
          else if (rainWindow < 1)
          {
            rainWindow = 1;
          }
          if (rainWindow != atoi(message.data))   // if I changed the value back inside the boundries, push that number back to Vera
          {
            send(msgTrippedVar1.set(rainWindow));
          }
        }
        else if (message.type == V_VAR2)
        {
          rainSensorThreshold = atoi(message.data);
          if (rainSensorThreshold > 10000)
          {
            rainSensorThreshold = 10000;
          }
          else if (rainSensorThreshold < 1)
          {
            rainSensorThreshold = 1;
          }
          if (rainSensorThreshold != atoi(message.data))  // if I changed the value back inside the boundries, push that number back to Vera
          {
            send(msgTrippedVar2.set(rainSensorThreshold));
          }
        }
      }
    }
    
    void prettyFade(void)
    {
      float val = (exp(sin(millis() / 2000.0 * PI)) - 0.36787944) * 108.0;
      analogWrite(ledPin, val);
    }
    
    void slowFlash(void)
    {
      static boolean ledState = true;
      static unsigned long pulseStart = millis();
      if (millis() - pulseStart < 100UL)
      {
        digitalWrite(ledPin, !ledState);
        pulseStart = millis();
      }
    }
    
    void receiveTime(unsigned long time)
    {
      DEBUG_PRINTLN(F("Time received..."));
      setTime(time);
      char theTime[6];
      sprintf(theTime, "%d:%2d", hour(), minute());
      DEBUG_PRINTLN(theTime);
    }
    

    It compiles fine with Arduino IDE 1.6.5, Mysensors 2.0.0 and libraries (Time, BH1750).



  • Yes that's the one.


Log in to reply
 

Suggested Topics

0
Online

11.4k
Users

11.1k
Topics

112.7k
Posts