Как сделать сервер времени (NTP) на Arduino
Для проекта нам понадобятся:
- Arduino UNO или иная совместимая плата;
- Ethernet-модуль, например, на микросхеме Wiznet W5100;
- приёмник ГНСС сигнала (GPS, ГЛОНАСС) c UART;
- модуль с часами реального времени DS3231 или DS1307;
- макетная плата (breadboard);
- соединительные провода;
- персональный компьютер со средой разработки Arduino IDE.
1Схема NTP сервера на Arduino
Протокол NTP – это протокол, который используется для синхронизации часов компьютера или иного устройства с сервером времени по сети. Любое сетевое устройство может послать сетевой пакет определённого вида серверу времени, а тот в ответ пришлёт точное значение времени. Запросившее устройство установит это значение на своих системных часах. Таким образом осуществляется синхронизация времени. Существует несколько популярных серверов точного времени. Так, например, в операционных системах Windows для синхронизации времени используются time.windows.com или time.nist.gov. Можно использовать и другие сервера, в том числе и свой собственный сервер. Мы реализуем сервер времени с помощью Arduino.
Откуда же мы будем брать значение точного времени? Для этого воспользуемся приёмником ГНСС сигналов. Такие приёмники с определённой периодичностью (обычно 1 раз в секунду или около того) возвращают данные в формате NMEA о своём местоположении, а также дате и времени. Описание формата NMEA можно скачать в конце статьи. Мы подключимся к приёмнику и по протоколу UART получим с него данные о точном времени.


Также нам понадобится модуль с часами реального времени (RTC), в который мы сохраним значение времени. И по запросу клиента будем возвращать время, считанное с этого модуля. Такие модули обычно имеют собственный элемент питания и сохраняют время даже при отсутствии внешнего источника питания.

И, конечно же, нам понадобится модуль с сетевым интерфейсом или т.н. Ethernet-шилд. Этот модуль позволит подключить Arduino к локальной сети или к компьютеру по Ethernet.

В качестве альтернативы можно использовать Wi-Fi модуль, разумеется. Лишь бы ваши устройства, время на которых необходимо синхронизировать с сервером времени, находились в одной локальной сети с сервером времени на Arduino.
Теперь соединим все наши части воедино. Для этого сначала соберём «бутерброд» из Arduino и сетевого шилда, который выполнен в виде мезонинной платы. Далее подключим модуль часов DS1307 к выводам A4 и A5, а это шина I2C, как мы помним. Следовательно, пин A4 – это SDA, пин A5 – SCL. Приёмник сигналов ГНСС необходимо подключить к UART. Для этого можно подключить его к стандартным выводам RX и TX Arduino (пины 0 и 1, соответственно). Но тогда мы не сможем одновременно работать с приёмником и отлаживаться с выводом отладочных сообщений в последовательный порт. Поэтому рекомендую реализовать программный UART с помощью штатной библиотеки SoftwareSerial. Для этого подключим GPS приёмник к любым цифровым выводам (кроме 0 и 1), например, к 10 и 11.

Не забудем питание и землю, разумеется. И модуль часов, и приёмник питаются одним напряжением, равным 5 вольтам.
2Скетч NTP сервера для Arduino
Напишем скетч для Arduino, в котором реализуем функциональность сервера времени с поддержкой протокола NTP и с минимальным использованием сторонних библиотек.
Общий алгоритм следующий. Сначала будем опрашивать приёмник спутникового сигнала, пока не получим от него NMEA пакет с корректным значением времени. Нужный нам пакет с временем начинается с заголовка "$GPRMC". В этих пакетах время и дата хранятся на 2 и на 10 позициях соответственно, разделённых запятыми. Координаты достоверны, когда статус (позиция 3) будет "A".

