Go Down

Topic: [Solved] Quick way to get time on Yùn (Read 549 times) previous topic - next topic

Zaszigre

Nov 24, 2017, 09:15 am Last Edit: Dec 07, 2017, 01:51 pm by Zaszigre
Hello again everyone !

I'm beginning on Arduino Yùn mini and I'm using it as a tiny alternative to Arduino Uno Wifi.
On Uno Wifi, I was using an RTC to get time but I figured out I could get rid of it on Yùn by using the time on the atheros chip.



Code: [Select]

typedef union YunSTime stime;
union YunSTime{
uint32_t i: 23;
struct{
byte lastSec : 6;
uint32_t stime: 17;
}t;
};

// [...]

void PdateSetup() {
  Pdate.begin("/bin/date");
  Pdate.addParameter("+%T");
  yunTime.i = 0x1F800; // set time to 00:00:00 with last second to 63. as it is unexpected, Time will be redefined.
}
void PdateUpdate(){
  Pdate.begin("/bin/date");
  Pdate.addParameter("+%T");
  if (!Pdate.running()){
  //if ((yunTime.t.lastSec & ~((yunTime.i >> 6) % 60)) || (yunTime.t.lastSec > 60)){//if a second has passed or last second is incoherent
    yunTime.t.lastSec += yunTime.t.lastSec % 60; //puts yunTime.ls into yunTime.s. others will be redefined anyway.
    char c = ':';

    Pdate.begin("date");
    Pdate.addParameter("+%T");
    Pdate.run();
    String string = Pdate.readString();// get time as a string
    Serial.print(string);
    //separate hours minutes and seconds
    yunTime.t.stime = (string.substring(0, 2)).toInt() * 3600; //get hours out of string from the start to the first ':'
    yunTime.t.stime += (string.substring(3, 5)).toInt() * 60;  //get minutes / / / / / / / / /the first to the secnd ':'
    yunTime.t.stime += (string.substring(6, 8)).toInt();       //get seconds / / / / / / / / /the secnd ':' to the end   
  }
}


the real problem with this solution is that each loops takes 1s+ because I need to update time at the beginning of loop()
Do you know a faster way to get a time reference ?
thank you ♥

DarkSabre

I can think of two possibilities off-hand.

-Request the timestamp during setup and get the value of millis() at the time of request. The current time can then be derived from the current millis() using the original timestamp and millis() pair. If this is a long term program, consider the eventual rollover of millis() after about 50 days and build in some protection. This is probably the fastest way?

-Write a process in python/php/whatever to loop in the background once called. Either have that loop print out the latest timestamp on some interval and Pdate.readString() until the queue is empty or have it send the latest timestamp whenever the sketch does Pdate.println("updateMe") or something. I think this will save you some time since you won't have to initialize the process each time, but there's still some back and forth between the sketch and linux.

It looks like you are converting a hh:mm:ss string from %T into essentially a seconds value. I don't know if it would help or not, but you can get a unix timestamp with %s.

Probably not needed, but you can install the coreutils-date package and get full access to the possible date commands. For example addParameter("+%s%N") would get you the unix timestamp plus nanoseconds.

Zaszigre

Thank you very much for your answer !

The first possibility you raise was an Idea I got yesterday. It is very fast and I'm as satisfied by this result as I was by the RTC result. Yes, my program is designed to keep running on and on for years so I dealt with the rollover by registrating the elapsed time (in second) between two calls of my function into an unsigned long long that can last so long we have the time to find a new planet to live on and find a way to travel up to it before the variable rolls over:
(2^64 -1)/(3600 * 24 * 365.25 * 1000) = 584,542,046.09 Millennia !!
(love those GIANT numbers)

I m a bit shameful to admit it but the reason why I don't use the linux on the Athemos chip is I not familiar that much with this OS and I really don't understand how to acess to it. I don't know how the Bridge works. I read about some Keys to call but i cannot find nowhere a list of them.

Finally, a you said, it is pointless to get nanoseconds since I'm casting millisecond into second yet. The only reason I call millis() is to get a quick time update but I just need time in seconds.

 It is time for me to get back into the laboratory to make it work and I'll post my code in here when it will.

