src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/http_api.c
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-13 22:08:02 -0500
committerLuke Hoersten <[email protected]>2026-06-13 22:08:02 -0500
commit4756daedaefd18919e96fadaabc7f1a00b383b4c (patch)
treeb2eeefb22f53673ac9437920d7447442a205fc8b /main/http_api.c
parente2ac22e58901e18b0b567b85d93a8f68335180bc (diff)
M2: HTTP server + mDNS service discovery + shared state
- viewport_state.{h,c}: shared state struct (config, run-state, counters, orientation, timestamps) behind a FreeRTOS mutex. Modules update it under viewport_state_lock(); GET /state serializes a snapshot. - http_api.{h,c}: starts esp_http_server on :80, registers GET /state. Returns the full JSON shape from the spec (name, version, configured, state, uptime_ms, last_frame_ms_ago, frames_received, decode_errors, state_post_failures, resolution, ip, free_heap, free_psram). - mdns_service.{h,c}: mdns_init + advertise _scrypted-viewport._tcp.local on :80 with version/resolution/orientation/name TXT records. mdns_service_refresh() reapplies hostname + TXT after /config writes (called from M4 onward). - main/CMakeLists.txt: add esp_http_server, esp_timer, json, mdns to REQUIRES. espressif/mdns added as managed component via main/idf_component.yml. app_main calls viewport_state_init early so any module can read defaults before /config arrives. Also adds TESTING.md tracking per-milestone verification status and an integration test plan to run after M9 (races, failure modes, longevity, power cycles, negative protocol, multi-viewport). M1 promoted to "🟡 builds clean" in TESTING.md; combined HW verification of M1+M2 will be one flash session. Build verified against ESP-IDF 5.4 for target esp32p4 (binary ~550 KB).
Diffstat (limited to 'main/http_api.c')
-rw-r--r--main/http_api.c93
1 files changed, 93 insertions, 0 deletions
diff --git a/main/http_api.c b/main/http_api.c
new file mode 100644
index 0000000..4c0008d
--- /dev/null
+++ b/main/http_api.c
@@ -0,0 +1,93 @@
+#include "http_api.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "cJSON.h"
+#include "esp_check.h"
+#include "esp_heap_caps.h"
+#include "esp_http_server.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+
+#include "net_eth.h"
+#include "viewport_state.h"
+
+static const char *TAG = "http_api";
+
+static const char *state_name(viewport_run_state_t s)
+{
+ switch (s) {
+ case VIEWPORT_STATE_AWAKE: return "awake";
+ case VIEWPORT_STATE_ASLEEP: return "asleep";
+ default: return "unconfigured";
+ }
+}
+
+static esp_err_t state_get_handler(httpd_req_t *req)
+{
+ viewport_state_lock();
+ viewport_state_t *st = viewport_state_get();
+
+ uint64_t now_us = (uint64_t)esp_timer_get_time();
+ uint64_t up_ms = (now_us - st->boot_us) / 1000;
+ int64_t last_age_ms = (st->last_frame_us < 0)
+ ? -1
+ : (int64_t)((now_us - (uint64_t)st->last_frame_us) / 1000);
+
+ cJSON *root = cJSON_CreateObject();
+ cJSON_AddItemToObject(root, "name",
+ st->viewport_name[0] ? cJSON_CreateString(st->viewport_name)
+ : cJSON_CreateNull());
+ cJSON_AddStringToObject(root, "version", VIEWPORT_VERSION);
+ cJSON_AddBoolToObject(root, "configured", st->configured);
+ cJSON_AddStringToObject(root, "state", state_name(st->state));
+ cJSON_AddNumberToObject(root, "uptime_ms", (double)up_ms);
+ cJSON_AddItemToObject(root, "last_frame_ms_ago",
+ last_age_ms < 0 ? cJSON_CreateNull()
+ : cJSON_CreateNumber((double)last_age_ms));
+ cJSON_AddNumberToObject(root, "frames_received", (double)st->frames_received);
+ cJSON_AddNumberToObject(root, "decode_errors", (double)st->decode_errors);
+ cJSON_AddNumberToObject(root, "state_post_failures", (double)st->state_post_failures);
+ cJSON_AddStringToObject(root, "resolution", viewport_state_resolution_str());
+ cJSON_AddStringToObject(root, "ip", net_eth_get_ip_str());
+ cJSON_AddNumberToObject(root, "free_heap",
+ (double)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+ cJSON_AddNumberToObject(root, "free_psram",
+ (double)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+
+ viewport_state_unlock();
+
+ char *body = cJSON_PrintUnformatted(root);
+ cJSON_Delete(root);
+ if (!body) return ESP_ERR_NO_MEM;
+
+ httpd_resp_set_type(req, "application/json");
+ esp_err_t err = httpd_resp_send(req, body, HTTPD_RESP_USE_STRLEN);
+ cJSON_free(body);
+ return err;
+}
+
+static const httpd_uri_t s_state_get = {
+ .uri = "/state",
+ .method = HTTP_GET,
+ .handler = state_get_handler,
+ .user_ctx = NULL,
+};
+
+esp_err_t http_api_start(void)
+{
+ httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
+ cfg.server_port = 80;
+ cfg.max_uri_handlers = 8;
+ cfg.lru_purge_enable = true;
+
+ httpd_handle_t server = NULL;
+ ESP_RETURN_ON_ERROR(httpd_start(&server, &cfg), TAG, "httpd_start");
+ ESP_RETURN_ON_ERROR(httpd_register_uri_handler(server, &s_state_get),
+ TAG, "register /state");
+
+ ESP_LOGI(TAG, "http server listening on :80 (GET /state)");
+ return ESP_OK;
+}