Когда получим значение времени, запишем его в модуль RTC. Подробно работу с часами реального времени мы рассматривали здесь.
Далее запустим сервер и в цикле будем постоянно слушать входящие запросы по протоколу UDP на порту 123 (это стандартный порт протокола NTP). Как только сервер получит NTP запрос, прочитаем время из модуля часов реального времени, «упакуем» в ответный NTP пакет и отправим клиенту, который запросил время.
В конце статьи приложена программа для тестирования связи с NTP сервером.
Скетч сервера времени NTP и Arduino (разворачивается)
#define debug true // для вывода отладочных сообщений #include <SoftwareSerial.h> #include <Wire.h> #include <Ethernet.h> #include <EthernetUdp.h> SoftwareSerial Serial1(10, 11); EthernetUDP Udp; // MAC, IP-адрес и порт NTP сервера: byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // задайте свой MAC IPAddress ip(192, 168, 0, 147); // задайте свой IP #define NTP_PORT 123 // стандартный порт, не менять #define RTC_ADDR 0x68 // i2c адрес RTC static const int NTP_PACKET_SIZE = 48; byte packetBuffer[NTP_PACKET_SIZE]; int year; byte month, day, hour, minute, second, hundredths; unsigned long date, time, age; uint32_t timestamp, tempval; void setup() { Wire.begin(); // стартуем I2C #if debug Serial.begin(115200); #endif Serial1.begin(4800); // старт UART для GPS модуля getGpsTime(); // получаем время GPS writeRtc(); // записываем время в RTC // запускаем Ethernet шилд в режиме UDP: Ethernet.begin(mac, ip); Udp.begin(NTP_PORT); #if debug Serial.println("NTP started"); #endif } void loop() { processNTP(); // обрабатываем приходящие NTP запросы } String serStr; // строка для хранения пакетов от GPS приёмника // Читает пакеты GPS приёмника из COM-порта и пытается найти в них время // Если время найдено, возвращает True, иначе - False void getGpsTime() { bool timeFound = false; while (!timeFound) { while (Serial1.available()>0) { char c = Serial1.read(); if (c != '\n') { serStr.concat(c); } else { timeFound = decodeTime(serStr); serStr = ""; } } } } // Декодирует вермя по NMEA пакету // и возвращает True в случае успеха и False в обратном случае bool decodeTime(String s) { #if debug Serial.println("NMEA Packet = " + s); #endif if (s.substring(0,6)=="$GPRMC") { String validFlag = s.substring(18,20); // Ждём валидные данные (флаг "V" - данные не валидны, "A" - данные валидны): if (validFlag == "A") { String timeStr = s.substring(7,17); // строка времени в формате ччммсс.сс hour = timeStr.substring(0,2).toInt(); minute = timeStr.substring(2,4).toInt(); second = timeStr.substring(4,6).toInt(); hundredths = timeStr.substring(7,10).toInt(); // ищем индекс 4-ой запятой с конца, после которой идёт дата int commaIndex = 1; for (int i=0;i<5;i++) { commaIndex = s.lastIndexOf(",", commaIndex-1); } String date = s.substring(commaIndex+1, commaIndex+7); // строка даты в формате ддммгг day = date.substring(0,2).toInt(); month = date.substring(2,4).toInt(); year = date.substring(4,6).toInt(); // передаются только десятки и единицы года #if debug printDate(); #endif return true; } } return false; } // Запоминает время в RTC void writeRtc() { byte arr[] = {0x00, dec2hex(second), dec2hex(minute), dec2hex(hour), 0x01, dec2hex(day), dec2hex(month), dec2hex(year)}; Wire.beginTransmission(RTC_ADDR); Wire.write(arr, 8); Wire.endTransmission(); #if debug Serial.print("Set date: "); printDate(); #endif } // Преобразует число из dec представления в hex представление byte dec2hex(byte b) { String bs = (String)b; byte res; if (bs.length()==2) { res = String(bs.charAt(0)).toInt() * 16 + String(bs.charAt(1)).toInt(); } else { res = String(bs.charAt(0)).toInt(); } #if debug Serial.println("dec " + (String)b + " = hex " + (String)res); #endif return res; } // Читает из RTC время и дату void getRtcDate() { Wire.beginTransmission(RTC_ADDR); Wire.write(byte(0)); Wire.endTransmission(); Wire.beginTransmission(RTC_ADDR); Wire.requestFrom(RTC_ADDR, 7); byte t[7]; int i = 0; while(Wire.available()) { t[i] = Wire.read(); i++; } Wire.endTransmission(); second = t[0]; minute = t[1]; hour = t[2]; day = t[4]; month = t[5]; year = t[6]; #if debug Serial.print("Get date: "); printDate(); #endif } // Обрабатывает запросы к NTP серверу void processNTP() { int packetSize = Udp.parsePacket(); if (packetSize) { Udp.read(packetBuffer, NTP_PACKET_SIZE); IPAddress remote = Udp.remoteIP(); int portNum = Udp.remotePort(); #if debug Serial.println(); Serial.print("Received UDP packet size "); Serial.println(packetSize); Serial.print("From "); for (int i=0; i<4; i++) { Serial.print(remote[i], DEC); if (i<3) { Serial.print("."); } } Serial.print(", port "); Serial.print(portNum); byte LIVNMODE = packetBuffer[0]; Serial.print(" LI, Vers, Mode :"); Serial.print(packetBuffer[0], HEX); byte STRATUM = packetBuffer[1]; Serial.print(" Stratum :"); Serial.print(packetBuffer[1], HEX); byte POLLING = packetBuffer[2]; Serial.print(" Polling :"); Serial.print(packetBuffer[2], HEX); byte PRECISION = packetBuffer[3]; Serial.print(" Precision :"); Serial.println(packetBuffer[3], HEX); for (int z=0; z<NTP_PACKET_SIZE; z++) { Serial.print(packetBuffer[z], HEX); if (((z+1) % 4) == 0) { Serial.println(); } } Serial.println(); #endif // Упаковываем данные в ответный пакет: packetBuffer[0] = 0b00100100; // версия, режим packetBuffer[1] = 1; // стратум packetBuffer[2] = 6; // интервал опроса packetBuffer[3] = 0xFA; // точность packetBuffer[7] = 0; // задержка packetBuffer[8] = 0; packetBuffer[9] = 8; packetBuffer[10] = 0; packetBuffer[11] = 0; // дисперсия packetBuffer[12] = 0; packetBuffer[13] = 0xC; packetBuffer[14] = 0; getRtcDate(); timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second); #if debug Serial.println("Timestamp = " + (String)timestamp); #endif tempval = timestamp; packetBuffer[12] = 71; //"G"; packetBuffer[13] = 80; //"P"; packetBuffer[14] = 83; //"S"; packetBuffer[15] = 0; //"0"; // Относительное время packetBuffer[16] = (tempval >> 24) & 0xFF; tempval = timestamp; packetBuffer[17] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[18] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[19] = (tempval) & 0xFF; packetBuffer[20] = 0; packetBuffer[21] = 0; packetBuffer[22] = 0; packetBuffer[23] = 0; // Копируем метку времени клиента packetBuffer[24] = packetBuffer[40]; packetBuffer[25] = packetBuffer[41]; packetBuffer[26] = packetBuffer[42]; packetBuffer[27] = packetBuffer[43]; packetBuffer[28] = packetBuffer[44]; packetBuffer[29] = packetBuffer[45]; packetBuffer[30] = packetBuffer[46]; packetBuffer[31] = packetBuffer[47]; // Метка времени packetBuffer[32] = (tempval >> 24) & 0xFF; tempval = timestamp; packetBuffer[33] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[34] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[35] = (tempval) & 0xFF; packetBuffer[36] = 0; packetBuffer[37] = 0; packetBuffer[38] = 0; packetBuffer[39] = 0; // Записываем метку времени packetBuffer[40] = (tempval >> 24) & 0xFF; tempval = timestamp; packetBuffer[41] = (tempval >> 16) & 0xFF; tempval = timestamp; packetBuffer[42] = (tempval >> 8) & 0xFF; tempval = timestamp; packetBuffer[43] = (tempval) & 0xFF; packetBuffer[44] = 0; packetBuffer[45] = 0; packetBuffer[46] = 0; packetBuffer[47] = 0; // Отправляем NTP ответ Udp.beginPacket(remote, portNum); Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } } // Выводит отформатированноую дату void printDate() { char sz[32]; sprintf(sz, "%02d.%02d.%04d %02d:%02d:%02d.%03d", day, month, year+2000, hour, minute, second, hundredths); Serial.println(sz); } const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 }; // число дней в месяцах const unsigned long seventyYears = 2208988800UL; // перевод времени unix в эпоху // Формирует метку времени от момента 01.01.1900 static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) { if (y >= 1970) { y -= 1970; } uint16_t days = d; for (uint8_t i=1; i<m; ++i) { days += pgm_read_byte(daysInMonth + i - 1); } if (m>2 && y%4 == 0) { ++days; } days += 365 * y + (y + 3) / 4 - 1; return days*24L*3600L + h*3600L + mm*60L + s + seventyYears; }
Функция getGpsTime() постоянно читает приходящие от ГНСС приёмника пакеты, и когда получает очередной пакет, проверяет, нет ли в нём валидных данных времени. Если время есть, то происходит его разбор. Также время можно сохранить в модуле RTC и таким образом проводить периодическую синхронизацию.
Проверка NMEA пакетов осуществляется в функции decodeTime().
Несколько слов о функции dec2hex(). В ней несколько извращённо число переводится из десятичного представления в 16-ное. Точнее, так. Модуль часов показывает время в виде, например, 16:52:08. Но здесь каждое из этих чисел не десятичное, а 16-ное. То есть, в действительности это время в RTC хранится так: 0x16:0x52:0x08. А с GPS-приёмника мы получаем время в десятичном формате. И чтобы записать те же 16 часов в модуль RTC, нужно преобразовать десятичное 16 в шестнадцатеричное 0x16, что является десятичным 22. А полное время 0x16:0x52:0x08 будет в десятичном представлении 22:82:08. Хм, 82 минуты, странно, да? :) Но такое уж надо сделать преобразование, чтобы модуль часов реального времени запомнил правильное время.
Кстати, можно делать преобразование времени проще, если рассматривать десятки и единицы отдельно. Например, 23 часа можно рассмотреть как 2 десятка и 3 единицы часов. Тогда в 16-ном представлении время так и будет записано: 0x2 0x3 = 0x23. Аналогично поступаем с минутами. Например, 49 минут = 0x4 десятка минут и 0x9 единиц минут. Я уже не буду переписывать функцию dec2hex(), и предлагаю читателю сделать это самостоятельно :)
3 Программа для тестирования NTP сервера на Arduino
В приложении к статье имеется архив с программой тестирования NTP.

