Библиотека для получения точного времени с NTP сервера для esp8266/esp32
- Работает на стандартном интерфейсе Udp.h
- Учёт времени ответа сервера и задержки соединения
- Получение времени с точностью до миллисекунд
- Интеграция с библиотекой Stamp для распаковки unix в часы, минуты итд
- Автоматическая синхронизация
- Поддержание хода времени на базе millis() между синхронизациями
- Секундный таймер для удобства автоматизации
- Обработка ошибок
- Асинхронный режим
- Поддержка внешнего RTC
- Кеширование DNS, стабильная работа и возможность проверки наличия Интернет
Все платформы
- Stamp v1.4.0+
GyverNTP; // параметры по умолчанию (gmt 0, период 3600 секунд (1 час))
GyverNTP(gmt); // часовой пояс в часах (например Москва 3)
GyverNTP(gmt, period); // часовой пояс в часах и период обновления в секундах
Начиная с версии 2.1.0 доступен глобальный объект
NTP
, свой создавать не нужно
// установить часовой пояс в часах или минутах (глобально для Stamp)
void setGMT(int16_t gmt);
// установить период обновления в секундах
void setPeriod(uint16_t prd);
// включить асинхронный режим (по умолч. true)
void asyncMode(bool async);
// установить хост (умолч. "pool.ntp.org")
void setHost(const String& host);
// установить хост IP
void setHost(const IPAddress& host);
// установить порт (умолч. 123)
void setPort(uint16_t port);
// получить пинг NTP сервера, мс
int16_t ping();
// вернёт true при изменении статуса online
bool statusChanged();
// не учитывать пинг соединения (умолч. false)
void ignorePing(bool ignore) {
_usePing = ignore;
}
// подключить RTC
void attachRTC(VirtualRTC& rtc);
// отключить RTC
void detachRTC();
// подключить обработчик ошибки
void onError(ErrorCallback cb);
// получить последнюю ошибку
Error getError();
// получить последнюю ошибку
const __FlashStringHelper* readError();
// есть ошибка
bool hasError();
// вернёт true, если tick ожидает ответа сервера в асинхронном режиме
bool busy();
// true - есть соединение с Интернет
bool online();
// запустить
bool begin();
// запустить с указанием часового пояса в часах или минутах (глобально для Stamp)
bool begin(int16_t gmt);
// выключить NTP
void end();
// синхронно обновить время с сервера. true при успехе
bool updateNow();
// тикер, вызывать в loop. Вернёт true каждую секунду, если синхронизирован. Синхронизируется по таймеру
bool tick();
- GyverNTP работает с WiFi UDP для esp8266/esp32, но может использоваться любой другой UDP клиент (см. ниже)
- Существует глобальный объект
NTP
(какSerial
,Wire
И проч.) - Нужно вызывать
tick()
в главном цикле программыloop()
, он синхронизирует время с сервера по своему таймеру и обеспечивает работу секундного таймера - Если основной цикл программы сильно загружен, а время нужно получать с максимальной точностью (несколько мс), то можно выключить асинхронный режим
asyncMode(false)
- Библиотека продолжает считать время после пропадания синхронизации. По моим тестам esp "уходит" на ~1.7 секунды за сутки, поэтому стандартный период синхронизации выбран 1 час
- Наследуется класс
StampKeeper
, который обеспечивает счёт времени, работу секундного таймера и удобную конвертацию времени
Часовой пояс задаётся для всех операций со Stamp/Datime в программе! Установка часового пояса в объекте NTP равносильна вызову setStampZone()
- установка глобального часового пояса для библиотеки Stamp:
void setup() {
// подключить к WiFi
NTP.begin(3); // запустить и указать часовой пояс
}
#include <GyverNTP.h>
void setup() {
// подключить к WiFi
NTP.begin(3); // запустить и указать часовой пояс
}
void loop() {
NTP.tick(); // вызывать тикер в loop
}
Для удобства автоматизации событий по таймеру в библиотеку встроен секундный таймер, он срабатывает в 0 миллисекунд каждой секунды. По условию таймера NTP гарантированно синхронизирован и выдаёт корректное время:
void loop() {
if (NTP.tick()) {
// новая секунда!
Serial.println(NTP.toString());
}
// или так
// NTP.tick();
// if (NTP.newSecond()) { }
}
Также можно подключить обработчик на секунду:
void newSecond() {
// ваш код
}
void setup() {
// подключить к WiFi
NTP.begin(3); // запустить и указать часовой пояс
NTP.onSecond(newSecond);
// или так
NTP.onSecond([](){
// ваш код
});
}
void loop() {
NTP.tick();
}
GyverNTP наследует StampConvert, то есть получать время можно множеством способов:
// каждую секунду
if (NTP.tick()) {
// вывод даты и времени строкой
Serial.print(NTP.toString()); // NTP.timeToString(), NTP.dateToString()
Serial.print(':');
Serial.println(NTP.ms()); // + миллисекунды текущей секунды. Внутри tick всегда равно 0
// вывод в Datime
Datime dt = NTP; // или Datime dt(NTP)
dt.year;
dt.second;
dt.hour;
dt.weekDay;
dt.yearDay;
// ... и прочие методы и переменные Datime
// чтение напрямую, медленнее чем вывод в Datime
NTP.second();
NTP.minute();
NTP.year();
// ... и прочие методы StampConvert
// сравнение
NTP == DaySeconds(12, 35, 0); // сравнение с DaySeconds (время равно 12:35:00)
NTP == 1738237474; // сравнение с unix
NTP == Datime(2025, 1, 30, 14, 14, 30); // сравнение с Datime
}
GyverNTP может использоваться и без тикера - нужно вручную вызвать updateNow
для синхронизации времени. В этом случае время будет считаться просто с момента последней синхронизации, обработчик секунд не будет работать:
void setup() {
// подключить к WiFi
NTP.begin(3); // запустить и указать часовой пояс
NTP.updateNow(); // синхронизировать
}
void loop() {
Serial.println(NTP.toString());
delay(1000);
}
Если период синхронизации очень большой или в системе надолго пропадает связь, часы рассинхронизируются и будут синхронизированы при следующем обращении к серверу. Если время ушло больше, чем на 1 секунду, то поведение будет следующим:
- Если внутренние часы "спешат" - секундный таймер перестанет срабатывать, пока реальное время не догонит внутреннее
- Если внутренние часы "отстают" - таймер будет вызываться каждую итерацию loop с прибавлением времени, пока внутреннее время не догонит реальное
Это сделано для того, чтобы при синхронизации не потерялись секунды - библиотека обработает каждую секунду и не будет повторяться, что очень важно для алгоритмов автоматизации.
NTP работает по UDP - очень легковесному и "дешёвому" протоколу связи, обращение к серверу практически не занимает времени. Благодаря этому NTP можно использовать для проверки связи с Интернет - там, где стандартный TCP клиент зависнет на несколько секунд, NTP асинхронно сообщит о потере связи. В рамках GyverNTP это можно использовать так:
void setup() {
// подключить к WiFi
NTP.begin(3);
NTP.setPeriod(5); // синхронизация каждые 5 секунд
}
void loop() {
NTP.tick();
// вернёт true при смене статуса
if (NTP.statusChanged()) {
Serial.println(NTP.online());
// здесь флаг online можно использовать для передачи в другие библиотеки
// например FastBot2
// bot.setOnline(NTP.online());
}
}
Библиотека поддерживает подключение внешнего RTC для синхронизации времени:
- Если пришло время синхронизироваться с NTP, но возникла ошибка - будет синхронизировано время с RTC
- Если успешно синхронизирован с NTP, то в RTC также будет записано актуальное время (не чаще чем
GNTP_RTC_WRITE_PERIOD
, по умолч. 1 час)
Объект RTC должен являться экземпляром класса VirtualRTC
, из готовых например GyverDS3231Min
. Можно написать и свой класс для подключения любого источника реального времени.
#include <Arduino.h>
#include <GyverNTP.h>
void setup() {
Serial.begin(115200);
WiFi.begin("WIFI_SSID", "WIFI_PASS");
while (WiFi.status() != WL_CONNECTED) delay(100);
Serial.println("Connected");
// обработчик ошибок
NTP.onError([]() {
Serial.println(NTP.readError());
Serial.print("online: ");
Serial.println(NTP.online());
});
// обработчик секунды (вызывается из тикера)
NTP.onSecond([]() {
Serial.println("new second!");
});
// обработчик синхронизации (вызывается из sync)
// NTP.onSync([](uint32_t unix) {
// Serial.println("sync: ");
// Serial.print(unix);
// });
NTP.begin(3); // запустить и указать часовой пояс
// NTP.setPeriod(30); // период синхронизации в секундах
// NTP.setHost("ntp1.stratum2.ru"); // установить другой хост
// NTP.setHost(IPAddress(1, 2, 3, 4)); // установить другой хост
// NTP.asyncMode(false); // выключить асинхронный режим
// NTP.ignorePing(true); // не учитывать пинг до сервера
// NTP.updateNow(); // обновить прямо сейчас
}
void loop() {
// тикер вернёт true каждую секунду в 0 мс секунды, если время синхронизировано
if (NTP.tick()) {
// вывод даты и времени строкой
Serial.print(NTP.toString()); // NTP.timeToString(), NTP.dateToString()
Serial.print(':');
Serial.println(NTP.ms()); // + миллисекунды текущей секунды. Внутри tick всегда равно 0
// вывод в Datime
Datime dt = NTP; // или Datime dt(NTP)
dt.year;
dt.second;
dt.hour;
dt.weekDay;
dt.yearDay;
// ... и прочие методы и переменные Datime
// чтение напрямую, медленнее чем вывод в Datime
NTP.second();
NTP.minute();
NTP.year();
// ... и прочие методы StampConvert
// сравнение
NTP == DaySeconds(12, 35, 0); // сравнение с DaySeconds (время равно 12:35:00)
NTP == 1738237474; // сравнение с unix
NTP == Datime(2025, 1, 30, 14, 14, 30); // сравнение с Datime
}
if (NTP.newSecond()) {
// новую секунду можно поймать и здесь
}
// изменился онлайн-статус
if (NTP.statusChanged()) {
Serial.print("STATUS: ");
Serial.println(NTP.online());
}
}
#include <Arduino.h>
#include <GyverNTP.h>
// GyverDS3231 поддерживает работу с GyverNTP
#include <GyverDS3231Min.h>
GyverDS3231Min rtc;
// можно написать свой класс и использовать любой другой RTC
class RTC : public VirtualRTC {
public:
void setUnix(uint32_t unix) {
Serial.print("SET RTC: ");
Serial.println(unix);
}
uint32_t getUnix() {
return 1738015299ul;
}
};
RTC vrtc;
void setup() {
Serial.begin(115200);
WiFi.begin("WIFI_SSID", "WIFI_PASS");
// while (WiFi.status() != WL_CONNECTED) delay(100);
Serial.println("Connected");
// GyverDS3231
Wire.begin();
rtc.begin();
NTP.begin(3); // запустить и указать часовой пояс
// подключить RTC
// NTP.attachRTC(vrtc);
NTP.attachRTC(rtc);
}
void loop() {
if (NTP.tick()) {
Serial.println(NTP.toString());
}
}
- v1.0
- v1.1 - мелкие улучшения и gmt в минутах
- v1.2 - оптимизация, улучшена стабильность, добавлен асинхронный режим
- v1.2.1 - изменён стандартный период обновления
- v1.3 - ускорена синхронизация при запуске в асинхронном режиме
- v1.3.1 - заинклудил WiFi библиотеку в файл
- v2.0 - добавлена зависимость от Stamp, больше возможностей, проверка онлайна для других библиотек
- v2.1 - добавлен глобальный объект NTP
- v2.2.0 - более стабильная работа, новые возможности
- Библиотеку можно найти по названию GyverNTP и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- Скачать библиотеку .zip архивом для ручной установки:
- Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
- Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
- Распаковать и положить в Документы/Arduino/libraries/
- (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек здесь
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
- Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
При нахождении багов создавайте Issue, а лучше сразу пишите на почту [email protected]
Библиотека открыта для доработки и ваших Pull Request'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код