Go Down

Topic: New library for Arduino Time and Date (Read 9824 times) previous topic - next topic

mem

I have a new library for date and time that is derived  from the playground DateTime code but has a different API that is intended to be more flexible and easier to use.

The new API is more consistent with Arduino function style. It also facilitates date and time code that can be used with a variety of distinctly different external time sources with minimum change to sketch logic.

Example sketches illustrate how similar sketch logic can be used with each of the following time sources: a Real Time Clock, internet NTP time service, GPS time data, and Serial time messages from a computer for time synchronization.

This is prototype code intended as proof of concept and has had limited testing.
Feedback on the API and implementation is welcome.

The download is here

Some of the functions available in the library include:

hour();            // the hour now  (0-23)
minute();          // the minute now (0-59)          
second();          // the second now (0-59)
day();             // the day now (1-31)
weekday();         // day of the week, Sunday is day 0
month();           // the month now (1-12)
year();            // the full four digit year: (2009, 2010 etc)

there are also functions to return the hour in 12 hour format
hour_12();         // the hour now in 12 hour format
AM_PM();           // returns false if AM, true if PM

now();              // returns the current time as seconds since Jan 1 1970

The time and date functions can take an optional parameter for the time:
for example:
 time_t t = now(); // store the current time in time variable t
 hour(t);          // returns the hour for the given time t

Storing a snapshot of the time in a variable and using this to get time elements avoids errors if the time rolls to a new second between accessing elements.

Functions for managing the timer services are:  
setTime(t);             // set the system time to the give time t
adjustTime(adjustment); // adjust system time by adding the adjustment value

timeStatus();       // indicates if time has been set and recently synchronized
                   // returns one of the following enumerations:
   timeNotSet      // the time has never been set, the clock started at Jan 1 1970
   timeNeedsSync   // the time had been set but a sync attempt did not succeed
   timeSet         // the time is set and is synced
Time is not valid if the status is timeNotSet. Otherwise, time can be used but may
have drifted if the status is timeNeedsSync.       

There are functions to set the external time source and interval between automatic time synchronization:
setSyncProvider(getTimeFunction);  // set the external time provider
setSyncInterval(interval);                  // set the number of seconds between re-sync


See the readme.txt file in the download for more details.

mem

#1
Dec 30, 2009, 12:45 pm Last Edit: Dec 30, 2009, 02:09 pm by mem Reason: 1
Here is an example sketch that gets time from a DS1307 RTC (the RTC library used is included in the download linked above)

Code: [Select]
#include <Time.h>  
#include <Wire.h>  
#include <DS1307Tiny.h>  // a basic DS1307 library that returns time as a time_t

void setup()  {
 Serial.begin(9600);
 setSyncProvider(RTC.get);   // the function to get the time from the RTC
 if(timeStatus()!= timeSet)
    Serial.println("Unable to sync with the RTC");
 else
    Serial.println("RTC has set the system time");      
}

void loop()
{
  digitalClockDisplay();  
  delay(1000);
}

void digitalClockDisplay()
{
 // digital clock display of the time
 Serial.print(hour(),DEC);
 printDigits(minute());
 printDigits(second());
 Serial.print(" ");
 Serial.print(day(),DEC);
 Serial.print(" ");
 Serial.print(month(),DEC);
 Serial.print(" ");
 Serial.print(year(),DEC);
 Serial.println();
}

void printDigits(byte digits){
 // utility function for digital clock display: prints preceding colon and leading 0
 Serial.print(":");
 if(digits < 10)
   Serial.print('0');
 Serial.print(digits,DEC);
}


Here is similar clock code but with time from synced from an NTP time server using the Arduino ethernet shield
Code: [Select]
#include <Time.h>
#include <Ethernet.h>
#include <UdpBytewise.h>  // UDP library from: bjoern@cs.stanford.edu 12/30/2008


byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 44 };
byte gateway[] = { 192, 168, 1, 254 };
byte time_dot_nist_dot_gov[] = { 192, 43, 244, 18}; // time.nist.gov

time_t prevDisplay = 0; // when the digital clock was displayed

