feat(help): public /help setup-and-troubleshooting page
CI / test (push) Has been cancelled

Anchor for the manual QR baked into the firmware Step 1/2 screen
(pictureframe-firmware e089911). One self-contained Twig page,
PUBLIC_ACCESS in security.yaml, /help excluded from the SPA catch-all
regex so it doesn't get swallowed.

Scope is deliberately tight: first-time setup screen by screen, the
captive-portal-didn't-open fallback (manual nav to 192.168.4.1 plus
the iOS lock-screen-scan caveat), the connection-failed retry path,
how to wipe the frame for a new account, photo-not-appearing
diagnosis, and what the yellow/red status borders mean. Anything
beyond that goes in the SPA proper, not here.

No frontend rebuild needed — pure server-rendered Twig + inline CSS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 11:06:42 -04:00
parent f30a6a8f87
commit 2be153a103
4 changed files with 200 additions and 1 deletions
+1
View File
@@ -36,6 +36,7 @@ security:
- { path: ^/register, roles: PUBLIC_ACCESS }
- { path: ^/setup, roles: PUBLIC_ACCESS }
- { path: ^/token, roles: PUBLIC_ACCESS }
- { path: ^/help, roles: PUBLIC_ACCESS }
- { path: ^/api/device, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('PUBLIC_ACCESS')]
class HelpController extends AbstractController
{
#[Route('/help', name: 'help', methods: ['GET'])]
public function index(): Response
{
return $this->render('help/index.html.twig');
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ class SpaController extends AbstractController
#[Route(
'/{path}',
name: 'spa',
requirements: ['path' => '^(?!api|setup|token|login|register|logout|_profiler|_wdt).*'],
requirements: ['path' => '^(?!api|setup|token|help|login|register|logout|_profiler|_wdt).*'],
defaults: ['path' => ''],
)]
public function index(): Response
+178
View File
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup help — pictureFrame</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, sans-serif;
background: #fdf6ee;
color: #3a2e22;
line-height: 1.55;
padding: 2rem 1rem 4rem;
}
.wrap { max-width: 640px; margin: 0 auto; }
h1 { font-size: 1.6rem; margin-bottom: .25rem; }
.lede { color: #8a7060; font-size: .95rem; margin-bottom: 2rem; }
h2 {
font-size: 1.15rem;
margin: 2.25rem 0 .5rem;
padding-bottom: .35rem;
border-bottom: 2px solid #c97c3a;
display: inline-block;
}
h3 { font-size: 1rem; margin: 1.25rem 0 .35rem; color: #5c3f1c; }
p { margin-bottom: .75rem; }
ul, ol { margin: .5rem 0 1rem 1.25rem; }
li { margin-bottom: .35rem; }
code, kbd {
background: #fff5e8;
border: 1px solid #f0c987;
border-radius: 6px;
padding: 1px 6px;
font-family: ui-monospace, monospace;
font-size: .9em;
}
.pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: .8125rem;
font-weight: 700;
color: #3a2e22;
}
.pill-yellow { background: #f0d000; }
.pill-red { background: #c03020; color: #fff; }
.pill-green { background: #10a040; color: #fff; }
.callout {
background: #fff5e8;
border-left: 4px solid #c97c3a;
border-radius: 6px;
padding: .75rem 1rem;
margin: 1rem 0;
}
.callout strong { color: #5c3f1c; }
.toc {
background: #fff9f2;
border: 1px solid #e8d9c4;
border-radius: 10px;
padding: 1rem 1.25rem;
margin-bottom: 2rem;
}
.toc h2 {
font-size: .95rem;
margin: 0 0 .5rem;
padding: 0;
border: none;
color: #8a7060;
text-transform: uppercase;
letter-spacing: .05em;
}
.toc ul { margin: 0; list-style: none; }
.toc li { margin: .15rem 0; }
.toc a { color: #c97c3a; text-decoration: none; }
.toc a:hover { text-decoration: underline; }
footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid #e8d9c4;
color: #8a7060;
font-size: .85rem;
}
</style>
</head>
<body>
<div class="wrap">
<h1>pictureFrame setup help</h1>
<p class="lede">Trouble joining your frame to the network, or stuck on a screen? Start here.</p>
<nav class="toc" aria-label="Contents">
<h2>On this page</h2>
<ul>
<li><a href="#first-time">First-time setup, screen by screen</a></li>
<li><a href="#captive-didnt-open">The "Connect to WiFi" page didn't open on my phone</a></li>
<li><a href="#connection-failed">The frame says "Connection Failed — try again"</a></li>
<li><a href="#start-over">Start over (give the frame to a new account, or wipe it)</a></li>
<li><a href="#photos">Photos aren't appearing, or aren't changing</a></li>
<li><a href="#status-borders">Coloured border around the photo</a></li>
</ul>
</nav>
<h2 id="first-time">First-time setup, screen by screen</h2>
<h3><span class="pill pill-yellow">Step 1 of 2</span> &nbsp; Yellow screen, two QR codes</h3>
<p>This is the WiFi-join screen. Your frame is broadcasting its own temporary network called <code>PictureFrame-XXXX</code> (where XXXX is the last four characters of its hardware ID).</p>
<ol>
<li><strong>Unlock your phone first.</strong> iOS will not open the connection page automatically if you scan from the lock screen.</li>
<li>Scan the large QR on the right with your phone's camera. Tap "Join PictureFrame-XXXX" when prompted.</li>
<li>Your phone should pop up a "Connect to WiFi" page where you enter your home network's name and password.</li>
<li>Tap <strong>Connect</strong>. The frame's screen will refresh (about 20 seconds — be patient, e-ink is slow).</li>
</ol>
<div class="callout">
<strong>If the connect page didn't pop up:</strong> see <a href="#captive-didnt-open">this section</a> below — there's a manual fallback URL.
</div>
<h3><span class="pill pill-green">Step 2 of 2</span> &nbsp; Green screen, single QR code</h3>
<p>The frame is now on your home WiFi. This QR points at the website where you'll create an account (or sign in) and link the frame to it.</p>
<ol>
<li>Scan the QR. Your browser opens to a setup page on <code>pictureframe.edholm.me</code>.</li>
<li>Create an account or sign in. The frame is linked to whichever account claims it first.</li>
<li>Upload your first photo. Within a minute or two the frame will pull it down and display it.</li>
</ol>
<h2 id="captive-didnt-open">The "Connect to WiFi" page didn't open on my phone</h2>
<p>This is the most common setup issue, and it's an iOS quirk more than a frame problem. Here's the fix order:</p>
<ol>
<li><strong>Make sure your phone is unlocked before scanning.</strong> If you scanned with the lock-screen camera, iOS joins the network but won't open the captive portal page automatically — even after unlocking.</li>
<li><strong>Open the "Connect to WiFi" page manually.</strong> With your phone connected to the <code>PictureFrame-XXXX</code> network, open Safari (or any browser) and go to <code>http://192.168.4.1</code>. The same form that should have popped up will load there.</li>
<li><strong>Forget the network and try again.</strong> In your phone's WiFi settings, tap the <code>i</code> next to <code>PictureFrame-XXXX</code> and tap "Forget This Network". Then unlock your phone, scan the yellow Step 1 QR again, and accept the join prompt.</li>
<li><strong>Try a different phone or laptop.</strong> Any device that can join WiFi and open a browser will work.</li>
</ol>
<h2 id="connection-failed">The frame says "Connection Failed — try again"</h2>
<p>You'll see this screen (red header instead of yellow) when the WiFi password you entered didn't work. The QR is the same — your phone might still have the <code>PictureFrame-XXXX</code> network saved.</p>
<ol>
<li>Reconnect your phone to <code>PictureFrame-XXXX</code> (it may have already auto-rejoined).</li>
<li>If the connect page doesn't open, navigate to <code>http://192.168.4.1</code> manually.</li>
<li>Re-enter your home network credentials carefully. Double-check the password — the most common cause is a typo or a missed capital letter.</li>
</ol>
<div class="callout">
<strong>Note:</strong> the frame doesn't display network names — when you enter the WiFi name, the frame can't tell you whether the network is reachable until it tries to connect. If you've fat-fingered the SSID name, the failure looks the same as a wrong password.
</div>
<h2 id="start-over">Start over (give the frame to a new account, or wipe it)</h2>
<p>If you need to re-link the frame to a different account — for example, you're giving it to someone else, or you want to clear the old WiFi credentials — you can reset it.</p>
<p><strong>Hold the small button on the back of the frame until the screen starts to flash.</strong> Don't worry about timing — just keep holding until you see the display refresh. The frame will wipe its saved network credentials and the photos it had cached, and go back to the yellow Step 1 screen.</p>
<p>The new account that scans the green Step 2 QR will be the one the frame is linked to going forward.</p>
<h2 id="photos">Photos aren't appearing, or aren't changing</h2>
<h3>I just uploaded a photo, when will it show up?</h3>
<p>Frames pull from the server roughly once per configured wake interval (often hourly or every few hours, depending on what you set). If you want the new photo to appear right now, the easiest thing is to <strong>unplug the frame and plug it back in</strong> — a power cycle forces an immediate sync regardless of the schedule.</p>
<h3>The same photo has been showing for a long time</h3>
<p>Open <code>pictureframe.edholm.me</code> on your phone, sign in, and check:</p>
<ul>
<li>The frame is shown as recently active (last sync timestamp).</li>
<li>Wake times are set to a reasonable schedule — if you set a single daily wake at 9am, that's the only time it will check.</li>
<li>You actually have multiple photos uploaded — a frame with one photo will keep showing that one photo.</li>
</ul>
<p>If the frame hasn't checked in for more than a day, it's probably not on WiFi — see the next section.</p>
<h2 id="status-borders">Coloured border around the photo</h2>
<p>If the frame can show a photo but something is wrong, it draws a thin coloured border around the image so you can tell at a glance:</p>
<ul>
<li><span class="pill pill-yellow">Yellow border</span> &nbsp; The frame is on WiFi but couldn't reach the server, or the server didn't have a fresh photo. Usually clears itself on the next wake.</li>
<li><span class="pill pill-red">Red border</span> &nbsp; The frame couldn't connect to WiFi at all. Check the router is up. If the network name or password has changed, you'll need to reset the frame (see "Start over" above) and re-enter the new credentials.</li>
</ul>
<footer>
<p>Still stuck? Email the person who gave you the frame — they'll be able to log in on the management website and see what state the frame is in.</p>
</footer>
</div>
</body>
</html>