A Raspberry Pi 5 + iPhone tool for physically hunting down a WiFi transmitter by walking toward increasing signal strength. The Pi hosts a WiFi hotspot and serves an HTTPS page; the iPhone joins the hotspot, opens the page in Safari, and gets a live arrow pointing toward the focused network’s transmitter.
For Agents
Repo lives at /Users/levander/wifi-hunter/. Hardware target is a Raspberry Pi 5 (model 2712, kernel 6.18, aarch64) reachable via ssh raspi (comes in over eth0 / 192.168.1.200, so wlan0 is free for the hotspot). Server is Python 3 stdlib only — no pip deps. Key non-obvious fact: the Pi 5 built-in WiFi scans the full band while hosting the AP, so no second USB dongle is needed — see pi5-scan-while-ap-mode.
Pi runs sudo python3 server.py — an HTTPS server on port 8443 with a self-signed cert (stdlib http.server + ssl). It shells out to iw dev wlan0 scan to enumerate nearby networks with RSSI.
iPhone joins hunter-ap, opens https://<pi-ap-ip>:8443/ in Safari, taps through the self-signed cert warning.
The page (index.html + direction.js) reads the phone’s GPS (Geolocation API) and compass (DeviceOrientation / webkitCompassHeading), shows a live-updating network list with focus/exclude controls, and draws:
an arrow toward increasing signal strength of the focused network, and
a canvas track of your path, colored by RSSI.
Direction Algorithm
The arrow is a least-squares gradient estimate over recent GPS + RSSI samples:
Project lat/lon to local meters via equirectangular approximation (flat-earth over the small search area).
Fit a plane rssi = a*x + b*y + c by least squares over the recent sample buffer.
Gradient (a, b) points toward the transmitter (direction of increasing RSSI).
bearing = atan2(a, b), then rotate by webkitCompassHeading to get a screen-relative arrow.
Verified by test_direction.js — a Node self-check that passes with < 2° error on synthetic data.
Secure Context Requirement
iOS Safari needs HTTPS
iOS Safari only exposes Geolocation and DeviceOrientation in a secure context (HTTPS). Plain HTTP silently yields no GPS/compass. A self-signed cert + tap-through works — hence port 8443 with ssl rather than a plain http.server.
iOS Geolocation: Diagnosing kCLError (field test 2026-07-15)
kCLError is NOT a cert or permission problem
When the hunter page status line shows Location error: ...kCLError..., that is an Apple Core Location error (kCLErrorLocationUnknown). It means permission was granted and Core Location was reached — the self-signed cert does not block geolocation. Core Location simply couldn’t get a fix.
Root Cause
Testing indoors and/or the hotspot having no internet — iOS can’t do an assisted first-fix (A-GPS uses the network). Raw GPS works without internet, but only outdoors after a cold start, and it takes time to lock.
Workarounds
Go outside with open sky.
Prime Core Location: open Apple Maps while on the hotspot to force a blue-dot fix, then return to Safari and reload — watchPosition then gets an immediate fix.
Ensure Location Services On, Safari → While Using, Precise Location On.
Wait 30–60 s for a cold GPS lock.
For Agents
Key diagnostic distinction: kCLErrorLocationUnknown (Core Location reached, no fix — fix by going outside / priming with Maps / waiting) vs a permission denial (different fix). If you see kCLError, the cert is not the blocker — do not chase the HTTPS/cert path.
Deployment & Persistence
The Pi auto-restores the hotspot + server on reboot (~15 s), so no manual bring-up is needed after a power cycle.
Environment=PATH=/usr/sbin:/usr/bin:/sbin:/bin — required so iw (/usr/sbin/iw) is found under systemd’s minimal PATH (unlike interactive sudo, systemd does not inherit secure_path).