Build and push server image / build-and-push (push) Successful in 32s
Battery (firmware + server, disabled by default): new battery.c reads a 2x200k voltage divider via ADC oneshot with curve-fitting calibration (the ESP32-C6's scheme), maps through a piecewise LiPo discharge curve, and restores the pin to button duty after each read -- the settled XIAO ESP32-C6 design shares the back button's GPIO0/A0, time-shared per wake. Skipped entirely when on mains (a 2x100k VBUS divider into a spare digital pin -- the 5V pin is dead on battery power, so presence = mains, where the charging voltage would read misleadingly full) or when the reading is implausible. The manage overlay gains a battery region (static outline glyph + "NN%", below the manage QR, all menu levels), and the device POSTs to the new /frame/battery endpoint after a successful fetch; the server stores percent + as-of timestamp, exposed via /api/queue and shown in the web UI. FRAME_BATTERY_ADC_GPIO / FRAME_VBUS_SENSE_GPIO default to -1 (fully inert on the dev board); compile-verified both disabled and enabled, hardware bring-up deferred until the ordered XIAO + batteries arrive. Orientation (server-side only): new config setting + web UI dropdown (landscape / portrait / landscape_flipped / portrait_flipped). Photos are composed/cropped at the logical hanging shape (portrait crops at 480x800, so face-aware crops match how the frame actually hangs), then rotated losslessly into the panel's native 800x480 byte layout after dithering -- the device never knows. Face-label anchors are transformed through the same rotation (logical_to_native()) so they stay attached to faces on rotated frames. Known documented limitation: the on-device manage overlay still renders in native orientation, so it appears sideways on a portrait-hung frame (QRs scan at any rotation; text reads sideways).
62 lines
3.0 KiB
C
62 lines
3.0 KiB
C
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "esp_err.h"
|
|
|
|
/* 5 fixed regions (manage QR, battery indicator, location, date, share
|
|
* QR) plus up to MANAGE_FACE_LABELS_MAX arbitrary-position named-face
|
|
* labels (see manage_face_label_t below). MANAGE_FACE_LABELS_MAX is
|
|
* capped small deliberately, not arbitrarily -- each label is its own
|
|
* malloc'd buffer, and the fixed regions alone already use a meaningful
|
|
* chunk of the ESP32-C6's limited RAM; this keeps worst-case overlay
|
|
* memory well clear of what the WiFi/HTTP stack needs alongside it. */
|
|
#define MANAGE_FACE_LABELS_MAX 4
|
|
#define MANAGE_OVERLAY_MAX_REGIONS (5 + MANAGE_FACE_LABELS_MAX)
|
|
|
|
typedef struct {
|
|
uint8_t *buf; /* malloc'd (w/2)*h bytes, packed 2px/byte; owned by the region */
|
|
int x0, y0; /* top-left corner, panel pixel coordinates (x0 is always even) */
|
|
int w, h; /* pixel dimensions (w is always even) */
|
|
} manage_overlay_region_t;
|
|
|
|
typedef struct {
|
|
manage_overlay_region_t regions[MANAGE_OVERLAY_MAX_REGIONS];
|
|
int count;
|
|
} manage_overlay_set_t;
|
|
|
|
typedef struct {
|
|
char name[16];
|
|
int x, y; /* anchor point (bottom-center of the face), panel pixel coordinates */
|
|
} manage_face_label_t;
|
|
|
|
typedef struct {
|
|
const char *management_url; /* top-right QR + "SCAN TO"/"MANAGE" caption -- always shown */
|
|
const char *location_line1; /* top-left text, line 1 (city); NULL/empty skips this region */
|
|
const char *location_line2; /* top-left text, line 2 (state/country); NULL/empty is fine if line1 is set */
|
|
const char *taken_at; /* bottom-right text; NULL/empty skips this region */
|
|
const char *share_url; /* bottom-left QR + "SCAN TO"/"DOWNLOAD" caption; NULL/empty skips this region */
|
|
const manage_face_label_t *face_labels; /* named-face labels ("level 2" menu); NULL/empty count skips these */
|
|
int face_label_count; /* clamped to MANAGE_FACE_LABELS_MAX internally */
|
|
int battery_percent; /* 0-100 shows an icon + percent below the manage QR; -1 skips it */
|
|
} manage_overlay_content_t;
|
|
|
|
/**
|
|
* Renders the manage-button overlay: always a "scan to manage" QR in the
|
|
* top-right corner, plus whichever of location_line1/taken_at/share_url
|
|
* are non-NULL/non-empty in their own corners (top-left, bottom-right,
|
|
* bottom-left respectively), plus one region per entry in face_labels
|
|
* (positioned near that face rather than a fixed corner -- see
|
|
* render_face_label_region() in the .c file for the clamping logic).
|
|
* Each region is its own separately malloc'd small buffer (not a full
|
|
* EPD_FRAME_BYTES frame). A failure rendering the top-right region fails
|
|
* the whole call; a failure rendering any other region just skips that
|
|
* region and keeps going. Caller must call manage_overlay_free() on out
|
|
* regardless of the return value (out->count reflects however many
|
|
* regions were actually populated).
|
|
*/
|
|
esp_err_t manage_overlay_render(const manage_overlay_content_t *content, manage_overlay_set_t *out);
|
|
|
|
/** Frees every populated region's buffer in overlay. */
|
|
void manage_overlay_free(manage_overlay_set_t *overlay);
|