Files
aes67-ESP32-P4/components/aes67_web/aes67_web.c
T
bsncubed c2621f192a 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>
2026-09-24 23:00:55 +10:00

132 lines
3.6 KiB
C

#include "aes67_web.h"
#include "aes67_cfg.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_timer.h"
#define MAX_STATUS_CBS 16
#define MAX_URIS 32
#define MAX_EARLY_URIS 16
static const char *TAG = "web";
extern const char index_html_start[] asm("_binary_index_html_start");
extern const char index_html_end[] asm("_binary_index_html_end");
esp_err_t cfg_web_register(void); // cfg.c
static httpd_handle_t s_httpd;
static status_cb_t s_status_cbs[MAX_STATUS_CBS];
static int s_status_n;
static httpd_uri_t s_early_uris[MAX_EARLY_URIS];
static int s_early_n;
esp_err_t status_register(status_cb_t cb)
{
if (s_status_n >= MAX_STATUS_CBS) {
return ESP_ERR_NO_MEM;
}
s_status_cbs[s_status_n++] = cb;
return ESP_OK;
}
esp_err_t web_register_uri(const httpd_uri_t *uri)
{
if (s_httpd) {
return httpd_register_uri_handler(s_httpd, uri);
}
if (s_early_n >= MAX_EARLY_URIS) {
return ESP_ERR_NO_MEM;
}
s_early_uris[s_early_n++] = *uri;
return ESP_OK;
}
esp_err_t web_send_json(httpd_req_t *req, const cJSON *json)
{
char *body = cJSON_PrintUnformatted(json);
if (!body) {
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "out of memory");
}
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Cache-Control", "no-store");
esp_err_t err = httpd_resp_sendstr(req, body);
cJSON_free(body);
return err;
}
static esp_err_t index_get(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/html; charset=utf-8");
// EMBED_TXTFILES appends a NUL; don't send it.
return httpd_resp_send(req, index_html_start, index_html_end - index_html_start - 1);
}
static esp_err_t status_get(httpd_req_t *req)
{
cJSON *st = cJSON_CreateObject();
for (int i = 0; i < s_status_n; i++) {
s_status_cbs[i](st);
}
esp_err_t err = web_send_json(req, st);
cJSON_Delete(st);
return err;
}
static void reboot_cb(void *arg)
{
esp_restart();
}
static esp_err_t reboot_post(httpd_req_t *req)
{
ESP_LOGW(TAG, "reboot requested via API");
httpd_resp_sendstr(req, "");
// Let the response go out before restarting.
const esp_timer_create_args_t args = { .callback = reboot_cb, .name = "reboot" };
esp_timer_handle_t t;
if (esp_timer_create(&args, &t) == ESP_OK) {
esp_timer_start_once(t, 500 * 1000);
}
return ESP_OK;
}
esp_err_t aes67_web_start(void)
{
esp_err_t err = cfg_web_register();
if (err != ESP_OK) {
return err;
}
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
cfg.max_uri_handlers = MAX_URIS;
cfg.stack_size = 8192;
cfg.lru_purge_enable = true;
cfg.uri_match_fn = httpd_uri_match_wildcard;
err = httpd_start(&s_httpd, &cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "httpd start failed: %s", esp_err_to_name(err));
return err;
}
static const httpd_uri_t core[] = {
{ .uri = "/", .method = HTTP_GET, .handler = index_get },
{ .uri = "/index.html", .method = HTTP_GET, .handler = index_get },
{ .uri = "/api/status", .method = HTTP_GET, .handler = status_get },
{ .uri = "/api/reboot", .method = HTTP_POST, .handler = reboot_post },
};
for (int i = 0; i < sizeof(core) / sizeof(core[0]); i++) {
httpd_register_uri_handler(s_httpd, &core[i]);
}
for (int i = 0; i < s_early_n; i++) {
httpd_register_uri_handler(s_httpd, &s_early_uris[i]);
}
ESP_LOGI(TAG, "httpd started, index.html %d bytes, %d extra routes",
(int)(index_html_end - index_html_start - 1), s_early_n);
return ESP_OK;
}