diff --git a/data/waveshare73-v1/ap_bg.bin b/data/waveshare73-v1/ap_bg.bin index 7852d38..0438f38 100644 Binary files a/data/waveshare73-v1/ap_bg.bin and b/data/waveshare73-v1/ap_bg.bin differ diff --git a/data/waveshare73-v1/ap_bg_preview.png b/data/waveshare73-v1/ap_bg_preview.png index e4b0cc6..81edecd 100644 Binary files a/data/waveshare73-v1/ap_bg_preview.png and b/data/waveshare73-v1/ap_bg_preview.png differ diff --git a/data/waveshare73-v1/ap_bg_retry.bin b/data/waveshare73-v1/ap_bg_retry.bin index f74b3d4..d86678e 100644 Binary files a/data/waveshare73-v1/ap_bg_retry.bin and b/data/waveshare73-v1/ap_bg_retry.bin differ diff --git a/data/waveshare73-v1/ap_bg_retry_preview.png b/data/waveshare73-v1/ap_bg_retry_preview.png index 350ce24..2bd48c8 100644 Binary files a/data/waveshare73-v1/ap_bg_retry_preview.png and b/data/waveshare73-v1/ap_bg_retry_preview.png differ diff --git a/scripts/gen_screens.py b/scripts/gen_screens.py index dbe8277..953a6ae 100644 --- a/scripts/gen_screens.py +++ b/scripts/gen_screens.py @@ -80,9 +80,12 @@ DIV2_X = 508 RIGHT_X = 510; RIGHT_W = 290 # 800-510 # QR positions (MUST match epd.cpp constants) -AP_QR_CELL = 5 +# WiFi-join QR — drawn at runtime by firmware. Cell shrunk from 5 to 4 +# (148 px instead of 185 px) to leave room for a second QR below it that +# opens Safari → forces iOS captive UI. +AP_QR_CELL = 4 AP_QR_MODS = 37 # version 5, ECC_LOW -AP_QR_PX = AP_QR_MODS * AP_QR_CELL # 185 +AP_QR_PX = AP_QR_MODS * AP_QR_CELL # 148 SETUP_QR_CELL = 5 SETUP_QR_MODS = 41 # version 6, ECC_LOW @@ -92,9 +95,13 @@ SETUP_QR_PX = SETUP_QR_MODS * SETUP_QR_CELL # 205 RIGHT_CX = RIGHT_X + RIGHT_W // 2 # 655 BODY_CY = BODY_Y + (H - BODY_Y) // 2 # 266 -AP_QR_X = RIGHT_CX - AP_QR_PX // 2 # 563 -AP_QR_Y = BODY_CY - AP_QR_PX // 2 # 174 (will nudge down below label) -AP_QR_Y = 185 # nudge down a bit for "SCAN TO CONNECT" label above +# Stacked layout: WiFi QR (top) + URL QR (bottom). Each has a label +# above it. AP_QR is dynamic — firmware overlays it at runtime. URL QR +# is static (always http://192.168.4.1/) and baked into the bg image. +AP_QR_X = RIGHT_CX - AP_QR_PX // 2 # centered horizontally +AP_QR_Y = 100 # below STEP 1 label +URL_QR_BOX = 4 # cell size in px +URL_QR_TARGET_Y = 320 # below STEP 2 label SETUP_QR_X = RIGHT_CX - SETUP_QR_PX // 2 # 553 SETUP_QR_Y = BODY_CY - SETUP_QR_PX // 2 # 164 @@ -213,15 +220,16 @@ def gen_ap(accent=YL, header="SETUP MODE — STEP 1 OF 2", qr_label="SCAN TO C bb = draw.textbbox((0,0), "WiFi", font=F_HEAD) draw.rectangle([28, BODY_Y+82, 28+bb[2]+2, BODY_Y+85], fill=accent) - # Steps — step 1 is the unlock-first prompt because iOS won't open - # the captive portal from a locked-phone scan, and we'd rather - # surface that requirement up front than have the user discover it - # by scanning, getting nothing, and giving up. + # Steps — step 1 is the unlock-first prompt (iOS won't fire the + # captive UI from a locked-phone scan). Two-QR flow because iOS + # in recent versions doesn't auto-open the captive portal even + # after CNA detects it; scanning the second QR opens Safari + # which forces the portal to render. steps = [ - ("Unlock your phone first", ""), - ("Scan the QR code →", "Phone joins PictureFrame-91F8"), - ("Browser opens — enter", "your home WiFi password"), - ("Tap Connect and watch", "for the QR code to change"), + ("Unlock your phone first", ""), + ("Scan QR 1 →", "joins PictureFrame WiFi"), + ("Scan QR 2 →", "page opens in Safari"), + ("Enter your WiFi password", "and tap Connect"), ] sy = BODY_Y + 95 step_pitch = 38 @@ -261,22 +269,46 @@ def gen_ap(accent=YL, header="SETUP MODE — STEP 1 OF 2", qr_label="SCAN TO C orientation_diagrams(draw, accent, show_active_ls=True) # ── Right panel ────────────────────────────────────────────── + # Stacked: STEP 1 (WiFi join QR, dynamic) above STEP 2 (URL QR, static + # → http://192.168.4.1/, baked into bg). The URL QR is the trick that + # forces iOS to open the captive portal: scanning a URL QR launches + # Safari, Safari hits 192.168.4.1, iOS sees the request go to a + # captive network and renders the portal instead of fighting whether + # to auto-show CNA. cx = RIGHT_CX - - # QR label — accent-colored on retry so the failure is unmistakable. label_color = accent if accent != YL else BK - text_center(draw, cx, AP_QR_Y - 26, qr_label, F_BIG, label_color) - # QR border: accent outer, black inner + # Step 1 — WiFi join (dynamic QR, overlaid by firmware) + text_center(draw, cx, AP_QR_Y - 22, "STEP 1 — JOIN WIFI", F_BIG, label_color) qx, qy, qp = AP_QR_X, AP_QR_Y, AP_QR_PX draw.rectangle([qx-6, qy-6, qx+qp+5, qy+qp+5], outline=accent, width=3) draw.rectangle([qx-3, qy-3, qx+qp+2, qy+qp+2], outline=BK, width=3) - - # Leave QR area white for firmware overlay leave_qr_white(draw, qx, qy, qp) + text_center(draw, cx, qy+qp+8, qr_label, F_FOOT, label_color) - # "Encodes WIFI:..." label below - text_center(draw, cx, qy+qp+10, "WIFI:T:WPA;S:PictureFrame-91F8;P:pictureframe;;", F_FOOT, (100,100,95)) + # Step 2 — URL QR (static, baked here so we don't need a second + # firmware render path; URL is fixed at http://192.168.4.1/). + url_qr = qrcode.QRCode( + version=None, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=URL_QR_BOX, + border=2, + ) + url_qr.add_data("http://" + "192.168.4.1" + "/") + url_qr.make(fit=True) + url_img = url_qr.make_image(fill_color=BK, back_color=WH).convert("RGB") + url_w, url_h = url_img.size + url_x = cx - url_w // 2 + url_y = URL_QR_TARGET_Y + + text_center(draw, cx, url_y - 22, "STEP 2 — OPEN PAGE", F_BIG, label_color) + # Decorative border around the static URL QR — same accent treatment + # as the WiFi QR for visual consistency. + draw.rectangle([url_x-6, url_y-6, url_x+url_w+5, url_y+url_h+5], outline=accent, width=3) + draw.rectangle([url_x-3, url_y-3, url_x+url_w+2, url_y+url_h+2], outline=BK, width=3) + img.paste(url_img, (url_x, url_y)) + + text_center(draw, cx, url_y + url_h + 8, "http://192.168.4.1/", F_FOOT, label_color) return img diff --git a/src/panels/waveshare73/v1/epd_driver.cpp b/src/panels/waveshare73/v1/epd_driver.cpp index 63ef97d..5670d69 100644 --- a/src/panels/waveshare73/v1/epd_driver.cpp +++ b/src/panels/waveshare73/v1/epd_driver.cpp @@ -175,14 +175,14 @@ static void draw_from_lfs(const char* path, uint8_t fallback_color, } void epd_draw_ap_screen(QRCode* qr) { - // AP_QR_X=563, AP_QR_Y=185, AP_QR_CELL=5 (must match gen_screens.py) - draw_from_lfs("/ap_bg.bin", COLOR_YELLOW, qr, 563, 185, 5); + // AP_QR_X=581, AP_QR_Y=100, AP_QR_CELL=4 (must match gen_screens.py) + draw_from_lfs("/ap_bg.bin", COLOR_YELLOW, qr, 581, 100, 4); } void epd_draw_ap_screen_retry(QRCode* qr) { // Same QR coordinates — only the bg .bin differs (red accents, // "Connection Failed — try again" label). - draw_from_lfs("/ap_bg_retry.bin", COLOR_RED, qr, 563, 185, 5); + draw_from_lfs("/ap_bg_retry.bin", COLOR_RED, qr, 581, 100, 4); } void epd_draw_setup_screen(QRCode* qr) {