• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Делюсь опытом ESP8266 синхронизация времени с NTP сервером. UnixTime и конвертация времени.

pcklz

New member
Нашел и отладил утилиты синхронизации времени с NTP сервером, конвертации времени из UNIX формата (число секунд с 01.01.1970) в обычный и обратно. Что очень полезно для программирования событий на ESP 8266. Естественно, чтобы использовать их, необходимо, чтобы ESP8266 была подключена к сети с выходом в интернет. До того, как произойдет синхронизация (может не произойти с первого раза), она пытается это делать каждые 10 секунд, после этого - раз в сутки, т.к. есть встроенные часы по таймеру Tcker.h

Также прикладываю утилиты конвертации IP адреса в/из строкового формата.

Код:
#include <WiFiUdp.h>
#include <Ticker.h>

/*******************настройки для NTP-синхронизации времени****************/
#define GMT 3
bool AskNTPTime=false;
unsigned int udpPort = 2390;      // local port to listen for UDP packets
IPAddress timeServerIP;           // time.nist.gov NTP server address
const char* ntpServerName = "time.nist.gov";
const int NTP_PACKET_SIZE = 48;   // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp;
/*******************настройки времени и даты*******************************/

uint8_t   blink_loop = 0;
uint16_t  frequency, blink_mode = 0;
uint8_t hour, minute, second, day, month, year, weekday;
uint8_t monthDays[12]={31,28,31,30,31,30,31,31,30,31,30,31};
unsigned long secsUnix;
unsigned long Tm_blink;
Ticker timer;

IPAddress ip, gateway, subnet;

void setup(void)
{
    Serial.begin(115200);
    Serial.println();
    timer.attach(1, timerTime);

   Serial.println("Starting UDP");
   udp.begin(udpPort);
   Serial.print("Local port: ");
   Serial.println(udp.localPort());

}
void loop() {

    if ( WiFi.status() == WL_CONNECTED)
        {
          if(AskNTPTime==true)
            {
             if(hour==0 && minute==0 && second<2) AskNTPTime==false;
            }
          else  if(second%10<2) Ask_NTP_Time();         
        }
}

void timerTime()
{
  if(AskNTPTime==true) secsUnix++;  // если время синхронизировано, то отсчитываем время в секундах

  second++;                        // ведем реальный отсчет времени 
  if (second > 59)
     {
      second = 0;
      minute++;
      if (minute > 59)
        {
         minute = 0;
         hour++;
         if (hour > 23)
           {
             hour = 0;
             day++;
             if(month>1)
               if(day>monthDays[month-1])
                 {
                  day=1;
                  month++;
                  if(month>12) { year++; month=1; }
                 }
            }
         }
      }   
}



String IP_to_String(IPAddress ip)
{
  return(String(ip[0])+"."+String(ip[1])+"."+String(ip[2])+"."+String(ip[3]));
}


IPAddress String_to_IP(String strIP)
{
int Parts[4] = {0,0,0,0};
int Part = 0;
for ( byte i=0; i<strIP.length(); i++ )
{
  char c = strIP.charAt(i);
  if ( c == '.' )
  {
    Part++;
    continue;
  }
  if ( c<48 || c>57 ) continue; // не цифровой символ
 
  Parts[Part] *= 10;
  Parts[Part] += c - '0';
}
IPAddress ip_str( Parts[0], Parts[1], Parts[2], Parts[3] );
return(ip_str);
}