void setup()
{
 Serial.begin(9600);
 Ethernet.begin(mac,ip,gateway);  
 Serial.println("waiting for sync");
 setSyncProvider(getNtpTime);  
}

void loop()
{  
 if(timeStatus()!= timeNotSet)
    // here if the time has been set
    if( now() != prevDisplay) //update the display only if the time has changed
    {
      prevDisplay = now();
      digitalClockDisplay();  
    }
}

void digitalClockDisplay(){
 // digital clock display of the time
 Serial.print(hour(),DEC);
 printDigits(minute());
 printDigits(second());
 Serial.print(" ");
 Serial.print(day(),DEC);
 Serial.print(" ");
 Serial.print(month(),DEC);
 Serial.print(" ");
 Serial.print(year(),DEC);
 Serial.println();
}

void printDigits(byte digits){
 // utility function for digital clock display: prints preceding colon and leading 0
 Serial.print(":");
 if(digits < 10)
   Serial.print('0');
 Serial.print(digits,DEC);
}

/*-------- NTP code ----------*/

unsigned long getNtpTime()
{
 sendNTPpacket(time_dot_nist_dot_gov);
 delay(1000);
 if ( UdpBytewise.available() ) {
   for(int i=0; i < 40; i++)
      UdpBytewise.read(); // ignore every field except the time
   const unsigned long seventy_years = 2208988800UL;        
   return getUlong() -  seventy_years;      
 }
 return 0; // return 0 if unable to get the time
}

unsigned long sendNTPpacket(byte *address)
{
 UdpBytewise.begin(123);
 UdpBytewise.beginPacket(address, 123);
 UdpBytewise.write(B11100011);   // LI, Version, Mode
 UdpBytewise.write(0);    // Stratum
 UdpBytewise.write(6);  // Polling Interval
 UdpBytewise.write(0xEC); // Peer Clock Precision
 write_n(0, 8);    // Root Delay & Root Dispersion
 UdpBytewise.write(49);
 UdpBytewise.write(0x4E);
 UdpBytewise.write(49);
 UdpBytewise.write(52);
 write_n(0, 32); //Reference and time stamps  
 UdpBytewise.endPacket();  
}

unsigned long getUlong()
{
   unsigned long ulong = (unsigned long)UdpBytewise.read() << 24;
   ulong |= (unsigned long)UdpBytewise.read() << 16;
   ulong |= (unsigned long)UdpBytewise.read() << 8;
   ulong |= (unsigned long)UdpBytewise.read();
   return ulong;
}

void write_n(int what, int how_many)
{
 for( int i = 0; i < how_many; i++ )
   UdpBytewise.write(what);
}


And a sketch that syncs time from serial messages (A Processing sketch that provides these messages is in the download)
Code: [Select]
#include <Time.h>  

#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
#define TIME_HEADER  'T'   // Header tag for serial time sync message
#define TIME_REQUEST  7    // ASCII bell character requests a time sync message

// T1262347200  //noon Jan 1 2010

void setup()  {

 Serial.begin(9600);
 pinMode(13, OUTPUT);            // led lit when synced
 setSyncProvider( requestSync);  //set function to call when sync required
}

void loop(){    
 if(Serial.available() )
 {
   processSyncMessage();
 }
 if(timeStatus()== timeNotSet)
   digitalWrite(13, !digitalRead(13)); //    Serial.println("waiting for sync message");
 else    
 {
   digitalWrite(13,timeStatus() == timeSet); // on if synced, off if needs refresh  
   digitalClockDisplay();  
 }
 delay(1000);
}

void digitalClockDisplay(){
 // digital clock display of the time
 Serial.print(hour(),DEC);
 printDigits(minute());
 printDigits(second());
 Serial.print(" ");
 Serial.print(day(),DEC);
 Serial.print(" ");
 Serial.print(month(),DEC);
 Serial.print(" ");
 Serial.print(year(),DEC);
 Serial.println();
}

void printDigits(byte digits){
 // utility function for digital clock display: prints preceding colon and leading 0
 Serial.print(":");
 if(digits < 10)
   Serial.print('0');
 Serial.print(digits,DEC);
}

