• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

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

Paul_B

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); 
}
 

CodeNameHawk

Moderator
Команда форума
Что из этого " ESP8266 синхронизация времени с NTP сервером. UnixTime и конвертация времени."
вы не нашли в примере? Разве что GMT_to_Unix. и конвертация в стринг.
Нашел и отладил утилиты синхронизации времени с NTP сервером
А чем не устроили утилиты синхронизации из примера, в что в них изменили?
Насколько отличается время из timerTime() и из сети за сутки?
(Есть подозрения, что если есп сильно занята, может набегать погрешность)
 

pvvx

Активный участник сообщества
Насколько отличается время из timerTime() и из сети за сутки?
На много, если используется режимы WiFi sleep (MODEM, LIGTH) или Deep-Sleep. По умолчанию, если вы включили только Station, то активен режим понижения потребления WiFi = MODEM. При всех sleep таймеры и прерывания у ESP8266 не работают, а работает только кривой RC-генератор в RTC.
Зависит от калибровки RC-генератора производимой один раз при старте SDK (+ в SDK есть специальные функции калибровки) и погоды (температуры и напряжения питания).
 

enjoynering

Well-known member
Т.е. в ардуино в режиме station only прерывания аля atachInterrupt() работать не будут? И таймер повышенный на mills() тоже?
 

pvvx

Активный участник сообщества
Т.е. в ардуино в режиме station only прерывания аля atachInterrupt() работать не будут? И таймер повышенный на mills() тоже?
Это смотря какой включен режим пониженного потребления у WiFi. Их там три: NONE, MODEM, LIGTH.
Если NONE - то всё работает. Если LIGTH, то будет дергаться...
Зачем вообще на модуле иметь время? Обычно с модуля пересылают что, а сервер или браузер имеет время приема.
При старте запросили NTP и запомнили. Если надо показать время старта - переслали это дикое число в броузер - он и распечатает через javascript с учетом всего на свете и вычислит сколько прошло, рассчитает поправку, перешлет модулю и т.д...
HTML:
<script type="text/javascript">
var x = ~sntp_time~*1000;
if(x){
var d = new Date(x);
document.getElementById('sntptime').innerHTML= d.toLocaleDateString()+" "+d.toLocaleTimeString();
}
</script>
Запрос у удаленного NTP всё равно не точен (тем более по примеру в данной теме :) ), без специальных алго фильтров и сотней опросов, и при единичном запросе его колбасит обычно на +-2 сек.

Вот древняя тема, если требуется синхронизация даже нескольких ESP8266 с точностью 20 мкс без фильтров и +-2 мкс с фильтрами.

Зачем вообще зависучей игрушке время? :)
Напишите в поиске форума "синхронизация" - только по этому слову выпадут десятки тем и скетчей как получить и обработать время по NTP. Каждый, вновь заходящий, без поиска пишет новую тему и новый глюко-скетч, когда в SDK всё строено и вызывается парой команд, да в протоколе WiFi уже есть синхронизация, точность которой и не снилась NTP...
 
Последнее редактирование:

shuraf

Member
Есть же возможность sdk-ашные возможности синхронизации ntp перенести в ардуино-скетч?
 
Последнее редактирование:

pvvx

Активный участник сообщества
Есть пример синхронизации из WiFi без NTP?
А на чем построены графики расхождения кварцев за отсчеты в 0.1 сек от температуры роутера и ESP в мкс ?
Всё в web-свалке, вам надо какую-то опцию включить при сборке проекта (забыл уже, т.к. у меня проектов много, а это было давно) и описать страничку HTML приема отсчетов c отображением в график на javascript и закинуть на диск web ESP. Тогда в любом броузере будет создавать аналогичный график :) Т.е. в данном случае такой синхронизации хватает для вывода звука на разнесенные колонки и триангуляции на источник звука в пару метров (с учетом скорости ветра) :)

Для отображения часов обычно ставят GPS и используют стандартный её выход строба секундных отсчетов. Но на Arduino невозможно даже с программным ФАПЧ получить точность внешних событий (изменений на GPIO, хоть по прерыванию) лучше 50 us из-за биений с запретами прерываний в системе.
Если поставите внешний MCU, то с бытовых GPS модулей и программным ФАПЧ сможете синхронизовать только вставку кадров (переключение на рекламу без срыва синхро) в транслируемое аналоговое телевидение и то с биениями... :)
 
Последнее редактирование:

gerkimuyda

