Sprint 1 Retro: WiFi, NTP & Secure Connectivity
Goal: Get the ESP32 online, teach it what time it is, and prove it can talk to Spotify over HTTPS.
Verdict: Online, time-aware, and TLS-verified. The little guy is ready to introduce himself to Spotify. ╰(°▽°)╯
What We Built
The ESP32 now boots through a full network stack initialization sequence: connects to WiFi with exponential backoff retry logic, syncs its clock via NTP to real-world time (UTC+3, because Istanbul), and makes a verified HTTPS request to Spotify’s API. Every step has screen feedback so you’re never staring at a blank display wondering what’s happening.
After boot, it settles into a ticking clock display. Big cyan digits, date below. A preview of the idle screen that will live in Sprint 10.
The boot sequence, start to finish:
- Splash screen with backlight fade-in
- WiFi connection (animated dots + attempt counter)
- NTP time sync
- HTTPS test request to Spotify (expects 401, gets 401)
- Clock display
All orchestrated from a thin main.cpp that still owns nothing.
The Architecture Decisions
Callback Pattern for WiFi Status ◝(⁰▿⁰)◜
The network module needs to report per-attempt progress to the screen, but it shouldn’t know the display module exists. Separation of concerns is non-negotiable.
Solution: networkInit() accepts an optional function pointer callback. The network module fires it on each retry attempt. main.cpp passes a function that updates the screen. The network module stays display-agnostic. The display module stays network-agnostic. Everyone’s happy.
bool networkInit(WiFiStatusCallback onAttempt = nullptr);
Two lines of API, zero coupling. This is the kind of pattern that pays for itself in Sprint 2 when we’ll want progress feedback for token refresh too.
Mozilla CA Bundle Over Hardcoded Certs (⌐■_■)
Spotify’s API is HTTPS-only. The ESP32 needs to validate TLS certificates. Three options existed:
-
Hardcode Spotify’s specific root CA cert. Works until Spotify changes CA provider. Then your desk gadget becomes a desk brick until you reflash. Spotify has changed CAs before. Next.
-
setInsecure(). Disables all certificate validation. Absolutely not. We’re not animals (¬_¬) -
ESP-IDF’s built-in Mozilla root CA bundle. ~140 trusted CAs, already compiled into the Arduino framework. Works with any HTTPS site. Survives CA rotations. Costs ~13% flash. Done.
The best part: the bundle was already there. No downloading cert files, no embedding binaries, no PEM strings in PROGMEM. One extern declaration, one setCACertBundle() call, and the ESP32 trusts the same certificate authorities your browser does.
NTP: The Pragmatic Trade-off
NTP uses plain UDP. No encryption, no authentication. A determined attacker on your LAN could feed the ESP32 a wrong time, which could theoretically mess with TLS certificate validation.
We evaluated this honestly:
- NTS (Network Time Security) would fix it, but the ESP32’s SNTP stack doesn’t support it
- Every IoT device in your house does the same thing
- If someone’s on your LAN spoofing NTP, you have bigger problems
- If the time is wrong enough to matter, HTTPS connections fail closed (certs won’t validate), which is the safe failure mode
For a home desk gadget, this is the right call. We sync once at boot from pool.ntp.org and move on with our lives.
The Security Audit ◉_◉
We ran a security analysis mid-sprint. It found four things:
WiFi credentials leaking to NVS flash (fixed). WiFi.begin() on ESP32 silently persists credentials to non-volatile storage by default. If someone extracts your flash chip, they get your WiFi password even if config.h was deleted. One line fix: WiFi.persistent(false) before WiFi.begin(). Now credentials live only in RAM.
NTP timeout was half what we configured (fixed). The polling loop did delay(500) but incremented a counter by 1, checking against a 10-second limit. So it actually waited 5 seconds. Switched to millis() for real wall-clock timing.
NTP is spoofable (accepted). Discussed above. Can’t fix without NTS support on ESP32.
Network metadata on Serial/screen (accepted). The SSID and IP address are shown on screen and printed to Serial. For a desk gadget where “Serial access” means “physically plugging in a USB cable to the device on your desk,” this is a feature, not a vulnerability.
Two real bugs caught, two conscious trade-offs documented. This is exactly what security reviews are for.
Things That Just Worked
This sprint was remarkably smooth compared to Sprint 0’s debugging war stories.
- WiFi connected on attempt 2. No drama. No weird driver issues. Just… worked.
- NTP synced in under a second.
pool.ntp.orgresponded almost instantly. - The built-in CA bundle linked without any configuration. We were prepared to download cert files, embed binaries, fight the linker. None of that happened. The symbol was already there.
- HTTPS to Spotify returned 401 on the first try. That’s the exact status code we wanted. It means: “I understand your request perfectly, I just don’t know who you are yet.” TLS handshake, DNS resolution, HTTP protocol, all working.
Sometimes the best debugging story is no debugging story (◕‿◕)
By the Numbers
| Metric | Value |
|---|---|
| Flash usage | 73.6% (964 KB / 1.3 MB) |
| RAM usage | 14.7% (48 KB / 327 KB) |
| Flash increase from Sprint 0 | +48.4% (WiFi + TLS + CA bundle) |
| WiFi connect time | ~2 seconds (2 attempts) |
| NTP sync time | <1 second |
| HTTPS test result | 401 (Unauthorized) — exactly right |
| Modules | 5 (display, input, led, network, main) |
| Security findings fixed | 2 (NVS persistence, NTP timeout) |
| Security findings accepted | 2 (NTP spoofing, metadata exposure) |
setInsecure() calls | 0 (as it should be) |
What’s Next
Sprint 2: Spotify OAuth2 Token Flow. The ESP32 knows how to talk HTTPS. Now it needs to introduce itself to Spotify with proper credentials. OAuth2 token refresh, access token management, and the Python helper script for the initial authorization dance. The 401 becomes a 200.
Sprint 1 complete. The ESP32 is online, time-aware, and TLS-verified. Time to teach it who we are. ᕙ(⇀‸↼‶)ᕗ