fix: single boot refresh with QR code, async WiFi scan, skip light sleep in AP mode
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -46,13 +46,24 @@
|
|||||||
async function scanNetworks() {
|
async function scanNetworks() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/wifi/scan');
|
const res = await fetch('/api/wifi/scan');
|
||||||
const networks = await res.json();
|
const data = await res.json();
|
||||||
|
if (data.scanning) {
|
||||||
|
// Scan in progress — retry after 2s
|
||||||
|
setTimeout(scanNetworks, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const select = document.getElementById('wifiSsid');
|
const select = document.getElementById('wifiSsid');
|
||||||
select.innerHTML = networks.map(n =>
|
if (data.length === 0) {
|
||||||
|
select.innerHTML = '<option value="">No networks found</option>';
|
||||||
|
setTimeout(scanNetworks, 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
select.innerHTML = data.map(n =>
|
||||||
`<option value="${n.ssid}">${n.ssid} (${n.rssi}dBm)</option>`
|
`<option value="${n.ssid}">${n.ssid} (${n.rssi}dBm)</option>`
|
||||||
).join('');
|
).join('');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Scan failed:', e);
|
console.error('Scan failed:', e);
|
||||||
|
setTimeout(scanNetworks, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ lib_deps =
|
|||||||
https://github.com/m5stack/M5PM1.git
|
https://github.com/m5stack/M5PM1.git
|
||||||
mathieucarbou/ESP Async WebServer @ ^3.0.6
|
mathieucarbou/ESP Async WebServer @ ^3.0.6
|
||||||
bblanchon/ArduinoJson @ ^7.0.0
|
bblanchon/ArduinoJson @ ^7.0.0
|
||||||
|
ricmoo/QRCode @ ^0.0.1
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
WebServer
|
WebServer
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,51 @@ void DisplayManager::begin(PowerManager& power) {
|
|||||||
|
|
||||||
Serial.printf("[display] Initialized: %dx%d, rotation=%d\n",
|
Serial.printf("[display] Initialized: %dx%d, rotation=%d\n",
|
||||||
_display->width(), _display->height(), _display->getRotation());
|
_display->width(), _display->height(), _display->getRotation());
|
||||||
|
}
|
||||||
|
|
||||||
showMessage("Immich Frame", "Starting up...");
|
void DisplayManager::showSetupScreen() {
|
||||||
|
_power->enableEPDPower();
|
||||||
|
|
||||||
|
_display->fillScreen(TFT_WHITE);
|
||||||
|
|
||||||
|
// Generate QR code for WiFi auto-connect
|
||||||
|
// Format: WIFI:T:nopass;S:PaperColor-Setup;;
|
||||||
|
const char* qrData = "WIFI:T:nopass;S:PaperColor-Setup;;";
|
||||||
|
QRCode qrcode;
|
||||||
|
uint8_t qrcodeData[qrcode_getBufferSize(6)];
|
||||||
|
qrcode_initText(&qrcode, qrcodeData, 6, ECC_LOW, qrData);
|
||||||
|
|
||||||
|
// Draw QR code centered vertically, left-of-center horizontally
|
||||||
|
int qrSize = qrcode.size;
|
||||||
|
int scale = 4; // pixels per QR module
|
||||||
|
int qrPixels = qrSize * scale;
|
||||||
|
int qrX = (DISPLAY_WIDTH / 3) - (qrPixels / 2);
|
||||||
|
int qrY = (DISPLAY_HEIGHT - qrPixels) / 2;
|
||||||
|
|
||||||
|
for (int y = 0; y < qrSize; y++) {
|
||||||
|
for (int x = 0; x < qrSize; x++) {
|
||||||
|
uint16_t color = qrcode_getModule(&qrcode, x, y) ? TFT_BLACK : TFT_WHITE;
|
||||||
|
_display->fillRect(qrX + x * scale, qrY + y * scale, scale, scale, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw text instructions to the right of QR code
|
||||||
|
int textX = DISPLAY_WIDTH / 2 + 40;
|
||||||
|
_display->setTextColor(TFT_BLACK);
|
||||||
|
_display->setTextDatum(middle_left);
|
||||||
|
_display->setTextSize(2);
|
||||||
|
_display->drawString("PaperColor", textX, DISPLAY_HEIGHT / 2 - 60);
|
||||||
|
_display->setTextSize(1);
|
||||||
|
_display->drawString("Scan QR to connect", textX, DISPLAY_HEIGHT / 2 - 20);
|
||||||
|
_display->drawString("or join WiFi:", textX, DISPLAY_HEIGHT / 2 + 10);
|
||||||
|
_display->setTextSize(1.5);
|
||||||
|
_display->drawString("PaperColor-Setup", textX, DISPLAY_HEIGHT / 2 + 40);
|
||||||
|
_display->setTextSize(1);
|
||||||
|
_display->drawString("Then open:", textX, DISPLAY_HEIGHT / 2 + 70);
|
||||||
|
_display->drawString("192.168.4.1", textX, DISPLAY_HEIGHT / 2 + 95);
|
||||||
|
|
||||||
|
triggerRefresh();
|
||||||
|
_power->disableEPDPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::showImage(const ProcessedImage& img) {
|
void DisplayManager::showImage(const ProcessedImage& img) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <M5GFX.h>
|
#include <M5GFX.h>
|
||||||
|
#include <qrcode.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "image_pipeline.h"
|
#include "image_pipeline.h"
|
||||||
#include "immich_client.h"
|
#include "immich_client.h"
|
||||||
@@ -12,6 +13,7 @@ class PowerManager;
|
|||||||
class DisplayManager {
|
class DisplayManager {
|
||||||
public:
|
public:
|
||||||
void begin(PowerManager& power);
|
void begin(PowerManager& power);
|
||||||
|
void showSetupScreen();
|
||||||
void showImage(const ProcessedImage& img);
|
void showImage(const ProcessedImage& img);
|
||||||
void showMessage(const char* title, const char* body);
|
void showMessage(const char* title, const char* body);
|
||||||
void showMetadata(const AssetInfo& info, uint8_t metaFlags, MetaPosition pos);
|
void showMetadata(const AssetInfo& info, uint8_t metaFlags, MetaPosition pos);
|
||||||
|
|||||||
10
src/main.cpp
10
src/main.cpp
@@ -199,19 +199,19 @@ void setup() {
|
|||||||
webServer.begin(settingsManager, immichClient, powerManager, wifiManager);
|
webServer.begin(settingsManager, immichClient, powerManager, wifiManager);
|
||||||
webServer.setActionCallback(webActionCallback);
|
webServer.setActionCallback(webActionCallback);
|
||||||
|
|
||||||
|
// Single display update after all init is complete
|
||||||
if (wifiManager.isAPMode()) {
|
if (wifiManager.isAPMode()) {
|
||||||
displayManager.showMessage("Setup Required",
|
displayManager.showSetupScreen();
|
||||||
"Connect to PaperColor-Setup WiFi");
|
|
||||||
} else {
|
} else {
|
||||||
displayManager.showMessage("Immich Frame", "Loading photos...");
|
displayManager.showMessage("Immich Frame", "Loading photos...");
|
||||||
|
|
||||||
// Create display task on Core 1
|
// Create display task on Core 1
|
||||||
xTaskCreatePinnedToCore(displayTask, "display", 32768, nullptr, 1,
|
xTaskCreatePinnedToCore(displayTask, "display", 32768, nullptr, 1,
|
||||||
&displayTaskHandle, 1);
|
&displayTaskHandle, 1);
|
||||||
}
|
|
||||||
|
|
||||||
// Enable light sleep for power saving
|
// Enable light sleep only in station mode (not supported in AP mode)
|
||||||
powerManager.enableLightSleep();
|
powerManager.enableLightSleep();
|
||||||
|
}
|
||||||
|
|
||||||
Serial.println("[main] Boot complete");
|
Serial.println("[main] Boot complete");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,25 @@ void AppWebServer::setupCaptivePortal() {
|
|||||||
request->send(LittleFS, "/setup.html", "text/html");
|
request->send(LittleFS, "/setup.html", "text/html");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle WiFi scan
|
// Kick off initial async WiFi scan
|
||||||
|
WiFi.scanNetworks(true);
|
||||||
|
|
||||||
|
// Handle WiFi scan (non-blocking — returns cached results or "scanning" status)
|
||||||
_server.on("/api/wifi/scan", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server.on("/api/wifi/scan", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
int n = WiFi.scanNetworks();
|
int n = WiFi.scanComplete();
|
||||||
|
if (n == WIFI_SCAN_RUNNING) {
|
||||||
|
// Scan in progress — tell client to retry
|
||||||
|
request->send(200, "application/json", "{\"scanning\":true}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == WIFI_SCAN_FAILED) {
|
||||||
|
// No scan started or failed — kick one off
|
||||||
|
WiFi.scanNetworks(true);
|
||||||
|
request->send(200, "application/json", "{\"scanning\":true}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan complete — return results
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
JsonArray arr = doc.to<JsonArray>();
|
JsonArray arr = doc.to<JsonArray>();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
@@ -59,6 +75,11 @@ void AppWebServer::setupCaptivePortal() {
|
|||||||
}
|
}
|
||||||
String response;
|
String response;
|
||||||
serializeJson(doc, response);
|
serializeJson(doc, response);
|
||||||
|
|
||||||
|
// Start a fresh scan for next request
|
||||||
|
WiFi.scanDelete();
|
||||||
|
WiFi.scanNetworks(true);
|
||||||
|
|
||||||
request->send(200, "application/json", response);
|
request->send(200, "application/json", response);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user