Всё, что требуется для проверки NTP сервера – это ввести адрес сервера и нажать кнопку «Отправить запрос». Соответственно, нужно знать адрес вашего NTP сервера на Arduino. Можно выбрать сервер из списка предложенных в меню («Выбрать сервер»).
Программа также позволяет запустить локальный NTP сервер. Время она будет брать из операционной системы. Данная возможность пригодится для каких-то отладочных целей.
Программа работает под ОС Windows с установленным .NET Framework версии 3.5 или выше.
Download attachments:
- Описание NMEA протокола обмена (1767 Downloads)
- Программа проверки NTP сервера (1996 Downloads)
Поблагодарить автора:
Поделиться
Related items
23 comments
-
Вадим Пятница, 24 Апрель 2020 17:34 Ссылка на комментарий
Добрый день! Наткнулся на Вашу статью по NTP серверу, решил собрать, т.к все для этого было, но возникла сложность с библиотекой EthernetUdp, которая присутствует в скетче. Где ее можно скачать? В интернете найти не удалось.
-
aave1 Пятница, 24 Апрель 2020 17:39 Ссылка на комментарий
Добрый день, Вадим!
EthernetUdp - это один из заголовочных файлов, который присутствует в библиотеке Ethernet. Эта библиотека присутствует в Arduino IDE "из коробки". Ничего специально скачивать не нужно. Просто добавьте импорт, как написано в скетче, и скетч должен скомпилироваться без проблем. -
Вадим Суббота, 25 Апрель 2020 21:12 Ссылка на комментарий
Да с этим, разобрался, но почему то при подключении к ПК, пропинговать его не могу и когда к роутеру подключаю с этим скетчем я его не вижу в устройствах подключенных к роутеру, хотя подсеть такая же как у роутра 192.168.1.1, индикатор ЛАН загорается. А в сеть не проходит, то есть также пропинговать не могу, в чем может быть сложнось?
-
aave1 Воскреснье, 26 Апрель 2020 08:34 Ссылка на комментарий
Вероятно, неправильно инициализировался Ethernet-шилд, а точнее - микросхема Wiznet. Эти шилды бывают нескольких разновидностей: на микросхемах Wiznet W5100, W5500 и т.д. И они могут несколько отличаться друг от друга. Я бы в таком случае попробовал другую библиотеку, например, Ethernet2 (https://github.com/adafruit/Ethernet2). Скетч при этом останется без изменений, кроме того, что в секции импорта будет указана другая библиотека.
-
Вадим Вторник, 28 Апрель 2020 04:14 Ссылка на комментарий
Заработало, спасибо!
А можете подсказать есть ли такой же модуль реального времени только который считает миллисекунды? Если известны конкретные модели подскажите пожалуйста. -
aave1 Вторник, 28 Апрель 2020 11:32 Ссылка на комментарий
Мне не известен такой модуль. И вряд ли вы вообще найдёте такой модуль RTC. Потому что получать время с точностью до миллисекунд, в общем-то, не имеет большого смысла. Ведь время на чтение показаний часов может занять десятки миллисекунд от момента формирования запроса до получения ответа, т.е. вся эта точность потеряется при обмене с Arduino.
Можно подумать, например, вот в каком направлении с имеющимся модулем часов. Каждый из описанных модулей часов имеет выход меандра SQW. Можно настроить его на частоту 1 кГц, и подать этот сигнал на вход прерываний Arduino. Каждое прерываение будем инкрементировать счётчик, а на 1000-ный отсчёт сбрасывать. Тогда, считывая показания часов и прибавляя к ним значение счётчика, мы как раз и будем иметь миллисекундную точность. -
kiv69 Суббота, 23 May 2020 12:37 Ссылка на комментарий
Как-то не очень толково описано.
Пытаюсь собрать на LGT8F328P и W5500 с 10-пиновым разъёмом.
Если я понял правильно, надо переопределить пины SoftwareSerial на другие, например, 3 и 4, так как 10 и 11 уже используются для W5500. -
kiv69 Понедельник, 25 May 2020 15:56 Ссылка на комментарий
Блин, сколько времени потратил.
Ладно, GPS не тот и протокол немного отличается. Переписал.
При получении валидного пакета printDate() выводила корректные дату-время, но к записи в RTC не переходило. Поправил.
Теперь вроде всё запустилось, но время выдаёт типа:
Get date: 37.05.2032 21:73:52.000
То есть ещё копать и копать. Либо с записью - чтением RTC проблемы, либо в программе что-то не то. -
kiv69 Понедельник, 25 May 2020 16:23 Ссылка на комментарий
Ага. При записи в RTC делаем конвертацию, а при чтении что, не надо?
-
kiv69 Понедельник, 25 May 2020 19:40 Ссылка на комментарий
Всё, запустил. Спасибо за девайс. Хоть и пришлось посидеть над скетчем , но удовольствие от результата всё же огромное.
Теперь в гараже и комп будет точное время получать, и часы настенные. -
aave1 Понедельник, 25 May 2020 19:48 Ссылка на комментарий
kiv69, я рад, что вы разобрались! Удачи!)
-
Роман Четверг, 18 Март 2021 18:39 Ссылка на комментарий
Подскажите, пожалуйста, функция writeRtc(); содержится в void setup(), т.е. выполняется при старте программы
каким образом потом часы RTC из которых мы отправляем врямя корректируются от данных полученных с GPS. т.е. вызов синхронизации где? -
aave1 Четверг, 18 Март 2021 18:51 Ссылка на комментарий
Роман! Мы вызываем writeRtc() один раз при старте скетча. Синхронизация времени в данном примере не показана. Показанного функционала практически достаточно, чтобы добавить синхронизацию самостоятельно.
-
Роман Суббота, 20 Март 2021 07:14 Ссылка на комментарий
значит описанного события по тексту "то происходит его разбор, далее запоминание в модуле RTC" не происходит, зачем тогда это писать и вводить людей в заблуждение
-
aave1 Суббота, 20 Март 2021 22:44 Ссылка на комментарий
Согласен! Видимо, планировал добавить это в скетч, но не добавил. Спасибо, что обратили внимание. Доделаю, когда будет время.
-
Андрей Среда, 28 Август 2024 19:36 Ссылка на комментарий
Спасибо за ваш труд.
Несколько моментов хотел бы дополнить в части улучшения точности выдачи времени. Может быть актуально для проектов с высокими требованиями к точности.
1. Микросхема RTC DS3231 имеет встроенные коррекции частоты своего генератора. Это автоматическая термокомпенсация (ничего делать не надо, работает внутри микросхемы). Итоговый уход часов не превысит 2.5 сек за месяц. А также можно вносить дополнительные коррекции. Например, если есть эталонный генератор (в виде GPS, об этом ниже).
2. Микросхема DS3231 может выдавать меандр с частотой 1Гц (настраивается, выход SQW). Эти импульсы синхронны с внутренним генератором - приходят строго в начале новой секунды. По ним можно определить время RTC с дискретностью до микросекунд.
3. Точность времени от GPS (полученная только по NMEA) невысокая, тк есть задержка на передачу пакета по RS232. Серьезные GPS-приемники выдают помимо NMEA также импульсы PPS. Импульс приходит строго в момент начала новой секунды мирового времени. Абсолютная точность - десятки-сотни наносекунд. Эти импульсы можно использовать для корректировки времени RTC, а также для подстройки частоты его генератора. -
Сергей Понедельник, 23 Сентябрь 2024 07:05 Ссылка на комментарий
kiv69 вы писали:
"При получении валидного пакета printDate() выводила корректные дату-время, но к записи в RTC не переходило. Поправил."
Подскажите что вы именно сделали? -
Константин Понедельник, 27 Январь 2025 09:01 Ссылка на комментарий
Не могу добиться, получения времени на компьютере, хотя программа для тестирования видит мой сервер и получает ответ. Скетч NTPSer писал на esp32, Проверял разными OS windows и отключал файэрволы. Заметил, что программа для тестирования мгновенно кидает запросы и сразу получает ответ NTP, а стандартными средствами синхронизации времени Windows кидает запрос через 700-900 мил. и похоже не получает вовсе. Куда рыть уже не знаю.
-
aave1 Вторник, 28 Январь 2025 14:41 Ссылка на комментарий
Константин,
не очень понятен ваш вопрос. Если программа принимает ответ сервера и корректно разбирает ответ, то должно быть всё в порядке. Может быть с настройками NTP сервера в ОС что-то? -
Константин Среда, 05 Февраль 2025 05:25 Ссылка на комментарий
Добрый день, aave1
Понимаю ваше непонимание, потому как сам запутался. Информации очень много в голове и нужно ее упорядочить. Проблема в том, что проект собираю на ESP32 dev module c интегрированным дисплей модулем. Поскольку собираю NTP сервер, то очень удобно будет смотреть служебную информацию на дисплее (это просто к слову).
Так вот проблема заключается в том, что при обращении встроенным NTP клиентом Windows XP,7.10.11 клиент игнорирует время и синхронизация времени проходит неудачей. Пробовал разные настройки NTP сервера, ну никак не хочет принимать время. НО если пользоваться вашей рекомендованной программой, то она принимает время от сервера, хотя не все поля заполнены stamp. Пробовал еще сторонние клиенты, тоже принимают время, но дата и время сильно разнятся (в плюс примерно 60 лет и время точно не как в NTP cервере).
В вашем скриншоте программы NTP клиента взятая для примера синхронизация была выполнена с time.nist.gov и видно что все поля stamp заполнены, но неизвестно как поля заполнены, когда синхронизация выполнена с вашего NTP сервера, это была бы небольшой подсказкой куда мне рыть, а может контрольная сумма не выполняется, из-за чего windows ntp client отказывается от синхронизации. Мне бы хотя бы раз получить синхронизацию времени в windows.
Буду благодарен любой помощи. -
aave1 Среда, 05 Февраль 2025 17:51 Ссылка на комментарий
Константин, тут могу только посоветовать несколько вещей.
1) Скачиваете снифер пакетов Wireshark. По этой статье https://wiki.wireshark.org/NTP фильтруете NTP пакеты и смотрите в чём разница между тем, что приходит в ответ на запрос вашего клиента к серверу в интернете, данные которого определяются корректно.
2) Читаете описание протокола NTP RFC 2030 (оно, кстати есть в моей программе в разделе справки) и ищете, где ошибка. -
Константин Понедельник, 10 Февраль 2025 03:39 Ссылка на комментарий
aave1 по вашей рекомендации погрузился в изучение. Я уже стал терять надежду но получилось. У Windows NTP клиента строгий и динамический контроль по синхронизации. Спасибо за помощь!
-
aave1 Понедельник, 10 Февраль 2025 18:17 Ссылка на комментарий
Константин,
Рад, что у вас получилось разобраться :)