Add Indicator WiFi setup as eighth home page

This commit is contained in:
mikecarper committed 2026-08-31 17:31:48 -07:00
1 parent a38e0370f9
commit 42888e6e3a
20 files changed
+1660 -1311

No files matched your search

File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -2,6 +2,9 @@
#ifdef WITH_WEBCONFIG
static_assert(sizeof(WEBCONFIG_AP_PREFIX) <= 28,
"WEBCONFIG_AP_PREFIX must be at most 27 bytes");
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
@@ -773,7 +776,8 @@ bool WebConfigServer::startSetupMode(char reply[]) {
WiFi.setAutoReconnect(false);
bool disconnect_ok = WiFi.disconnect(false, true);
delay(100);
snprintf(_ap_ssid, sizeof(_ap_ssid), "MeshCore-Setup-%02X%02X", _pub_key[0], _pub_key[1]);
snprintf(_ap_ssid, sizeof(_ap_ssid), "%s-%02X%02X",
WEBCONFIG_AP_PREFIX, _pub_key[0], _pub_key[1]);
bool ap_ok = false;
for (uint8_t attempt = 1; attempt <= 6 && !ap_ok; ++attempt) {
#ifdef WEBCONFIG_AP_PASSWORD
+3
View File
@@ -52,6 +52,9 @@ class DNSServer;
#ifndef WEBCONFIG_SESSION_TTL_MS
#define WEBCONFIG_SESSION_TTL_MS (20UL * 60UL * 1000UL)
#endif
#ifndef WEBCONFIG_AP_PREFIX
#define WEBCONFIG_AP_PREFIX "MeshCore-Setup"
#endif
class WebConfigServer {
public:
+10
View File
@@ -67,6 +67,16 @@ public:
virtual void fillRect(int x, int y, int w, int h) = 0;
virtual void drawRect(int x, int y, int w, int h) = 0;
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
// Optional QR renderer. Coordinates and size use the same logical units as
// the rest of the UI. Drivers without QR support leave the caller's
// fallback layout in place.
virtual bool drawQrCode(const char* text, int x, int y, int size) {
(void)text;
(void)x;
(void)y;
(void)size;
return false;
}
virtual uint16_t getTextWidth(const char* str) = 0;
virtual bool getTouch(int* x, int* y) {
(void)x;
+59
View File
@@ -1,6 +1,7 @@
#include "LGFXDisplay.h"
#include "ColorTheme.h"
#include "IndicatorRenderProfile.h"
#include <lgfx/utility/lgfx_qrcode.h>
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 1
@@ -345,6 +346,64 @@ void LGFXDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
}
}
bool LGFXDisplay::drawQrCode(const char* text, int x, int y, int size) {
if (text == nullptr || text[0] == 0 || x < 0 || y < 0 || size <= 0
|| size > width() || size > height()
|| x > width() - size || y > height() - size) {
return false;
}
// LovyanGFX's convenience renderer hardcodes pure colors. On the
// Indicator's indexed canvas its light color maps to palette slot 15,
// which is deliberately reserved for transparent emoji pixels. Generate
// the matrix directly so both colors use stable semantic palette entries.
static constexpr uint8_t max_version = 10;
static constexpr uint8_t max_modules = 4 * max_version + 17;
uint8_t modules[(max_modules * max_modules + 7) / 8];
QRCode qr = {};
bool initialized = false;
for (uint8_t version = 1; version <= max_version; ++version) {
if (lgfx_qrcode_initText(&qr, modules, version, ECC_LOW, text) == 0) {
initialized = true;
break;
}
}
if (!initialized) return false;
static constexpr int quiet_zone = 4;
const int scaled_size = size * _coordinateScale;
const int module_size = scaled_size / (qr.size + quiet_zone * 2);
if (module_size <= 0) return false;
const int used_size = module_size * (qr.size + quiet_zone * 2);
const int left = x * _coordinateScale + (scaled_size - used_size) / 2;
const int top = y * _coordinateScale + (scaled_size - used_size) / 2;
const uint32_t light = renderColor(UIColor::primary_txt);
const uint32_t dark = renderColor(UIColor::window_bkg);
buffer.fillRect(x * _coordinateScale, y * _coordinateScale,
scaled_size, scaled_size, light);
const int matrix_left = left + quiet_zone * module_size;
const int matrix_top = top + quiet_zone * module_size;
for (uint_fast8_t row = 0; row < qr.size; ++row) {
int run_start = -1;
for (uint_fast16_t column = 0; column <= qr.size; ++column) {
const bool filled = column < qr.size
&& lgfx_qrcode_getModule(&qr, column, row);
if (filled && run_start < 0) {
run_start = column;
} else if (!filled && run_start >= 0) {
buffer.fillRect(
matrix_left + run_start * module_size,
matrix_top + row * module_size,
(column - run_start) * module_size,
module_size, dark);
run_start = -1;
}
}
}
return true;
}
uint16_t LGFXDisplay::getTextWidth(const char* str) {
String mapped = mapText(str);
return (buffer.textWidth(mapped.c_str()) + _coordinateScale - 1)
+5
View File
@@ -69,6 +69,10 @@ public:
LGFXDisplay(int w, int h, LGFX_Device &disp)
: DisplayDriver(w/(UI_ZOOM*UI_COORD_SCALE),
h/(UI_ZOOM*UI_COORD_SCALE)), display(&disp) {}
LGFXDisplay(int logical_w, int logical_h, uint8_t coordinate_scale,
float output_zoom, LGFX_Device &disp)
: DisplayDriver(logical_w, logical_h), display(&disp),
_coordinateScale(coordinate_scale), _outputZoom(output_zoom) {}
bool begin();
bool isOn() override { return _isOn; }
void turnOn() override;
@@ -86,6 +90,7 @@ public:
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
bool drawQrCode(const char* text, int x, int y, int size) override;
uint16_t getTextWidth(const char* str) override;
void translateUTF8ToBlocks(char* dest, const char* src,
size_t dest_size) override;
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <stddef.h>
#include <string.h>
namespace mesh {
namespace ui {
inline bool appendWiFiQrText(char*& output, size_t& remaining,
const char* value, bool escape) {
if (value == nullptr) return false;
while (*value != 0) {
const bool special = escape && strchr("\\;,\":", *value) != nullptr;
const size_t required = special ? 2 : 1;
if (remaining <= required) {
*output = 0;
return false;
}
if (special) {
*output++ = '\\';
--remaining;
}
*output++ = *value++;
--remaining;
}
*output = 0;
return true;
}
// Build the de-facto WiFi QR payload understood by Android and iOS cameras.
// A null or empty password denotes an open network.
inline bool buildWiFiSetupQrPayload(char* payload, size_t payload_size,
const char* ssid,
const char* password = nullptr) {
if (payload == nullptr || payload_size == 0
|| ssid == nullptr || ssid[0] == 0) {
return false;
}
char* output = payload;
size_t remaining = payload_size;
payload[0] = 0;
const bool protected_network = password != nullptr && password[0] != 0;
if (!appendWiFiQrText(output, remaining,
protected_network ? "WIFI:T:WPA;S:"
: "WIFI:T:nopass;S:",
false)
|| !appendWiFiQrText(output, remaining, ssid, true)) {
return false;
}
if (protected_network
&& (!appendWiFiQrText(output, remaining, ";P:", false)
|| !appendWiFiQrText(output, remaining, password, true))) {
return false;
}
return appendWiFiQrText(output, remaining, ";;", false);
}
} // namespace ui
} // namespace mesh