Inspiration

Every "smart space" product we looked at had the same flaw: it solves the counting problem by creating a surveillance problem. Cameras, badge readers, and Bluetooth sniffers all require storing something that identifies you or captures your image. We wanted to know if a room was crowded without ever knowing who was in it. The question became: what signal do bodies leave behind that contains crowd density but nothing else? The answer is WiFi.

What it does

Echolocate is a privacy-first occupancy sensing system built around a $15 ESP32 microcontroller. The sensor passively reads WiFi Channel State Information (CSI), the subtle per-subcarrier amplitude distortions that human bodies create in a wireless signal, and classifies the space as empty, low, moderate, or high occupancy in real time. No camera. No microphone. No personally identifiable signal.

The system has three layers:

Firmware running on the ESP32 that streams CSI over serial and simultaneously serves a live /health, /stats, and /csi/latest HTTP endpoint directly from the device, so a judge can curl the sensor without running any backend A Python backend that ingests the CSI stream, runs an online Welford calibration to establish an empty-room baseline, applies a variance-ratio classifier, stores observations in SQLite, and exposes a REST + WebSocket API A PWA dashboard with tabs for visitors, operators, public transparency, and diagnostics, served as static files with zero npm dependencies or build steps Claude powers the operator-facing Space Design Reports, turning raw occupancy trends into actionable spatial recommendations like "this entrance is a chronic chokepoint.

How we built it

We started with the firmware because everything else depended on its data contract. The ESP32 streams CSI as CSV lines over UART. Rather than block all backend development on hardware availability, we built a Python simulator (sim/esp32_sim.py) that emits the exact same CSV format over TCP and serves the same HTTP endpoints, making the simulator and real hardware behaviorally indistinguishable from the backend's perspective.

The statistical core uses Welford's online algorithm to compute a rolling mean and variance without storing raw samples. Occupancy is classified by the ratio of current variance to the calibrated empty-room baseline, which makes the detector self-calibrating across different environments and antenna placements.

We wrote 47 pytest tests covering CSI parsing, Welford correctness, occupancy thresholds, token rotation, storage privacy invariants, and a full end-to-end stack test that boots the simulator and backend together.

Challenges we ran into

CSI is noisier than the literature suggests. A single ESP32 can reliably distinguish "empty vs. crowded" but the four-level classification is aspirational in real deployments. We were honest about this: the strongest demo artifact is the Space Design Report, which doesn't need ML-grade accuracy to produce a useful recommendation.

Privacy-by-architecture is harder than privacy-by-policy. Saying "we don't store images" is easy. Proving it required encoding the invariant in the SQLite schema (no image column can exist) and writing tests that assert the schema will never silently grow one. The public Transparency endpoint must not leak raw_input or raw_output from Claude calls, and that too is enforced by a test, not a comment.

iPhone hotspots and UDP don't mix. Our original firmware used UDP keep-alives to measure gateway latency. iOS silently drops UDP to closed ports. We switched to esp_ping (ICMP) and exposed a ping_replies counter in /health so a single curl confirms the WiFi loop is alive on demo day.

What we learned

The most durable insight is that governance is a technical artifact, not a policy document. Every privacy claim Echolocate makes is backed by a test that will fail if a future change violates it. The public audit trail for Claude decisions isn't a feature we bolted on; it's what makes the system trustworthy to the people inside the space, not just the operator who deployed it.

Built With

Share this project:

Updates