calculate running 60 minute total

I have a weather station (Davis VP2) that will send my Arduino rain data. It comes in as one byte and just counts up to 255 then rolls over. Each digit is 0.1 inches of rain. I want to send the data to Weather Underground and they want it as a rolling 60 minute total. I was thinking of creating a 60 byte array (one element for each minute). Then looping through the array to calculate the total. As new data comes in for a particular minute, I'd overwrite the old data for that minute. It seems like this approach should work okay, but I was wondering if there are other ways I could do this?

Hi, Does the rain gauge send the byte each time it is updated, or on a regular basis whether it has changed or not?

You could use your 60 byte array as a "circular buffer", and keep a separate running total for the last hour. Lets say the time is now 23 minutes pas the hour. You would subtract the value in index 23 of the array (which is now an hour old) from your running total, then replace array index 23 with your new value for the last minute, and also add that to the running total. This means: 1. you don't have to keep adding up the 60 array elements and 2 . you don't need to move the values around in the array.

So its more elegant and efficient. In practice, you would never notice any performance improvement!

Paul

How are you tracking the time? Does the Arduino have an RTC module on it, or are you counting the minutes in some other way? Is the data being sent to the WU an average for the hour? Something like the following might work if I understand the problem correctly:

void setup()
{
   // Whatever it takes to set things up??
 }

void loop()
{
  static int currentMinute, lastMinute = 0;
  static float hourlyRainTotal = 0.0;

  currentMinute = GetMinute();      // Might be a call to a RTC module

  if (currentMinute != to lastMinute) {      // Has a minute gone by since last reading?
    hourlyRainTotal += GetRainTotal(currentMinute);
    if (lastMinute == 59) {
      hourlyRainTotal = GetRainTotal(59);
      hourlyRainTotal = 0.0;      // Get ready for next hour
    }
    lastMinute = currentMinute;
  }
}

float GetRainTotal(int minute)
{
  static byte rainPerMinute[60];
  int i;
  int sum= 0;

  total[minute] = ReadDavisVP2();      // Get the current rain value

  if (minute == 59) {      // We are ready to find an hour's worth of rain 
    for (i = 0; i < 60; i++) {
      sum += rainPerMinute[i];
    }
    return sum * .1;      // The total rain during the last hour
  } else {      // Return the current rain total for the current minute
    return total[minute] * .1;
}

Clearly I haven’t tested the code, so this is just an outline of how it might work.

I'll be using the time.h library and getting the time from NTP time servers on the internet.

I don't want to reset the hourly rain to zero after an hour is over, I want to drop off any rain that's over an hour old. For example lets say my rain came in like this (I'm skipping minutes with zero rain)

minute 1 = 0.1" minute 2 = 0.2" minute 3 = 0.1" minute 55 = 0.3"

So at the end of the hour, my rain total would be all these added up = 0.7" Assuming it's not still raining, at 1 hour and one minute, I'd drop off the 0.1" from an hour ago and my new hourly rain total would be .2 + .1 + 3. = 0.6 At 1 hour 2 minutes the hourly total would be .1 + .3 = .4" At 1 hour and 55 minutes, I'd finally drop off all the old rain and my hourly rain total would be zero.

Yes, that's how I understood your original post. See my suggestion above.

PaulRB: Yes, that's how I understood your original post. See my suggestion above.

I was looking at econjacks code and missed your post. I like your suggestion. I prefer to make the code more "elegant" even if I'll never notice the performance difference. Thank you!

I'll post my code once I write it.

Here’s what I came up with. I haven’t tested it yet.
rainCounter is a global byte and it’s basically a pulse counter from the rain gauge. It just counts up by one every time the rain gauge measuring cups tips. When it reaches 255, it just rolls over to zero and keeps on going.

void updateRainAccum()
{ 
  static uint8_t rainEachMinute[60]; // array holds incremental rain for each minute of the hour
  static uint8_t prevRainCnt = 0;    // rain count (not incremental) from previous minute
  
  if ( isNewMinute() )
  {
    DateTime now = rtc.now();
    uint8_t newMinute = now.minute();
    
    // Calculate new rain since since the last minute
    int newRain; // incremental new rain since last minute
    if ( rainCounter < prevRainCnt )
    { newRain = (256 - prevRainCnt) + rainCounter; } // counter has rolled over
    else
    { newRain = rainCounter - prevRainCnt; }
    
    // add new rain and remove rain from an hour ago
    loopData.rainRate = loopData.rainRate + newRain - rainEachMinute[newMinute];   
      
    rainEachMinute[newMinute] = newRain;  // Update array with latest rain amount
    prevRainCnt = rainCounter; 
    
    // Increment daily rain counter
    loopData.dayRain += newRain;
  } 

  // reset daily rain accumulation
  if ( isNewDay() );
  { loopData.dayRain = 0; } 
  
} // end updateRainAccum()

I had a thunderstorm come through and after looking at the graphs I realized that looking at the rain for the last 60 minutes actually was too much averaging. I could have torrential rain for 10 minutes and no rain for the other 50 and you wouldn’t really see a huge spike. So I decided to just track for 5 minutes, then multiply by 12 to get the hourly rate. Here’s the new code.

  static byte rainEachMinute[5];  // array holds incremental rain for a 5 minute period
  static byte minuteIndex = 0;       // index for rainEachMinute
  if ( isNewMinute() )
  {
    // Calculate new rain since since the last minute
    int newRain = 0; // incremental new rain since last minute
    if ( rainCounter < prevRainCnt )
    { newRain = (256 - prevRainCnt) + rainCounter; } // variable has rolled over
    else
    { newRain = rainCounter - prevRainCnt; }
    
    rainEachMinute[minuteIndex++] = newRain;
    if ( minuteIndex == 5 )
    { minuteIndex = 0; }
    
    // calculate hourly rain rate
    uint16_t sumRain = 0;
    for (byte i = 0; i < 5; i++)
    { sumRain += rainEachMinute[i]; }
    rainRate = sumRain * 12;
    
    prevRainCnt = rainCounter;
  }

@ScottG,

I just wrapped up a tipping bucket rain gage (here in princeton, FYI) and though I would share it with you.

I looked at hourly tips in order to be able to feed data to my home automation system.

Take a look, it has another feature which stores recent history (120hrs) in EEPROM (circular buffer).

I hope you can get something out of it.

here is a snippet, file attached:

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);
}

RainGauge.ino (8.36 KB)