Zaszigre

#3
Dec 07, 2017, 01:23 pm Last Edit: Dec 07, 2017, 01:32 pm by Zaszigre
Hey guys,
I've just forget to post my code:

to get a time reference, I first use the bridge to get the time from Linux side as a string and get the different part isolated in a tm structure:
Code: [Select]
Serial.print(F("Setting up Time references : "));
  Process Starttime;
  Starttime.begin("/bin/date");
 
  Starttime.run();
  String string = Starttime.readString();
  tm startTime = {string.substring(17, 19).toInt(),         // Seconds asfter th minute
                  string.substring(14, 16).toInt(), // Minutes after the hour
                  string.substring(11, 13).toInt(), // Hours since Midnight
                  string.substring(8, 10).toInt(),  // Day of the month
                  0,                                // Months since January
                  0,                                // Years since 1900
                  0,                                // Days since Sunday
                  0,                                // days since January 1
                  -1};                              // Daylight Saving Tim flag ( (> 0 : true) (0 : false) ( < 0 : undefined)

  startTime.tm_year = (string.substring(24, 28).toInt() - 1900);// I don't know why it doesn't work inside the array declaration


As you can see, I can't put all the raw values in the structure because I need to treat the string:
get an int from the day of the week :
Code: [Select]
String FL = string.substring(0, 1);
  if(FL == "M")
    startTime.tm_wday = 1;
  else if(FL == "T")
    startTime.tm_wday = (string.substring(1, 2) == "u") ? 2 : 4;
  else if(FL == "M")
    startTime.tm_wday = 3;
  else if(FL == "F")
    startTime.tm_wday = 5;
  else if(string.substring(0, 2) == "u")
    startTime.tm_wday = 0;
  else
    startTime.tm_wday = 6;


get an int from the month:
Code: [Select]
FL = string.substring(4, 5);
  if (FL == "J")
    startTime.tm_mon = ((string.substring(4, 7) == "Jan") ? 0 : ((string.substring(4, 7) == "Jun") ? 5 : 6));
  else if (FL == "F")
    startTime.tm_mon = 1;
  else if (FL == "M")
    startTime.tm_mon = ((string.substring(4, 7) == "Mar") ? 2 : 4);
  else if (FL == "A")
    startTime.tm_mon = ((string.substring(4, 7) == "Apr") ? 3 : 7);
  else if (FL == "S")
    startTime.tm_mon = 8;
  else if (FL == "O")
    startTime.tm_mon = 9;
  else if (FL == "N")
    startTime.tm_mon = 10;
  else
    startTime.tm_mon = 11;


to determine the day of the year, I use the month to figure out how many day has passed before the month has started. I've used a complex loop instead of a switch because I'm a bit lazy and because I think it uses less constants this way.
maybe you'll be afraid by the encapsulated ternary operators… just sorry about that.
it just means "if it's february put 28, else put one time in two 30 or 31 and add one if it's a leap year".
then I just need to add the day of the month to the amount:

Code: [Select]
bool bisxl = ((startTime.tm_year + 1900) % 4) ? false : true, b = true;
  uint8_t J = 31, F = 28 + bisxl, A = 30;
  int nbDay = 0;
  // count the number of days that passed before the current month.
  for(int i = 0; i < startTime.tm_mon ; i++, nbDay += (((i == 1) ? 28 : 30 + ((b) ? 1 : 0)) + ((bisxl) ? 1 : 0)), b = !b){
  // i      : month tested
  // nbDay  : total of days that passed before the month i
  // b      : is the month a 31 or 30 month
      if(i == 7)
        b = !b;
  }
  startTime.tm_yday = nbDay + startTime.tm_mday;


Last thing I need to do it converting this structure into a time_t type variable to use it in a easy and fast way.:
Code: [Select]
  yunTime = mktime(&startTime);
  if((yunTime == -1)){
    Serial.println(F("Error while converting time into UNIX Timestamp"));
    while(1);
  }
  else
    PrintDateTime(yunTime);
}


