Redesign setup screen: title + two-step layout with a config QR

Adds a "2. CONFIGURATION" step alongside WiFi setup: a second QR code
linking straight to the captive portal page (http://<ap-ip>/), for anyone
who's joined the AP but wants a one-scan shortcut to the config form
instead of relying on the captive-portal popup. The AP netif is now
created (but not started) before the display renders, since the AP's IP
is fixed at netif creation and needed for this QR before the network is
actually up.

Pulls the pixel/text drawing primitives (previously private to
qr_onboarding.c) out into epd_draw.c/.h so the upcoming status screen can
reuse them instead of duplicating.
This commit is contained in:
2026-07-18 14:07:56 -04:00
parent 1b9226326a
commit ecc42f23c0
5 changed files with 190 additions and 80 deletions
+88
View File
@@ -0,0 +1,88 @@
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "epd_draw.h"
void epd_draw_pixel(uint8_t *frame, int x, int y, epd_color_t color)
{
if (x < 0 || x >= EPD_WIDTH || y < 0 || y >= EPD_HEIGHT) {
return;
}
size_t byte_index = (size_t)y * EPD_BYTES_PER_ROW + (size_t)x / 2;
uint8_t nibble = (uint8_t)color & 0x0F;
if (x % 2 == 0) {
frame[byte_index] = (frame[byte_index] & 0x0F) | (nibble << 4);
} else {
frame[byte_index] = (frame[byte_index] & 0xF0) | nibble;
}
}
/* fonts.h's sFONT tables cover the printable ASCII range starting at ' '
* (0x20), one glyph per character, MSB-first within each row (see
* components/epaper_fonts). */
static bool font_pixel_set(const sFONT *font, char c, int row, int col)
{
if (c < ' ' || (unsigned char)c > 0x7E) {
return false;
}
int bytes_per_row = (font->Width + 7) / 8;
size_t glyph_index = (size_t)(c - ' ');
const uint8_t *glyph = font->table + glyph_index * font->Height * bytes_per_row + (size_t)row * bytes_per_row;
int byte_idx = col / 8;
int bit_in_byte = 7 - (col % 8);
return (glyph[byte_idx] >> bit_in_byte) & 0x1;
}
void epd_draw_text(uint8_t *frame, const sFONT *font, const char *text, int origin_x, int origin_y)
{
int cursor_x = origin_x;
for (const char *p = text; *p != '\0'; p++) {
char c = (char)toupper((unsigned char)*p);
for (int row = 0; row < font->Height; row++) {
for (int col = 0; col < font->Width; col++) {
if (font_pixel_set(font, c, row, col)) {
epd_draw_pixel(frame, cursor_x + col, origin_y + row, EPD_COLOR_BLACK);
}
}
}
cursor_x += font->Width;
}
}
void epd_draw_text_centered(uint8_t *frame, const sFONT *font, const char *text, int center_x, int y)
{
int width = (int)strlen(text) * font->Width;
epd_draw_text(frame, font, text, center_x - width / 2, y);
}
void epd_draw_line(uint8_t *frame, int x0, int y0, int x1, int y1, int thickness)
{
int dx = x1 - x0;
int dy = y1 - y0;
int steps = abs(dx) > abs(dy) ? abs(dx) : abs(dy);
if (steps == 0) {
steps = 1;
}
int half = thickness / 2;
for (int i = 0; i <= steps; i++) {
int x = x0 + dx * i / steps;
int y = y0 + dy * i / steps;
for (int oy = -half; oy <= half; oy++) {
for (int ox = -half; ox <= half; ox++) {
epd_draw_pixel(frame, x + ox, y + oy, EPD_COLOR_BLACK);
}
}
}
}
void epd_draw_checkmark(uint8_t *frame, int x, int y, int size)
{
int thickness = size / 6;
if (thickness < 2) {
thickness = 2;
}
/* Short down-stroke, then long up-stroke -- classic checkmark proportions. */
epd_draw_line(frame, x, y + size / 2, x + size / 3, y + size - thickness, thickness);
epd_draw_line(frame, x + size / 3, y + size - thickness, x + size, y, thickness);
}