v1.2.2-rp2040 — AI mode now actually works (Cloudflare Worker proxy)
TL;DR
Routing Stockfish API calls through a tiny Cloudflare Worker proxy so the board talks plain HTTP instead of HTTPS. Result: 100% reliable AI mode where it was previously failing constantly.
| Before (v1.2.1, direct HTTPS) | After (v1.2.2, proxy) |
|---|---|
| TLS handshake: 9.5s timeout | connect(): 40-700ms |
| Success rate: ~0% in our session | 8/8 successful in our session |
| User experience: red flash, retry, give up | Bot just plays, ~2s per move |
Why
The Arduino Nano RP2040 Connect's WiFiNINA TLS stack (NINA-W102 firmware 3.0.1) fundamentally cannot complete TLS 1.3 handshakes with Cloudflare-fronted endpoints like stockfish.online. Diagnostic data is conclusive:
[DBG] Pre-flight: WiFi status=3 RSSI=-41 dBm IP=192.168.0.99
[DBG] Calling client.connect(stockfish.online:443)...
[DBG] connect() returned FALSE after 9312ms ← TLS handshake times out
[DBG] connect() returned FALSE after 9515ms ← attempt 2 also fails
[DBG] connect() returned FALSE after 9508ms ← attempt 3 also fails
[DBG] connect() returned FALSE after 30015ms ← attempt 4 (after WiFi reset) also fails
[DBG] connect() returned FALSE after 13ms ← attempt 5 (NINA wedged now) also fails
5 attempts in a row, RSSI -41 dBm (excellent signal), ALL fail. Even WiFi.disconnect() → WiFi.end() → WiFi.begin() (which IP renewal proves works) doesn't recover. The NINA-W102 just cannot speak the cipher suite Cloudflare currently negotiates.
Fix: Cloudflare Worker proxy
A 76-line JavaScript Worker that:
- Accepts plain HTTP from the board (no TLS handshake on the constrained device)
- Forwards to
stockfish.onlineover HTTPS server-side - Returns just the JSON
Default URL (free Cloudflare tier, 100,000 requests/day — enough for ~2,000 active boards):
https://openchess-proxy.semichcsc.workers.dev
Don't trust the public proxy? Self-host in 30 seconds:
cd worker
npx wrangler login
npx wrangler deployThen change STOCKFISH_API_URL in your arduino_secrets.h. See worker/README.md for the full instructions.
Verified
8 consecutive Stockfish API calls on hardware (Italian Game opening), all succeeded on attempt 1:
[DBG] connect() returned TRUE after 304ms → Total: 1943ms → bestmove e7e5
[DBG] connect() returned TRUE after 40ms → Total: 2049ms → bestmove b8c6
[DBG] connect() returned TRUE after 314ms → Total: 2082ms → bestmove g8f6
[DBG] connect() returned TRUE after 700ms → Total: 2625ms → bestmove h7h6
[DBG] connect() returned TRUE after 48ms → Total: 2646ms → bestmove d7d5
[DBG] connect() returned TRUE after 67ms → Total: 2036ms → bestmove f6d5
[DBG] connect() returned TRUE after 145ms → Total: 2088ms → bestmove c8e6
Sketch uses 151672 bytes (0%) of program storage space.
Global variables use 44640 bytes (16%) of dynamic memory.
=== Self-tests complete: 10/10 passed ===
Defense in depth (still active)
All hardening from earlier v1.2.2 iterations is preserved and still useful in case the proxy ever wobbles:
- 5-attempt retry loop with exponential backoff (0/500/1000/2000/4000 ms)
- Pre-flight
WiFi.status()check with RSSI logged - Quick reconnect (~8s budget) if link drops between attempts
- Full NINA module reset automatically after 3 consecutive failures
- Bounded read loop with 8s inter-byte timeout (no
readString()hangs) - Hard 4096-byte response cap (defensive against OOM)
- Detailed failure classification logs in serial output
- Amber retry pulse on rank 8 during backoff (different colour from the blue thinking pulse)
- Red flash only after all 5 attempts exhausted (was after 1 single failure)
How to flash
Drag-and-drop OpenChess-v1.2.2-rp2040.uf2 onto the RPI-RP2 USB drive after double-tapping the white reset button.
Known limitations (deferred to v1.3)
chess_bot.cppstill uses direct board mutation instead ofChessEngine::applyMove, so castling in AI mode doesn't auto-update castling rights, and the row-axis serial print is still mirrored ("f3 to e5" should read "f6 to e4"). Will be fixed in v1.3 when bot is refactored to use the same engine path as Human-vs-Human mode.- Promotion in AI mode still auto-Q (5-char API parse not implemented).
- Difficulty selection requires recompile.
Source
- Branch
feat/v1.2.2-cloudflare-proxy - Worker source
worker/proxy.js - Full CHANGELOG.md