diff --git a/components/aes67_sdp_sap/aes67_sdp_sap.c b/components/aes67_sdp_sap/aes67_sdp_sap.c index 6e34f6b..639b5de 100644 --- a/components/aes67_sdp_sap/aes67_sdp_sap.c +++ b/components/aes67_sdp_sap/aes67_sdp_sap.c @@ -164,6 +164,26 @@ static bool sap_wanted(uint8_t *ttl) return on; } +// When the PTP GM changes, the SDP's ts-refclk changes: bump the session version (RFC 4566 o=) +// so receivers see a new description. Runs whether or not SAP is on (manual SDP users too). +static void check_gm_change(void) +{ + static char last[24]; + char gm[24]; + if (!aes67_ptp_gm_id(gm) || strcmp(gm, last) == 0) { + return; + } + if (last[0]) { + cJSON *a = cfg_get("aes67"); + double ver = cJSON_GetObjectItemCaseSensitive(a, "session_ver")->valuedouble + 1; + cJSON_Delete(a); + // Not applied: re-applying the aes67 group would restart the stream. + esp_err_t err = cfg_set_number("aes67", "session_ver", ver, false); + ESP_LOGI(TAG, "PTP TimeTransmitter %s -> %s: session_ver %.0f (%s)", last, gm, ver, esp_err_to_name(err)); + } + strcpy(last, gm); +} + static void sap_task(void *arg) { static char sdp[SDP_MAX]; @@ -172,6 +192,7 @@ static void sap_task(void *arg) while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); + check_gm_change(); esp_netif_ip_info_t ip; esp_netif_t *netif = aes67_net_netif(); bool have_ip = netif && esp_netif_get_ip_info(netif, &ip) == ESP_OK && ip.ip.addr; diff --git a/components/aes67_web/cfg.c b/components/aes67_web/cfg.c index 143d204..c42d22f 100644 --- a/components/aes67_web/cfg.c +++ b/components/aes67_web/cfg.c @@ -232,6 +232,37 @@ cJSON *cfg_get(const char *group) return copy; } +esp_err_t cfg_set_number(const char *group, const char *key, double value, bool apply) +{ + if (!s_lock) { + return ESP_ERR_INVALID_STATE; + } + xSemaphoreTake(s_lock, portMAX_DELAY); + cfg_group_t *g = find(group); + cJSON *cur = g ? cJSON_GetObjectItemCaseSensitive(g->values, key) : NULL; + esp_err_t err = ESP_OK; + if (!cur || !cJSON_IsNumber(cur)) { + err = ESP_ERR_NOT_FOUND; + } else { + cJSON_SetNumberValue(cur, value); + nvs_handle_t h; + if (s_nvs_ok && (err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h)) == ESP_OK) { + err = store(h, g); + if (err == ESP_OK) { + err = nvs_commit(h); + } + nvs_close(h); + } + } + cJSON *copy = err == ESP_OK && apply && g->apply ? cJSON_Duplicate(g->values, true) : NULL; + xSemaphoreGive(s_lock); + if (copy) { + g->apply(copy); + cJSON_Delete(copy); + } + return err; +} + /* ----- HTTP ----- */ static esp_err_t config_get(httpd_req_t *req) diff --git a/components/aes67_web/include/aes67_cfg.h b/components/aes67_web/include/aes67_cfg.h index 6f464ea..5adfac0 100644 --- a/components/aes67_web/include/aes67_cfg.h +++ b/components/aes67_web/include/aes67_cfg.h @@ -20,6 +20,9 @@ esp_err_t cfg_register(const char *group, const char *defaults_json, cfg_validate_cb_t validate, cfg_apply_cb_t apply); // Copy of a group's current values; caller frees with cJSON_Delete. NULL if not registered. cJSON *cfg_get(const char *group); +// Firmware-side change of one number (e.g. aes67.session_ver): stored like a POST, no validation. +// apply = false skips the group's apply callback (e.g. to avoid restarting the stream). +esp_err_t cfg_set_number(const char *group, const char *key, double value, bool apply); // Validation helpers for validate callbacks. Each returns false and fills err on failure. bool cfg_check_num(const cJSON *g, const char *key, double min, double max, char *err, size_t n); diff --git a/docs/aes67-core-base.md b/docs/aes67-core-base.md index 87142be..1e19629 100644 --- a/docs/aes67-core-base.md +++ b/docs/aes67-core-base.md @@ -89,7 +89,7 @@ When this device is the GM, offset/frequency/delay show "–". - SDP: v, o (aes67 IP, session_id, session_ver), s, c=mcast/ttl, t=0 0, a=clock-domain:PTPv2 , m=audio port RTP/AVP pt, a=rtpmap, a=recvonly, a=ptime, a=ts-refclk:ptp=IEEE1588-2008::, a=mediaclk:direct. - SAP to 239.255.255.255:9875 every 30 s, plus immediately on change; deletion packet on disable or shutdown. - Implemented (aes67_sdp_sap): SAPv1, payload type `application/sdp`, msg id hash = 16-bit hash of the SDP, TTL = aes67.ttl. The SDP is checked once a second; on a change the old hash is deleted before the new one is announced. Nothing is announced until a PTP GM is known (else ts-refclk would be all zeros). Shutdown deletion via `esp_register_shutdown_handler` (covers reboot and OTA). - - Not done yet: bumping session_ver on a GM change (needs a firmware-side config write; relevant once the device can become GM, step 6). A GM change already re-announces immediately with the new ts-refclk. + - GM change: aes67.session_ver is bumped (stored via `cfg_set_number`, without re-applying the aes67 group so the stream keeps running) and SAP re-announces within 1 s. The first GM after boot is not counted as a change. - Media 2 (ST 2022-7 redundancy): not possible on single-port boards like the ESP32-P4-ETH. Keep the schema open for a `mcast2`/`port2` pair on dual-NIC hardware (Riedel applies one PTP config to both Media 1 and Media 2). ## AES67 RX (future, for receiver projects) — Riedel Director 4-wire input defaults