void processSyncMessage() {
 // if time sync available from serial port, update time and return true
 while(Serial.available() >=  TIME_MSG_LEN ){  // time message consists of a header and ten ascii digits
   char c = Serial.read() ;
   Serial.print(c);  
   if( c == TIME_HEADER ) {      
     time_t pctime = 0;
     for(int i=0; i < TIME_MSG_LEN -1; i++){  
       c = Serial.read();          
       if( c >= '0' && c <= '9'){  
         pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
       }
     }  
     setTime(pctime);   // Sync Arduino clock to the time received on the serial port
   }  
 }
}

time_t requestSync()
{
 Serial.print(TIME_REQUEST,BYTE);  
 return 0; // the time will be sent later in response to serial mesg
}

AlphaBeta

Nice library!

As for the API, I think you should get rid of all underscores.

Suggestion:
hourFormat12();
isAM();
Being this is an Arduino API I would've included
isPM();

The AM_PM should at least reverse the return values. I always expect the firstmentioned check to have priority.
Additionally the ternary operator expects a reaction to true before false.

mem

Thanks AlphaBeta, good suggestions. I will rename the hour_12 and am_pm functions in  the next release.

agib

#4
Dec 30, 2009, 10:09 pm Last Edit: Dec 30, 2009, 10:11 pm by agib Reason: 1
I agree with AlphaBeta about the isAM() and isPM() functions.

It might also be useful to have a function that returns the length of time since the last sync, I could imagine it would be a common use case to check the time since the last sync before attempting a new one.

mem

> I agree with AlphaBeta about the isAM() and isPM() functions
Me too, I have already updated my copy of the code as per AlphaBeta's suggestion, I will wait until tomorrow to see if there are any other obvious changes before uploading a new version.

> It might also be useful to have a function that returns the length of time since the last sync?

The system provides a call back to re-sync time using a user settable interval between syncs. The interval should be set to a duration less than the time the arduino clock could drift by up to one second. Therefore a resync would always occur before the time has drifted by a second.

The default interval is 5 minutes (to cope with boards using resonators) but any time from seconds to years can be set, or callbacks can be disabled.

It would be easy to add the function you suggest but I can't think of a use case where this would be necessary. If the user wants complete manual control of time sync, this can be done in the current version by calling the setTime function whenever appropriate.

ahdavidson

There's a discussion going on right now over on the Arduino Developers list about this very topic. Are you following it there?
.andy

mem

#7
Dec 31, 2009, 11:32 pm Last Edit: Dec 31, 2009, 11:33 pm by mem Reason: 1
Hi Andy.
Yes, I have been active in that discussion and the code in this thread is intended as a prototype of a proposal made to the developer team.

I have created a thread here in the hope that users interested in Arduino time functionality will provide feedback on the API proposed above.


mem

The release version of the library is available in the Plaground. See this link for details:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1262783962/0

Please post any comments about the new library in that thread.

jgrepps

#10
Feb 09, 2010, 10:23 pm Last Edit: Feb 09, 2010, 10:37 pm by jgrepps Reason: 1
It seems like there is no way to break out of a sync loop.

I'm using the Daytime protocol to fetch time from a time server.  Obviously, the fetch could fail due to any number of Ethernet issues.  I have included timeouts in my code to prevent infinite loops when fetching the time.  But it appears that this is not the case in the Time.h API.  If I call setSyncProvider() and set the setSyncInterval() to say 60 seconds.  I let it sync the clock the first time.  I pull the Ethernet cord out and wait for it to re-sync.  When the 60 seconds expires it continues in a loop and appears to never end.

mem

Is this with the example code? If so, what do you see displayed on the serial monitor?

If its ok with the example code and not with your application, can you post the relevant code.

mem

#12
Feb 10, 2010, 10:22 am Last Edit: Feb 10, 2010, 10:28 am by mem Reason: 1
Jgrepps,
I have confirmed that the example sketch in the download correctly handles the  loss of sync. Once the sync interval has elapsed it will try to resync on every request for the time and will timeout after around a second if no sync message is received. The arduino internal clock provides time even if it can't be synced.  The arduino clock automatically gets resynced on the first call to get the time after the sync source (the network) is re-established.