New member
стандартные arduino примеры об этом не знают. :) Вы бы взяли и рассказали как надо в картинках.
Как гласит народная мудрость: - Сказавши "а", надо сказать и "б" ... :):):)
:D Так там ведь все просто: Arduino/sntp.h и Arduino/sntp.c
Делаем [inline]#include "lwip/sntp.h"[/inline], а потом:
Код:
sntp_stop();
sntp_setservername(0,"ntp.time.in.ua");
sntp_setservername(1,"pool.ntp.org");
sntp_set_timezone(2);
sntp_init();
Вуаля. И остается только получать время [inline]uint32 Time = sntp_get_current_timestamp()[/inline] o_O
ps1: системе надо секунду-две для синхронизации в фоне, прежде чем функция начнет отдавать значение отличное от нуля.
ps2: [inline]Serial.print( sntp_get_real_time(sntp_get_current_timestamp()) );[/inline]
ps3: Синхронизация раз в час.
ps4: на всякий случай, как всегда, предупреждаю, что это с последней версией библиотеки. Что творилось в прошлых - не знаю.
https://github.com/esp8266/Arduino/releases/download/2.4.0-rc2/package_esp8266com_index.json
 
Последнее редактирование:
  • Like
Реакции: kab

kab

New member
@gerkimuyda
Не сочтите за наглость, но, может у Вас получится переложить на язык Arduino мысли @pvvx по поводу синхронизации времени без интернета, а only используя время из WiFi?
 

gerkimuyda

New member
kab, у меня вместо приведенных выше серверов прописан мой роутер, на который, естественно, возложена миссия ntp-сервера и клиента, заодно и в его dns-ах статикой прописаны его ип на всякие time.windows.net и т.д. o_O
Еще в раздаваемых dhcp-опциях указан ntp-сервер, но использовать ntp из dhcp-настроек в esp8266 у меня сходу не получилось. Да и я особо и не разбирался. Там еще опции #define перед компиляцией менять надо и специальную функцию вызывать... В общем, для "хомячков" не пригодный путь :D Да и альтернатива ему - использовать ntp-сервером IP, полученный как "шлюз по умолчанию" (т.е. сам роутер - это в большинстве случаев так и обстоит на самом деле). :rolleyes:
Зачем усложнять, если проще все локальные устройства синхронизировать по одному времени (с один локальным ntp), а для timestamp в БД использовать локальную метку времени, которая легко отслеживает timezone и переход на летнее/зимнее время.
А вы пробовали полученное от одного клиента будущее значение времени из одной таймзоны, сохранить универсально на сервере (в другой таймзоне), чтобы другому клиенту отдать в его (третей) таймзоне? С учетом того, что к моменту наступления этого будущего времени сервер и первый клиент может уже сменить летнее/зимнее время, а для второго клиента этот переход еще не наступил? o_O
Вы любите теоретические задачки - Вам понравится. :) (А я сейчас отдыхаю и не хочу напрягаться. Подобная ситуация у меня была в связке php+javascript)
 

pvvx

Активный участник сообщества
Не сочтите за наглость, но, может у Вас получится переложить на язык Arduino мысли @pvvx по поводу синхронизации времени без интернета, а only используя время из WiFi?
Там ничего сложного нет, кроме борьбы с самой концепцией Arduino IDE, её распределению системных процессов и организации делать всё полингом, а не по событиям. И надо патчить либу SDK, где уже описано...
Как оптимальная модификация, является установка на роутер системы синхронизации с внешним временем его TSF. Тогда все устройства в сети будут знать точное время каждый beacon (каждую 0.1 секунду) и не тратить на это время и ресурсы. Протокол WiFi позволяет это, иметь в счетчике TSF хоть текущее Московское время с шагом в мкс.
Так-же, в некоторых роутерах есть свой NTP, который определяется по DHCP. Делается это для того, что с дальним сервером синхронизация скачет, а в местной интрасети задержки минимальны и точность местного NTP лучше.
Еще в раздаваемых dhcp-опциях указан ntp-сервер, но использовать ntp из dhcp-настроек в esp8266 у меня сходу не получилось. Да и я особо и не разбирался. Там еще опции #define перед компиляцией менять надо и специальную функцию вызывать... В общем, для "хомячков" не пригодный путь :D
Это есть в web-свалке, когда-то было реализовано "по просьбе трудящихся" :)
Какие-то наброски проявлялись в Arduino, но я не особо слежу за ними и не знаю - доделали ли это там :) Ведь для этого надо вставить до десятка строк, т.е. очень и оочень сложно, при наличии десятков примеров в сети :) :)
 
Последнее редактирование:

gerkimuyda

New member
вы бы объяснили хомячкам, что у вас OpenWrt стоит для этих целей.
Нет. Обычный Mikrotik, правда на нем можно раскатать OpenWrt, но я этого не делал. А NTP-сервер есть и в мыльницах типа D-Lint, TP-Link и т.д. Просто внимательнее посмотрите настройки или документацию. Да если у вас хороший инет - просто пингами определите самый ближайший ntp-сервер к вам и по нему синхронизуйтесь... Может у провайдера он молча установлен?
 
Сверху Снизу