How to add a time stamp from RTC on event?

Hi,

I am struggling to add a timestamp when an event occurs.
I would like to add a timestamp from an RTC when the maximum windspeed happened.
I have made a String variable, MaxwindTime, which should take on the current time, TimeBuffer, of the RTC. However, this doesn't seem to work.
Any ideas how to do this?

#include "Sodaq_DS3231.h"

char TimeBuffer[20] = "";   //Full date and time stamp
String MaxWindTime;
int anemometerOutput; //Read analogue input A0 (0-1023)
int CurrentWindSpeed;
int MaxWind=0;
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void setup() {
  Serial.begin(9600);
  rtc.begin(); //Start the RTC library code
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void loop() {
  windSpeed();
  maxWind();
  Clock();
  printWindData();
  delay(500);
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void windSpeed() {
   anemometerOutput = analogRead(A0); //Read analogue input A0 (0-1023)
   CurrentWindSpeed = map(anemometerOutput,0,1023,0,180); //10-bits input, converts 0 to 5V to 0kph to 180kph
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
 void maxWind(){
  if(CurrentWindSpeed > MaxWind) {
    MaxWind = CurrentWindSpeed; 
    MaxWindTime = TimeBuffer;
}  
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void printWindData() {
   Serial.print("Current windspeed = ");
   Serial.print(CurrentWindSpeed);
   Serial.print("kph, ");
   Serial.print(CurrentWindSpeed / 3.6,1); //Conversion to meters/second, 1 decimal
   Serial.print("m/s, ");
   Serial.print(CurrentWindSpeed * 0.539957,1); //Conversion to knots, 1 decimal
   Serial.println("knots.");
   
   Serial.print("MaxWind = ");
   Serial.print(MaxWind);
   Serial.print(" kph, occurred at ");
   Serial.println(MaxWindTime);
   Serial.println(""); 
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void Clock() {
     DateTime now = rtc.now(); //Read the current date-time from the RTC
     sprintf(TimeBuffer, "%04d-%02d-%02d %02d:%02d:%02d", now.year(), now.month(), now.date(), now.hour(), now.minute(), now.second());
}

You are Serial.printing your data. This implies you are using the serial monitor or, you are using a proper terminal programme but still don't know what you are doing - or perhaps have the wrong one. In latter event, the RTC is redundant. If you are using a kosher terminal programme, like Realterm, you timestamp the data using the the PC onboard clock. And you get to record it. RealTerm is a freebie and there is a swag of others like it.

By all means go through the intellectual exercise, but about the only time you actually need an RTC is when you are recording the data locally, and have no internet connection. I just printed hrs, mins, and secs individually, interspersed with the colons.

Nick's advice is sound. If you still want to keep track locally, create a small struct with two members, time and windspeed. Update these only when windspeed exceeds the stored value. In that way, each time you want to report, these values will contain the event data since the last report, Reset both after reporting.

Thanks guys,

Yes, I'm using the serial monitor whilst developing the code and debugging. However this code snippet will be used as a standalone setup with no pc in sight for kilometers.
DKWatson, what you recommend is what I want to achieve but I'm none the wiser.

So, this is what I want to achieve:
When the Max wind is higher then the previous value then it will need to read the RTC DS3231 and add a timestamp to the value. Both values will then be stored on an SD-card. The max values will be reset at midnight of each day. The SD-card will be collected once per month. On the SD-card I will then see the max gust of each day and at what time it happened.

I am familiar with Realterm but this won't be running on the standalone unit.
I hope this makes sense.
Thanks for your help!

It doesn't make sense to write to the SD card every time the wind speed changes. SD cards are FLASH memory and as such have a limited life. If you only wish to record daily maximums, keep the values in a variable and do your update to SD as planned after midnight. If you're concerned about loss of data due to power fluctuations, set up a storage buffer in EEPROM and write the EEPROM to SD at the end of the day.

OK, you have justified the clock, and I commented on this because there was no sign of an SD. What I do is zero the max. value at midnight, poll at 1 second intervals, and change the value if it exceeds the previous. Adding the time data would be triggered by the same condition.

   CurrentWindSpeed = map(anemometerOutput,0,1023,0,180); //10-bits input, converts 0 to 5V to 0kph to 180kph

Why are you using map() ?

CurrentWindSpeed = (anemometerOutput / 1023.0) * 180.0; //10-bits input, converts 0 to 5V to 0kph to 180kph

and, if you wish, you could replace these

int CurrentWindSpeed;
int MaxWind=0;

with these

float CurrentWindSpeed;
float MaxWind=0;

More importantly, you should not be using capital-S Strings.

char TimeBuffer[20] = "";   //Full date and time stamp
String MaxWindTime;

The char array (like you have for TimeBuffer) is fine. You should do the same for MaxWindTime, like so:

char TimeBuffer[20] = "";   //Full date and time stamp
char MaxWindTime[20] = "";

If you want to copy one character array to another, do not use the = sign to do so.

    MaxWindTime = TimeBuffer; // Don't do this when using char() arrays! It won't work!

To copy from one character array to another, you use strcpy(), like so:

    strcpy(MaxWindTime, TimeBuffer);

More info here: http://www.cplusplus.com/reference/cstring/strcpy/

DKWatson:
It doesn't make sense to write to the SD card every time the wind speed changes. SD cards are FLASH memory and as such have a limited life.

EEPROM also has a limited life.

odometer:
EEPROM also has a limited life.

10 to 100 times that of FLASH.

Just as a quick math exercise, the published life of FLASH is 10,000 writes. If you write once an hour, 10,000 hours is 416 days or just over 13 months - achievable for any product. The published life of EEPROM is 100,000 writes with many manufacturers citing 1,000,000. Ignoring the latter, 100,000 hours is over 11 years. Probably time for a replacement before then.

Thank you very much odometer!
I have taken your advice on board.

The code is now working; you will find it underneath.

I will write the max wind speed to an EEPROM every 15 minutes as a safety measure in case of failure.
Yes, I'm aware of the limited write/read cycles of flash memory.

In future, I'm working on it now, I will use arrays to work out the average wind speed over a period of 10 minutes and also to work out the wind run for the day.

You guys have been a great help and I thank you very much for that.

My biggest problem now will be to work out the wind run. I am planning of creating 3 arrays:

One minute array[12]: Taking wind speed measurement every 5 seconds.
Ten minute array[10]: Accumulating the One minute array over a period of 10 minutes.
One hour array[6]: Accumulating the Ten minute array over a period of one hour.
One day array[24]: Accumulating the One hour array over a period of one day.

If you think this thought process could be improved then I'm happy to take your suggestions on board.
Cheers guys!

Here's my working code:

#include "Sodaq_DS3231.h"

char TimeBuffer[20] = "";   //Full date and time stamp
char DayStamp[8];           //Short date stamp
int anemoMeter;             //Read analogue input A0 (0-1023)
int CurrentWindSpeed;       //Wind speed measured now
int MaxWind=0;              //Highest wind speed of the day
char maxWindTime[20] = "";  //Is the time of the day at which the max wind occurred
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void setup() {
  Serial.begin(9600);
  rtc.begin(); //Start the RTC library code
  //pinMode(anemoMeter, INPUT);
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void loop() {
  windSpeed();
  maxWind();
  Clock();
  printWindData();
  delay(500);
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void windSpeed() {
   anemoMeter = analogRead(A0); //Read analogue input A0 (0-1023)
   CurrentWindSpeed = (anemoMeter / 1023.0) * 180.0; //10-bits input, converts 0 to 5V to 0kph to 180kph
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
 void maxWind(){
  if(CurrentWindSpeed > MaxWind) {
   MaxWind = CurrentWindSpeed;
   Serial.print("MaxWind = ");
   Serial.print(MaxWind);
   Serial.print(" kph, occurred at ");
   Serial.println(TimeBuffer);
   Serial.println("");
   strcpy(maxWindTime, TimeBuffer);
}  
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void printWindData() {
   Serial.print("Current windspeed = ");
   Serial.print(CurrentWindSpeed);
   Serial.print("kph, ");
   Serial.print(CurrentWindSpeed / 3.6,1); //Conversion to meters/second, 1 decimal
   Serial.print("m/s, ");
   Serial.print(CurrentWindSpeed * 0.539957,1); //Conversion to knots, 1 decimal
   Serial.println("knots.");
   Serial.print("Max wind of ");
   Serial.print(MaxWind);
   Serial.print(" occurred at ");
   Serial.println(maxWindTime);
   Serial.println("");
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
/*void AnemoReset(){
     MaxWind = 0;
}//Reset the max wind speed to zero at midnight
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void TenMinAv(){
  
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void windRun(){
  
}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void Clock() {
     DateTime now = rtc.now(); //Read the current date-time from the RTC
     sprintf(TimeBuffer, "%04d-%02d-%02d %02d:%02d:%02d", now.year(), now.month(), now.date(), now.hour(), now.minute(), now.second());
     sprintf(DayStamp, "%02d/%02d", now.date(), now.month());}
/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
void newDay() {  //Resets the max windspeed and time at the start of a new day
    DateTime now = rtc.now();
    if (now.hour() == 23 && now.minute() == 59 && now.second() == 59) {  //If the time is 23:59:59 then roll over to a new day
    Serial.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
    Serial.print("The maximum wind speed for ");
    Serial.print (DayStamp);
    Serial.print (" = ");
    Serial.print (MaxWind);
    Serial.print (" kph.");
    Serial.print(" This happened at ");
    Serial.print(maxWindTime);
    Serial.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
    //AnemoReset(); //Call the anemoReset function
    delay(1000);}}

One minute array[12]: Taking wind speed measurement every 5 seconds.
Ten minute array[10]: Accumulating the One minute array over a period of 10 minutes.
One hour array[6]: Accumulating the Ten minute array over a period of one hour.
One day array[24]: Accumulating the One hour array over a period of one day.

Let me read this back to you. Into a 12 value array you want to accumulate 5 second data to have an average after 1 minute. This 1 minute average is then going into a 10 value array that will give you a 10 minute average. The 10 minute average will go into a 6 value array for a 1 hour average and then 24 for a day. Does that sound right?

In a minute I will take a reading every 5 seconds. After a minute I will calculate the average.
This average will then go in the 10 minute array.
After 10 minutes the 10-minute array will then be full.
I will then add the values together and store that number in the 1-hour array.
I will then clear the 10 minute array and start collecting the one minute averages again.
Once the 1-hour array is full I will store that total in the 24-hour array and then clear the 1-hour array.
At the end of the day I will have 24 1-hour totals in the array and at midnight I will then add them all together. This will give me the total wind run for the day.
I will then clear all arrays and start from scratch again.

It sounds complicated but I think that is the only way I work with lots of samples.

I could use one big array of 12 x 10 x 6 x 24 values but that would require an array of 17,280 values and I'm afraid that there won't be sufficient memory in the chip.
However, currently I'm using a MEGA256 as testing board but for the final project I will move on to an ESP32.
Does that make sense?

I've been racking my brains over this and this is the only solution plausible in my opinion. However, I still call myself a novice in regards to coding.
Cheers!

LRAT:
I could use one big array of 12 x 10 x 6 x 24 values but that would require an array of 17,280 values and I'm afraid that there won't be sufficient memory in the chip.

Why on earth would you need an array of 17280 values?

Just add the numbers up as you go, and then divide by 17280 at the end! Just make sure that the datatype you are using for the sum is big enough to hold it: int and unsigned int max out at 32767 and 65535, respectively.

Hi Odometer,

That is a fantastic idea! And so simple.
Thank you very much for sharing!
Karma added to all who responded.
Cheers

Here's a little class I use. Create 4 buffers (minute(12), ten_minute(10), hour(6) and day(24)). These are circular buffers so they only preserve the last n values, where n is the size of the buffer. The stats are updated each time you record data. This is set up for byte arrays, but you can easily change to long or float.

For your application:

  • every 5 seconds minute.add_data(some_value), and just keep doing it
  • every minute, ten_minute.add_data(minute.avg)
  • every ten minutes, hour.add_data(ten_minute.avg)
  • and every hour, day.add_data(hour.avg)

This will keep rolling statistics until reset. Values exposed for each of the current windows.

CPP class

// class running_stats
// copyleft DKWatson 2018
// class to keep running statistics
// instantiate with running_stats v_name(n); or running_stats v_name;
// where v_name is your variable name and n is the length of the buffer
// if the parameter is omitted, the default is BUFLGTH
// in its current configuration, data is stored in a byte array
// this implements a simple circular buffer retaining the most recent n entries
// basic stats are calculated at the time of data entry
// for now, the stats kept are:
//      sum     - rolling total of the most recent n entries
//      count   - total number of entries since last reset
//      cum_sum - cumulative total of all entries since last reset
//      avg     - rolling average of the most recent n entries
//      cum_avg - average of all entries since last reset
//
// Use
// running_stats myStats(12);       - initialize
// myStats.add_data(some_value);    - add some_value to buffer
// print(myStats.avg);              - print rolling average (or others)
// myStats.get_data(i)              - returns ith element in buffer
// myStats.reset();                 - um, reset?
//
#define BUFLGTH 32
class running_stats
{   
    uint8_t     index;
    uint8_t     data[BUFLGTH];
    
    public:
    
        uint8_t     len;
        
        running_stats(uint8_t a)    // constructor with length parameter
        {
            len = a;
        }
        
        running_stats()             // default constructor
        {
            len = BUFLGTH;
        }

        uint16_t    sum;
        uint16_t    count;
        uint32_t    cum_sum;
        float       avg;
        float       cum_avg;
        
        void add_data(uint8_t ch)
        {
            count ++;
            sum   += ch; cum_sum += ch; // add newest
            sum   -= data[index];       // subtract oldest
            uint8_t _temp    = ((index + 1) % len);
            data[index] = ch;
            index = _temp;
            avg   = ((float)sum / ((count <= len)?count:len));
            cum_avg = ((float)cum_sum / count);
        }
        
        uint8_t get_data(uint8_t i) {return data[i];}
        
        void reset()
        {
            index = 0;
            sum = count = cum_sum = avg = cum_avg = 0;
            for(uint8_t i = 0; i < len; i++) data[i] = 0;
        }
};

Or, C struct with functions

// running_stats
// copyleft DKWatson 2018
// structure and function to keep running statistics
// declare struct as running_stats v_name = {n};
// where v_name is your variable name and n is the length of the buffer
// this is a cheat where the struct is being initialized with only one
//  value which is the length variable
// in its current configuration, data is stored in a byte array
// this implements a simple circular buffer retaining the most recent n entries
// basic stats are calculated at the time of data entry
// for now, the stats kept are:
//      sum     - rolling total of the most recent n entries
//      count   - total number of entries since last reset
//      cum_sum - cumulative total of all entries since last reset
//      avg     - rolling average of the most recent n entries
//      cum_avg - average of all entries since last reset
//
// Use
// running_stats myStats = {12};    - initialize
// add_data(&myStats, some_value);  - add some_value to buffer
// print(myStats.avg);              - print rolling average (or others)
// myStats.data[i]                  - returns ith element in buffer
// reset_stats(&myStats);           - um, reset?
//
#define BUFLGTH 32

typedef struct
{
    uint8_t     len;
    uint8_t     index;
    uint8_t     data[BUFLGTH];
    uint16_t    sum;
    uint16_t    count;
    uint32_t    cum_sum;
    float       avg;
    float       cum_avg;
} running_stats;

void add_data(running_stats *r_s, uint8_t ch)
{
    r_s->count++;
    r_s->sum                += ch; r_s->cum_sum += ch;  // add newest
    r_s->sum                -= r_s->data[r_s->index];   // subtract oldest
    uint8_t _temp              = ((r_s->index + 1) % r_s->len);
    r_s->data[r_s->index]   = ch;
    r_s->index              = _temp;
    r_s->avg                = ((float)r_s->sum / ((r_s->count <= r_s->len)?r_s->count:r_s->len));
    r_s->cum_avg            = ((float)r_s->cum_sum / r_s->count);
}

void reset_stats(running_stats *r_s)
{
    r_s->sum = r_s->count = r_s->cum_sum = 0;
    r_s->avg = r_s->cum_avg = r_s->index = 0;
    for(uint8_t i = 0; i < r_s->len; i++) r_s->data[i] = 0;
}