ntp_clock.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. #include <arpa/inet.h>
  2. #include <errno.h>
  3. #include <string.h>
  4. #include <sys/socket.h>
  5. #include <unistd.h>
  6. #include "esp_log.h"
  7. #include "esp_timer.h"
  8. #include "freertos/FreeRTOS.h"
  9. #include "freertos/task.h"
  10. #include "ntp_clock.h"
  11. static const char *TAG = "ntp_clock";
  12. // Timing packet types (from shairport-sync)
  13. #define TIMING_REQUEST 0xD2
  14. #define TIMING_RESPONSE 0xD3
  15. // Timing request packet structure (32 bytes)
  16. typedef struct __attribute__((packed)) {
  17. uint8_t leader; // 0x80
  18. uint8_t type; // 0xD2 for request
  19. uint16_t seqno; // sequence number (network byte order)
  20. uint32_t filler; // padding
  21. uint64_t origin; // our transmit time (will be echoed back)
  22. uint64_t receive; // zeroed in request
  23. uint64_t transmit; // zeroed in request
  24. } timing_packet_t;
  25. // Number of measurements to keep for stability
  26. #define TIMING_HISTORY_SIZE 8
  27. #define MIN_MEASUREMENTS 3
  28. #define TIMING_INTERVAL_MS 3000 // Send request every 3 seconds
  29. #define NTP_STACK_SIZE 3072
  30. // Timing state
  31. static struct {
  32. bool running;
  33. TaskHandle_t task_handle;
  34. int socket;
  35. struct sockaddr_in remote_addr;
  36. // Clock offset tracking
  37. bool locked;
  38. int64_t offset_ns; // Current best offset
  39. int64_t measurements[TIMING_HISTORY_SIZE];
  40. int64_t dispersions[TIMING_HISTORY_SIZE];
  41. int measurement_count;
  42. int measurement_index;
  43. } ntp = {0};
  44. // Convert local time (microseconds) to NTP timestamp format (for packet)
  45. static uint64_t local_us_to_ntp(int64_t local_us) {
  46. // NTP timestamp: upper 32 bits = seconds, lower 32 bits = fraction
  47. uint32_t secs = (uint32_t)(local_us / 1000000);
  48. uint64_t frac_us = local_us % 1000000;
  49. uint32_t frac = (uint32_t)((frac_us << 32) / 1000000);
  50. return ((uint64_t)secs << 32) | frac;
  51. }
  52. // Convert NTP timestamp from packet to nanoseconds
  53. static int64_t ntp_to_ns(uint32_t secs, uint32_t frac) {
  54. int64_t ns = (int64_t)secs * 1000000000LL;
  55. ns += ((int64_t)frac * 1000000000LL) >> 32;
  56. return ns;
  57. }
  58. // Process timing response and calculate offset
  59. static void process_timing_response(const uint8_t *packet, size_t len,
  60. int64_t arrival_ns) {
  61. if (len < sizeof(timing_packet_t)) {
  62. return;
  63. }
  64. // Extract timestamps from response
  65. // Origin: our original transmit time (echoed back)
  66. // Receive: when remote received our request
  67. // Transmit: when remote sent response
  68. // Offsets in packet (after 8-byte header)
  69. // Bytes 8-15: origin timestamp (our transmit time, echoed)
  70. // Bytes 16-23: receive timestamp (remote's receive time)
  71. // Bytes 24-31: transmit timestamp (remote's transmit time)
  72. uint32_t origin_secs = ntohl(*(uint32_t *)(packet + 8));
  73. uint32_t origin_frac = ntohl(*(uint32_t *)(packet + 12));
  74. uint32_t receive_secs = ntohl(*(uint32_t *)(packet + 16));
  75. uint32_t receive_frac = ntohl(*(uint32_t *)(packet + 20));
  76. uint32_t transmit_secs = ntohl(*(uint32_t *)(packet + 24));
  77. uint32_t transmit_frac = ntohl(*(uint32_t *)(packet + 28));
  78. int64_t departure_ns = ntp_to_ns(origin_secs, origin_frac);
  79. int64_t remote_receive_ns = ntp_to_ns(receive_secs, receive_frac);
  80. int64_t remote_transmit_ns = ntp_to_ns(transmit_secs, transmit_frac);
  81. // Calculate offset using NTP formula:
  82. // Round-trip time = (arrival - departure) - (transmit - receive)
  83. // Offset = ((receive - departure) + (transmit - arrival)) / 2
  84. // = remote_transmit + (return_time - remote_processing) / 2 - arrival
  85. int64_t round_trip_ns = arrival_ns - departure_ns;
  86. int64_t remote_processing_ns = remote_transmit_ns - remote_receive_ns;
  87. int64_t network_delay_ns = round_trip_ns - remote_processing_ns;
  88. // Offset = remote_transmit + network_delay/2 - arrival
  89. // This gives: remote_time = local_time + offset
  90. int64_t offset_ns = remote_transmit_ns + (network_delay_ns / 2) - arrival_ns;
  91. // Dispersion is the uncertainty (half round-trip time)
  92. int64_t dispersion_ns = network_delay_ns / 2;
  93. // Sanity check: reject if round-trip is negative or too large (>1 second)
  94. if (round_trip_ns < 0 || round_trip_ns > 1000000000LL) {
  95. ESP_LOGW(TAG, "Rejecting measurement: RTT=%lld ms",
  96. (long long)(round_trip_ns / 1000000));
  97. return;
  98. }
  99. // Store measurement
  100. int idx = ntp.measurement_index;
  101. ntp.measurements[idx] = offset_ns;
  102. ntp.dispersions[idx] = dispersion_ns;
  103. ntp.measurement_index = (idx + 1) % TIMING_HISTORY_SIZE;
  104. if (ntp.measurement_count < TIMING_HISTORY_SIZE) {
  105. ntp.measurement_count++;
  106. }
  107. // Find best measurement (lowest dispersion)
  108. int64_t best_offset = offset_ns;
  109. int64_t best_dispersion = dispersion_ns;
  110. for (int i = 0; i < ntp.measurement_count; i++) {
  111. if (ntp.dispersions[i] < best_dispersion) {
  112. best_dispersion = ntp.dispersions[i];
  113. best_offset = ntp.measurements[i];
  114. }
  115. }
  116. ntp.offset_ns = best_offset;
  117. // Consider locked after enough measurements
  118. if (!ntp.locked && ntp.measurement_count >= MIN_MEASUREMENTS) {
  119. ntp.locked = true;
  120. ESP_LOGI(TAG, "NTP timing locked: offset=%lld ms, dispersion=%lld us",
  121. (long long)(ntp.offset_ns / 1000000),
  122. (long long)(best_dispersion / 1000));
  123. }
  124. ESP_LOGD(TAG, "Timing: RTT=%lld us, offset=%lld ms",
  125. (long long)(round_trip_ns / 1000), (long long)(offset_ns / 1000000));
  126. }
  127. // Send timing request
  128. static void send_timing_request(void) {
  129. timing_packet_t req;
  130. memset(&req, 0, sizeof(req));
  131. req.leader = 0x80;
  132. req.type = TIMING_REQUEST;
  133. req.seqno = htons(7); // Fixed sequence number (like shairport-sync)
  134. // Set transmit field to our current time — the AirPlay source echoes
  135. // request bytes 24-31 (transmit) into response bytes 8-15 (origin).
  136. int64_t now_us = esp_timer_get_time();
  137. uint64_t ntp_time = local_us_to_ntp(now_us);
  138. // Split into network byte order (avoid unaligned access)
  139. uint8_t *tx_bytes = (uint8_t *)&req.transmit;
  140. uint32_t secs = htonl((uint32_t)(ntp_time >> 32));
  141. uint32_t frac = htonl((uint32_t)(ntp_time & 0xFFFFFFFF));
  142. memcpy(tx_bytes, &secs, 4);
  143. memcpy(tx_bytes + 4, &frac, 4);
  144. sendto(ntp.socket, &req, sizeof(req), 0, (struct sockaddr *)&ntp.remote_addr,
  145. sizeof(ntp.remote_addr));
  146. }
  147. // Timing task: sends requests and processes responses
  148. static void ntp_task(void *pvParameters) {
  149. uint8_t packet[64];
  150. struct sockaddr_in src_addr;
  151. socklen_t addr_len;
  152. // Initial delay before first request
  153. vTaskDelay(pdMS_TO_TICKS(300));
  154. TickType_t last_request = 0;
  155. while (ntp.running) {
  156. // Send timing request periodically
  157. TickType_t now = xTaskGetTickCount();
  158. if (now - last_request >= pdMS_TO_TICKS(TIMING_INTERVAL_MS)) {
  159. send_timing_request();
  160. last_request = now;
  161. }
  162. // Check for response (with short timeout)
  163. addr_len = sizeof(src_addr);
  164. ssize_t len = recvfrom(ntp.socket, packet, sizeof(packet), 0,
  165. (struct sockaddr *)&src_addr, &addr_len);
  166. if (len < 0) {
  167. if (errno == EAGAIN || errno == EWOULDBLOCK) {
  168. continue;
  169. }
  170. if (ntp.running) {
  171. ESP_LOGE(TAG, "recvfrom error: %d", errno);
  172. }
  173. break;
  174. }
  175. if (len >= 2) {
  176. int64_t arrival_ns = esp_timer_get_time() * 1000LL;
  177. uint8_t pkt_type = packet[1];
  178. if (pkt_type == TIMING_RESPONSE) {
  179. process_timing_response(packet, len, arrival_ns);
  180. }
  181. }
  182. }
  183. ntp.task_handle = NULL;
  184. vTaskDelete(NULL);
  185. }
  186. static bool ntp_wait_for_task_stopped(int timeout_ticks) {
  187. while (ntp.task_handle != NULL && timeout_ticks-- > 0) {
  188. vTaskDelay(pdMS_TO_TICKS(50));
  189. }
  190. return ntp.task_handle == NULL;
  191. }
  192. esp_err_t ntp_clock_start_client(uint32_t remote_ip, uint16_t remote_port) {
  193. if (ntp.running) {
  194. // If already running to same target, keep going
  195. if (ntp.remote_addr.sin_addr.s_addr == remote_ip &&
  196. ntohs(ntp.remote_addr.sin_port) == remote_port) {
  197. return ESP_OK;
  198. }
  199. // Stop existing and restart with new target
  200. ntp_clock_stop();
  201. if (!ntp_wait_for_task_stopped(20)) {
  202. ESP_LOGE(TAG, "Previous NTP task did not stop");
  203. return ESP_ERR_INVALID_STATE;
  204. }
  205. }
  206. if (ntp.task_handle != NULL) {
  207. ESP_LOGW(TAG, "NTP task still stopping, waiting");
  208. if (!ntp_wait_for_task_stopped(20)) {
  209. ESP_LOGE(TAG, "NTP task still active");
  210. return ESP_ERR_INVALID_STATE;
  211. }
  212. }
  213. ntp.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  214. if (ntp.socket < 0) {
  215. ESP_LOGE(TAG, "Failed to create socket: %d", errno);
  216. return ESP_FAIL;
  217. }
  218. // Set receive timeout (100ms for responsive checking)
  219. struct timeval tv = {.tv_sec = 0, .tv_usec = 100000};
  220. setsockopt(ntp.socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  221. // Setup remote address
  222. memset(&ntp.remote_addr, 0, sizeof(ntp.remote_addr));
  223. ntp.remote_addr.sin_family = AF_INET;
  224. ntp.remote_addr.sin_addr.s_addr = remote_ip;
  225. ntp.remote_addr.sin_port = htons(remote_port);
  226. // Reset state
  227. ntp.locked = false;
  228. ntp.offset_ns = 0;
  229. ntp.measurement_count = 0;
  230. ntp.measurement_index = 0;
  231. memset(ntp.measurements, 0, sizeof(ntp.measurements));
  232. memset(ntp.dispersions, 0, sizeof(ntp.dispersions));
  233. ntp.running = true;
  234. ntp.task_handle = NULL;
  235. BaseType_t task_ret = xTaskCreate(ntp_task, "ntp_clock", NTP_STACK_SIZE, NULL,
  236. 5, &ntp.task_handle);
  237. if (task_ret != pdPASS || ntp.task_handle == NULL) {
  238. ESP_LOGE(TAG, "Failed to create NTP task");
  239. close(ntp.socket);
  240. ntp.socket = -1;
  241. ntp.running = false;
  242. return ESP_FAIL;
  243. }
  244. uint8_t *ip = (uint8_t *)&remote_ip;
  245. ESP_LOGI(TAG, "NTP timing client started -> %d.%d.%d.%d:%d", ip[0], ip[1],
  246. ip[2], ip[3], remote_port);
  247. return ESP_OK;
  248. }
  249. void ntp_clock_stop(void) {
  250. if (!ntp.running) {
  251. return;
  252. }
  253. ntp.running = false;
  254. if (ntp.socket >= 0) {
  255. close(ntp.socket);
  256. ntp.socket = -1;
  257. }
  258. if (ntp.task_handle) {
  259. if (!ntp_wait_for_task_stopped(20)) {
  260. ESP_LOGW(TAG, "NTP task did not exit within timeout");
  261. }
  262. }
  263. ntp.locked = false;
  264. ntp.measurement_count = 0;
  265. ESP_LOGI(TAG, "NTP timing stopped");
  266. }
  267. bool ntp_clock_is_locked(void) {
  268. return ntp.locked;
  269. }
  270. int64_t ntp_clock_get_offset_ns(void) {
  271. return ntp.offset_ns;
  272. }