# NTP Node Time ## 1. Назначение Обмен временем с прямыми ETCP-пирами. Если у узла ещё нет поправки, он принимает время синхронизированного пира с оценкой сетевой задержки. Поправка хранится в `instance->ntp` и применяется getters из `ntp_time.h`; часы ОС не меняются. Отключение интернет-NTP (`ntp.enabled=0`) не отключает этот обмен. Уже синхронизированный узел повторно не принимает поправку от пиров, но интернет-NTP может её обновить. ## 2. Как пользоваться В uasync-потоке после создания `instance->connections` вызвать `ntp_node_time_init()`. Он привязывает raw packet ID `ETCP_ID_NTP_TIME = 0x04` (прямой ETCP, без роутера), подписывается на новые соединения и на INIT всех существующих, в том числе pending. На INIT отправляется `TIME_SYNC`; при запуске модуля пакет также отправляется уже инициализированным соединениям. Повторный активный init возвращает 0 без дублирования подписок. При занятом ID или ошибке регистрации возвращается -1; свои частичные регистрации снимаются. `TIME_SYNC` — 10 байт: ID (1), flags (1, bit 0 = synced), время отправителя (int64 Unix мкс в нативном порядке байтов). Синхронизированный отправитель передаёт скорректированное время, остальные — локальное. При первом сообщении сохраняется исходное смещение для диагностики. Если получатель не синхронизирован, а отправитель синхронизирован, применяется: `offset_us = local_receive_us - sender_us - mean_link_rtt_us / 2`. Среднее RTT — арифметическое по всем элементам `conn->links`, включая нулевые значения и down-линки; `rtt_last` переводится из 0,1 мс в микросекунды. При отсутствии линков задержка считается нулевой. Это приближённая оценка: симметричность пути и точность часов пира не гарантируются. После принятия поправки устанавливаются `ntp.synced` и `ntp.last_sync_tb`, затем время рассылается остальным. Синхронизированный получатель отвечает несинхронизированному отправителю. Когда оба синхронизированы, ответа нет — бесконечный обмен не возникает. Полученный entry и его dgram освобождаются при обработке, в том числе при заполненном кеше диагностики. `ntp_node_sync_peers()` рассылает время по соединениям с `initialized && links_up`, если модуль активен и есть поправка. Вызывается также после успешной интернет-NTP-синхронизации. **Нюансы:** - Кеш содержит до 32 диагностических записей, а не ограничивает число соседей. При заполнении вытесняется запись с наименьшим `last_seen_tb` (при равенстве — первая по индексу); исходное смещение новой записи сбрасывается. - Диагностическое смещение — `raw_local_receive_us - sender_us`, без вычитания задержки. Оно отличается от глобальной поправки, принятой с оценкой половины RTT. - При повторном сообщении модуль сравнивает смещение с предыдущим. Модуль разности строго >2 с даёт WARN, строго >10 с — ERROR. Это изменение относительного смещения: оно может включать коррекцию часов отправителя и изменение задержки, а не только дрейф часов. - `destroy` вызывается в uasync-потоке вне callbacks модуля/соединений. Снимает raw binding, подписку новых соединений и свои callbacks со всех текущих соединений, очищает кеш. Глобальную поправку не меняет. Повторный вызов и NULL безопасны. ## 3. API **Структуры:** - `struct NTP_NODE_PEER` — ID пира, предыдущее диагностическое смещение, наличие исходного значения и время последнего обновления. - `struct NTP_NODE_TIME` — кеш, число занятых записей и флаг активности; контракты полей/функций описаны в `ntp_node_time.h`. **Функции:** - `ntp_node_time_init(inst)` — 0 при успехе/уже активном модуле, -1 при ошибке; inst и очередь connections обязательны. - `ntp_node_time_destroy(inst)` — снимает регистрации и очищает кеш, сохраняя глобальную поправку. - `ntp_node_sync_peers(inst)` — рассылает время готовым прямым соседям; для NULL/неактивного/несинхронизированного модуля ничего не делает.