Try it with the example sketch and post the results. If its not working for you then please say more about your computer operating system and arduino version.
If you want to see the sync status, you can modify the example loop code as follows to print a message when the system is unable to sync the time
Code: [Select]
void loop()
{  
 if( now() != prevDisplay) //update the display only if the time has changed
 {
   prevDisplay = now();
   if(timeStatus() == timeNeedsSync)
     Serial.print("not synced: ");
   digitalClockDisplay();  
 }
}


If the never ending loop was caused by your sketch implementation or the networking code, providing more details may help others with a similar application to yours to understand the problem and how to overcome it.

jgrepps

First off, let me say thank you for putting this library together and publishing it.  It has been a big help to me since my entire project it dependent on time.

I've stripped down my code to just include the functions that set the time.  I've used several libraries and modified the Ethernet library for my project.  You'll need to place the libraries I included in your include path.

Change the Ethernet config in the example if needed.  Once you upload the code to the Arduino, view the Serial interface.  Once you see "Leaving Setup!!!" on the Serial interface, pull the Ethernet cord attached to your Arduino.  Wait 60 seconds and watch the output.

There are two things going on.  The code is trying to sync the time and it's also outputing the current time (synced in the Setup function) every 10 seconds.  After the 60 seconds expires and it trys to connect you'll see messages indicating that the connection is failing and you'll see fragments of the time being output to the Serial interface.

I left it in this state for over 5 minutes and what I discovered is that the time get's reset to 1/1/1970 several times.  After leaving it for 5 minutes I see that the sync function eventually gives up but only for another 60 seconds (the length of the sync interval).  In my opinion it should give up and never try to sync again unless a state changes or a function is called (i.e. leave it up to the user to code in a contingency).  Also the time is reset which I don't feel is a good thing.

Also what is causing the sync function to run concurrently with other tasks.  I've looked through Time.h and Time.cpp and I can't figure out what time variable lapses and causes the sync function to be called.

My example is here as example.zip (it includes the pde and all libraries needed):

http://john.grepps.com/Arduino/

mem

#14
Feb 10, 2010, 07:11 pm Last Edit: Feb 10, 2010, 07:16 pm by mem Reason: 1
The sync logic is handled in the now() method that is called whenever the application needs the time. The code in time.cpp can be a little hard to follow if you are not familiar with function pointers but here is an explanation that I hope will help:
The [font=Courier New]now()[/font] method first updates the system time by however many seconds have elapsed since the last time the now method was called
(ignore the time drift stuff, that's only used for diagnostics not relevant to your problem)
The code then checks if enough seconds have elapsed since the last time it was synced, and if so and if a sync function was provided through a call in setup to setSyncProvider that sync function is called (in your example it's the set_time function)
If this function returns a time that is not 0 then the system time is set to the  returned value, otherwise the value is ignored and the system time continues to be driven from the Arduino clock (through those calls to millis mentioned earlier)
The status is set indicating if the clock has been set and if it needs sync
Finally, the function returns the system time.

You can test if your problem is related to the Time library by replacing the set_time code in your example with this debug code. I will not have time to try this so I hope this will be enough information for you to get this test going.

In your example sketch, add a dummy  set_time function :
Code: [Select]
time_t dummy_set_time()
{
  Serial.println("Time will not be synced");
  return 0;
}


Sync the clock to get it started by calling the original set_time function in setup as follows:

Code: [Select]
void setup()
{
  Serial.begin(115200);
  Ethernet.begin(mac,ip);
  setSyncInterval(60);
  setSyncProvider(dummy_set_time); // note this calls the dummy function
  time_t t = set_time();
  Serial.println(t);
  setTime(t); // set the clock;
  if (timeStatus() == timeSet) { Serial.println("Time has been set."); }
  else { Serial.println("Time has NOT been set."); }
  Serial.println("Leaving Setup!!!");
}


Run the sketch and you should see the digital clock display update every second. After 60 seconds you should see the message printed every second by the dummy set time function but the clock should continue to print the correct time even though the sync failed. If so, the problem you have in your project is in the logic getting the time from the network. If this is the case then its best if you could start a new thread for that discussion so this thread can stay focused on the time library.

Go Up