Utility · keep open during the lab
Troubleshoot
The dashboard stacks four layers, server, network, bridge, browser, and each fails with its own accent. The ladder's order matters more than ever this week because a browser cheerfully displays a stale page over a dead server, so prove the bottom before believing the top.
Debug in layers
curl localhost:5000/api/light in a Pi terminal; B, the same URL from a phone's browser, reading refused vs timeout the Week 9 way; C, compare /api/light's JSON against the mosquitto_sub firehose; D, open the browser console (F12) and read the red lines.Symptoms and fixes
| Layer | Symptom | Likely cause | Fix |
|---|---|---|---|
| A | Address already in use at startup | An older app.py still holds port 5000, often in a forgotten SSH terminal | Find and Ctrl-C it; as a last resort run on another port and allow it in UFW |
| A | Browser shows 500; page says Internal Server Error | Your route's Python raised | The full traceback is in the Flask terminal: read it bottom-up, Week 4's rule unchanged |
| A | TemplateNotFound: dashboard.html | The file is beside app.py instead of inside a folder named exactly templates/ | Make the folder, move the file; restart and re-test |
| A | 404 on a route you wrote | Path mismatch: a typo, case, or the stray trailing slash; or the method (POST route hit by GET) | Compare the request line in Flask's log against the decorator, character by character |
| B | Works on the Pi; other devices get connection refused, instantly | The bind wall: no host="0.0.0.0", so Flask listens on loopback only, Mosquitto's Week 9 wall in Python | Add the host argument, restart; re-run the phone test |
| B | Other devices hang, then time out | Dropped silently: UFW has no 5000 rule, or the device is on a different network (phone on mobile data is the classic) | sudo ufw allow 5000; confirm the phone is on the bench Wi-Fi, not LTE |
| C | /api/light answers but pct stays null while the firehose shows the node publishing | The bridge never hears: loop_start() missing, or the subscribed topic does not exactly match (STATION constant, s7 vs s07, case) | Confirm the loop_start line; print inside on_message; compare topics against the firehose string |
| C | Values freeze; updated ages past a few seconds | The node stopped, the broker restarted and took the thread's session, or the MQTT thread died | Firehose first (is data flowing at all?), then restart app.py; subscribe-inside-on_connect already guards the reconnect case |
| C | Node's connection drops every few seconds; two startup banners in the Flask terminal | debug=True: the auto-reloader imports the module twice, two MQTT clients share one id and kick each other, Week 9's duplicate-id rule | app.run(..., debug=True, use_reloader=False), or leave debug off |
| C | Status chip says online while the node sits unplugged | Stale retained status, or the chip never falls back; the will may not have fired yet (keep-alive window) | Wait out 1.5 × keep-alive; clear ghost retained per Week 9's command; have the page also gray the chip when updated ages |
| D | Page loads; number never changes; no errors anywhere server-side | The JavaScript never runs its loop: setInterval missing, a typo before it threw, or the browser cached an old page | Open the console (F12): red lines name the script error; hard-refresh (Ctrl-Shift-R) to defeat the cache |
| D | Console: Uncaught TypeError: ... is null on the update line | getElementById found nothing: the id in the HTML and the one in the script disagree | Match them exactly; ids are case-sensitive strings, Week 9's topic-typo lesson in miniature |
| D | Set button does nothing; network tab shows the POST returning 400 | The form value did not parse: empty input, comma decimal, or the body was not form-encoded | Send new URLSearchParams({sp}) as the body; validate the input; watch Flask's log line for the request |
Before you ask for help
- Name your layer, and show its one-command test failing (the curl, the phone screen, the JSON-vs-firehose pair, the console).
- Bring both logs: the Flask terminal (every request is a line; 500s carry tracebacks) and, for layer D, the browser console.
- Say the exact URL you tested, scheme, host, port, path; half of this week's mysteries are a wrong quarter of a URL.
- State what you changed last and whether the server was restarted since; edited Python serves nothing until it is.