ptp_clock.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. #include <arpa/inet.h>
  2. #include <errno.h>
  3. #include <netinet/in.h>
  4. #include <string.h>
  5. #include <sys/socket.h>
  6. #include <sys/time.h>
  7. #include <unistd.h>
  8. #include "esp_log.h"
  9. #include "esp_timer.h"
  10. #include "freertos/FreeRTOS.h"
  11. #include "freertos/task.h"
  12. #include "ptp_clock.h"
  13. #include "spiram_task.h"
  14. static const char *TAG = "ptp_clock";
  15. // PTP multicast addresses and ports
  16. #define PTP_MULTICAST_ADDR "224.0.1.129"
  17. #define PTP_EVENT_PORT 319
  18. #define PTP_GENERAL_PORT 320
  19. // PTP message types
  20. #define PTP_MSG_SYNC 0x0
  21. #define PTP_MSG_DELAY_REQ 0x1
  22. #define PTP_MSG_FOLLOW_UP 0x8
  23. #define PTP_MSG_DELAY_RESP 0x9
  24. #define PTP_MSG_ANNOUNCE 0xB
  25. // PTP header size and timestamp offset
  26. #define PTP_HEADER_SIZE 34
  27. #define PTP_TIMESTAMP_OFFSET 34
  28. #define PTP_TIMESTAMP_SIZE 10
  29. // Synchronization parameters
  30. //
  31. // WiFi-side timestamping jitter on ESP32 is ~20–30 ms in practice, so a tight
  32. // 40 ms lock threshold can take 10+ seconds to satisfy. Loosen the lock
  33. // criteria to converge in <1 s while still rejecting genuine outliers via
  34. // the median filter:
  35. // • LOCK_THRESHOLD_NS: 50 ms — accept normal WiFi jitter
  36. // • OUTLIER_THRESHOLD_NS: 75 ms — keep the threshold strictly larger than
  37. // LOCK_THRESHOLD_NS so a borderline sample
  38. // isn't both kept and counted against lock
  39. // • MIN_SAMPLES_FOR_LOCK: 4 — ~500 ms at 8 Hz SYNC rate
  40. // • LOCK_STABLE_TIME_MS: 250 — confirm stability without long wait
  41. #define LOCK_THRESHOLD_NS 50000000LL // 50ms - tolerant of WiFi jitter
  42. #define MIN_SAMPLES_FOR_LOCK 4
  43. #define LOCK_STABLE_TIME_MS 250 // 250ms of stable readings to declare lock
  44. #define LOCK_TIMEOUT_MS 5000
  45. #define OUTLIER_THRESHOLD_NS 50000000LL // 50ms - reject samples beyond this
  46. // Asymmetric filter parameters (modeled after nqptp):
  47. // Network delays only ADD positive bias to the measured offset, so
  48. // offset_measured = true_offset - one_way_delay
  49. // The LARGEST measured offsets correspond to the SHORTEST delays and are
  50. // therefore the most accurate. We accept positive jitter (= shorter delay)
  51. // quickly and dampen negative jitter (= longer delay) heavily. This causes
  52. // the filter to converge to the minimum-delay offset, matching the behaviour
  53. // of nqptp used by shairport-sync and ensuring tight multi-room sync.
  54. #define SMOOTH_POS_STARTUP_DIV 1 // accept positive jitter fully at start
  55. #define SMOOTH_POS_STEADY_DIV 16 // later, apply 1/16 of positive jitter
  56. #define SMOOTH_NEG_DIV 256 // always apply only 1/256 of negative jitter
  57. #define SMOOTH_NEG_CLAMP_NS (-2500000LL) // clamp negative jitter at -2.5ms
  58. #define STARTUP_DURATION_MS 1000 // first second: aggressive positive tracking
  59. // Threshold above which we reset the PTP smoothing filter on resume.
  60. // E.g. at 50 ppm crystal accuracy, 30 s of pause accumulates ~1.5 ms of drift —
  61. // large enough to be audible in multi-room but well within the 50 ms outlier
  62. // window. Below this threshold the drift is negligible (<0.25 ms at 5 s).
  63. #define PTP_LONG_PAUSE_THRESHOLD_MS 30000
  64. // PTP state
  65. static struct {
  66. bool running;
  67. TaskHandle_t task_handle;
  68. spiram_task_mem_t task_mem;
  69. int event_socket;
  70. int general_socket;
  71. // Synchronization state
  72. bool locked;
  73. uint32_t lock_start_ms;
  74. uint32_t lock_candidate_start_ms;
  75. uint32_t last_sync_ms;
  76. int64_t filtered_offset_ns; // PTP_time = local_time + offset
  77. uint32_t sample_count;
  78. // Asymmetric smoothing state (replaces median ring buffer)
  79. int64_t previous_offset;
  80. uint32_t previous_offset_time_ms; // 0 = no previous sample yet
  81. uint32_t mastership_start_ms; // when continuous tracking began
  82. // Two-step sync tracking
  83. uint16_t last_sync_seq;
  84. int64_t last_sync_local_ns;
  85. bool awaiting_followup;
  86. // Statistics
  87. uint32_t sync_count;
  88. uint32_t followup_count;
  89. uint32_t announce_count;
  90. uint32_t rejected_master_count; // SYNC/FOLLOW_UP from a non-matching master
  91. uint32_t outlier_count; // samples rejected by 50ms threshold
  92. // Master clock filter (0 = accept any master)
  93. uint64_t expected_clock_id;
  94. } ptp = {0};
  95. // Parse 8-byte clockIdentity (big-endian) from PTP sourcePortIdentity
  96. // (header bytes 20-27).
  97. static uint64_t parse_ptp_clock_id(const uint8_t *data) {
  98. uint64_t id = 0;
  99. for (int i = 0; i < 8; i++) {
  100. id = (id << 8) | data[20 + i];
  101. }
  102. return id;
  103. }
  104. // Parse 48-bit seconds + 32-bit nanoseconds from PTP timestamp
  105. static uint64_t parse_ptp_timestamp_ns(const uint8_t *data) {
  106. // Seconds: 6 bytes big-endian
  107. uint64_t seconds = 0;
  108. for (int i = 0; i < 6; i++) {
  109. seconds = (seconds << 8) | data[i];
  110. }
  111. // Nanoseconds: 4 bytes big-endian
  112. uint32_t nanos = ((uint32_t)data[6] << 24) | ((uint32_t)data[7] << 16) |
  113. ((uint32_t)data[8] << 8) | (uint32_t)data[9];
  114. return seconds * 1000000000ULL + nanos;
  115. }
  116. // Get local time in nanoseconds (from esp_timer)
  117. static inline int64_t get_local_time_ns(void) {
  118. return (int64_t)esp_timer_get_time() * 1000LL;
  119. }
  120. // Update offset with new sample using asymmetric smoothing (nqptp-style).
  121. //
  122. // The key insight from nqptp: since we are a passive PTP listener (no
  123. // DELAY_REQ/DELAY_RESP), every measured offset contains a one-way network
  124. // delay bias: offset_measured = true_offset - delay.
  125. // LARGER offsets come from SHORTER delays and are MORE accurate.
  126. //
  127. // By accepting positive jitter (larger offset = shorter delay) quickly and
  128. // dampening negative jitter (smaller offset = longer delay) slowly, the
  129. // filter converges to the offset corresponding to the minimum network
  130. // delay — the best available approximation of the true clock offset.
  131. static void update_offset(int64_t new_offset_ns) {
  132. uint32_t now_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
  133. ptp.last_sync_ms = now_ms;
  134. ptp.sample_count++;
  135. int64_t smoothed_offset;
  136. if (ptp.previous_offset_time_ms == 0) {
  137. // First sample (or after reset): accept unconditionally
  138. smoothed_offset = new_offset_ns;
  139. ptp.mastership_start_ms = now_ms;
  140. } else {
  141. // Reject obvious outliers (more than 50ms from current estimate)
  142. int64_t diff = new_offset_ns - ptp.filtered_offset_ns;
  143. if (diff < 0) {
  144. diff = -diff;
  145. }
  146. if (diff > OUTLIER_THRESHOLD_NS) {
  147. ptp.outlier_count++;
  148. return;
  149. }
  150. int64_t jitter = new_offset_ns - ptp.previous_offset;
  151. uint32_t mastership_time_ms = now_ms - ptp.mastership_start_ms;
  152. if (jitter >= 0) {
  153. // Positive jitter: offset increased → shorter network delay → more
  154. // accurate. Accept quickly, especially during startup.
  155. if (mastership_time_ms < STARTUP_DURATION_MS) {
  156. smoothed_offset = ptp.previous_offset + jitter / SMOOTH_POS_STARTUP_DIV;
  157. } else {
  158. smoothed_offset = ptp.previous_offset + jitter / SMOOTH_POS_STEADY_DIV;
  159. }
  160. } else {
  161. // Negative jitter: offset decreased → longer network delay → less
  162. // reliable. Clamp and apply only a tiny fraction.
  163. int64_t clamped_jitter = jitter;
  164. if (clamped_jitter < SMOOTH_NEG_CLAMP_NS) {
  165. clamped_jitter = SMOOTH_NEG_CLAMP_NS;
  166. }
  167. smoothed_offset = ptp.previous_offset + clamped_jitter / SMOOTH_NEG_DIV;
  168. }
  169. }
  170. ptp.previous_offset = smoothed_offset;
  171. ptp.previous_offset_time_ms = now_ms;
  172. ptp.filtered_offset_ns = smoothed_offset;
  173. // Check lock status: once we have enough samples and the offset is stable,
  174. // declare lock. Use the deviation between the raw sample and the smoothed
  175. // value as a stability indicator.
  176. if (ptp.sample_count >= MIN_SAMPLES_FOR_LOCK) {
  177. int64_t dev = new_offset_ns - smoothed_offset;
  178. if (dev < 0) {
  179. dev = -dev;
  180. }
  181. if (dev < LOCK_THRESHOLD_NS) {
  182. if (!ptp.locked) {
  183. if (ptp.lock_candidate_start_ms == 0) {
  184. ptp.lock_candidate_start_ms = now_ms;
  185. }
  186. if ((now_ms - ptp.lock_candidate_start_ms) >= LOCK_STABLE_TIME_MS) {
  187. ptp.locked = true;
  188. ptp.lock_start_ms = now_ms;
  189. ptp.lock_candidate_start_ms = 0;
  190. ESP_LOGI(TAG,
  191. "LOCKED: offset=%+lldns dev=%lldns samples=%lu "
  192. "sync=%lu followup=%lu",
  193. (long long)ptp.filtered_offset_ns, (long long)dev,
  194. (unsigned long)ptp.sample_count,
  195. (unsigned long)ptp.sync_count,
  196. (unsigned long)ptp.followup_count);
  197. }
  198. }
  199. } else {
  200. ptp.lock_candidate_start_ms = 0;
  201. if (ptp.locked && dev > LOCK_THRESHOLD_NS * 4) {
  202. ptp.locked = false;
  203. ptp.lock_start_ms = 0;
  204. ESP_LOGW(TAG, "LOST LOCK: dev=%lldns (threshold=%lldns)",
  205. (long long)dev, (long long)(LOCK_THRESHOLD_NS * 4));
  206. }
  207. }
  208. }
  209. }
  210. // Process SYNC message (records receive time)
  211. static void process_sync(const uint8_t *data, size_t len, uint16_t seq) {
  212. ptp.sync_count++;
  213. ptp.last_sync_seq = seq;
  214. ptp.last_sync_local_ns = get_local_time_ns();
  215. ptp.awaiting_followup = true;
  216. // Check if this is a one-step sync (timestamp in SYNC itself)
  217. // One-step: flags bit 9 (twoStepFlag) is 0
  218. uint16_t flags = ((uint16_t)data[6] << 8) | data[7];
  219. bool two_step = (flags & 0x0200) != 0;
  220. if (!two_step && len >= PTP_HEADER_SIZE + PTP_TIMESTAMP_SIZE) {
  221. // One-step sync - timestamp is in the SYNC message
  222. uint64_t ptp_time_ns = parse_ptp_timestamp_ns(data + PTP_TIMESTAMP_OFFSET);
  223. // Apply correctionField for one-step sync as well
  224. if (len >= 16) {
  225. int64_t correction_field =
  226. ((int64_t)data[8] << 56) | ((int64_t)data[9] << 48) |
  227. ((int64_t)data[10] << 40) | ((int64_t)data[11] << 32) |
  228. ((int64_t)data[12] << 24) | ((int64_t)data[13] << 16) |
  229. ((int64_t)data[14] << 8) | (int64_t)data[15];
  230. correction_field /= 65536; // convert from 2^-16 ns to ns
  231. ptp_time_ns = (uint64_t)((int64_t)ptp_time_ns + correction_field);
  232. }
  233. int64_t offset = (int64_t)ptp_time_ns - ptp.last_sync_local_ns;
  234. update_offset(offset);
  235. ptp.awaiting_followup = false;
  236. }
  237. }
  238. // Process FOLLOW_UP message (contains precise timestamp for preceding SYNC)
  239. static void process_followup(const uint8_t *data, size_t len, uint16_t seq) {
  240. if (!ptp.awaiting_followup) {
  241. return;
  242. }
  243. // FOLLOW_UP should match the sequence of the last SYNC
  244. if (seq != ptp.last_sync_seq) {
  245. return;
  246. }
  247. ptp.followup_count++;
  248. ptp.awaiting_followup = false;
  249. if (len >= PTP_HEADER_SIZE + PTP_TIMESTAMP_SIZE) {
  250. uint64_t ptp_time_ns = parse_ptp_timestamp_ns(data + PTP_TIMESTAMP_OFFSET);
  251. // Apply correctionField (IEEE 1588 §11.4.4.2.1):
  252. // The correctionField accumulates residence time and path delay
  253. // corrections from PTP-aware network elements. It is a signed
  254. // 64-bit value in units of 2^-16 nanoseconds.
  255. if (len >= 16) {
  256. int64_t correction_field =
  257. ((int64_t)data[8] << 56) | ((int64_t)data[9] << 48) |
  258. ((int64_t)data[10] << 40) | ((int64_t)data[11] << 32) |
  259. ((int64_t)data[12] << 24) | ((int64_t)data[13] << 16) |
  260. ((int64_t)data[14] << 8) | (int64_t)data[15];
  261. correction_field /= 65536; // convert from 2^-16 ns to ns
  262. ptp_time_ns = (uint64_t)((int64_t)ptp_time_ns + correction_field);
  263. }
  264. // offset = PTP_time - local_time_at_sync_receipt
  265. int64_t offset = (int64_t)ptp_time_ns - ptp.last_sync_local_ns;
  266. update_offset(offset);
  267. }
  268. }
  269. // Process received PTP message
  270. static void process_ptp_message(const uint8_t *data, size_t len,
  271. bool is_event_port) {
  272. if (len < PTP_HEADER_SIZE) {
  273. return;
  274. }
  275. uint8_t msg_type = data[0] & 0x0F;
  276. uint16_t seq = ((uint16_t)data[30] << 8) | data[31];
  277. // If a master filter is set, reject messages from other clocks.
  278. // This applies only to messages that contribute to offset estimation
  279. // (SYNC / FOLLOW_UP); ANNOUNCE and others are ignored anyway.
  280. if (ptp.expected_clock_id != 0 &&
  281. (msg_type == PTP_MSG_SYNC || msg_type == PTP_MSG_FOLLOW_UP)) {
  282. uint64_t src_clock_id = parse_ptp_clock_id(data);
  283. if (src_clock_id != ptp.expected_clock_id) {
  284. ptp.rejected_master_count++;
  285. return;
  286. }
  287. }
  288. switch (msg_type) {
  289. case PTP_MSG_SYNC:
  290. if (is_event_port) {
  291. process_sync(data, len, seq);
  292. }
  293. break;
  294. case PTP_MSG_FOLLOW_UP:
  295. if (!is_event_port) {
  296. process_followup(data, len, seq);
  297. }
  298. break;
  299. case PTP_MSG_ANNOUNCE:
  300. ptp.announce_count++;
  301. // Could track master identity here if needed
  302. break;
  303. default:
  304. break;
  305. }
  306. }
  307. // Create and bind multicast socket
  308. static int create_ptp_socket(uint16_t port) {
  309. int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  310. if (sock < 0) {
  311. ESP_LOGE(TAG, "Failed to create socket: %d", errno);
  312. return -1;
  313. }
  314. // Allow address reuse
  315. int opt = 1;
  316. setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
  317. // Bind to port
  318. struct sockaddr_in addr = {0};
  319. addr.sin_family = AF_INET;
  320. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  321. addr.sin_port = htons(port);
  322. if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
  323. ESP_LOGE(TAG, "Failed to bind to port %d: %d", port, errno);
  324. close(sock);
  325. return -1;
  326. }
  327. // Join multicast group
  328. struct ip_mreq mreq = {0};
  329. mreq.imr_multiaddr.s_addr = inet_addr(PTP_MULTICAST_ADDR);
  330. mreq.imr_interface.s_addr = htonl(INADDR_ANY);
  331. if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) <
  332. 0) {
  333. ESP_LOGE(TAG, "Failed to join multicast group: %d", errno);
  334. close(sock);
  335. return -1;
  336. }
  337. // Set receive timeout
  338. struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
  339. setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
  340. return sock;
  341. }
  342. // PTP task - listens for messages on both ports
  343. static void ptp_task(void *pvParameters) {
  344. uint8_t buffer[256];
  345. while (ptp.running) {
  346. fd_set read_fds;
  347. FD_ZERO(&read_fds);
  348. int max_fd = -1;
  349. if (ptp.event_socket >= 0) {
  350. FD_SET(ptp.event_socket, &read_fds);
  351. if (ptp.event_socket > max_fd) {
  352. max_fd = ptp.event_socket;
  353. }
  354. }
  355. if (ptp.general_socket >= 0) {
  356. FD_SET(ptp.general_socket, &read_fds);
  357. if (ptp.general_socket > max_fd) {
  358. max_fd = ptp.general_socket;
  359. }
  360. }
  361. if (max_fd < 0) {
  362. vTaskDelay(pdMS_TO_TICKS(100));
  363. continue;
  364. }
  365. struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
  366. int ret = select(max_fd + 1, &read_fds, NULL, NULL, &tv);
  367. if (ret < 0) {
  368. if (!ptp.running) {
  369. break; // Sockets closed during shutdown
  370. }
  371. if (errno != EINTR) {
  372. ESP_LOGE(TAG, "select error: %d", errno);
  373. }
  374. continue;
  375. }
  376. if (ret == 0) {
  377. // Timeout - check if we lost lock due to no messages
  378. } else {
  379. // Check event port (SYNC messages)
  380. if (ptp.event_socket >= 0 && FD_ISSET(ptp.event_socket, &read_fds)) {
  381. ssize_t len = recv(ptp.event_socket, buffer, sizeof(buffer), 0);
  382. if (len > 0) {
  383. process_ptp_message(buffer, (size_t)len, true);
  384. }
  385. }
  386. // Check general port (FOLLOW_UP messages)
  387. if (ptp.general_socket >= 0 && FD_ISSET(ptp.general_socket, &read_fds)) {
  388. ssize_t len = recv(ptp.general_socket, buffer, sizeof(buffer), 0);
  389. if (len > 0) {
  390. process_ptp_message(buffer, (size_t)len, false);
  391. }
  392. }
  393. }
  394. }
  395. // Cleanup
  396. if (ptp.event_socket >= 0) {
  397. close(ptp.event_socket);
  398. ptp.event_socket = -1;
  399. }
  400. if (ptp.general_socket >= 0) {
  401. close(ptp.general_socket);
  402. ptp.general_socket = -1;
  403. }
  404. ptp.task_handle = NULL;
  405. vTaskDelete(NULL);
  406. }
  407. esp_err_t ptp_clock_init(void) {
  408. if (ptp.running) {
  409. return ESP_ERR_INVALID_STATE;
  410. }
  411. memset(&ptp, 0, sizeof(ptp));
  412. ptp.event_socket = -1;
  413. ptp.general_socket = -1;
  414. // Create sockets
  415. ptp.event_socket = create_ptp_socket(PTP_EVENT_PORT);
  416. if (ptp.event_socket < 0) {
  417. return ESP_FAIL;
  418. }
  419. ptp.general_socket = create_ptp_socket(PTP_GENERAL_PORT);
  420. if (ptp.general_socket < 0) {
  421. close(ptp.event_socket);
  422. ptp.event_socket = -1;
  423. return ESP_FAIL;
  424. }
  425. // Start task
  426. ptp.running = true;
  427. BaseType_t ret = task_create_spiram(ptp_task, "ptp_clock", 4096, NULL, 6,
  428. &ptp.task_handle, &ptp.task_mem);
  429. if (ret != pdPASS) {
  430. ESP_LOGE(TAG, "Failed to create PTP task");
  431. close(ptp.event_socket);
  432. close(ptp.general_socket);
  433. ptp.event_socket = -1;
  434. ptp.general_socket = -1;
  435. ptp.running = false;
  436. return ESP_FAIL;
  437. }
  438. return ESP_OK;
  439. }
  440. void ptp_clock_stop(void) {
  441. if (!ptp.running) {
  442. return;
  443. }
  444. ptp.running = false;
  445. // Close sockets to unblock select
  446. if (ptp.event_socket >= 0) {
  447. close(ptp.event_socket);
  448. ptp.event_socket = -1;
  449. }
  450. if (ptp.general_socket >= 0) {
  451. close(ptp.general_socket);
  452. ptp.general_socket = -1;
  453. }
  454. // Wait for task to exit (task sets task_handle = NULL before vTaskDelete)
  455. for (int i = 0; i < 20 && ptp.task_handle != NULL; i++) {
  456. vTaskDelay(pdMS_TO_TICKS(50));
  457. }
  458. if (ptp.task_handle != NULL) {
  459. ESP_LOGW(TAG, "PTP task did not exit in time");
  460. }
  461. task_free_spiram(&ptp.task_mem);
  462. }
  463. void ptp_clock_clear(void) {
  464. ptp.locked = false;
  465. ptp.lock_start_ms = 0;
  466. ptp.lock_candidate_start_ms = 0;
  467. ptp.last_sync_ms = 0;
  468. ptp.filtered_offset_ns = 0;
  469. ptp.sample_count = 0;
  470. ptp.previous_offset = 0;
  471. ptp.previous_offset_time_ms = 0;
  472. ptp.mastership_start_ms = 0;
  473. ptp.last_sync_seq = 0;
  474. ptp.last_sync_local_ns = 0;
  475. ptp.awaiting_followup = false;
  476. ptp.sync_count = 0;
  477. ptp.followup_count = 0;
  478. // Drop the master filter so the next session can lock to whatever master
  479. // its anchor packet names (which may differ from the previous session).
  480. ptp.expected_clock_id = 0;
  481. }
  482. void ptp_clock_notify_resume(uint32_t pause_duration_ms) {
  483. if (pause_duration_ms < PTP_LONG_PAUSE_THRESHOLD_MS) {
  484. return; // drift too small to matter
  485. }
  486. // Reset the "previous sample" pointer so the very next PTP FOLLOW_UP is
  487. // accepted unconditionally (the first-sample path in update_offset()).
  488. // This mirrors what nqptp does on its "B" (begin) signal:
  489. // "when the clock goes from inactive to active, NQPTP resets clock
  490. // smoothing to the new offset" -- nqptp-shm-structures.h
  491. //
  492. // We keep filtered_offset_ns so that audio_timing can continue to use the
  493. // last-known offset until the first new sample arrives (~125 ms away).
  494. // We also reset mastership_start_ms so the STARTUP_DURATION_MS aggressive-
  495. // positive window kicks in again for a faster upward catch-up.
  496. ptp.previous_offset_time_ms = 0;
  497. ptp.mastership_start_ms = 0;
  498. ESP_LOGI(TAG,
  499. "notify_resume: pause=%lu ms, resetting PTP smoothing "
  500. "(est. drift %.1f ms @ 50ppm)",
  501. (unsigned long)pause_duration_ms,
  502. (float)pause_duration_ms * 50.0f / 1000000.0f);
  503. }
  504. bool ptp_clock_is_locked(void) {
  505. if (ptp.locked && ptp.last_sync_ms > 0) {
  506. uint32_t now_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
  507. if ((now_ms - ptp.last_sync_ms) > LOCK_TIMEOUT_MS) {
  508. ptp.locked = false;
  509. ptp.lock_start_ms = 0;
  510. ptp.lock_candidate_start_ms = 0;
  511. }
  512. }
  513. return ptp.locked;
  514. }
  515. uint64_t ptp_clock_get_time_ns(void) {
  516. int64_t local_ns = get_local_time_ns();
  517. return (uint64_t)(local_ns + ptp.filtered_offset_ns);
  518. }
  519. int64_t ptp_clock_get_offset_ns(void) {
  520. return ptp.filtered_offset_ns;
  521. }
  522. void ptp_clock_set_master_clock_id(uint64_t clock_id) {
  523. if (clock_id == ptp.expected_clock_id) {
  524. return;
  525. }
  526. ESP_LOGI(TAG, "PTP master clock_id %s: %016llx", clock_id ? "set" : "cleared",
  527. (unsigned long long)clock_id);
  528. ptp.expected_clock_id = clock_id;
  529. // Drop accumulated samples / lock state — they may have come from a
  530. // different (wrong) master.
  531. ptp.locked = false;
  532. ptp.lock_start_ms = 0;
  533. ptp.lock_candidate_start_ms = 0;
  534. ptp.filtered_offset_ns = 0;
  535. ptp.sample_count = 0;
  536. ptp.previous_offset = 0;
  537. ptp.previous_offset_time_ms = 0;
  538. ptp.awaiting_followup = false;
  539. }
  540. uint64_t ptp_clock_get_master_clock_id(void) {
  541. return ptp.expected_clock_id;
  542. }
  543. void ptp_clock_get_stats(ptp_stats_t *stats) {
  544. stats->sync_count = ptp.sync_count;
  545. stats->followup_count = ptp.followup_count;
  546. stats->last_offset_ns = ptp.previous_offset;
  547. stats->filtered_offset_ns = ptp.filtered_offset_ns;
  548. if (ptp.locked && ptp.lock_start_ms > 0) {
  549. uint32_t now_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
  550. stats->lock_time_ms = now_ms - ptp.lock_start_ms;
  551. } else {
  552. stats->lock_time_ms = 0;
  553. }
  554. }