bool Ask_NTP_Time()
{
  int cb;
  while (udp.parsePacket() > 0) {delay(1);}

  WiFi.hostByName(ntpServerName, timeServerIP);
  sendNTPpacket(timeServerIP); // send an NTP packet to a time server
                               // wait to see if a reply is available
  unsigned long beginWait = millis();
  while (millis() - beginWait < 2000)
    {
      cb = udp.parsePacket();
      if (cb >= NTP_PACKET_SIZE)
         {
          Serial.print("packet received, length=");
          Serial.println(cb);
                                                   // We've received a packet, read the data from it
          udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
   
          //the timestamp starts at byte 40 of the received packet and is four bytes,
          // or two words, long. First, esxtract the two words:
          unsigned long secsSince1900;
          secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
          secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
          secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
          secsSince1900 |= (unsigned long)packetBuffer[43];
       
          Serial.print("Seconds since Jan 1 1900 = " );
          Serial.println(secsSince1900);
       
          // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
          const unsigned long seventyYears = 2208988800UL;
          // subtract seventy years:
       
          secsUnix = secsSince1900 - seventyYears;
       
          // print Unix time:
          Serial.print("Unix time = ");
          Serial.println(secsUnix);
          Unix_to_GMT(secsUnix);
             
       
          AskNTPTime=true; // признак того, что время синхронизировано
          // выдерживаем паузу
          while (millis() - beginWait < 3000) {delay(10);}
       
          Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
          Serial.println(Time_to_String());
             
          return(true);
        }
    } 
  Serial.println("No packet yet");
  while (millis() - beginWait < 1000) {delay(10);}
  return(false);
  // wait ten seconds before asking for the time again
}


// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(IPAddress & address)
{
  Serial.println("sending NTP packet...IP="+IP_to_String(address));
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();

}

void Unix_to_GMT(unsigned long epoch)
{
// корректировка часового пояса и синхронизация
  epoch = epoch + GMT * 3600;   

  second=epoch%60;
  epoch/=60; // now it is minutes
  minute=epoch%60;
  epoch/=60; // now it is hours
  hour=epoch%24;
  epoch/=24; // now it is days
  weekday=(epoch+4)%7; // day week, 0-sunday
  year=70;
  int days=0;
  while(days + ((year % 4) ? 365 : 366) <= epoch) {
     days += (year % 4) ? 365 : 366;
     year++;
  }
  epoch -= days; // now it is days in this year, starting at 0

  days=0;
  month=0;
  byte monthLength=0;
  for (month=0; month<12; month++) {
    if (month==1) { // february
      if (year%4) monthLength=28;
      else monthLength=29;
    }
    else monthLength = monthDays[month];
    if (epoch>=monthLength) epoch-=monthLength;
    else break; 
  }
  month++;       // jan is month 1
  day=epoch+1;  // day of month
}

String Time_to_String()
{
String Tm,wd;
int k,n;
switch(weekday)
  {
    case 0:
         wd="su";;
         break;
    case 1:
         wd="mo";;
         break;
    case 2:
         wd="tu";;
         break;
    case 4:
         wd="th";;
         break;
    case 5:
         wd="fr";;
         break; 
    case 6:
         wd="sf";;
         break;
  }
k=day/10;
n=day%10;
Tm=String(k)+String(n)+".";
k=month/10;
n=month%10;
Tm=Tm+String(k)+String(n)+".";
k=1900+year;
Tm=Tm+String(k)+","+wd+" ";
k=hour/10;
n=hour%10;
Tm=Tm+String(k)+String(n)+":";
k=minute/10;
n=minute%10;
Tm=Tm+String(k)+String(n)+":";
k=second/10;
n=second%10;
Tm=Tm+String(k)+String(n);
return(Tm);
}

unsigned long GMT_to_Unix(byte y,byte mon,byte d,byte h,byte mt,byte s)
{
// считает количество секунд от текущего времени до времени  d.mon.y h:mt:s
  unsigned long epoch=0;
  epoch+=s;
  epoch+=mt*60;
  epoch+=h*3600;
  d--;
  epoch+=d*24*3600;
  mon--;
  byte monthLength;
  for (int m=mon; m>=1; m--) {
    if (m==2) { // february
      if (year%4) monthLength=28;
      else monthLength=29;
    }
    else monthLength = monthDays[m-1];
    epoch+=monthLength*24*3600;
  }
  y--;
  int days=0;

  while(y>=70) {
     days += (y % 4) ? 365 : 366;
     y--;
  }
  epoch+=days*24*3600;
  epoch = epoch - GMT * 3600;
  return(epoch);
}




Здравствуйте, можете скинуть сайт или саму библиотеку wifiudp.h нигде не могу ее найти
 

p-a-h-a

