110 lines
4.0 KiB
C
110 lines
4.0 KiB
C
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
|
|
#include "epd7in3e.h"
|
|
#include "epd_draw.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 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);
|
|
|
|
return err;
|
|
}
|