diff --git a/data/setup.html b/data/setup.html
index 2473104..347b428 100644
--- a/data/setup.html
+++ b/data/setup.html
@@ -46,13 +46,24 @@
async function scanNetworks() {
try {
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');
- select.innerHTML = networks.map(n =>
+ if (data.length === 0) {
+ select.innerHTML = '';
+ setTimeout(scanNetworks, 3000);
+ return;
+ }
+ select.innerHTML = data.map(n =>
``
).join('');
} catch (e) {
console.error('Scan failed:', e);
+ setTimeout(scanNetworks, 3000);
}
}
diff --git a/platformio.ini b/platformio.ini
index 8867597..1667bce 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -22,6 +22,7 @@ lib_deps =
https://github.com/m5stack/M5PM1.git
mathieucarbou/ESP Async WebServer @ ^3.0.6
bblanchon/ArduinoJson @ ^7.0.0
+ ricmoo/QRCode @ ^0.0.1
lib_ignore =
WebServer
diff --git a/src/display_manager.cpp b/src/display_manager.cpp
index 0d55a7c..4062699 100644
--- a/src/display_manager.cpp
+++ b/src/display_manager.cpp
@@ -21,8 +21,51 @@ void DisplayManager::begin(PowerManager& power) {
Serial.printf("[display] Initialized: %dx%d, rotation=%d\n",
_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) {
diff --git a/src/display_manager.h b/src/display_manager.h
index 028e39e..f7ef462 100644
--- a/src/display_manager.h
+++ b/src/display_manager.h
@@ -2,6 +2,7 @@
#include
#include
+#include
#include "config.h"
#include "image_pipeline.h"
#include "immich_client.h"
@@ -12,6 +13,7 @@ class PowerManager;
class DisplayManager {
public:
void begin(PowerManager& power);
+ void showSetupScreen();
void showImage(const ProcessedImage& img);
void showMessage(const char* title, const char* body);
void showMetadata(const AssetInfo& info, uint8_t metaFlags, MetaPosition pos);
diff --git a/src/main.cpp b/src/main.cpp
index 4c7e5fa..dfe5b88 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -199,19 +199,19 @@ void setup() {
webServer.begin(settingsManager, immichClient, powerManager, wifiManager);
webServer.setActionCallback(webActionCallback);
+ // Single display update after all init is complete
if (wifiManager.isAPMode()) {
- displayManager.showMessage("Setup Required",
- "Connect to PaperColor-Setup WiFi");
+ displayManager.showSetupScreen();
} else {
displayManager.showMessage("Immich Frame", "Loading photos...");
// Create display task on Core 1
xTaskCreatePinnedToCore(displayTask, "display", 32768, nullptr, 1,
&displayTaskHandle, 1);
- }
- // Enable light sleep for power saving
- powerManager.enableLightSleep();
+ // Enable light sleep only in station mode (not supported in AP mode)
+ powerManager.enableLightSleep();
+ }
Serial.println("[main] Boot complete");
}
diff --git a/src/web_server.cpp b/src/web_server.cpp
index 59f6de3..9eb7186 100644
--- a/src/web_server.cpp
+++ b/src/web_server.cpp
@@ -46,9 +46,25 @@ void AppWebServer::setupCaptivePortal() {
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) {
- 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;
JsonArray arr = doc.to();
for (int i = 0; i < n; i++) {
@@ -59,6 +75,11 @@ void AppWebServer::setupCaptivePortal() {
}
String response;
serializeJson(doc, response);
+
+ // Start a fresh scan for next request
+ WiFi.scanDelete();
+ WiFi.scanNetworks(true);
+
request->send(200, "application/json", response);
});