Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2dc7552c2 | |||
| f6dfa80b19 | |||
| 2dd8f60cd6 | |||
| 2522901918 | |||
| 8b3bd39857 | |||
| f3779696d5 |
@@ -47,7 +47,11 @@ Repo: https://gitea.apointless.space/bsncubed/aes67-ESP32-P4
|
|||||||
- [x] 2b. Firmware update: /api/ota upload + rollback self-test. Test: update to a new build, then deliberately flash a build that fails its self-test and confirm it rolls back. After this, flash over the network; keep USB for recovery.
|
- [x] 2b. Firmware update: /api/ota upload + rollback self-test. Test: update to a new build, then deliberately flash a build that fails its self-test and confirm it rolls back. After this, flash over the network; keep USB for recovery.
|
||||||
- [x] 3. PTP TimeReceiver: lock to an existing GM (Riedel), fill `status.ptp`. Confirm EMAC hardware timestamps work. First check whether the installed ESP-IDF has a PTP example/component for the P4 before writing one.
|
- [x] 3. PTP TimeReceiver: lock to an existing GM (Riedel), fill `status.ptp`. Confirm EMAC hardware timestamps work. First check whether the installed ESP-IDF has a PTP example/component for the P4 before writing one.
|
||||||
- [ ] 4. AES67 TX with a 1 kHz test tone, PTP-paced; /stream.sdp. Verify: import SDP on a Riedel Artist 4-wire AES67 port, and check packets/timestamps in Wireshark.
|
- [ ] 4. AES67 TX with a 1 kHz test tone, PTP-paced; /stream.sdp. Verify: import SDP on a Riedel Artist 4-wire AES67 port, and check packets/timestamps in Wireshark.
|
||||||
- [ ] 5. SAP discovery, then syslog, then health/temperatures, then VLAN split (one at a time).
|
Done and checked with a receiver script (all ptimes, L16/L24, tone phase-locked to PTP); still open: the Riedel import and a Wireshark capture.
|
||||||
|
- [x] 5. SAP discovery, then syslog, then health/temperatures (one at a time). VLAN split moved to phase 2.
|
||||||
- [ ] 6. PTP TimeTransmitter: BMCA roles (auto/master), hybrid mode.
|
- [ ] 6. PTP TimeTransmitter: BMCA roles (auto/master), hybrid mode.
|
||||||
- [ ] 7. Sources: HLS player, then cspot (Spotify Connect), then failover + /api/player.
|
- [ ] 7. Sources: HLS player, then cspot (Spotify Connect), then failover + /api/player.
|
||||||
- [ ] 8. Mono sum, gain, polish.
|
- [ ] 8. Mono sum, gain, polish.
|
||||||
|
|
||||||
|
## Phase 2 (parked)
|
||||||
|
- [ ] VLAN split: AES67 untagged + internet tagged (inet.vlan_id/pcp), per aes67-core-base.md "Network". Needs a tagged VLAN with DHCP on the switch port. Config group `inet` and the UI fields exist already; nothing is applied yet.
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
idf_component_register(SRCS "aes67_health.c"
|
idf_component_register(SRCS "aes67_health.c"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES aes67_web esp_app_format esp_timer heap)
|
PRIV_REQUIRES aes67_web esp_app_format esp_driver_tsens esp_timer heap)
|
||||||
|
|||||||
@@ -1,9 +1,98 @@
|
|||||||
#include "aes67_health.h"
|
#include "aes67_health.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "aes67_web.h"
|
#include "aes67_web.h"
|
||||||
|
#include "driver/temperature_sensor.h"
|
||||||
#include "esp_app_desc.h"
|
#include "esp_app_desc.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#define MAX_SENSORS 8
|
||||||
|
#define SAMPLE_MS 2000
|
||||||
|
#define HYSTERESIS_C 5.0f
|
||||||
|
#define SOC_WARN_C 85.0f
|
||||||
|
|
||||||
|
static const char *TAG = "health";
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[16];
|
||||||
|
health_temp_read_cb_t read;
|
||||||
|
float warn_c;
|
||||||
|
bool valid, hot;
|
||||||
|
float c, min_c, max_c;
|
||||||
|
} sensor_t;
|
||||||
|
|
||||||
|
static sensor_t s_sensors[MAX_SENSORS];
|
||||||
|
static int s_n;
|
||||||
|
static SemaphoreHandle_t s_lock;
|
||||||
|
static temperature_sensor_handle_t s_tsens;
|
||||||
|
|
||||||
|
esp_err_t health_temp_register(const char *name, health_temp_read_cb_t read, float warn_c)
|
||||||
|
{
|
||||||
|
if (!s_lock || s_n >= MAX_SENSORS) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||||
|
sensor_t *t = &s_sensors[s_n++];
|
||||||
|
strlcpy(t->name, name, sizeof(t->name));
|
||||||
|
t->read = read;
|
||||||
|
t->warn_c = warn_c;
|
||||||
|
xSemaphoreGive(s_lock);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On-die sensor: measures the die, not ambient; expect well above room temperature.
|
||||||
|
static esp_err_t soc_read(float *c)
|
||||||
|
{
|
||||||
|
return temperature_sensor_get_celsius(s_tsens, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void health_task(void *arg)
|
||||||
|
{
|
||||||
|
while (1) {
|
||||||
|
for (int i = 0; i < s_n; i++) {
|
||||||
|
float c;
|
||||||
|
if (s_sensors[i].read(&c) != ESP_OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||||
|
sensor_t *t = &s_sensors[i];
|
||||||
|
t->c = c;
|
||||||
|
t->min_c = t->valid && t->min_c < c ? t->min_c : c;
|
||||||
|
t->max_c = t->valid && t->max_c > c ? t->max_c : c;
|
||||||
|
t->valid = true;
|
||||||
|
bool was_hot = t->hot;
|
||||||
|
if (t->warn_c > 0) {
|
||||||
|
if (!t->hot && c >= t->warn_c) {
|
||||||
|
t->hot = true;
|
||||||
|
} else if (t->hot && c < t->warn_c - HYSTERESIS_C) {
|
||||||
|
t->hot = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(s_lock);
|
||||||
|
// Logged outside the lock; goes to syslog like any warning.
|
||||||
|
if (t->hot != was_hot) {
|
||||||
|
if (t->hot) {
|
||||||
|
ESP_LOGW(TAG, "temperature %s %.1f °C: at or above %.0f °C", t->name, c, t->warn_c);
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "temperature %s %.1f °C: back below %.0f °C", t->name, c,
|
||||||
|
t->warn_c - HYSTERESIS_C);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(SAMPLE_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double round1(float v)
|
||||||
|
{
|
||||||
|
return (double)(int)(v * 10.0f + (v < 0 ? -0.5f : 0.5f)) / 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
static void health_status(cJSON *st)
|
static void health_status(cJSON *st)
|
||||||
{
|
{
|
||||||
@@ -11,9 +100,46 @@ static void health_status(cJSON *st)
|
|||||||
cJSON_AddNumberToObject(st, "uptime_s", (double)(esp_timer_get_time() / 1000000));
|
cJSON_AddNumberToObject(st, "uptime_s", (double)(esp_timer_get_time() / 1000000));
|
||||||
cJSON_AddNumberToObject(st, "heap_free", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
cJSON_AddNumberToObject(st, "heap_free", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||||
cJSON_AddNumberToObject(st, "psram_free", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
cJSON_AddNumberToObject(st, "psram_free", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||||
|
|
||||||
|
cJSON *temps = cJSON_AddArrayToObject(st, "temps");
|
||||||
|
xSemaphoreTake(s_lock, portMAX_DELAY);
|
||||||
|
for (int i = 0; i < s_n; i++) {
|
||||||
|
const sensor_t *t = &s_sensors[i];
|
||||||
|
if (!t->valid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cJSON *o = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(o, "name", t->name);
|
||||||
|
cJSON_AddNumberToObject(o, "c", round1(t->c));
|
||||||
|
cJSON_AddNumberToObject(o, "min_c", round1(t->min_c));
|
||||||
|
cJSON_AddNumberToObject(o, "max_c", round1(t->max_c));
|
||||||
|
if (t->warn_c > 0) {
|
||||||
|
cJSON_AddNumberToObject(o, "warn_c", t->warn_c);
|
||||||
|
}
|
||||||
|
cJSON_AddItemToArray(temps, o);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(s_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t aes67_health_init(void)
|
esp_err_t aes67_health_init(void)
|
||||||
{
|
{
|
||||||
|
s_lock = xSemaphoreCreateMutex();
|
||||||
|
if (!s_lock) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
// 20..100 °C (+-2 °C): the die runs well above ambient and the warning level must be in range.
|
||||||
|
temperature_sensor_config_t cfg = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 100);
|
||||||
|
esp_err_t err = temperature_sensor_install(&cfg, &s_tsens);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = temperature_sensor_enable(s_tsens);
|
||||||
|
}
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
health_temp_register("SoC", soc_read, SOC_WARN_C);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "on-die temperature sensor: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
if (xTaskCreate(health_task, "health", 3072, NULL, 2, NULL) != pdPASS) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
return status_register(health_status);
|
return status_register(health_status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,11 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
// Registers the health fields (fw, uptime_s, heap_free, psram_free) in /api/status.
|
// Read one temperature in °C. Return ESP_OK on success.
|
||||||
|
typedef esp_err_t (*health_temp_read_cb_t)(float *celsius);
|
||||||
|
|
||||||
|
// Registers the health fields (fw, uptime_s, heap_free, psram_free, temps) in /api/status,
|
||||||
|
// adds the on-die "SoC" sensor and starts the 2 s sampling task.
|
||||||
esp_err_t aes67_health_init(void);
|
esp_err_t aes67_health_init(void);
|
||||||
|
// Add a temperature sensor (e.g. an external I2C one from the board). warn_c <= 0: no warning.
|
||||||
|
esp_err_t health_temp_register(const char *name, health_temp_read_cb_t read, float warn_c);
|
||||||
|
|||||||
@@ -268,6 +268,9 @@ static void servo(int64_t offset)
|
|||||||
ESP_LOGI(TAG, "clock stepped by %+lld ns (%s), frequency %+.3f ppm", -offset, esp_err_to_name(err),
|
ESP_LOGI(TAG, "clock stepped by %+lld ns (%s), frequency %+.3f ppm", -offset, esp_err_to_name(err),
|
||||||
s.freq_ppb / 1000);
|
s.freq_ppb / 1000);
|
||||||
s.stepped = true;
|
s.stepped = true;
|
||||||
|
s.offset_ns = 0; // the pre-step offset is history: not for status or the summary
|
||||||
|
s.sum_max_ns = 0;
|
||||||
|
s.sum_start_us = 0;
|
||||||
s.prev_t2 = 0; // rate across the step is meaningless
|
s.prev_t2 = 0; // rate across the step is meaningless
|
||||||
s.bad = 0;
|
s.bad = 0;
|
||||||
set_locked(false);
|
set_locked(false);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
idf_component_register(SRCS "aes67_sdp_sap.c"
|
idf_component_register(SRCS "aes67_sdp_sap.c"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES aes67_net aes67_ptp aes67_web esp_netif)
|
PRIV_REQUIRES aes67_net aes67_ptp aes67_web esp_netif esp_timer lwip)
|
||||||
|
|||||||
@@ -7,9 +7,21 @@
|
|||||||
#include "aes67_net.h"
|
#include "aes67_net.h"
|
||||||
#include "aes67_ptp.h"
|
#include "aes67_ptp.h"
|
||||||
#include "aes67_web.h"
|
#include "aes67_web.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
#define SDP_MAX 1024
|
#define SDP_MAX 1024
|
||||||
|
#define SAP_ADDR "239.255.255.255"
|
||||||
|
#define SAP_PORT 9875
|
||||||
|
#define SAP_INTERVAL_US (30 * 1000000LL)
|
||||||
|
#define SAP_MIME "application/sdp"
|
||||||
|
|
||||||
|
static const char *TAG = "sap";
|
||||||
|
|
||||||
// Same lines, in the same order, as the SDP preview in web/index.html.
|
// Same lines, in the same order, as the SDP preview in web/index.html.
|
||||||
size_t aes67_sdp_build(char *buf, size_t size)
|
size_t aes67_sdp_build(char *buf, size_t size)
|
||||||
@@ -82,8 +94,140 @@ static esp_err_t sdp_get(httpd_req_t *req)
|
|||||||
return httpd_resp_send(req, buf, len);
|
return httpd_resp_send(req, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----- SAP (RFC 2974) ----- */
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int fd;
|
||||||
|
uint8_t ttl;
|
||||||
|
uint32_t origin; // IPv4 of the announcement (network order)
|
||||||
|
char sdp[SDP_MAX]; // what is currently announced ("" = nothing)
|
||||||
|
size_t len;
|
||||||
|
uint16_t hash;
|
||||||
|
} s_sap = { .fd = -1 };
|
||||||
|
|
||||||
|
static uint16_t sdp_hash(const char *sdp, size_t len)
|
||||||
|
{
|
||||||
|
uint32_t h = 2166136261u; // FNV-1a folded to 16 bits; 0 is not allowed
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
h = (h ^ (uint8_t)sdp[i]) * 16777619u;
|
||||||
|
}
|
||||||
|
uint16_t v = (uint16_t)(h ^ (h >> 16));
|
||||||
|
return v ? v : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sap_send(bool deletion)
|
||||||
|
{
|
||||||
|
static uint8_t pkt[8 + sizeof(SAP_MIME) + SDP_MAX];
|
||||||
|
pkt[0] = deletion ? 0x24 : 0x20; // V=1, IPv4, announce / delete (T bit)
|
||||||
|
pkt[1] = 0; // no authentication
|
||||||
|
pkt[2] = s_sap.hash >> 8;
|
||||||
|
pkt[3] = s_sap.hash & 0xff;
|
||||||
|
memcpy(pkt + 4, &s_sap.origin, 4);
|
||||||
|
memcpy(pkt + 8, SAP_MIME, sizeof(SAP_MIME)); // includes the terminating NUL
|
||||||
|
size_t n = 8 + sizeof(SAP_MIME);
|
||||||
|
memcpy(pkt + n, s_sap.sdp, s_sap.len);
|
||||||
|
n += s_sap.len;
|
||||||
|
|
||||||
|
struct sockaddr_in dst = { .sin_family = AF_INET, .sin_port = htons(SAP_PORT) };
|
||||||
|
inet_aton(SAP_ADDR, &dst.sin_addr);
|
||||||
|
if (sendto(s_sap.fd, pkt, n, 0, (struct sockaddr *)&dst, sizeof(dst)) < 0) {
|
||||||
|
ESP_LOGW(TAG, "send failed (errno %d)", errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sap_delete(const char *why)
|
||||||
|
{
|
||||||
|
if (s_sap.len && s_sap.fd >= 0) {
|
||||||
|
sap_send(true);
|
||||||
|
ESP_LOGI(TAG, "deleted announcement %04x (%s)", s_sap.hash, why);
|
||||||
|
}
|
||||||
|
s_sap.len = 0;
|
||||||
|
s_sap.sdp[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs from esp_restart() (reboot, OTA): withdraw the session so receivers drop it now.
|
||||||
|
static void sap_shutdown(void)
|
||||||
|
{
|
||||||
|
if (s_sap.len) {
|
||||||
|
sap_delete("shutdown");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20)); // let lwIP put it on the wire
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sap_wanted(uint8_t *ttl)
|
||||||
|
{
|
||||||
|
cJSON *a = cfg_get("aes67");
|
||||||
|
bool on = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(a, "enabled")) &&
|
||||||
|
strcmp(cJSON_GetObjectItemCaseSensitive(a, "discovery")->valuestring, "sap") == 0;
|
||||||
|
*ttl = (uint8_t)cJSON_GetObjectItemCaseSensitive(a, "ttl")->valuedouble;
|
||||||
|
cJSON_Delete(a);
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sap_task(void *arg)
|
||||||
|
{
|
||||||
|
static char sdp[SDP_MAX];
|
||||||
|
int64_t last_us = 0;
|
||||||
|
uint8_t ttl_set = 0;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
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;
|
||||||
|
uint8_t ttl;
|
||||||
|
char gm[24];
|
||||||
|
if (!sap_wanted(&ttl) || !have_ip) {
|
||||||
|
sap_delete(have_ip ? "discovery off or stream disabled" : "no IP");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!aes67_ptp_gm_id(gm)) {
|
||||||
|
// Without a GM the SDP's ts-refclk would be all zeros: don't advertise that.
|
||||||
|
sap_delete("no PTP TimeTransmitter");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s_sap.fd < 0) {
|
||||||
|
s_sap.fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
uint8_t loop = 0;
|
||||||
|
setsockopt(s_sap.fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
|
||||||
|
}
|
||||||
|
if (ttl != ttl_set) { // same scope as the session
|
||||||
|
setsockopt(s_sap.fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
|
||||||
|
ttl_set = ttl;
|
||||||
|
}
|
||||||
|
struct in_addr ifa = { .s_addr = ip.ip.addr };
|
||||||
|
setsockopt(s_sap.fd, IPPROTO_IP, IP_MULTICAST_IF, &ifa, sizeof(ifa));
|
||||||
|
|
||||||
|
size_t len = aes67_sdp_build(sdp, sizeof(sdp));
|
||||||
|
if (!len) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
if (len != s_sap.len || memcmp(sdp, s_sap.sdp, len) != 0 || ip.ip.addr != s_sap.origin) {
|
||||||
|
sap_delete("SDP changed");
|
||||||
|
memcpy(s_sap.sdp, sdp, len);
|
||||||
|
s_sap.len = len;
|
||||||
|
s_sap.hash = sdp_hash(sdp, len);
|
||||||
|
s_sap.origin = ip.ip.addr;
|
||||||
|
sap_send(false);
|
||||||
|
last_us = now;
|
||||||
|
ESP_LOGI(TAG, "announcing %04x to " SAP_ADDR ":%d every 30 s", s_sap.hash, SAP_PORT);
|
||||||
|
} else if (now - last_us >= SAP_INTERVAL_US) {
|
||||||
|
sap_send(false);
|
||||||
|
last_us = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t aes67_sdp_sap_init(void)
|
esp_err_t aes67_sdp_sap_init(void)
|
||||||
{
|
{
|
||||||
static const httpd_uri_t uri = { .uri = "/stream.sdp", .method = HTTP_GET, .handler = sdp_get };
|
static const httpd_uri_t uri = { .uri = "/stream.sdp", .method = HTTP_GET, .handler = sdp_get };
|
||||||
return web_register_uri(&uri);
|
esp_err_t err = web_register_uri(&uri);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = esp_register_shutdown_handler(sap_shutdown);
|
||||||
|
}
|
||||||
|
if (err == ESP_OK && xTaskCreate(sap_task, "sap", 4096, NULL, 3, NULL) != pdPASS) {
|
||||||
|
err = ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
// Registers GET /stream.sdp. (SAP: step 5.)
|
// Registers GET /stream.sdp and starts the SAP announcer (RFC 2974): 239.255.255.255:9875 every
|
||||||
|
// 30 s and on SDP change while aes67.discovery is "sap", the stream is enabled and a PTP GM is
|
||||||
|
// known (else ts-refclk would be all zeros); deletion
|
||||||
|
// when that stops and at shutdown.
|
||||||
esp_err_t aes67_sdp_sap_init(void);
|
esp_err_t aes67_sdp_sap_init(void);
|
||||||
// Build the current SDP (RFC 4566 + RFC 7273, AES67). Returns the length, or 0 if it didn't fit.
|
// Build the current SDP (RFC 4566 + RFC 7273, AES67). Returns the length, or 0 if it didn't fit.
|
||||||
size_t aes67_sdp_build(char *buf, size_t size);
|
size_t aes67_sdp_build(char *buf, size_t size);
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
idf_component_register(SRCS "aes67_syslog.c"
|
idf_component_register(SRCS "aes67_syslog.c"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES aes67_web)
|
PRIV_REQUIRES aes67_net aes67_web esp_netif log lwip)
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
#include "aes67_syslog.h"
|
#include "aes67_syslog.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "aes67_cfg.h"
|
#include "aes67_cfg.h"
|
||||||
|
#include "aes67_net.h"
|
||||||
|
#include "aes67_web.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "lwip/netdb.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
|
#define QUEUE_LEN 48
|
||||||
|
#define MSG_MAX 240
|
||||||
|
|
||||||
// facility: 16 = local0 (PRI = facility * 8 + severity).
|
// facility: 16 = local0 (PRI = facility * 8 + severity).
|
||||||
static const char LOG_DEFAULTS[] =
|
static const char LOG_DEFAULTS[] =
|
||||||
"{\"syslog\":false,\"host\":\"\",\"port\":514,\"level\":\"info\",\"facility\":16,\"format\":\"rfc5424\"}";
|
"{\"syslog\":false,\"host\":\"\",\"port\":514,\"level\":\"info\",\"facility\":16,\"format\":\"rfc5424\"}";
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t severity; // 3 error, 4 warning, 6 info, 7 debug
|
||||||
|
char text[MSG_MAX]; // "tag: message" without the level/timestamp prefix
|
||||||
|
} msg_t;
|
||||||
|
|
||||||
|
static QueueHandle_t s_queue;
|
||||||
|
static SemaphoreHandle_t s_fmt_lock;
|
||||||
|
static TaskHandle_t s_task;
|
||||||
|
static vprintf_like_t s_uart;
|
||||||
|
static volatile bool s_enabled;
|
||||||
|
static volatile uint8_t s_min_severity = 6;
|
||||||
|
static volatile bool s_reconfig = true;
|
||||||
|
static volatile uint32_t s_dropped;
|
||||||
|
|
||||||
static bool log_validate(const cJSON *g, char *err, size_t n)
|
static bool log_validate(const cJSON *g, char *err, size_t n)
|
||||||
{
|
{
|
||||||
static const char *const levels[] = { "error", "warn", "info", "debug", NULL };
|
static const char *const levels[] = { "error", "warn", "info", "debug", NULL };
|
||||||
@@ -22,7 +50,231 @@ static bool log_validate(const cJSON *g, char *err, size_t n)
|
|||||||
cfg_check_enum(g, "format", formats, err, n);
|
cfg_check_enum(g, "format", formats, err, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t severity_of(const char *level)
|
||||||
|
{
|
||||||
|
return level[0] == 'e' ? 3 : level[0] == 'w' ? 4 : level[0] == 'i' ? 6 : 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_filter(void)
|
||||||
|
{
|
||||||
|
cJSON *l = cfg_get("log");
|
||||||
|
s_enabled = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(l, "syslog"));
|
||||||
|
s_min_severity = severity_of(cJSON_GetObjectItemCaseSensitive(l, "level")->valuestring);
|
||||||
|
cJSON_Delete(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_apply(const cJSON *g)
|
||||||
|
{
|
||||||
|
read_filter();
|
||||||
|
s_reconfig = true;
|
||||||
|
if (s_task) {
|
||||||
|
xTaskNotifyGive(s_task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- esp_log hook ----- */
|
||||||
|
|
||||||
|
// "\033[0;32mI (1234) tag: message\033[0m\n" -> severity + "tag: message"
|
||||||
|
static bool parse_line(const char *in, msg_t *m)
|
||||||
|
{
|
||||||
|
char *o = m->text, *end = m->text + MSG_MAX - 1;
|
||||||
|
const char *p = in;
|
||||||
|
char level = 0;
|
||||||
|
bool prefix_done = false;
|
||||||
|
while (*p && o < end) {
|
||||||
|
if (*p == '\033') { // skip ANSI colour codes
|
||||||
|
while (*p && *p != 'm') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!level) {
|
||||||
|
level = *p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!prefix_done) { // skip " (1234) "
|
||||||
|
if (*p == ')') {
|
||||||
|
prefix_done = true;
|
||||||
|
p++;
|
||||||
|
if (*p == ' ') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*p == '\n' || *p == '\r') {
|
||||||
|
p++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*o++ = *p++;
|
||||||
|
}
|
||||||
|
*o = 0;
|
||||||
|
m->severity = level == 'E' ? 3 : level == 'W' ? 4 : level == 'I' ? 6 : 7;
|
||||||
|
return prefix_done && m->text[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int log_hook(const char *fmt, va_list ap)
|
||||||
|
{
|
||||||
|
va_list copy;
|
||||||
|
va_copy(copy, ap);
|
||||||
|
int ret = s_uart(fmt, ap); // UART output unchanged
|
||||||
|
|
||||||
|
if (s_enabled && xTaskGetCurrentTaskHandle() != s_task &&
|
||||||
|
xSemaphoreTake(s_fmt_lock, 0) == pdTRUE) {
|
||||||
|
static char line[MSG_MAX + 32];
|
||||||
|
static msg_t m;
|
||||||
|
vsnprintf(line, sizeof(line), fmt, copy);
|
||||||
|
if (parse_line(line, &m) && m.severity <= s_min_severity &&
|
||||||
|
xQueueSend(s_queue, &m, 0) != pdTRUE) {
|
||||||
|
s_dropped++;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(s_fmt_lock);
|
||||||
|
} else if (s_enabled && xTaskGetCurrentTaskHandle() != s_task) {
|
||||||
|
s_dropped++; // formatter busy: drop rather than block
|
||||||
|
}
|
||||||
|
va_end(copy);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- sender ----- */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct sockaddr_in dst;
|
||||||
|
bool have_dst;
|
||||||
|
uint8_t facility;
|
||||||
|
bool rfc5424;
|
||||||
|
char host[64];
|
||||||
|
char hostname[64];
|
||||||
|
} sender_cfg_t;
|
||||||
|
|
||||||
|
static void load_sender(sender_cfg_t *c)
|
||||||
|
{
|
||||||
|
cJSON *l = cfg_get("log"), *n = cfg_get("net");
|
||||||
|
strlcpy(c->host, cJSON_GetObjectItemCaseSensitive(l, "host")->valuestring, sizeof(c->host));
|
||||||
|
c->facility = (uint8_t)cJSON_GetObjectItemCaseSensitive(l, "facility")->valuedouble;
|
||||||
|
c->rfc5424 = strcmp(cJSON_GetObjectItemCaseSensitive(l, "format")->valuestring, "rfc5424") == 0;
|
||||||
|
uint16_t port = (uint16_t)cJSON_GetObjectItemCaseSensitive(l, "port")->valuedouble;
|
||||||
|
strlcpy(c->hostname, cJSON_GetObjectItemCaseSensitive(n, "hostname")->valuestring, sizeof(c->hostname));
|
||||||
|
cJSON_Delete(l);
|
||||||
|
cJSON_Delete(n);
|
||||||
|
|
||||||
|
c->have_dst = false;
|
||||||
|
memset(&c->dst, 0, sizeof(c->dst));
|
||||||
|
c->dst.sin_family = AF_INET;
|
||||||
|
c->dst.sin_port = htons(port);
|
||||||
|
if (inet_aton(c->host, &c->dst.sin_addr)) {
|
||||||
|
c->have_dst = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_DGRAM }, *res = NULL;
|
||||||
|
if (c->host[0] && getaddrinfo(c->host, NULL, &hints, &res) == 0 && res) {
|
||||||
|
c->dst.sin_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
|
||||||
|
c->have_dst = true;
|
||||||
|
}
|
||||||
|
if (res) {
|
||||||
|
freeaddrinfo(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 5424: <PRI>1 - HOSTNAME APP-NAME - - - MSG (no wall-clock time yet: NILVALUE)
|
||||||
|
// RFC 3164: <PRI>HOSTNAME TAG: MSG (TIMESTAMP omitted; the server adds it)
|
||||||
|
static int format_msg(char *out, size_t size, const sender_cfg_t *c, const msg_t *m)
|
||||||
|
{
|
||||||
|
const char *colon = strstr(m->text, ": ");
|
||||||
|
char tag[33] = "-";
|
||||||
|
const char *msg = m->text;
|
||||||
|
if (colon && colon - m->text < (int)sizeof(tag)) {
|
||||||
|
memcpy(tag, m->text, colon - m->text);
|
||||||
|
tag[colon - m->text] = 0;
|
||||||
|
msg = colon + 2;
|
||||||
|
}
|
||||||
|
int pri = c->facility * 8 + m->severity;
|
||||||
|
if (c->rfc5424) {
|
||||||
|
return snprintf(out, size, "<%d>1 - %s %s - - - %s", pri, c->hostname, tag, msg);
|
||||||
|
}
|
||||||
|
return snprintf(out, size, "<%d>%s %s: %s", pri, c->hostname, tag, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void syslog_task(void *arg)
|
||||||
|
{
|
||||||
|
sender_cfg_t c = { 0 };
|
||||||
|
int fd = -1; // created once the interface is up: lwIP may not be running yet at init
|
||||||
|
msg_t m;
|
||||||
|
char out[MSG_MAX + 128];
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
// Hold messages until the interface has an IP (early boot logs stay queued).
|
||||||
|
esp_netif_ip_info_t ip;
|
||||||
|
esp_netif_t *netif = aes67_net_netif();
|
||||||
|
if (!netif || esp_netif_get_ip_info(netif, &ip) != ESP_OK || !ip.ip.addr) {
|
||||||
|
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(500));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fd < 0 && (fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s_reconfig) {
|
||||||
|
s_reconfig = false;
|
||||||
|
load_sender(&c);
|
||||||
|
if (s_enabled && !c.have_dst) {
|
||||||
|
ESP_LOGW("syslog", "cannot resolve '%s'", c.host); // not sent: logged from this task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (xQueueReceive(s_queue, &m, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s_enabled && c.have_dst) {
|
||||||
|
int n = format_msg(out, sizeof(out), &c, &m);
|
||||||
|
sendto(fd, out, n < (int)sizeof(out) ? n : (int)sizeof(out) - 1, 0,
|
||||||
|
(struct sockaddr *)&c.dst, sizeof(c.dst));
|
||||||
|
}
|
||||||
|
static uint32_t reported;
|
||||||
|
if (s_dropped != reported) {
|
||||||
|
reported = s_dropped;
|
||||||
|
msg_t d = { .severity = 4 };
|
||||||
|
snprintf(d.text, sizeof(d.text), "syslog: %lu messages dropped so far (queue full)",
|
||||||
|
(unsigned long)reported);
|
||||||
|
xQueueSend(s_queue, &d, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t test_post(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
if (!s_enabled) {
|
||||||
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "syslog is disabled");
|
||||||
|
}
|
||||||
|
msg_t m = { .severity = 6 }; // info, regardless of the level filter
|
||||||
|
snprintf(m.text, sizeof(m.text), "syslog: test message from the web UI");
|
||||||
|
if (xQueueSend(s_queue, &m, 0) != pdTRUE) {
|
||||||
|
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "syslog queue full");
|
||||||
|
}
|
||||||
|
return httpd_resp_sendstr(req, "");
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t aes67_syslog_init(void)
|
esp_err_t aes67_syslog_init(void)
|
||||||
{
|
{
|
||||||
return cfg_register("log", LOG_DEFAULTS, log_validate, NULL);
|
esp_err_t err = cfg_register("log", LOG_DEFAULTS, log_validate, log_apply);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
s_queue = xQueueCreate(QUEUE_LEN, sizeof(msg_t));
|
||||||
|
s_fmt_lock = xSemaphoreCreateMutex();
|
||||||
|
if (!s_queue || !s_fmt_lock) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
read_filter();
|
||||||
|
if (xTaskCreate(syslog_task, "syslog", 4096, NULL, 2, &s_task) != pdPASS) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
static const httpd_uri_t uri = { .uri = "/api/log/test", .method = HTTP_POST, .handler = test_post };
|
||||||
|
err = web_register_uri(&uri);
|
||||||
|
s_uart = esp_log_set_vprintf(log_hook);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
|
||||||
// Registers the "log" config group. (Sender itself: step 5.)
|
// Registers the "log" config group, hooks esp_log (still printed on the UART) and starts the
|
||||||
|
// sender task. Messages logged before the interface has an IP are queued (up to the queue size).
|
||||||
esp_err_t aes67_syslog_init(void);
|
esp_err_t aes67_syslog_init(void);
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ esp_err_t aes67_web_start(void)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// httpd warns on every client reset (errno 104) and every 404 (e.g. UI polling a route the
|
||||||
|
// firmware doesn't have yet): noise in syslog. Our handlers log the 4xx cases that matter.
|
||||||
|
esp_log_level_set("httpd_txrx", ESP_LOG_ERROR);
|
||||||
|
esp_log_level_set("httpd_uri", ESP_LOG_ERROR);
|
||||||
|
|
||||||
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
||||||
cfg.max_uri_handlers = MAX_URIS;
|
cfg.max_uri_handlers = MAX_URIS;
|
||||||
cfg.stack_size = 8192;
|
cfg.stack_size = 8192;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Reference devices for UI and defaults: Riedel Bolero (PTP status), Riedel Direct
|
|||||||
## Temperatures
|
## Temperatures
|
||||||
- `status.temps` = array of {name, c, min_c, max_c, warn_c}; min/max since boot, warn_c optional. Any number of sensors; the UI renders one row per sensor and marks HIGH (red) at or above warn_c.
|
- `status.temps` = array of {name, c, min_c, max_c, warn_c}; min/max since boot, warn_c optional. Any number of sensors; the UI renders one row per sensor and marks HIGH (red) at or above warn_c.
|
||||||
- ESP32-P4: on-die sensor via the `temperature_sensor` driver (driver/temperature_sensor.h). Pick the measurement range to suit (e.g. -10..80 °C), sample every ~2 s in the health task, and report it as "SoC". It measures the die, not ambient: expect it to read well above room temperature.
|
- ESP32-P4: on-die sensor via the `temperature_sensor` driver (driver/temperature_sensor.h). Pick the measurement range to suit (e.g. -10..80 °C), sample every ~2 s in the health task, and report it as "SoC". It measures the die, not ambient: expect it to read well above room temperature.
|
||||||
|
- Implemented with range 20..100 °C (±2 °C; the warning level must lie inside the range), warn_c 85 °C. On the test board the die reads ~27 °C at idle in a cool room.
|
||||||
- Boards may add external sensors (e.g. I2C) by appending to the same array; the project or board component registers them.
|
- Boards may add external sensors (e.g. I2C) by appending to the same array; the project or board component registers them.
|
||||||
- Log a warning (goes to syslog) when crossing warn_c, with hysteresis (e.g. clear at warn_c - 5).
|
- Log a warning (goes to syslog) when crossing warn_c, with hysteresis (e.g. clear at warn_c - 5).
|
||||||
|
|
||||||
@@ -87,6 +88,8 @@ When this device is the GM, offset/frequency/delay show "–".
|
|||||||
- Discovery: manual (SDP export only) or SAP. NMOS IS-04/05 is a possible future option (Riedel SIC cards have an NMOS tab).
|
- Discovery: manual (SDP export only) or SAP. NMOS IS-04/05 is a possible future option (Riedel SIC cards have an NMOS tab).
|
||||||
- SDP: v, o (aes67 IP, session_id, session_ver), s, c=mcast/ttl, t=0 0, a=clock-domain:PTPv2 <dom>, m=audio port RTP/AVP pt, a=rtpmap, a=recvonly, a=ptime, a=ts-refclk:ptp=IEEE1588-2008:<gm>:<dom>, a=mediaclk:direct.
|
- SDP: v, o (aes67 IP, session_id, session_ver), s, c=mcast/ttl, t=0 0, a=clock-domain:PTPv2 <dom>, m=audio port RTP/AVP pt, a=rtpmap, a=recvonly, a=ptime, a=ts-refclk:ptp=IEEE1588-2008:<gm>:<dom>, a=mediaclk:direct.
|
||||||
- SAP to 239.255.255.255:9875 every 30 s, plus immediately on change; deletion packet on disable or shutdown.
|
- 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.
|
||||||
- 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).
|
- 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
|
## AES67 RX (future, for receiver projects) — Riedel Director 4-wire input defaults
|
||||||
@@ -95,7 +98,7 @@ When this device is the GM, offset/frequency/delay show "–".
|
|||||||
- Channel selection: which channel of the stream feeds a mono output.
|
- Channel selection: which channel of the stream feeds a mono output.
|
||||||
|
|
||||||
## Network
|
## Network
|
||||||
- Default: one untagged interface. VLAN split option: AES67 untagged + internet tagged (inet.vlan_id/pcp) using the ESP-IDF vlan_support approach (examples/network/vlan_support, ESP32-P4 supported). AES67 side has no gateway; default route and DNS are on inet.
|
- Default: one untagged interface. VLAN split (phase 2, not implemented yet) option: AES67 untagged + internet tagged (inet.vlan_id/pcp) using the ESP-IDF vlan_support approach (examples/network/vlan_support, ESP32-P4 supported). AES67 side has no gateway; default route and DNS are on inet.
|
||||||
- PTP, RTP, SAP and IGMP bind to the AES67 netif. HTTP server filters on `web_on`.
|
- PTP, RTP, SAP and IGMP bind to the AES67 netif. HTTP server filters on `web_on`.
|
||||||
- DHCP sends net.hostname (option 12), so the router's lease list / local DNS shows it; after a rename it applies at the next lease renewal.
|
- DHCP sends net.hostname (option 12), so the router's lease list / local DNS shows it; after a rename it applies at the next lease renewal.
|
||||||
- mDNS (espressif/mdns): `<net.hostname>.local` plus the web UI as `_http._tcp` port 80. A hostname change applies live (the new name answers after ~1 s of RFC 6762 probing).
|
- mDNS (espressif/mdns): `<net.hostname>.local` plus the web UI as `_http._tcp` port 80. A hostname change applies live (the new name answers after ~1 s of RFC 6762 probing).
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,8 @@ void app_main(void)
|
|||||||
chip.revision / 100, chip.revision % 100, chip.cores);
|
chip.revision / 100, chip.revision % 100, chip.cores);
|
||||||
|
|
||||||
project_cfg_defaults();
|
project_cfg_defaults();
|
||||||
|
// First, so boot messages are queued for syslog until the interface has an IP.
|
||||||
|
ESP_ERROR_CHECK(aes67_syslog_init());
|
||||||
|
|
||||||
esp_eth_handle_t eth;
|
esp_eth_handle_t eth;
|
||||||
ESP_ERROR_CHECK(aes67_board_eth_init(ð));
|
ESP_ERROR_CHECK(aes67_board_eth_init(ð));
|
||||||
@@ -34,7 +36,6 @@ void app_main(void)
|
|||||||
ESP_ERROR_CHECK(aes67_ptp_init());
|
ESP_ERROR_CHECK(aes67_ptp_init());
|
||||||
ESP_ERROR_CHECK(aes67_ptp_start(eth));
|
ESP_ERROR_CHECK(aes67_ptp_start(eth));
|
||||||
ESP_ERROR_CHECK(aes67_tx_init());
|
ESP_ERROR_CHECK(aes67_tx_init());
|
||||||
ESP_ERROR_CHECK(aes67_syslog_init());
|
|
||||||
project_cfg_register();
|
project_cfg_register();
|
||||||
ESP_ERROR_CHECK(aes67_ota_init());
|
ESP_ERROR_CHECK(aes67_ota_init());
|
||||||
ESP_ERROR_CHECK(aes67_sdp_sap_init());
|
ESP_ERROR_CHECK(aes67_sdp_sap_init());
|
||||||
|
|||||||
Reference in New Issue
Block a user