mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 22:42:25 +00:00
Add WiFi setup QR to ESP32 displays
This commit is contained in:
1 parent
8ef309a299
commit
1264f62e9d
11 files changed
+165
-3
No files matched your search
@@ -70,6 +70,7 @@ MultiSerialInterface interface_manager;
|
||||
#include <helpers/CompanionWiFiNtpPolicy.h>
|
||||
#include <helpers/ui/DisplayTextLayout.h>
|
||||
#include <helpers/ui/IndicatorRenderProfile.h>
|
||||
#include <helpers/ui/WiFiSetupQrDisplay.h>
|
||||
#include <helpers/esp32/SntpOperationCoordinator.h>
|
||||
#include <helpers/esp32/TlsClockValidity.h>
|
||||
#include <helpers/esp32/WiFiRadioPolicy.h>
|
||||
@@ -1286,6 +1287,11 @@ void halt() {
|
||||
char setup_ip[16] = {0};
|
||||
if (WebConfigServer::getSetupInfo(setup_ssid, sizeof(setup_ssid),
|
||||
setup_ip, sizeof(setup_ip))) {
|
||||
if (mesh::ui::drawWiFiSetupQr(
|
||||
*companion_setup_display, setup_ssid, setup_ip)) {
|
||||
companion_setup_display->endFrame();
|
||||
return;
|
||||
}
|
||||
if (hasLargeCompanionSetupDisplay()) {
|
||||
renderLargeCompanionSetupDisplay(
|
||||
"WebUI setup", "Join open WiFi:", setup_ssid, setup_ip, false);
|
||||
@@ -1749,7 +1755,11 @@ void setup() {
|
||||
}
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
companion_display_available = disp != NULL;
|
||||
#if defined(SENSECAP_INDICATOR_LORA)
|
||||
companion_display_begin_status = display.beginStatus();
|
||||
#else
|
||||
companion_display_begin_status = companion_display_available ? 1 : 0;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "target.h"
|
||||
#include <Arduino.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/ui/WiFiSetupQrDisplay.h>
|
||||
|
||||
#ifdef DISPLAY_REDRAW_ON_CHANGE
|
||||
#include <helpers/ui/DisplayFrameSignature.h>
|
||||
@@ -175,6 +176,9 @@ void UITask::renderCurrScreen() {
|
||||
char wc_ssid[33], wc_ip[16];
|
||||
if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) {
|
||||
// setup portal active: show join instructions instead of the home screen
|
||||
if (mesh::ui::drawWiFiSetupQr(*_display, wc_ssid, wc_ip)) {
|
||||
return;
|
||||
}
|
||||
_display->setTextSize(1);
|
||||
_display->setColor(UIColor::corp_blue);
|
||||
_display->setCursor(0, 0);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#endif
|
||||
#include <Arduino.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/ui/WiFiSetupQrDisplay.h>
|
||||
|
||||
#ifdef DISPLAY_REDRAW_ON_CHANGE
|
||||
#include <helpers/ui/DisplayFrameSignature.h>
|
||||
@@ -175,6 +176,9 @@ void UITask::renderCurrScreen() {
|
||||
char wc_ssid[33], wc_ip[16];
|
||||
if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) {
|
||||
// setup portal active: show join instructions instead of the home screen
|
||||
if (mesh::ui::drawWiFiSetupQr(*_display, wc_ssid, wc_ip)) {
|
||||
return;
|
||||
}
|
||||
_display->setTextSize(1);
|
||||
_display->setColor(UIColor::corp_blue);
|
||||
_display->setCursor(0, 0);
|
||||
|
||||
@@ -89,7 +89,10 @@ lib_deps = ${arduino_base.lib_deps}
|
||||
ESP32Async/AsyncTCP @ 3.5.0
|
||||
ESP32Async/ESPAsyncWebServer @ 3.12.0
|
||||
bblanchon/ArduinoJson @ 7.4.3
|
||||
; Generic WiFi-join QR rendering is linked only by ESP32 display builds.
|
||||
ricmoo/QRCode @ 0.0.1
|
||||
build_src_filter = ${arduino_base.build_src_filter}
|
||||
+<helpers/ui/DisplayDriver.cpp>
|
||||
+<helpers/esp32/WebConfigServer.cpp>
|
||||
+<helpers/esp32/PortableErrorStrings.cpp>
|
||||
+<helpers/esp32/WiFiOtaSeeder.cpp>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <helpers/WebConfigBatch.h>
|
||||
#include <helpers/WiFiPowerSave.h>
|
||||
#include <helpers/WiFiReconnectPolicy.h>
|
||||
#include <helpers/ui/DisplayBuildFlags.h>
|
||||
|
||||
class AsyncWebServer;
|
||||
class AsyncWebServerRequest;
|
||||
@@ -53,7 +54,12 @@ class DNSServer;
|
||||
#define WEBCONFIG_SESSION_TTL_MS (20UL * 60UL * 1000UL)
|
||||
#endif
|
||||
#ifndef WEBCONFIG_AP_PREFIX
|
||||
#define WEBCONFIG_AP_PREFIX "MeshCore-Setup"
|
||||
#if defined(MESHCORE_HAS_REAL_DISPLAY)
|
||||
// Keeps an open-network WiFi QR in Version 1 on 64-pixel-high screens.
|
||||
#define WEBCONFIG_AP_PREFIX "MC"
|
||||
#else
|
||||
#define WEBCONFIG_AP_PREFIX "MeshCore-Setup"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class WebConfigServer {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
// DISPLAY_CLASS is supplied as a build token (for example SSD1306Display).
|
||||
// Convert it to a boolean that shared ESP32 code can use without including a
|
||||
// concrete driver. Undefined identifiers evaluate to zero in a #if expression.
|
||||
#define MESHCORE_NULL_DISPLAY_CLASS_NullDisplayDriver 1
|
||||
#define MESHCORE_DISPLAY_TOKEN_JOIN_INNER(prefix, name) prefix##name
|
||||
#define MESHCORE_DISPLAY_TOKEN_JOIN(prefix, name) \
|
||||
MESHCORE_DISPLAY_TOKEN_JOIN_INNER(prefix, name)
|
||||
|
||||
#if defined(DISPLAY_CLASS) \
|
||||
&& !MESHCORE_DISPLAY_TOKEN_JOIN(MESHCORE_NULL_DISPLAY_CLASS_, DISPLAY_CLASS)
|
||||
#define MESHCORE_HAS_REAL_DISPLAY 1
|
||||
#endif
|
||||
|
||||
#undef MESHCORE_DISPLAY_TOKEN_JOIN
|
||||
#undef MESHCORE_DISPLAY_TOKEN_JOIN_INNER
|
||||
#undef MESHCORE_NULL_DISPLAY_CLASS_NullDisplayDriver
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(MESHCORE_HAS_REAL_DISPLAY)
|
||||
|
||||
#include <qrcode.h>
|
||||
|
||||
bool DisplayDriver::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;
|
||||
}
|
||||
|
||||
// A 64-pixel square can hold at most Version 9 with the QR-standard
|
||||
// four-module quiet zone. Setup AP payloads are deliberately kept at
|
||||
// Version 1 on small screens so each module is 2x2 physical pixels.
|
||||
static constexpr uint8_t max_version = 9;
|
||||
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 (qrcode_initText(&qr, modules, version, ECC_LOW, text) == 0) {
|
||||
initialized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!initialized) return false;
|
||||
|
||||
static constexpr int quiet_zone = 4;
|
||||
const int module_size = 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 + (size - used_size) / 2;
|
||||
const int top = y + (size - used_size) / 2;
|
||||
const int matrix_left = left + quiet_zone * module_size;
|
||||
const int matrix_top = top + quiet_zone * module_size;
|
||||
|
||||
// Dark-theme OLED/TFT drivers use a dark window and light popup text;
|
||||
// e-paper reverses that convention. Always emit the conventional dark
|
||||
// matrix on a light background instead of relying on scanner inversion.
|
||||
const ColorVal light = isEink() ? UIColor::window_bkg : UIColor::popup_txt;
|
||||
const ColorVal dark = isEink() ? UIColor::primary_txt : UIColor::window_bkg;
|
||||
setColor(light);
|
||||
fillRect(x, y, size, size);
|
||||
setColor(dark);
|
||||
for (uint8_t row = 0; row < qr.size; ++row) {
|
||||
for (uint8_t column = 0; column < qr.size; ++column) {
|
||||
if (qrcode_getModule(&qr, column, row)) {
|
||||
fillRect(matrix_left + column * module_size,
|
||||
matrix_top + row * module_size,
|
||||
module_size, module_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayBuildFlags.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -70,6 +72,9 @@ public:
|
||||
// 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.
|
||||
#if defined(ESP32_PLATFORM) && defined(MESHCORE_HAS_REAL_DISPLAY)
|
||||
virtual bool drawQrCode(const char* text, int x, int y, int size);
|
||||
#else
|
||||
virtual bool drawQrCode(const char* text, int x, int y, int size) {
|
||||
(void)text;
|
||||
(void)x;
|
||||
@@ -77,6 +82,7 @@ public:
|
||||
(void)size;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
virtual uint16_t getTextWidth(const char* str) = 0;
|
||||
virtual bool getTouch(int* x, int* y) {
|
||||
(void)x;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include "WiFiSetupQrPayload.h"
|
||||
|
||||
namespace mesh {
|
||||
namespace ui {
|
||||
|
||||
// Render a setup-AP join QR at the largest size that still leaves the right
|
||||
// half of compact displays available for a short textual fallback/reference.
|
||||
// Returns false when the payload or panel geometry cannot be represented.
|
||||
inline bool drawWiFiSetupQr(DisplayDriver& display, const char* ssid,
|
||||
const char* address) {
|
||||
char payload[256];
|
||||
const char* password = nullptr;
|
||||
#ifdef WEBCONFIG_AP_PASSWORD
|
||||
password = WEBCONFIG_AP_PASSWORD;
|
||||
#endif
|
||||
if (!buildWiFiSetupQrPayload(
|
||||
payload, sizeof(payload), ssid, password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int qr_size = display.height() < display.width() / 2
|
||||
? display.height() : display.width() / 2;
|
||||
if (!display.drawQrCode(payload, 0, 0, qr_size)) return false;
|
||||
|
||||
const int text_x = qr_size + 4;
|
||||
display.setTextSize(1);
|
||||
display.setColor(UIColor::primary_txt);
|
||||
display.setCursor(text_x, 2);
|
||||
display.print("SCAN TO");
|
||||
display.setCursor(text_x, 12);
|
||||
display.print("JOIN:");
|
||||
display.drawTextEllipsized(
|
||||
text_x, 22, display.width() - text_x, ssid);
|
||||
display.setCursor(text_x, 37);
|
||||
display.print("THEN OPEN");
|
||||
display.drawTextEllipsized(
|
||||
text_x, 48, display.width() - text_x, address);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace mesh
|
||||
@@ -44,7 +44,7 @@ inline bool buildWiFiSetupQrPayload(char* payload, size_t payload_size,
|
||||
|
||||
if (!appendWiFiQrText(output, remaining,
|
||||
protected_network ? "WIFI:T:WPA;S:"
|
||||
: "WIFI:T:nopass;S:",
|
||||
: "WIFI:S:",
|
||||
false)
|
||||
|| !appendWiFiQrText(output, remaining, ssid, true)) {
|
||||
return false;
|
||||
|
||||
@@ -109,7 +109,14 @@ TEST(WiFiSetupQrPayload, EncodesOpenSetupNetwork) {
|
||||
char payload[64];
|
||||
EXPECT_TRUE(mesh::ui::buildWiFiSetupQrPayload(
|
||||
payload, sizeof(payload), "MC-Set-90DF"));
|
||||
EXPECT_STREQ("WIFI:T:nopass;S:MC-Set-90DF;;", payload);
|
||||
EXPECT_STREQ("WIFI:S:MC-Set-90DF;;", payload);
|
||||
|
||||
char small_payload[32];
|
||||
EXPECT_TRUE(mesh::ui::buildWiFiSetupQrPayload(
|
||||
small_payload, sizeof(small_payload), "MC-90DF"));
|
||||
EXPECT_STREQ("WIFI:S:MC-90DF;;", small_payload);
|
||||
// MC-XXXX uses 16 payload bytes, within QR Version 1-L's 17-byte capacity.
|
||||
EXPECT_LE(strlen(small_payload), 17U);
|
||||
}
|
||||
|
||||
TEST(WiFiSetupQrPayload, EscapesProtectedNetworkFields) {
|
||||
|
||||
Reference in new issue
Block a user