Step 2.2: config registry and NVS store, GET/POST /api/config

- aes67_web/cfg.c: cfg_register(group, defaults, validate, apply),
  cfg_override_defaults() for project defaults, cfg_get(). Stored values
  are loaded on register and merged over defaults (known keys, matching
  types). NVS keeps only keys that differ from defaults, one JSON string
  per group, so new firmware defaults still reach untouched settings.
- POST /api/config: type check against defaults, per-group validation,
  nothing stored unless every group passes; 400 "group.key: reason".
  Unknown keys/groups are ignored. Returns the new config.
- Core groups registered by their components with the UI's ranges:
  ptp (aes67_ptp), aes67 (aes67_tx, incl. multicast range and mono_sum
  => 1 channel), net/inet (aes67_net), log (aes67_syslog).
- Project: PROJECT.def overrides (hostname p4-aes67, name P4 AES67) and
  the "source" group in main/project_cfg.c.
- Config is stored only; nothing is applied live yet.
- Verified on board: defaults, valid save, 8 rejected values (nothing
  stored), persistence across reboot, restore to defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 23:00:55 +10:00
parent 35275b8ccf
commit c2621f192a
20 changed files with 744 additions and 15 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
idf_component_register(SRCS "aes67_net.c"
INCLUDE_DIRS "include"
REQUIRES esp_eth esp_netif esp_event
PRIV_REQUIRES aes67_web)
PRIV_REQUIRES aes67_web lwip)
+61
View File
@@ -1,7 +1,10 @@
#include "aes67_net.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "aes67_cfg.h"
#include "aes67_web.h"
#include "esp_eth.h"
#include "esp_event.h"
@@ -10,6 +13,14 @@
static const char *TAG = "net";
// AES67 / untagged interface.
static const char NET_DEFAULTS[] =
"{\"hostname\":\"aes67\",\"vlan\":false,\"web_on\":\"both\",\"dhcp\":true,"
"\"ip\":\"\",\"mask\":\"\",\"gw\":\"\",\"dns\":\"\"}";
// Internet / tagged interface, only used with net.vlan.
static const char INET_DEFAULTS[] =
"{\"vlan_id\":10,\"pcp\":0,\"dhcp\":true,\"ip\":\"\",\"mask\":\"\",\"gw\":\"\",\"dns\":\"\"}";
static esp_netif_t *s_netif;
static char s_link[24] = "down";
@@ -48,6 +59,52 @@ static void ip_event(void *arg, esp_event_base_t base, int32_t id, void *data)
IP2STR(&ev->ip_info.ip), IP2STR(&ev->ip_info.netmask), IP2STR(&ev->ip_info.gw));
}
// Static addressing needs ip + mask; gw/dns are optional (none on the AES67 side with VLAN split).
static bool check_addr(const cJSON *g, char *err, size_t n)
{
bool dhcp = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(g, "dhcp"));
return cfg_check_ipv4(g, "ip", dhcp, err, n) &&
cfg_check_ipv4(g, "mask", dhcp, err, n) &&
cfg_check_ipv4(g, "gw", true, err, n) &&
cfg_check_ipv4(g, "dns", true, err, n);
}
// RFC 1123 label: letters, digits, hyphen; not starting or ending with a hyphen.
static bool check_hostname(const cJSON *g, char *err, size_t n)
{
if (!cfg_check_str(g, "hostname", 1, 63, err, n)) {
return false;
}
const char *h = cJSON_GetObjectItemCaseSensitive(g, "hostname")->valuestring;
size_t len = strlen(h);
for (size_t i = 0; i < len; i++) {
if (!isalnum((unsigned char)h[i]) && h[i] != '-') {
snprintf(err, n, "hostname: only letters, digits and '-'");
return false;
}
}
if (h[0] == '-' || h[len - 1] == '-') {
snprintf(err, n, "hostname: must not start or end with '-'");
return false;
}
return true;
}
static bool net_validate(const cJSON *g, char *err, size_t n)
{
static const char *const web_on[] = { "both", "inet", "aes67", NULL };
return check_hostname(g, err, n) &&
cfg_check_enum(g, "web_on", web_on, err, n) &&
check_addr(g, err, n);
}
static bool inet_validate(const cJSON *g, char *err, size_t n)
{
return cfg_check_int(g, "vlan_id", 1, 4094, err, n) &&
cfg_check_int(g, "pcp", 0, 7, err, n) &&
check_addr(g, err, n);
}
static void net_status(cJSON *st)
{
char ip[16] = "", mac_s[18] = "";
@@ -69,6 +126,10 @@ static void net_status(cJSON *st)
esp_err_t aes67_net_init(esp_eth_handle_t eth)
{
// Config is stored only for now; applying it (hostname, static IP, VLAN) comes later.
ESP_ERROR_CHECK(cfg_register("net", NET_DEFAULTS, net_validate, NULL));
ESP_ERROR_CHECK(cfg_register("inet", INET_DEFAULTS, inet_validate, NULL));
ESP_ERROR_CHECK(esp_netif_init());
esp_err_t err = esp_event_loop_create_default();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {