From 21af41ff66d896343e9407b1c6999d69db9379c2 Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Sat, 18 Jul 2026 15:34:37 -0400 Subject: [PATCH] Fetch refresh interval from server instead of hardcoding it Replaces check_server_reachable() (bare bool, GET /health) with fetch_frame_config(), which GETs the server's new /frame/config endpoint instead -- doubles as the reachability check (any completed HTTP response counts, same as before) and delivers the server-configured refresh_interval_s, used for the success-path deep sleep duration instead of the Kconfig-only default. Parses the tiny JSON response with a hand-rolled scalar extractor (json_extract_uint) rather than pulling in a JSON library -- cJSON isn't bundled in this ESP-IDF install, and a single flat integer field doesn't justify a new dependency. Verified standalone against exactly the JSON shape the server emits, including a missing-field fallback case. FRAME_SLEEP_INTERVAL_S (Kconfig) is now just the fallback used before the device has ever reached a configured server, or if the response is missing/unparseable -- documented as such in its help text. --- firmware/main/Kconfig.projbuild | 21 +++++--- firmware/main/frame_client.c | 85 +++++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/firmware/main/Kconfig.projbuild b/firmware/main/Kconfig.projbuild index 3560c5e..0fb7d5a 100644 --- a/firmware/main/Kconfig.projbuild +++ b/firmware/main/Kconfig.projbuild @@ -45,11 +45,13 @@ menu "ESPresso Frame Configuration" stored home WiFi network before counting it as a failed retry. config FRAME_SERVER_CHECK_TIMEOUT_MS - int "Tools server reachability check timeout (ms)" + int "Tools server /frame/config request timeout (ms)" default 3000 help - How long to wait for an HTTP response from the tools server - when checking reachability on the post-connect status screen. + How long to wait for a GET /frame/config response from the + tools server -- doubles as both the reachability check for the + post-connect status screen and the source of the + server-configured refresh interval below. config FRAME_FETCH_TIMEOUT_MS int "Image fetch HTTP timeout (ms)" @@ -61,12 +63,17 @@ menu "ESPresso Frame Configuration" check's timeout. config FRAME_SLEEP_INTERVAL_S - int "Deep sleep interval between refreshes (seconds)" + int "Fallback deep sleep interval between refreshes (seconds)" default 3600 help - How long the device deep-sleeps between successful - fetch-and-display cycles. Lower this for bench testing so you - don't have to wait an hour per iteration. + The refresh interval is normally set server-side (the "Refresh + interval" field in the server's web UI, delivered via GET + /frame/config) so it can be changed without reflashing. This + value is only a fallback: used before the device has ever + successfully reached a configured server, or if the server's + response doesn't include a valid interval (e.g. an older + server version). Lower this for bench testing so you don't + have to wait an hour per iteration. config FRAME_RETRY_INTERVAL_S int "Deep sleep interval after a failed cycle (seconds)" diff --git a/firmware/main/frame_client.c b/firmware/main/frame_client.c index bbcb3dc..244282d 100644 --- a/firmware/main/frame_client.c +++ b/firmware/main/frame_client.c @@ -111,12 +111,52 @@ esp_err_t frame_wifi_connect_sta(const frame_config_t *cfg) return result; } -/* Probes the server's /health endpoint -- any completed HTTP response - * means the socket-level connection succeeded, which is all this checks. */ -static bool check_server_reachable(const char *toolsserver) +typedef struct { + bool reachable; + uint32_t refresh_interval_s; /* CONFIG_FRAME_SLEEP_INTERVAL_S if absent/unparseable */ +} frame_server_config_t; + +/* Finds the first integer value associated with "key" in a small JSON + * blob, e.g. 3600 in {"refresh_interval_s": 3600}. Not a general JSON + * parser -- just enough for this project's small, flat config response, + * to avoid pulling in a JSON library for one scalar field. */ +static bool json_extract_uint(const char *json, const char *key, uint32_t *out) { + char needle[48]; + snprintf(needle, sizeof(needle), "\"%s\"", key); + const char *pos = strstr(json, needle); + if (pos == NULL) { + return false; + } + pos = strchr(pos, ':'); + if (pos == NULL) { + return false; + } + pos++; + while (*pos == ' ') { + pos++; + } + char *end; + unsigned long value = strtoul(pos, &end, 10); + if (end == pos) { + return false; + } + *out = (uint32_t)value; + return true; +} + +/* GETs the server's /frame/config -- doubles as both the reachability + * check (any completed HTTP response means the socket-level connection + * succeeded) and the source of the server-configurable refresh interval. */ +static frame_server_config_t fetch_frame_config(const char *toolsserver) +{ + frame_server_config_t result = { + .reachable = false, + .refresh_interval_s = CONFIG_FRAME_SLEEP_INTERVAL_S, + }; + char url[160]; - snprintf(url, sizeof(url), "http://%s/health", toolsserver); + snprintf(url, sizeof(url), "http://%s/frame/config", toolsserver); esp_http_client_config_t config = { .url = url, @@ -124,13 +164,38 @@ static bool check_server_reachable(const char *toolsserver) .timeout_ms = CONFIG_FRAME_SERVER_CHECK_TIMEOUT_MS, }; esp_http_client_handle_t client = esp_http_client_init(&config); - esp_err_t err = esp_http_client_perform(client); - esp_http_client_cleanup(client); + esp_err_t err = esp_http_client_open(client, 0); if (err != ESP_OK) { ESP_LOGW(TAG, "Server '%s' not reachable: %s", toolsserver, esp_err_to_name(err)); + esp_http_client_cleanup(client); + return result; } - return err == ESP_OK; + + esp_http_client_fetch_headers(client); + result.reachable = true; + + char body[256]; + int total = 0; + int n; + while (total < (int)sizeof(body) - 1 && + (n = esp_http_client_read(client, body + total, sizeof(body) - 1 - total)) > 0) { + total += n; + } + body[total] = '\0'; + + esp_http_client_close(client); + esp_http_client_cleanup(client); + + uint32_t interval; + if (json_extract_uint(body, "refresh_interval_s", &interval)) { + result.refresh_interval_s = interval; + } else { + ESP_LOGW(TAG, "'%s' response missing refresh_interval_s, using fallback %ds", url, + (int)result.refresh_interval_s); + } + + return result; } typedef struct { @@ -212,10 +277,10 @@ void frame_client_run(const frame_config_t *cfg) } } - uint32_t sleep_seconds = CONFIG_FRAME_SLEEP_INTERVAL_S; - bool server_reachable = check_server_reachable(cfg->toolsserver); + frame_server_config_t server_cfg = fetch_frame_config(cfg->toolsserver); + uint32_t sleep_seconds = server_cfg.refresh_interval_s; - if (!server_reachable) { + if (!server_cfg.reachable) { /* Nothing's been drawn yet this cycle (aside from the optional * PENDING screen above), so this is cheap diagnostics without * compounding flashing. */