diff options
| author | Luke Hoersten <[email protected]> | 2026-07-17 18:56:03 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-07-17 18:56:03 -0500 |
| commit | 9d94f710f7b00d109bf6f6ee57996f43bce5c67f (patch) | |
| tree | b86320353884536d2d1b342efa27600b19c845de | |
| parent | 866015678918560a0d0519be8a2549e4fc23b2cd (diff) | |
main: code-review fixes — brightness units, OTA stall cap, stats + locking
- display: s_last_pwm cached the raw 0-100 percentage at init but
display_wake writes it straight to REG_PWM (0-255 duty) — first wake
ran visibly dim until a /config brightness change. Shared pct_to_duty
helper now converts in both paths.
- http_api: cap consecutive OTA recv timeouts (a stalled client spun
forever holding s_ota_in_progress, wedging OTA until reboot); cap
viewport name at 54 chars so viewport-<name> fits the 63-byte mDNS
label; log mdns_service_refresh failures; /state builds JSON from a
snapshot instead of holding the state lock across ~25 cJSON allocs;
respond_400 delegates to respond_status.
- stream_server: bytes_in_window now counts painted frames only (frames
discarded while asleep inflated the first post-wake window's MB/s,
avg-jpeg and chunk/wire averages; recv_bytes folded in); so_rcvbuf
carried into the stats snapshot under the mux; drop dead HEADER_BYTES;
merge read_body_instrumented into read_n.
- state_machine: transition mutex serializes concurrent wake/sleep
(display side-effects ran after the state lock dropped, so racing
callers could leave the backlight contradicting st->state); wake-path
placeholder paint now takes the decoder lock like the stream path
(concurrent esp_lcd_panel_draw_bitmap from two tasks isn't safe).
- local_screens: overlay paint takes the decoder lock too; check the
overlay timer create; panel dims from viewport_state.h.
- jpeg_decoder: try_lock before init returns busy instead of passing a
NULL semaphore to xSemaphoreTake.
- net_eth: clear cached IP string on link down (stale /state + info).
- dead code: display_present_bgr888, TOUCH_FT5426_ADDR, touch s_task,
JPEG_DECODER_MAX_OUTPUT_BYTES; doc drift in nvs_config.h /
jpeg_decoder.h / app_main flag legend.
| -rw-r--r-- | main/app_main.c | 4 | ||||
| -rw-r--r-- | main/display.c | 33 | ||||
| -rw-r--r-- | main/display.h | 10 | ||||
| -rw-r--r-- | main/http_api.c | 59 | ||||
| -rw-r--r-- | main/jpeg_decoder.c | 3 | ||||
| -rw-r--r-- | main/jpeg_decoder.h | 6 | ||||
| -rw-r--r-- | main/local_screens.c | 15 | ||||
| -rw-r--r-- | main/net_eth.c | 3 | ||||
| -rw-r--r-- | main/nvs_config.h | 4 | ||||
| -rw-r--r-- | main/ota.c | 3 | ||||
| -rw-r--r-- | main/state_machine.c | 25 | ||||
| -rw-r--r-- | main/stream_server.c | 75 | ||||
| -rw-r--r-- | main/stream_server.h | 2 | ||||
| -rw-r--r-- | main/touch.c | 3 |
14 files changed, 143 insertions, 102 deletions
diff --git a/main/app_main.c b/main/app_main.c index f1aae13..c131fe0 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -56,7 +56,9 @@ void app_main(void) // don't get a DHCP lease. mDNS + HTTP advertise / bind anyway and will // start serving the moment the link comes up. // ------------------------------------------------------------------ - char flags[8] = { '-','-','-','-','-','-','-', 0 }; // E M H S D J T + char flags[5] = { '-','-','-','-', 0 }; // E M H S (display-task + // subsystems D J T report + // separately in dsp_init) esp_err_t eth_err = net_eth_init(); mark(eth_err, 'E', &flags[0]); diff --git a/main/display.c b/main/display.c index 7441200..8b86812 100644 --- a/main/display.c +++ b/main/display.c @@ -32,7 +32,6 @@ static const char *TAG = "display"; #define I2C_FREQ_HZ 400000 #define PANEL_MCU_ADDR 0x45 // ATTINY88 on the Pi 7" panel architecture -#define TOUCH_FT5426_ADDR 0x38 // ATTINY88 register map. Source: // linux/drivers/regulator/rpi-panel-attiny-regulator.c (rpi-6.6.y) @@ -63,8 +62,8 @@ enum { // ESP32-P4. The Linux upstream modeline differs in HFP/VSW; if pixels look // shifted left/right these are the first knobs to revisit. // ============================================================================ -#define PANEL_H_ACTIVE 800 -#define PANEL_V_ACTIVE 480 +#define PANEL_H_ACTIVE VIEWPORT_PANEL_WIDTH +#define PANEL_V_ACTIVE VIEWPORT_PANEL_HEIGHT #define RPI_HSW 2 #define RPI_HBP 46 #define RPI_HFP 210 @@ -428,6 +427,18 @@ static esp_err_t dsi_bring_up(void) // ============================================================================ // Public API // ============================================================================ + +// 0-100 user percentage → gamma-corrected 0-255 PWM duty. s_last_pwm +// always holds a DUTY, never a raw percentage — display_wake() writes it +// straight to REG_PWM. +static uint8_t pct_to_duty(uint8_t pct) +{ + if (pct > 100) pct = 100; + float frac = (float)pct / 100.0f; + float gamma = powf(frac, 2.2f); + return (uint8_t)(gamma * 255.0f + 0.5f); +} + esp_err_t display_init(void) { if (s_up) return ESP_OK; @@ -448,8 +459,9 @@ esp_err_t display_init(void) // Cache configured brightness, leave backlight off — first wake applies it. viewport_state_lock(); - s_last_pwm = viewport_state_get()->brightness; + uint8_t pct = viewport_state_get()->brightness; viewport_state_unlock(); + s_last_pwm = pct_to_duty(pct); mcu_write_u8(REG_PWM, 0); return ESP_OK; @@ -461,10 +473,7 @@ i2c_master_bus_handle_t display_i2c_bus(void) { return s_i2c_bus; } esp_err_t display_set_brightness(uint8_t pct) { - if (pct > 100) pct = 100; - float frac = (float)pct / 100.0f; - float gamma = powf(frac, 2.2f); - uint8_t duty = (uint8_t)(gamma * 255.0f + 0.5f); + uint8_t duty = pct_to_duty(pct); s_last_pwm = duty; if (!s_up) return ESP_ERR_INVALID_STATE; @@ -497,14 +506,6 @@ static inline void rgb565_to_rgb888(uint16_t px, uint8_t *out) out[2] = (uint8_t)(((px >> 11) & 0x1F) * 255 / 31); // R } -esp_err_t display_present_bgr888(const void *bgr888) -{ - if (!s_up) return ESP_ERR_INVALID_STATE; - return esp_lcd_panel_draw_bitmap(s_panel, 0, 0, - PANEL_H_ACTIVE, PANEL_V_ACTIVE, - bgr888); -} - void *display_back_buffer(size_t *out_size) { if (!s_up) return NULL; diff --git a/main/display.h b/main/display.h index 679a408..1a0e0a0 100644 --- a/main/display.h +++ b/main/display.h @@ -38,16 +38,6 @@ esp_err_t display_present_rgb565(const uint16_t *src, uint16_t src_w, uint16_t src_h); -// Zero-copy hot path. Source is already 800x480 with bytes in [B, G, R] -// memory order (i.e. the format the panel pipeline natively wants); -// hand it straight to esp_lcd_panel_draw_bitmap. No CPU pixel work, no -// format conversion, no rotation — Scrypted is responsible for sending -// the buffer pre-rotated and pre-scaled to panel-native dimensions. -// If the buffer happens to be one of the panel's own framebuffers -// (see display_back_buffer / display_flip_back_buffer below), the IDF -// driver skips the memcpy entirely; otherwise it copies via CPU. -esp_err_t display_present_bgr888(const void *bgr888); - // Framebuffer accessors for the zero-memcpy frame path. The DPI panel // owns three BGR888 framebuffers (triple buffering): one scanning out, // one pending display at the next frame boundary, one free. diff --git a/main/http_api.c b/main/http_api.c index 5e84db6..6954974 100644 --- a/main/http_api.c +++ b/main/http_api.c @@ -33,14 +33,22 @@ static const char *TAG = "http_api"; #define MAX_BODY_BYTES 2048 #define MIN_IDLE_TIMEOUT 5000 +// mDNS hostname is "viewport-<name>" and a DNS label caps at 63 bytes, +// so the name itself must stay <= 63 - strlen("viewport-") = 54 chars. +#define MAX_VIEWPORT_NAME 54 // ============================================================================ // GET /state // ============================================================================ static esp_err_t state_get_handler(httpd_req_t *req) { + // Snapshot under the lock, build JSON unlocked — the stream decode-task + // takes this mutex on every painted frame, so keep the hold time to a + // struct copy rather than ~25 cJSON allocations. viewport_state_lock(); - viewport_state_t *st = viewport_state_get(); + viewport_state_t snap = *viewport_state_get(); + viewport_state_unlock(); + viewport_state_t *st = &snap; uint64_t now_us = (uint64_t)esp_timer_get_time(); uint64_t up_ms = (now_us - st->boot_us) / 1000; @@ -88,8 +96,6 @@ static esp_err_t state_get_handler(httpd_req_t *req) round((double)temp_c * 10.0) / 10.0); } - viewport_state_unlock(); - // Most recent closed window of live-stream stats. Populated every // 30 painted stream frames; zero before the first window rolls. // last_paint_event_us_low is the low 32 bits of the Scrypted @@ -188,12 +194,17 @@ static esp_err_t config_get_handler(httpd_req_t *req) // ============================================================================ // POST /config — partial-update, atomic, validated // ============================================================================ -static esp_err_t respond_400(httpd_req_t *req, const char *reason) +static esp_err_t respond_status(httpd_req_t *req, const char *status, const char *body) { - ESP_LOGW(TAG, "/config 400: %s", reason); - httpd_resp_set_status(req, "400 Bad Request"); + httpd_resp_set_status(req, status); httpd_resp_set_type(req, "text/plain"); - return httpd_resp_send(req, reason, HTTPD_RESP_USE_STRLEN); + return httpd_resp_send(req, body, body ? HTTPD_RESP_USE_STRLEN : 0); +} + +static esp_err_t respond_400(httpd_req_t *req, const char *reason) +{ + ESP_LOGW(TAG, "400: %s", reason); + return respond_status(req, "400 Bad Request", reason); } static esp_err_t read_body(httpd_req_t *req, char *buf, size_t cap) @@ -233,9 +244,10 @@ static esp_err_t config_post_handler(httpd_req_t *req) if ((j = cJSON_GetObjectItemCaseSensitive(root, "viewport"))) { if (!cJSON_IsString(j) || j->valuestring[0] == '\0' || - strlen(j->valuestring) >= sizeof(vp)) { + strlen(j->valuestring) > MAX_VIEWPORT_NAME) { cJSON_Delete(root); - return respond_400(req, "viewport must be a non-empty string < 64 chars"); + return respond_400(req, "viewport must be a non-empty string <= 54 chars" + " (mDNS hostname label limit)"); } strncpy(vp, j->valuestring, sizeof(vp) - 1); have_vp = true; @@ -333,7 +345,11 @@ static esp_err_t config_post_handler(httpd_req_t *req) display_set_brightness(bright); } if (name_or_orient_changed) { - mdns_service_refresh(); + esp_err_t mdns_err = mdns_service_refresh(); + if (mdns_err != ESP_OK) { + ESP_LOGW(TAG, "mdns_service_refresh failed: %s", + esp_err_to_name(mdns_err)); + } } httpd_resp_set_status(req, "204 No Content"); @@ -383,13 +399,6 @@ static esp_err_t state_post_handler(httpd_req_t *req) // ============================================================================ // POST /frame // ============================================================================ -static esp_err_t respond_status(httpd_req_t *req, const char *status, const char *body) -{ - httpd_resp_set_status(req, status); - httpd_resp_set_type(req, "text/plain"); - return httpd_resp_send(req, body, body ? HTTPD_RESP_USE_STRLEN : 0); -} - static esp_err_t frame_post_handler(httpd_req_t *req) { // Content-Type must be image/jpeg. @@ -573,10 +582,24 @@ static esp_err_t firmware_post_handler(httpd_req_t *req) uint8_t buf[4096]; int remaining = req->content_len; int last_logged_pct = -1; + int consecutive_timeouts = 0; while (remaining > 0) { int want = remaining < (int)sizeof(buf) ? remaining : (int)sizeof(buf); int n = httpd_req_recv(req, (char *)buf, want); - if (n == HTTPD_SOCK_ERR_TIMEOUT) continue; + if (n == HTTPD_SOCK_ERR_TIMEOUT) { + // A stalled client would otherwise spin here forever with + // s_ota_in_progress held, wedging OTA until reboot. Each + // timeout is one httpd recv-timeout period, so ~10 in a row + // means the sender is gone. + if (++consecutive_timeouts >= 10) { + err_status = "408 Request Timeout"; + err_msg = "body stalled"; + result = ESP_FAIL; + goto done; + } + continue; + } + consecutive_timeouts = 0; if (n <= 0) { err_status = "400 Bad Request"; err_msg = "body read failed"; diff --git a/main/jpeg_decoder.c b/main/jpeg_decoder.c index 7fee771..8afecca 100644 --- a/main/jpeg_decoder.c +++ b/main/jpeg_decoder.c @@ -44,6 +44,9 @@ esp_err_t jpeg_decoder_init(void) bool jpeg_decoder_try_lock(uint32_t timeout_ms) { + // Callable before jpeg_decoder_init (e.g. a wake POST landing in the + // display-task boot window) — treat "no decoder yet" as busy. + if (!s_mutex) return false; return xSemaphoreTake(s_mutex, pdMS_TO_TICKS(timeout_ms)) == pdTRUE; } diff --git a/main/jpeg_decoder.h b/main/jpeg_decoder.h index 495b1a5..beb907b 100644 --- a/main/jpeg_decoder.h +++ b/main/jpeg_decoder.h @@ -7,10 +7,10 @@ #include "esp_err.h" #define JPEG_DECODER_MAX_INPUT_BYTES (1024 * 1024) // 1 MB -#define JPEG_DECODER_MAX_OUTPUT_BYTES (800 * 480 * 3) // BGR888 panel native -// One-time setup of the ESP32-P4 hardware JPEG decoder. Allocates reusable -// DMA-aligned input + output buffers in PSRAM. +// One-time setup of the ESP32-P4 hardware JPEG decoder. Allocates a reusable +// DMA-aligned input buffer in PSRAM; output goes to caller-provided buffers +// (the display back-framebuffer in the hot paths). esp_err_t jpeg_decoder_init(void); // The decoder owns shared scratch buffers. Caller must serialize access. diff --git a/main/local_screens.c b/main/local_screens.c index 1277d00..351c7c5 100644 --- a/main/local_screens.c +++ b/main/local_screens.c @@ -9,13 +9,14 @@ #include "chip_temp.h" #include "display.h" +#include "jpeg_decoder.h" #include "net_eth.h" #include "viewport_state.h" static const char *TAG = "screens"; -#define PANEL_W 800 -#define PANEL_H 480 +#define PANEL_W VIEWPORT_PANEL_WIDTH +#define PANEL_H VIEWPORT_PANEL_HEIGHT #define MAX_BUF_PX (PANEL_W * PANEL_H) // 8x8 bitmap font, lit pixels = foreground. Covers the characters used by @@ -150,7 +151,11 @@ esp_err_t local_screens_init(void) .callback = &overlay_expired_cb, .name = "info_overlay", }; - esp_timer_create(&args, &s_overlay_timer); + esp_err_t err = esp_timer_create(&args, &s_overlay_timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "overlay timer create failed: %s", esp_err_to_name(err)); + return err; + } return ESP_OK; } @@ -174,7 +179,11 @@ esp_err_t local_screens_overlay(uint32_t duration_ms) if (!display_is_up()) return ESP_ERR_INVALID_STATE; esp_timer_stop(s_overlay_timer); display_wake(); + // Serialize the overlay paint against the stream decode-task's paints + // (concurrent esp_lcd_panel_draw_bitmap calls aren't safe). + if (!jpeg_decoder_try_lock(500)) return ESP_ERR_TIMEOUT; esp_err_t err = local_screens_show_info(); + jpeg_decoder_unlock(); if (err != ESP_OK) return err; s_overlay_active = true; return esp_timer_start_once(s_overlay_timer, diff --git a/main/net_eth.c b/main/net_eth.c index 28ef782..3b4a76c 100644 --- a/main/net_eth.c +++ b/main/net_eth.c @@ -53,6 +53,9 @@ static void on_eth_event(void *arg, esp_event_base_t event_base, case ETHERNET_EVENT_DISCONNECTED: ESP_LOGW(TAG, "link down"); xEventGroupClearBits(s_event_group, BIT_GOT_IP); + // Clear the cached address so /state and the info screen don't + // keep reporting a lease we no longer hold. + s_ip_str[0] = '\0'; break; case ETHERNET_EVENT_START: ESP_LOGI(TAG, "ethernet started"); diff --git a/main/nvs_config.h b/main/nvs_config.h index b90577d..6157517 100644 --- a/main/nvs_config.h +++ b/main/nvs_config.h @@ -4,8 +4,8 @@ // Read the persisted config into viewport_state. Safe to call on a fresh // device: missing keys keep their first-boot defaults from viewport_state_init. -// Sets state = ASLEEP and configured = true if a viewport name + scrypted URL -// are both present. +// Recomputes `configured` (true iff a scrypted URL is present); run state +// is untouched (stays at the boot default). esp_err_t nvs_config_load(void); // Persist the current viewport_state to NVS atomically. The caller is expected @@ -29,6 +29,9 @@ void ota_arm_healthy_timer(void) .callback = &healthy_cb, .name = "ota_healthy", }; + // The handle is deliberately never deleted: one ~50-byte allocation + // per boot, and deleting from inside the callback would race the + // dispatcher. esp_timer_handle_t t = NULL; if (esp_timer_create(&args, &t) != ESP_OK) return; esp_timer_start_once(t, OTA_HEALTHY_DELAY_US); diff --git a/main/state_machine.c b/main/state_machine.c index 5e688bc..eb2548c 100644 --- a/main/state_machine.c +++ b/main/state_machine.c @@ -4,15 +4,22 @@ #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #include "freertos/task.h" #include "display.h" +#include "jpeg_decoder.h" #include "local_screens.h" #include "state_client.h" static const char *TAG = "state"; static esp_timer_handle_t s_idle_timer; +// Serializes whole transitions (state write + display side-effects). +// Without it, concurrent wake+sleep callers (HTTP task, touch task, idle +// timer) can interleave display_wake/display_sleep after the state lock +// is dropped and leave the backlight not matching st->state. +static SemaphoreHandle_t s_transition_mutex; // ms = idle_timeout_ms snapshotted under the caller's state lock. // ms == 0 disables the timer (caller's idle_timeout_ms feature). @@ -31,6 +38,8 @@ static void idle_timer_fired(void *arg) esp_err_t state_machine_init(void) { + s_transition_mutex = xSemaphoreCreateMutex(); + if (!s_transition_mutex) return ESP_ERR_NO_MEM; esp_timer_create_args_t args = { .callback = &idle_timer_fired, .name = "viewport_idle", @@ -43,11 +52,14 @@ esp_err_t state_machine_set(viewport_run_state_t target) if (target != VIEWPORT_STATE_AWAKE && target != VIEWPORT_STATE_ASLEEP) return ESP_ERR_INVALID_ARG; + xSemaphoreTake(s_transition_mutex, portMAX_DELAY); + viewport_state_lock(); viewport_state_t *st = viewport_state_get(); if (st->state == target) { viewport_state_unlock(); + xSemaphoreGive(s_transition_mutex); return ESP_OK; // idempotent no-op } st->state = target; @@ -63,8 +75,16 @@ esp_err_t state_machine_set(viewport_run_state_t target) // backlight comes up. Without the delay, esp_lcd_panel_draw_bitmap // returns before the panel has been refreshed and the user sees // a flash of the previous /frame's contents. - if (configured) local_screens_show_loading(); - else local_screens_show_info(); + // + // Decoder lock: the stream decode-task paints under this lock, + // and concurrent esp_lcd_panel_draw_bitmap calls from two tasks + // aren't safe. If the decoder is busy >500 ms a live frame is + // painting anyway — skip the placeholder and just light up. + if (jpeg_decoder_try_lock(500)) { + if (configured) local_screens_show_loading(); + else local_screens_show_info(); + jpeg_decoder_unlock(); + } vTaskDelay(pdMS_TO_TICKS(33)); display_wake(); } @@ -75,6 +95,7 @@ esp_err_t state_machine_set(viewport_run_state_t target) if (display_is_up()) display_sleep(); ESP_LOGI(TAG, "ASLEEP"); } + xSemaphoreGive(s_transition_mutex); return ESP_OK; } diff --git a/main/stream_server.c b/main/stream_server.c index a5c6613..9fc0063 100644 --- a/main/stream_server.c +++ b/main/stream_server.c @@ -31,9 +31,6 @@ static const char *TAG = "stream"; #define HEADER_V0_BYTES 8 // legacy: jpeg_len + seq #define HEADER_V1_BYTES 16 // current: magic + jpeg_len + seq + event_us_low -#define HEADER_BYTES HEADER_V0_BYTES // FIONREAD threshold for - // "another header may already - // be queued" check. #define MAGIC_V1 0x56505254u // "VPRT" big-endian #define NUM_BODY_BUFS 3 // ping-pong ring: 1 recv + 1 pending // + 1 decode → recv never blocks. @@ -132,41 +129,29 @@ static esp_err_t alloc_body_bufs(void) return ESP_OK; } -// recv() in a loop until n bytes are read or the connection drops. -// Used for short fixed-size reads (header bytes). -static esp_err_t read_n(int fd, void *buf, size_t n) -{ - uint8_t *p = (uint8_t *)buf; - size_t got = 0; - while (got < n) { - ssize_t r = recv(fd, p + got, n - got, 0); - if (r <= 0) return ESP_FAIL; - got += (size_t)r; - } - return ESP_OK; -} - // ───────────────────────── recv-task ───────────────────────────────────────── -// Per-frame body read with instrumentation: count recv() syscalls and track -// chunk size distribution. Returns ESP_OK on full body; ESP_FAIL on EOF/error. -static esp_err_t read_body_instrumented(int fd, void *buf, size_t n, - uint32_t *out_calls, - uint32_t *out_chunk_min, - uint32_t *out_chunk_max) +// recv() in a loop until n bytes are read or the connection drops, with +// optional instrumentation: count recv() syscalls and track chunk size +// distribution (pass NULL out-params to skip, e.g. for short header reads). +// Returns ESP_OK on full read; ESP_FAIL on EOF/error. +static esp_err_t read_n(int fd, void *buf, size_t n, + uint32_t *out_calls, + uint32_t *out_chunk_min, + uint32_t *out_chunk_max) { uint8_t *p = (uint8_t *)buf; size_t got = 0; - *out_calls = 0; - *out_chunk_min = UINT32_MAX; - *out_chunk_max = 0; + if (out_calls) *out_calls = 0; + if (out_chunk_min) *out_chunk_min = UINT32_MAX; + if (out_chunk_max) *out_chunk_max = 0; while (got < n) { ssize_t r = recv(fd, p + got, n - got, 0); if (r <= 0) return ESP_FAIL; uint32_t rb = (uint32_t)r; - (*out_calls)++; - if (rb < *out_chunk_min) *out_chunk_min = rb; - if (rb > *out_chunk_max) *out_chunk_max = rb; + if (out_calls) (*out_calls)++; + if (out_chunk_min && rb < *out_chunk_min) *out_chunk_min = rb; + if (out_chunk_max && rb > *out_chunk_max) *out_chunk_max = rb; got += (size_t)r; } return ESP_OK; @@ -247,7 +232,7 @@ static void handle_client_recv(int fd, const char *peer) while (1) { uint8_t first4[4]; - if (read_n(fd, first4, 4) != ESP_OK) { + if (read_n(fd, first4, 4, NULL, NULL, NULL) != ESP_OK) { ESP_LOGI(TAG, "client %s disconnected (header read)", peer); return; } @@ -257,7 +242,7 @@ static void handle_client_recv(int fd, const char *peer) uint32_t jpeg_len, seq, event_us_low; if (first_word == MAGIC_V1) { uint8_t rest[HEADER_V1_BYTES - 4]; - if (read_n(fd, rest, sizeof(rest)) != ESP_OK) { + if (read_n(fd, rest, sizeof(rest), NULL, NULL, NULL) != ESP_OK) { ESP_LOGI(TAG, "client %s disconnected (v1 header read)", peer); return; } @@ -269,7 +254,7 @@ static void handle_client_recv(int fd, const char *peer) | ((uint32_t)rest[10] << 8) | (uint32_t)rest[11]; } else { uint8_t rest[HEADER_V0_BYTES - 4]; - if (read_n(fd, rest, sizeof(rest)) != ESP_OK) { + if (read_n(fd, rest, sizeof(rest), NULL, NULL, NULL) != ESP_OK) { ESP_LOGI(TAG, "client %s disconnected (v0 header read)", peer); return; } @@ -306,8 +291,8 @@ static void handle_client_recv(int fd, const char *peer) uint8_t *body = s_bufs[s_recv_idx]; int64_t t0 = esp_timer_get_time(); uint32_t calls = 0, chunk_min = 0, chunk_max = 0; - if (read_body_instrumented(fd, body, jpeg_len, - &calls, &chunk_min, &chunk_max) != ESP_OK) { + if (read_n(fd, body, jpeg_len, + &calls, &chunk_min, &chunk_max) != ESP_OK) { ESP_LOGI(TAG, "client %s disconnected (body read)", peer); return; } @@ -431,7 +416,6 @@ static void decode_task(void *arg) uint64_t gap_samples = 0; // first frame of a conn has no gap int64_t page_min = INT64_MAX, page_max = 0, page_sum = 0; uint32_t wire_min = UINT32_MAX, wire_max = 0; // kbit/s - uint64_t recv_bytes = 0; // bytes behind recv_sum uint32_t queued_min = UINT32_MAX, queued_max = 0; uint64_t queued_sum = 0; uint32_t calls_min = UINT32_MAX, calls_max = 0; uint64_t calls_sum = 0; uint32_t chunk_min = UINT32_MAX, chunk_max = 0; uint64_t chunk_total_calls = 0; @@ -455,8 +439,6 @@ static void decode_task(void *arg) int64_t idle_us = t_prev_paint_done ? (t_entry - t_prev_paint_done) : 0; int64_t dwait_us = t_signal - t_wait_start; - bytes_in_window += meta.jpeg_len; - // New connection? Reset stale-seq tracking. recv-task bumped // conn_id at accept; if we observe a different one here, we're // starting fresh and seq=1 should paint. @@ -558,9 +540,12 @@ static void decode_task(void *arg) if (recv_us < recv_min) recv_min = recv_us; if (recv_us > recv_max) recv_max = recv_us; - recv_sum += recv_us; - recv_bytes += meta.jpeg_len; // painted-frame bytes, matches recv_sum - // for the window wire-rate average + recv_sum += recv_us; + // Painted-frame bytes only, so avg-jpeg / MB/s / chunk_avg / + // wire_avg all share the same denominator population as the + // other per-frame stats (frames discarded while asleep or + // stale-seq'd don't skew the window). + bytes_in_window += meta.jpeg_len; if (dec_us < dec_min) dec_min = dec_us; if (dec_us > dec_max) dec_max = dec_us; dec_sum += dec_us; @@ -600,7 +585,7 @@ static void decode_task(void *arg) uint32_t chunk_avg = chunk_total_calls > 0 ? (uint32_t)(bytes_in_window / chunk_total_calls) : 0; uint32_t wire_avg = recv_sum > 0 - ? (uint32_t)((recv_bytes * 8000u) / (uint64_t)recv_sum) : 0; + ? (uint32_t)((bytes_in_window * 8000u) / (uint64_t)recv_sum) : 0; int64_t gap_avg = gap_samples > 0 ? (gap_sum / (int64_t)gap_samples) : 0; @@ -671,7 +656,6 @@ static void decode_task(void *arg) .recv_chunk_min = (chunk_min == UINT32_MAX ? 0 : chunk_min), .recv_chunk_avg = chunk_avg, .recv_chunk_max = chunk_max, - .so_rcvbuf = s_stats.so_rcvbuf, .recv_dropped_oldest = dropped_oldest, .decode_idle_min_us = (uint32_t)(dwait_min == INT64_MAX ? 0 : dwait_min), .decode_idle_avg_us = (uint32_t)(dwait_sum / window_samples), @@ -688,7 +672,10 @@ static void decode_task(void *arg) .pend_age_max_us = (uint32_t)page_max, }; portENTER_CRITICAL(&s_stats_mux); - s_stats = snap; + // so_rcvbuf is written by recv-task under this same mux at + // accept time; carry it forward inside the critical section. + snap.so_rcvbuf = s_stats.so_rcvbuf; + s_stats = snap; portEXIT_CRITICAL(&s_stats_mux); t_window_start = now; @@ -699,7 +686,7 @@ static void decode_task(void *arg) gap_min = page_min = INT64_MAX; gap_max = page_max = gap_sum = page_sum = 0; gap_samples = 0; - wire_min = UINT32_MAX; wire_max = 0; recv_bytes = 0; + wire_min = UINT32_MAX; wire_max = 0; queued_min = calls_min = chunk_min = UINT32_MAX; queued_max = calls_max = chunk_max = 0; queued_sum = calls_sum = chunk_total_calls = 0; diff --git a/main/stream_server.h b/main/stream_server.h index 2562738..eff4fa8 100644 --- a/main/stream_server.h +++ b/main/stream_server.h @@ -44,7 +44,7 @@ esp_err_t stream_server_start(uint16_t port); // microseconds. Fields are zero before the first window rolls. typedef struct { uint64_t frames; // count of painted frames in the window - uint64_t bytes; // total body bytes received in the window + uint64_t bytes; // body bytes of painted frames in the window uint64_t window_us; // wall-clock span of the window uint64_t window_end_us; // esp_timer_get_time() at window roll uint32_t recv_min_us, recv_avg_us, recv_max_us; diff --git a/main/touch.c b/main/touch.c index b470536..54c309f 100644 --- a/main/touch.c +++ b/main/touch.c @@ -30,7 +30,6 @@ static const char *TAG = "touch"; #define TAP_DEBOUNCE_MS 150 static i2c_master_dev_handle_t s_dev; -static TaskHandle_t s_task; static esp_err_t ft_read(uint8_t reg, uint8_t *buf, size_t n) { @@ -124,6 +123,6 @@ esp_err_t touch_init(void) "(tap=toggle wake/sleep, %dms hold=info overlay)", LONG_PRESS_MS); - BaseType_t ok = xTaskCreate(touch_task, "touch", 3072, NULL, 4, &s_task); + BaseType_t ok = xTaskCreate(touch_task, "touch", 3072, NULL, 4, NULL); return (ok == pdPASS) ? ESP_OK : ESP_FAIL; } |
