From d1599a726d661ffbc3faa98328f114cad2a06670 Mon Sep 17 00:00:00 2001 From: Matt Edholm Date: Sat, 9 May 2026 12:48:11 -0400 Subject: [PATCH] fix(provisioning): mirror aqua-iq's working AP pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After repeated 200-OK / DHCP-option / DNS-hijack permutations failed to make the iOS captive banner fire reliably, port the configuration that empirically works on the aqua-iq Pi to the ESP32: * WPA2-PSK secured AP (was open). iOS handles secured-network captive-portal detection more aggressively than open networks. The PSK ('pictureframe') is baked into both firmware and the WIFI: QR so the user never types it — there's no real secret value here. * Explicit channel 6 (was the softAP default of channel 1). Channel 6 is the "middle" 2.4 GHz channel and tends to be less contested in dense environments; aqua-iq picks it for the same reason. * 1.5 s settle delay after softAP. The radio + DHCP server need a beat before they're ready to handle a phone that joins the moment the SSID is broadcast (aqua-iq sleeps 3 s for NetworkManager+dnsmasq to fully initialize; the ESP softAP stack is faster but a small pad still kills race conditions). * CNA paths revert to 302 redirect → "/". This is what aqua-iq does and what WiFiManager does. Serving the portal HTML inline at 200 on these endpoints (the previous attempt) didn't reliably trigger the iOS banner. The redirect is what iOS / Android / Windows look for. QR string format updates from WIFI:S:NAME;T:nopass;; to WIFI:T:WPA;S:NAME;P:pictureframe;; — phones consume both, but the WPA variant is needed now that the AP requires a password. Bg image's format-string footnote regenerated to match. Co-Authored-By: Claude Opus 4.7 (1M context) --- data/waveshare73-v1/ap_bg.bin | Bin 192000 -> 192000 bytes data/waveshare73-v1/ap_bg_preview.png | Bin 12241 -> 12318 bytes data/waveshare73-v1/ap_bg_retry.bin | Bin 192000 -> 192000 bytes data/waveshare73-v1/ap_bg_retry_preview.png | Bin 11864 -> 11941 bytes scripts/gen_screens.py | 2 +- src/config.h | 9 ++++ src/main.cpp | 53 ++++++++++++++------ 7 files changed, 49 insertions(+), 15 deletions(-) diff --git a/data/waveshare73-v1/ap_bg.bin b/data/waveshare73-v1/ap_bg.bin index c6b04f7cc93644a8cc79297d87b4115dfe1069ca..7852d38148cae6e125e05ee1587a55fa8adae3f5 100644 GIT binary patch delta 1436 zcmb7Ey=qiJ6y7_3t8$n_C?03lWq z!ajyoU~PN}zn>X%FW7i@nVIwVo$s7E_n!Q`af1J8^DP`5hM1Nw4jwF@UyQrYhhe>! zM!Ah`YI9DpZL#IlQiPJn2nkxQF=rU?XyE}5l)#(bqiePgcLLe1o6o`%rMvqote@3% zFfyGNr%M12eBGZ!<1Y0O|dSjc)^8Y1*jtEIgznrp3)V9Dl7o%+tr(1-yBmJR&r4MsgPEh5N%wC){qS-`^stY~;Yv zL;wVW9~7KJDRa7Q>ovgHs@WpD^DpniM$hjI5|7M@O&2sr#EZCgKCH8Sc!{h{fhp}N%)$T$k<6l*fE+pvQ^qwa07=l0 zyd-PBa#sq~b}|`m8=9vLo0dD4$Fc(yA$c55lF?cR6=bY(6bL!;79ITI15a%DaiNK_ zG_kR-;Nj-P6k1vz90ZCIRH1sQ3_M5!yt5+*Xp&1SoP=9-7Iz2>C2JKwUJ=G<60jVe z56m~7X};qeJ4A8x<}Q(ZB|1xp7=}+Lu&j+=gpRMo99U9fj30yEX)5%2gqAc+#x4YP z1RAOJ4g#fLke-28o5d1XH{(SpG@4>PYNSG7a`=3{Q@jU{GIp5pMdJ6bkIhtqe}@W(e2!vv0)!6i XE19X%UZ81RLLOSbRsHQV^Vs_VRV(L{ diff --git a/data/waveshare73-v1/ap_bg_preview.png b/data/waveshare73-v1/ap_bg_preview.png index d3caf282712d8739015b8a685674a94a315e4424..e4b0cc63eafe200e448147e6880f7d5eda68568a 100644 GIT binary patch delta 10883 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

