2009-03-12

NTP для Arduino

Недавно наткнулся на упоминание о протоколе SNTP в контексте его использования для Arduino - проект аналоговых часов 3MeterClock.

В описании говорится, что часы используют для получения времени Simple NTP. Сначала я прочел это как SNMP, затем SMTP. Потом прочел по буквам и понял, что требуется man, а именно: как поднять сервер SNTP, потому что ну очень хочется написать скетч для Arduino для синхронизации по сети.

Из RFC2030 выяснилось, что никакого отдельного сервера SNTP не надо. Для NTP или SNTP сервера клиенты NTP и SNTP неразличимы, также как и для NTP или SNTP клиента неразличимы NTP и SNTP сервера. Алилуйя!

Все отличия заключаются в том, что значительно упрощены вычисления, с целью максимально упростить жизнь клиенту и добиться точности не хуже микросекунд.

Каково же было мое удивление, когда в исходниках все того же 3MeterClock-а я обнаружил... обращение к порту 13! Опять внимательно покопавшись в RFC, пришел к выводу, что у Arduino есть как минимум ТРИ разных варианта получения информации о времени по сети:
  1. SNTP/NTP - при помощи UDP по порту 123. Клиенту надо послать пакет запроса, в ответ сервер его заполнит различными временными метками, и, если клиент озаботился засечь время отсылки запроса, можно относительно просто получить время с учетом RTT (RFC2030, RFC1305). Время в метке NTP отсчитывается в секундах с 01.01.1900 (еще есть fraction part в 1/65535-х долях секунды, но такая точность лично мне пока не нужна).
  2. Time protocol - при помощи TCP или UDP на порт 37. Клиенту надо послать пустой пакет, в ответ вернется 32-битное время, формат тот же (RFC868).
  3. DayTime protocol - при помощи TCP или UDP на порт 13. Клиенту надо послать также пустой пакет, но в ответ вернется дата и время в виде ASCII-строки (RFC867).
Забавно, но уже после копания в RFC нашел это же в тексте книги К. Джамса, К. Коуп. Программирование для Internet в среде Windows, которую издательство Питер-пресс опубликовало на своем сайте.

Получается, реально в 3MeterClock используется вовсе не SNTP, а DayTime. Логично, ведь не нужно корпеть над переводом мегачисла секунд, натикавших с 01.01.1900, в текстовую дату и время. Одна беда: стандарт протокола не заставляет строго форматировать возвращаемую строку. Неудивительно, поскольку протокол предназначен в основном для дебаговых целей, в отличие от NTP/SNTP.

То есть, можно увидеть как Tuesday, February 22, 1982 17:37:43-PST, так и 02 FEB 82 07:59:01 PST. Ясно дело, что если "заточиться" под определенный сервер, то будет вам счастье (однозначный парсинг текстовой строки). Вот авторы и "прибили гвоздем" свой sketch к серверу time.nist.gov, который отвечает 54901 09-03-11 10:44:51 50 0 0 831.6 UTC(NIST).

Следовательно, авторы проекта немного схалявили, что вполне простительно :) Однако, можно было бы не путать DayTime и SNTP, вводя читателей в заблуждение. Впрочем, пытливый читатель все равно разберется :)

Мысль про SNTP изначально правильная, потому что шансов получить время по NTP гораздо больше, чем через Time или DayTime. И даже если заранее неизвестен сервер времени, то можно попробовать спросить спросить время в режиме anycast (если не страшно, что вас обманут). Однако, как же быть с вычислениями?

Для начала, надо преобразовать время NTP в эпоху UNIX, т.е. получить число секунд, натикавших не с 01.01.1900, а с 01.01.1970. По счастью, эта разница уже подсчитана: надо вычесть из NTP 2208988800UL. Во многих случаях, одного этого уже достаточно, чтобы Arduino мог правильно общаться с внешним миром. Но что, если хочется немного больше - например, поддерживать время в МК или индицировать его на дисплее?...

Была бы под руками <time.h>, можно было бы ей воспользоваться. Но, увы, её нет среди библиотек avr, и придется обратиться к GNUтым исходникам glibc. Выдирается с мясом процедура __offtime, выносится пара структур и... готово:

4 комментария:

BlackSnow комментирует...

мог бы поподробнее написать.

Unknown комментирует...

Эх, печалька. Думал готовый алгоритм нашел. Ну спасибо за подсказку, пойду препарировать __offtime :)

id комментирует...

Мужики, ну не тормозите, пожалуйста - пять лет статье! Давно уже есть стандартный пример к библиотеке Ethernet в ArduinoIDE, так и называется - UdpNtpClient.

Unknown комментирует...

Да мне не к ардуине нужна. Пробовал выдрать алгоритм из ардуиновской библиотеки, но не работает почему-то. Сижу вот разбираюсь.