Add third button: "scan to manage" QR overlay on the current photo
Pressing the manage button (GPIO1) overlays a small QR code -- "SCAN TO MANAGE" -- in the top-right corner of whatever photo is currently on screen, linking to the server's config page, then reverts to the plain photo after 30 seconds. The overlay is spliced into the existing streaming fetch as chunks pass through (frame_client.c's http_read_fn), rather than buffering the full 192,000-byte frame in RAM: only the small overlay rectangle itself (~30KB) is ever held in memory, generated via new stride-parameterized drawing helpers (epd_draw_*_ex in epd_draw.c) that let the existing QR/text drawing code target an arbitrarily-sized buffer instead of a full-frame one. epd7in3e.c is untouched -- it has no idea an overlay exists.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "epd7in3e.h"
|
||||
#include "status_screen.h"
|
||||
#include "manage_qr_overlay.h"
|
||||
|
||||
#include "frame_client.h"
|
||||
|
||||
@@ -200,25 +201,65 @@ static frame_server_config_t fetch_frame_config(const char *toolsserver)
|
||||
|
||||
typedef struct {
|
||||
esp_http_client_handle_t client;
|
||||
size_t stream_pos; /* running absolute offset into the frame, for overlay splicing */
|
||||
const manage_qr_overlay_t *overlay; /* NULL = no overlay this fetch */
|
||||
} http_read_ctx_t;
|
||||
|
||||
/* Splices overlay pixels over the real photo bytes in chunk wherever
|
||||
* chunk's absolute byte range [chunk_start, chunk_start+chunk_len) within
|
||||
* the full frame intersects the overlay's rectangle. Rows/chunks outside
|
||||
* the overlay's footprint are left completely untouched. overlay->x0 is
|
||||
* always even (see manage_qr_overlay.h), so byte_x0 below is exact. */
|
||||
static void splice_overlay(uint8_t *chunk, size_t chunk_len, size_t chunk_start, const manage_qr_overlay_t *overlay)
|
||||
{
|
||||
int byte_x0 = overlay->x0 / 2;
|
||||
int byte_w = overlay->w / 2;
|
||||
size_t chunk_end = chunk_start + chunk_len;
|
||||
|
||||
for (int row = overlay->y0; row < overlay->y0 + overlay->h; row++) {
|
||||
size_t row_start = (size_t)row * EPD_BYTES_PER_ROW + (size_t)byte_x0;
|
||||
size_t row_end = row_start + (size_t)byte_w;
|
||||
|
||||
size_t lo = row_start > chunk_start ? row_start : chunk_start;
|
||||
size_t hi = row_end < chunk_end ? row_end : chunk_end;
|
||||
if (lo >= hi) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t overlay_row_offset = (size_t)(row - overlay->y0) * (size_t)byte_w + (lo - row_start);
|
||||
memcpy(chunk + (lo - chunk_start), overlay->buf + overlay_row_offset, hi - lo);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulls the next chunk straight out of the in-progress HTTP response --
|
||||
* epd_display_stream() calls this to feed the panel without ever holding
|
||||
* the full ~192KB frame in RAM. */
|
||||
* epd_write_frame() calls this to feed the panel without ever holding
|
||||
* the full ~192KB frame in RAM. Splices in ctx->overlay's pixels (if
|
||||
* set) as chunks pass through, so the panel driver never needs to know
|
||||
* an overlay exists at all. */
|
||||
static size_t http_read_fn(uint8_t *chunk, size_t chunk_size, void *ctx_)
|
||||
{
|
||||
http_read_ctx_t *ctx = (http_read_ctx_t *)ctx_;
|
||||
int n = esp_http_client_read(ctx->client, (char *)chunk, (int)chunk_size);
|
||||
return n > 0 ? (size_t)n : 0;
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ctx->overlay != NULL) {
|
||||
splice_overlay(chunk, (size_t)n, ctx->stream_pos, ctx->overlay);
|
||||
}
|
||||
ctx->stream_pos += (size_t)n;
|
||||
|
||||
return (size_t)n;
|
||||
}
|
||||
|
||||
/* GETs /frame/image (or, if force_advance, POSTs /frame/advance to skip
|
||||
* ahead immediately) and streams the response directly into the panel.
|
||||
* Returning non-ESP_OK means the panel was never actually refreshed --
|
||||
* ahead immediately) and streams the response directly into the panel,
|
||||
* splicing in overlay's pixels (if non-NULL) as it streams. Returning
|
||||
* non-ESP_OK means the panel was never actually refreshed --
|
||||
* epd_display_stream() (see epd7in3e.c) refuses to trigger a physical
|
||||
* refresh on a short/wrong-size stream, so a failure here always leaves
|
||||
* the visible screen exactly as it was. */
|
||||
static esp_err_t fetch_and_display(const frame_config_t *cfg, bool force_advance)
|
||||
static esp_err_t fetch_and_display(const frame_config_t *cfg, bool force_advance, const manage_qr_overlay_t *overlay)
|
||||
{
|
||||
char url[160];
|
||||
snprintf(url, sizeof(url), "http://%s/%s", cfg->toolsserver, force_advance ? "frame/advance" : "frame/image");
|
||||
@@ -247,7 +288,7 @@ static esp_err_t fetch_and_display(const frame_config_t *cfg, bool force_advance
|
||||
}
|
||||
ESP_LOGI(TAG, "Fetching frame (%d bytes) from '%s'", content_length, url);
|
||||
|
||||
http_read_ctx_t ctx = { .client = client };
|
||||
http_read_ctx_t ctx = { .client = client, .overlay = overlay };
|
||||
uint32_t crc = 0;
|
||||
err = epd_write_frame(http_read_fn, &ctx, &crc);
|
||||
|
||||
@@ -275,7 +316,50 @@ static esp_err_t fetch_and_display(const frame_config_t *cfg, bool force_advance
|
||||
return err;
|
||||
}
|
||||
|
||||
void frame_client_run(const frame_config_t *cfg, bool force_advance)
|
||||
/* Runs the appropriate fetch for this cycle: a plain fetch, or -- if
|
||||
* show_management_qr -- a fetch with the "scan to manage" QR overlay
|
||||
* spliced in, held on screen for 30s (device stays awake, doesn't sleep
|
||||
* the panel or the chip), then reverted with a second plain fetch.
|
||||
* Returns non-ESP_OK only if the FIRST fetch failed; a revert failure
|
||||
* afterward is logged but doesn't count as an overall failure -- the QR
|
||||
* itself displayed fine, which was the point of the button. */
|
||||
static esp_err_t run_fetch_cycle(const frame_config_t *cfg, bool force_advance, bool show_management_qr)
|
||||
{
|
||||
if (!show_management_qr) {
|
||||
return fetch_and_display(cfg, force_advance, NULL);
|
||||
}
|
||||
|
||||
char management_url[160];
|
||||
snprintf(management_url, sizeof(management_url), "http://%s/", cfg->toolsserver);
|
||||
|
||||
manage_qr_overlay_t overlay;
|
||||
esp_err_t overlay_err = manage_qr_overlay_render(management_url, &overlay);
|
||||
if (overlay_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Could not render management QR overlay (%s), showing photo normally",
|
||||
esp_err_to_name(overlay_err));
|
||||
return fetch_and_display(cfg, force_advance, NULL);
|
||||
}
|
||||
|
||||
esp_err_t err = fetch_and_display(cfg, force_advance, &overlay);
|
||||
manage_qr_overlay_free(&overlay);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Showing management QR for 30s");
|
||||
vTaskDelay(pdMS_TO_TICKS(30000));
|
||||
|
||||
/* force_advance is always false here -- reverting shouldn't skip
|
||||
* ahead a second time. */
|
||||
esp_err_t revert_err = fetch_and_display(cfg, false, NULL);
|
||||
if (revert_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to revert management QR overlay (%s)", esp_err_to_name(revert_err));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void frame_client_run(const frame_config_t *cfg, bool force_advance, bool show_management_qr)
|
||||
{
|
||||
esp_err_t epd_err = epd_init();
|
||||
bool have_display = (epd_err == ESP_OK);
|
||||
@@ -309,7 +393,7 @@ void frame_client_run(const frame_config_t *cfg, bool force_advance)
|
||||
* worth it to stop false-failing on the common case. */
|
||||
bool image_ok = true;
|
||||
if (have_display) {
|
||||
esp_err_t fetch_err = fetch_and_display(cfg, force_advance);
|
||||
esp_err_t fetch_err = run_fetch_cycle(cfg, force_advance, show_management_qr);
|
||||
image_ok = (fetch_err == ESP_OK);
|
||||
if (!image_ok) {
|
||||
/* epd_display_stream() never triggers a physical refresh on a
|
||||
|
||||
Reference in New Issue
Block a user