Member
Ребята, подскажите как синхронизировать время посредством time.h?
На ESP32 это делается так struct tm timeinfo; getLocalTime(&timeinfo); // Судя по этой статье https://lastminuteengineers.com/esp32-ntp-server-date-time-tutorial/
А как с ESP8266? Как синхронизировать время в случае разности в ходе часов? Или все делается само под капотом?

Игрался, написал тестовый скеч:
C++:
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <time.h>                           //http://www.c-cpp.ru/funkcii/timeh
const int8_t timezone = 2;
const uint16_t daysavetime = 3600; // Летнее время 3600, иначе 0

const char *ssid = "****"; // SSID WiFi network
const char *pass = "****";     // Password  WiFi network

void setup()
{
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, pass);
  Serial.println();
  configTime(3600 * timezone, daysavetime * 3600, "time.google.com", "time.windows.com", "pool.ntp.org");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print('.');
    delay(500);
  }
}

void loop()
{
  time_t unixTime;
  time(&unixTime);
  struct tm *timestruct = localtime(&unixTime);

  // struct tm* timestruct = gmtime(&unixTime);// гринвич
  Serial.println(timestruct->tm_sec);  // Serial.println(timestruct->tm_wday);//день недели
  if ((time_t)unixTime > (uint8_t)255) //пропускаем пока время не обновиться
  {
    char TimeStringBuf[24];
    strftime(TimeStringBuf, sizeof(TimeStringBuf), "%Y.%0m.%0d     %0H:%0M:%0S", timestruct); // http://www.c-cpp.ru/content/strftime
    // unixTime = mktime(timestruct); // Преобразует структуру времени в юникстайм
    Serial.println(TimeStringBuf); // 2022.04.01   01:30:10
    Serial.println(asctime(timestruct)); // Fri Apr 1 01:30:10 2022
    Serial.print(ctime(&unixTime)); // Fri Apr  1 02:01:00 2022\n
    Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (timestruct->tm_year) + 1900, (timestruct->tm_mon) + 1, timestruct->tm_mday, timestruct->tm_hour, timestruct->tm_min, timestruct->tm_sec); // Now is : 2022 - 04 - 01 02 : 35 : 42

  }
  delay(1000);

}
/*
struct tm
{
  int tm_sec;     // секунды после минут [0,59]
  int tm_min;     // минуты после часов [0,59]
  int tm_hour;    // часы после полуночи [0,23]
  int tm_mday;    // день месяца [1,31]
  int tm_mon;     // месяц года (январь = 0) [0,11]
  int tm_year;    // год (1900 год = 0)
  int tm_wday;    // день недели (вс = 0) [0,6]
  int tm_yday;    // день года (1 января = 0) [0,365]
  int tm_isdst;   // флаг перехода на летнее время (>0- вкл.)
};
*/
 

p-a-h-a

Member
Спасибо. Разобрался.
Время обновляется автоматически раз в час.
Можно изменить:
Код:
uint32_t sntp_update_delay_MS_rfc_not_less_than_15000 () {
  return 1 * 60 * 1000UL; // 1 minute
}
Можно вызвать обратную функцию, которая срабатывает при синхронизации с NTP:
Код:
#include <coredecls.h>

void time_is_set(bool from_sntp /* <= this parameter is optional */) {
  Serial.println("NTP updated");
  Serial.println(from_sntp);
}
 

enjoynering

Well-known member
Все так.

Учите, по стандарту минимальное время между обновлениями времени (запросами к NTP серверу) 16 секунд. Если меньше, то NTP сервер скорее всего отправит вас в бан. Ну и если вы планируете много устройств, то лучше добавить немножко random(). Чтоб после отключения света все устройства не ломанулись одновременно на NTP сервер.
 

enjoynering

Well-known member
да еще почитал про time_is_set(), она не срабатывает по синхронизации с NTP.

по синхронизации срабатывает settimeofday_cb(callbackFn), а уже она вызывает вашу time_is_set()

Код:
void time_is_set(void) {
  gettimeofday(&cbtime, NULL);
  cbtime_set = true;
  Serial.println("------------------ settimeofday() was called ------------------");
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  settimeofday_cb(time_is_set);
подробнее тут - https://gitter.im/esp8266/Arduino?at=5d3c6f507e00fc4ace5bfe33
 
Сверху Снизу