Pages: [1] 2   Go Down
Author Topic: New library for Arduino Time and Date  (Read 7289 times)
0 Members and 1 Guest are viewing this topic.
London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here is an example sketch that gets time from a DS1307 RTC (the RTC library used is included in the download linked above)

Code:
#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:
#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:
#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
}
« Last Edit: December 30, 2009, 08:09:18 am by mem » Logged

Norway@Oslo
Offline Offline
Edison Member
*
Karma: 12
Posts: 2033
loveArduino(true);
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

0
Offline Offline
Newbie
*
Karma: 0
Posts: 46
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: December 30, 2009, 04:11:25 pm by agib » Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

> 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.
Logged

Seattle, USA
Offline Offline
Full Member
***
Karma: 0
Posts: 248
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

.andy

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: December 31, 2009, 05:33:35 pm by mem » Logged

Seattle, USA
Offline Offline
Full Member
***
Karma: 0
Posts: 248
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Great idea.
Logged

.andy

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: February 09, 2010, 04:37:07 pm by jgrepps » Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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.
« Last Edit: February 10, 2010, 04:28:56 am by mem » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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/
Logged

London
Offline Offline
Faraday Member
**
Karma: 8
Posts: 6240
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 now() 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:
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:
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.
« Last Edit: February 10, 2010, 01:16:38 pm by mem » Logged

Pages: [1] 2   Go Up
Jump to: