#include #include #include #include "esp_check.h" #include "esp_log.h" #include "epd7in3e.h" #include "fonts.h" #include "qrcodegen.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 TEXT_MARGIN_X 40 #define TEXT_LINE_GAP 16 static void set_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; } } 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++) { set_pixel(frame, origin_x + x * QR_MODULE_PX + dx, origin_y + y * QR_MODULE_PX + dy, color); } } } } } /* 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; } static int 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)) { set_pixel(frame, cursor_x + col, origin_y + row, EPD_COLOR_BLACK); } } } cursor_x += font->Width; } return cursor_x; } esp_err_t qr_onboarding_show(const char *ssid, const char *password) { char payload[96]; int written = snprintf(payload, sizeof(payload), "WIFI:T:WPA;S:%s;P:%s;;", ssid, password); ESP_RETURN_ON_FALSE(written > 0 && (size_t)written < sizeof(payload), ESP_ERR_INVALID_SIZE, TAG, "QR payload too long"); 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 (payload too long for max version)"); /* 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); int qr_size = qrcodegen_getSize(qrcode); int qr_px = qr_size * QR_MODULE_PX; int qr_x = (EPD_WIDTH - qr_px) / 2; int qr_y = 24; draw_qr(frame, qrcode, qr_x, qr_y); int text_y = qr_y + qr_px + 32; char ssid_line[64]; snprintf(ssid_line, sizeof(ssid_line), "SSID: %s", ssid); draw_text(frame, &Font24, ssid_line, TEXT_MARGIN_X, text_y); char pass_line[64]; snprintf(pass_line, sizeof(pass_line), "PASSWORD: %s", password); draw_text(frame, &Font24, pass_line, TEXT_MARGIN_X, text_y + Font24.Height + TEXT_LINE_GAP); esp_err_t err = epd_display_buffer(frame, EPD_FRAME_BYTES); free(frame); return err; }