• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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
 
Сверху Снизу