then, to update Time each loop, I've just declared a variable to get the number of millisecond elapsed since the begining to the last update of the program with millis() and compare it to the current amout of millisecond elapsed. When more than 999 millisecond has passed, I convert it into second and add it to the TimeStamp. It sound complex but here is my (almost) complete code :

main.ino:
Code: [Select]
time_t yunTime, timeTrigger = 0;
unsigned long lastmillis = millis(), milliscount = 0;
setup(){
  lastmillis = millis();
  PdateSetup();
}
void loop(){
  PdateUpdate();
}


time.ino:
Code: [Select]
void PdateSetup() {
  Process Starttime;
  Starttime.begin("/bin/date");
 
  Starttime.run();
  String string = Starttime.readString();
  tm startTime = {string.substring(17, 19).toInt(),         // Seconds asfter th minute
                  string.substring(14, 16).toInt(), // Minutes after the hour
                  string.substring(11, 13).toInt(), // Hours since Midnight
                  string.substring(8, 10).toInt(),  // Day of the month
                  0,                                // Months since January
                  0,                                // Years since 1900
                  0,                                // Days since Sunday
                  0,                                // days since January 1
                  -1};                              // Daylight Saving Tim flag ( (> 0 : true) (0 : false) ( < 0 : undefined)
 
  startTime.tm_year = (string.substring(24, 28).toInt() - 1900);
 
  //turn day of week into an int
  String FL = string.substring(0, 1);
  if(FL == F("M"))
    startTime.tm_wday = 1;
  else if(FL == F("T"))
    startTime.tm_wday = (string.substring(1, 2) == "u") ? 2 : 4;
  else if(FL == F("M"))
    startTime.tm_wday = 3;
  else if(FL == F("F"))
    startTime.tm_wday = 5;
  else if(string.substring(0, 2) == F("u"))
    startTime.tm_wday = 0;
  else
    startTime.tm_wday = 6;
   
  // turn month into an int
  FL = string.substring(4, 5);
  if (FL == F("J"))
    startTime.tm_mon = ((string.substring(4, 7) == F("Jan")) ? 0 : ((string.substring(4, 7) == "Jun") ? 5 : 6));
  else if (FL == F("F"))
    startTime.tm_mon = 1;
  else if (FL == F("M"))
    startTime.tm_mon = ((string.substring(4, 7) == F("Mar")) ? 2 : 4);
  else if (FL == F("A"))
    startTime.tm_mon = ((string.substring(4, 7) == F("Apr")) ? 3 : 7);
  else if (FL == F("S"))
    startTime.tm_mon = 8;
  else if (FL == F("O"))
    startTime.tm_mon = 9;
  else if (FL == F("N"))
    startTime.tm_mon = 10;
  else
    startTime.tm_mon = 11;
 
  bool bisxl = ((startTime.tm_year + 1900) % 4) ? false : true, b = true;
  uint8_t J = 31, F = 28 + bisxl, A = 30;
  int nbDay = 0;
  // count the number of days that passed before the current month.
  for(int i = 0; i < startTime.tm_mon ; i++, nbDay += (((i == 1) ? 28 : 30 + ((b) ? 1 : 0)) + ((bisxl) ? 1 : 0)), b = !b){
  // i      : month tested
  // nbDay  : total of days that passed before the month i
  // b      : is the month a 31 or 30 month
      if(i == 7)
        b = !b;
  }
  startTime.tm_yday = nbDay + startTime.tm_mday;
  yunTime = mktime(&startTime);
  if((yunTime == -1)){
    while(1)
      errorBlink();
  }
}
 

void PdateUpdate(){
 milliscount += (millis() - lastmillis);
 lastmillis = millis();
 if(milliscount / 1000){
  yunTime += milliscount / 1000;
  milliscount -= (milliscount / 1000) * 1000;
 }
}



To set an Alarm, I recommend to use the timer interruptions of your chip but if (like me) you don't know how to do it, here is my function:
Code: [Select]
bool delayYunTime(time_t delay_, time_t now_, time_t *dst) {
  if (now_ >= *dst) {
   *dst = delay_ + now_;
    return true;
  }
  else {
    return false;
  }
}

Go Up