Nf_T{B6`39^j4jAec zjHf@yZcN1K;kf{m7pdhgd%P$kb7my^8RpXh zg!6w1$CyB{Z(+*V1n_k_U4@PZPtHN_?Q69B=gV}*R9TQ5TYH(&gv7RaGnXKyLikV8B z-3i`jZb=>ah<#-iJW25W)2GkxKl_i2NGv86x74^4+X3Suz2}Dq86^ zcqDtA=GiBGyQ$uO_aL9mn8t;}7GswTo+qjrygB9R z7?VZZ5mPq-cO#2FhK`>{NLj;lHyel%ra7U-8=lKy81uZ$e)#os9^7WI-g&Bq7VDc| zB8F30qTHsZ=Y14pWL2tP)UYPc&MfAucNNs>W0dzVE0qMFDS3O${)OIn3`#2EZlc*o zIq=LakpcUn`}~AMv8og>OS&T7NvJ4uME~shC|l3M$J@PZ4T=$+bA!^7yJ5k@#qPk zr~g^Roj?z%sYw_)*E|nBL1GELbS+0Am#rddX_gyT%LsGw=SslaU?w{=41Oiqp6A2a z_XtgU)!`KlL*buhz^LtKB##ZMW3;VCz%07nJWt%vq>0AFc5U<>d_<(yS(D;$X`W%6cE!JpZ>_NF&W-Y1HV?JP+-eB+S*2Av%vg8$M#pdZDWgm zS0b8^_w4x~u)M=cZm#D$HMddAiFbHWg5_E4dTYhaT+u6Bp#^m`!he>WpD?r*w(1$E za`~!YjByw#R2l8Sw=$8<+nr18djl2b*SCZ7xZcB>5ZAivnK{Rg_ufrD-~9+V5adcW z>2Jcn9tlzq zkUvl-b8CN-16PyYk94$_QU%UG0XnlhM2|8?h;1)$7(a6fdIFl-k%(X-2tp z1xA!2*vySdwKwK`O-xiQUg(NXl~eh{zwe6+#$k@>>a8Ey}T1Go>_lD5rwXH%zHre)jdAoXE8_;yEQ;LcM-+IB_V)e}jGCli6*W z0$gB^T^5dRS5Y3*d=5&*hYk%B4~d3oT1pzdmyS$xQ`mZ7q5SHm^aqRz;v27AlzDkg zT;EIMFyV!)L#DuiT&=zWq!-Td(^`e`R=f*@j{NH%Z+$ z^qyWS79In{+p%RSqQVROqn<$p{sizcZu^hOGD;W8lj0+&vPODAZ~l0whj1wV$|eS+~0w2IS9s#3B%oaOF3MN z&Q`fpju%TH=`MpCKZFi6pCM|4X!Bg_WhjcKLz9!ON{AP!Jk)`(#FhD_4fO!93*?Kv ziLVTgL?J@eBSdD0PQFR$1qUpP*|b*{Od3v4^81p!cZan*Phc6MRNn<)eeax$BQtLW z-)g+Vd_vtB=KqFzHG~26wmjJ69FUz7a@0OR2%z)46K7oPxTEUCMAE?s5cJ0>KtuI^ z;Vd{skg;hPU0@YZDswH~LADMAzPZW-l%#@<4FB2Zd&%;N$>Qv{0?R-{*p@+osK4D- zg$zbsM}-sc%KqEfV(N%Ss`C1b$L2VN^K zIjC(3F1@g<15_AHS*3;K7S|KDZCe|H0VsBJ<2>;`=Kzblk~lGuNy*Z*qV_}wr4|IfffAuh{q Wh?kn7KiGZ_Xy4S=EK#?A{J#MAup=n| literal 11864 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 D{KUR^ diff --git a/scripts/gen_screens.py b/scripts/gen_screens.py index a9e4bb7..dbe8277 100644 --- a/scripts/gen_screens.py +++ b/scripts/gen_screens.py @@ -276,7 +276,7 @@ def gen_ap(accent=YL, header="SETUP MODE — STEP 1 OF 2", qr_label="SCAN TO C leave_qr_white(draw, qx, qy, qp) # "Encodes WIFI:..." label below - text_center(draw, cx, qy+qp+10, "WIFI:S:PictureFrame-91F8;T:nopass;;", F_FOOT, (100,100,95)) + text_center(draw, cx, qy+qp+10, "WIFI:T:WPA;S:PictureFrame-91F8;P:pictureframe;;", F_FOOT, (100,100,95)) return img diff --git a/src/config.h b/src/config.h index e98ee3a..d9df0e1 100644 --- a/src/config.h +++ b/src/config.h @@ -128,6 +128,15 @@ // ── Network ────────────────────────────────────────────────────────────── #define APP_BASE_URL "https://pictureframe.edholm.me" #define AP_IP "192.168.4.1" +// AP security — WPA2-PSK with a fixed password baked into both the +// firmware and the WIFI: QR. Open networks have flakier iOS captive- +// portal behavior (esp. on lock-screen-camera join paths); a secured +// network is the pattern aqua-iq uses successfully on the same iOS +// versions. The password is embedded in the QR so the user never types +// it — there's no real secret value. +#define AP_PASSWORD "pictureframe" +#define AP_CHANNEL 6 +#define AP_SETTLE_MS 1500 #define WIFI_TIMEOUT_MS 30000 // Server's X-Interval-Ms is the primary schedule — driven by the user's // rotationIntervalMinutes / wakeTimes settings. The constants below are diff --git a/src/main.cpp b/src/main.cpp index b86325d..5f7326e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,7 +26,10 @@ static String g_req_pass; // ── QR helpers ─────────────────────────────────────────────────────────────── static void show_ap_qr(const String& apSsid, bool retry = false) { - String content = "WIFI:S:" + apSsid + ";T:nopass;;"; + // WIFI: QR with embedded WPA2 PSK so the phone joins silently after + // the user accepts the join prompt — no password entry. T:WPA;P:... + // is the standard format consumed by iOS Camera and most QR apps. + String content = "WIFI:T:WPA;S:" + apSsid + ";P:" + String(AP_PASSWORD) + ";;"; QRCode qr; uint8_t buf[qrcode_getBufferSize(5)]; @@ -145,6 +148,18 @@ static void handle_connect() { server.send_P(200, "text/html", CONNECTING_HTML); } +// CNA probe handler — log the hit and 302-redirect to "/". The redirect +// is the trigger iOS / Android / Windows actually look for to raise the +// captive banner. Serving the portal HTML inline (200 OK) didn't work +// reliably for our user; aqua-iq uses 302 on the same paths and the +// captive UI fires every time, so match that. +static void handle_captive() { + log_provisioning_request(); + server.sendHeader("Location", "http://" AP_IP "/", true); + server.sendHeader("Cache-Control", "no-store, must-revalidate"); + server.send(302, "text/plain", ""); +} + // Diagnostic page — plain text dump of the ring buffer, oldest first. // User browses to http://192.168.4.1/log after joining the AP. Tells // us whether iOS hit us at all and which CNA paths it tried. @@ -206,13 +221,17 @@ static void enter_provisioning(const String& mac, bool retry = false) { server.on("/", HTTP_GET, handle_root); server.on("/connect", HTTP_POST, handle_connect); server.on("/log", HTTP_GET, handle_log); - server.on("/hotspot-detect.html", handle_root); // iOS / macOS - server.on("/library/test/success.html", handle_root); // iOS legacy - server.on("/generate_204", handle_root); // Android - server.on("/gen_204", handle_root); // Android variant - server.on("/ncsi.txt", handle_root); // Windows - server.on("/connecttest.txt", handle_root); // Windows 10+ - server.onNotFound(handle_root); + // CNA probe paths — return 302 redirect to "/" rather than 200 + portal + // body. This is the aqua-iq / WiFiManager pattern that empirically fires + // the iOS captive banner reliably; serving the portal body inline at + // these endpoints didn't. + server.on("/hotspot-detect.html", handle_captive); // iOS / macOS + server.on("/library/test/success.html", handle_captive); // iOS legacy + server.on("/generate_204", handle_captive); // Android + server.on("/gen_204", handle_captive); // Android variant + server.on("/ncsi.txt", handle_captive); // Windows + server.on("/connecttest.txt", handle_captive); // Windows 10+ + server.onNotFound(handle_captive); // WebServer doesn't capture arbitrary request headers unless told to const char* tracked_headers[] = { "User-Agent", "Host" }; @@ -220,18 +239,24 @@ static void enter_provisioning(const String& mac, bool retry = false) { WiFi.onEvent(on_ap_event); WiFi.mode(WIFI_AP); - WiFi.softAP(apSsid.c_str()); + // WPA2-PSK secured AP on channel 6 — matches aqua-iq's working + // configuration. softAP signature: ssid, psk, channel, ssid_hidden, + // max_connection. Channel 6 is the "middle" 2.4 GHz channel, less + // contested than the default 1 in dense WiFi areas. + WiFi.softAP(apSsid.c_str(), AP_PASSWORD, AP_CHANNEL); // Power-save can park the radio between beacons and drop iOS CNA // probes that arrive during a sleep window — keep the AP fully awake // so the captive-detect window is deterministic. WiFi.setSleep(false); - // Trust the default ESP-IDF softAP DHCP server: it offers the AP IP - // as DNS automatically. We previously did a stop / set-options / - // restart dance on top of softAP to "force" the DNS offer — but - // that races with iOS's DHCP request (a fast join can hit DHCP-stop - // mid-handshake) and likely caused more failures than it fixed. + // Settle delay — softAPIP is valid immediately, but DHCP server and + // beacon stability take a moment to come up cleanly. aqua-iq's + // NetworkManager+dnsmasq pattern needs ~3 s here; the ESP softAP + // stack is faster, but a 1.5 s pad still saves us from edge-case + // races where a phone associates and starts DHCP before the AP + // stack is fully ready. + delay(AP_SETTLE_MS); server.begin(); dns.setErrorReplyCode(DNSReplyCode::NoError);