Add Indicator WiFi setup as eighth home page
This commit is contained in:
1 parent
a38e0370f9
commit
42888e6e3a
20 files changed
+1660
-1311
No files matched your search
+1128
-1128
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user