Server: Frame.panel_type (new column + migration) is auto-derived from the device's reported board (X-Frame-Board), never user-set -- the panel is a property of the hardware, not a picker in the UI. image_pipeline's packing/render pipeline is parameterized by panel geometry instead of hardcoded 800x480 globals, with the real confirmed 13.3in geometry (1600x1200) registered alongside the original 7.3in panel. Existing 7.3in frames are unaffected (column default + board mapping both resolve to the original panel). Board identifiers are also renamed (devkit/xiao -> devkit_esp32c6/ xiao_esp32c6, plus new "ee02") since the EE02 board also carries a XIAO module -- "xiao" alone stopped disambiguating hardware. The server keeps accepting the legacy bare names indefinitely for already-flashed devices. Firmware: scaffolds a third build target (ee02, ESP32-S3 -- a real chip-target change, not just a same-chip Kconfig variant like xiao) and a new epd13in3e driver component skeleton. The actual panel init/LUT/ refresh register sequence isn't ported from vendor demo code yet (none was available), so that component deliberately fails to compile (#error) rather than risk sending unverified register values to real hardware -- devkit/xiao are unaffected and build identically to before. CI's ee02 build step is continue-on-error for the same reason.
118 lines
4.3 KiB
C
118 lines
4.3 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
|
|
#include "epd_board.h"
|
|
#include "epd_draw.h"
|
|
#include "fonts.h"
|
|
#include "qrcodegen.h"
|
|
#include "wifi_provisioning.h"
|
|
|
|
#include "qr_onboarding.h"
|
|
|
|
static const char *TAG = "qr_onboarding";
|
|
|
|
#define QR_MAX_VERSION 10
|
|
#define QR_BUFFER_LEN qrcodegen_BUFFER_LEN_FOR_VERSION(QR_MAX_VERSION)
|
|
#define QR_MODULE_PX 8
|
|
#define TITLE_Y 12
|
|
#define COLUMN_TOP_Y 56
|
|
#define CAPTION_GAP 16
|
|
#define LINE_GAP 8
|
|
|
|
static void draw_qr(uint8_t *frame, const uint8_t *qrcode, int origin_x, int origin_y)
|
|
{
|
|
int size = qrcodegen_getSize(qrcode);
|
|
for (int y = 0; y < size; y++) {
|
|
for (int x = 0; x < size; x++) {
|
|
epd_color_t color = qrcodegen_getModule(qrcode, x, y) ? EPD_COLOR_BLACK : EPD_COLOR_WHITE;
|
|
for (int dy = 0; dy < QR_MODULE_PX; dy++) {
|
|
for (int dx = 0; dx < QR_MODULE_PX; dx++) {
|
|
epd_draw_pixel(frame, origin_x + x * QR_MODULE_PX + dx, origin_y + y * QR_MODULE_PX + dy, color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Encodes payload as a QR code and draws one "step" of the setup screen:
|
|
* a centered heading, the QR itself, and zero or more centered caption
|
|
* lines underneath (e.g. SSID/password as plaintext). */
|
|
static esp_err_t draw_qr_step(uint8_t *frame, int center_x, int top_y, const char *heading, const char *payload,
|
|
const char *const *captions, int caption_count)
|
|
{
|
|
epd_draw_text_centered(frame, &Font24, heading, center_x, top_y);
|
|
int y = top_y + Font24.Height + CAPTION_GAP;
|
|
|
|
uint8_t temp_buffer[QR_BUFFER_LEN];
|
|
uint8_t qrcode[QR_BUFFER_LEN];
|
|
bool ok = qrcodegen_encodeText(payload, temp_buffer, qrcode, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN,
|
|
QR_MAX_VERSION, qrcodegen_Mask_AUTO, true);
|
|
ESP_RETURN_ON_FALSE(ok, ESP_FAIL, TAG, "QR encoding failed for '%s' (too long for max version)", heading);
|
|
|
|
int qr_size = qrcodegen_getSize(qrcode);
|
|
int qr_px = qr_size * QR_MODULE_PX;
|
|
draw_qr(frame, qrcode, center_x - qr_px / 2, y);
|
|
y += qr_px + CAPTION_GAP;
|
|
|
|
for (int i = 0; i < caption_count; i++) {
|
|
epd_draw_text_centered(frame, &Font24, captions[i], center_x, y);
|
|
y += Font24.Height + LINE_GAP;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t qr_onboarding_show(const char *ssid, const char *password, const char *config_url)
|
|
{
|
|
char wifi_payload[96];
|
|
int written = snprintf(wifi_payload, sizeof(wifi_payload), "WIFI:T:WPA;S:%s;P:%s;;", ssid, password);
|
|
ESP_RETURN_ON_FALSE(written > 0 && (size_t)written < sizeof(wifi_payload), ESP_ERR_INVALID_SIZE, TAG,
|
|
"WiFi QR payload too long");
|
|
|
|
/* Allocated on demand rather than statically reserved: this runs once,
|
|
* before WiFi/HTTP buffers exist, so the heap has plenty of headroom,
|
|
* and the space is freed again as soon as the screen is drawn. */
|
|
uint8_t *frame = malloc(EPD_FRAME_BYTES);
|
|
ESP_RETURN_ON_FALSE(frame != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate frame buffer");
|
|
memset(frame, (EPD_COLOR_WHITE << 4) | EPD_COLOR_WHITE, EPD_FRAME_BYTES);
|
|
|
|
epd_draw_text_centered(frame, &Font24, "ESPRESSO FRAME SETUP", EPD_WIDTH / 2, TITLE_Y);
|
|
|
|
/* Divider between the two steps. */
|
|
epd_draw_line(frame, EPD_WIDTH / 2, TITLE_Y + Font24.Height + 14, EPD_WIDTH / 2, EPD_HEIGHT - 10, 1);
|
|
|
|
char ssid_line[64];
|
|
snprintf(ssid_line, sizeof(ssid_line), "SSID: %s", ssid);
|
|
char pass_line[64];
|
|
snprintf(pass_line, sizeof(pass_line), "PASSWORD: %s", password);
|
|
const char *wifi_captions[] = { ssid_line, pass_line };
|
|
|
|
esp_err_t err = draw_qr_step(frame, EPD_WIDTH / 4, COLUMN_TOP_Y, "1. CONNECT TO WIFI", wifi_payload, wifi_captions, 2);
|
|
if (err != ESP_OK) {
|
|
free(frame);
|
|
return err;
|
|
}
|
|
|
|
const char *config_captions[] = { config_url };
|
|
err = draw_qr_step(frame, EPD_WIDTH * 3 / 4, COLUMN_TOP_Y, "2. CONFIGURE DEVICE", config_url, config_captions, 1);
|
|
if (err != ESP_OK) {
|
|
free(frame);
|
|
return err;
|
|
}
|
|
|
|
err = epd_display_buffer(frame, EPD_FRAME_BYTES);
|
|
free(frame);
|
|
|
|
if (err == ESP_OK) {
|
|
/* This just overwrote the panel with non-photo content -- the
|
|
* tracked last-displayed-photo CRC no longer describes what's
|
|
* actually on screen. */
|
|
frame_config_invalidate_last_display_crc32();
|
|
}
|
|
|
|
return err;
|
|
}
|