backend to get my location
  • Rust 95.6%
  • Nix 3.3%
  • Shell 1.1%
Find a file
2026-06-19 11:58:55 +02:00
.direnv feat: new way to get Data 2026-06-14 23:08:44 +02:00
src feat: better pin dropping 2026-06-19 11:58:55 +02:00
.envrc balls 2026-01-06 19:39:09 +00:00
.gitignore balls 2026-01-06 19:39:09 +00:00
Cargo.lock feat: rewrite 2026-06-19 02:40:13 +02:00
Cargo.toml feat: rewrite 2026-06-19 02:40:13 +02:00
flake.lock my git slow today 2026-01-06 20:28:07 +00:00
flake.nix my git slow today 2026-01-06 20:28:07 +00:00
README.md feat: rewrite 2026-06-19 02:40:13 +02:00

whereAmI

Axum service that drives an ESP32 NeoPixel strip from famtrack locations. The server owns all state — the strip is a dumb renderer that opens a WebSocket and applies whatever LED frames the server pushes.

                                ┌─────── famtrack (locations)
                                ├─────── BigDataCloud (reverse geocode)
                                ▼
ESP32 ◀── wss:// frame push ── whereAmI ◀── browser (web UI, password-gated)

The server polls each device's location every 60s, maps the resolved country to a list of LED positions, renders a sparse frame, and broadcasts it to every connected ESP. Color/brightness/name per device are configurable from the web UI and persisted to disk.

Web UI

https://<host>/ — dashboard with one card per device (color picker, brightness slider, rename), a country-inspect panel for territory mapping, a strip-wide test panel, and a "connected strips" indicator.

First visit redirects to /login. Enter the password from WEB_PASSWORD; the resulting cookie lasts a year and survives server restarts (sessions are persisted).

ESP endpoint

GET wss://<host>/ws/esp — gated by X-API-Key. The ESP sends two extra headers on the handshake:

Header Example Purpose
X-Strip-Id esp-AABBCCDDEEFF Identifier shown in /admin/connected and logs
X-Device-Mac AA:BB:CC:DD:EE:FF Raw MAC, logged on connect

Once upgraded, the server sends the current frame immediately and a new frame every time anything changes (poll result, UI edit, override toggle).

Frame format (server → ESP, JSON text)

{ "type": "frame",
  "leds": [ { "pos": 222, "rgb": [255, 128, 0] },
            { "pos": 252, "rgb": [255, 128, 0] } ] }

Sparse: only lit LEDs are included. The ESP clears the strip and applies the list each frame. Brightness scaling is applied server-side (rgb is the final value).

API endpoints (X-API-Key)

Method Path Description
GET /ws/esp ESP WebSocket upgrade
GET /admin/connected JSON list of currently connected strip IDs
GET /get/location Legacy: returns {"country":"XX"} for {"key":"<device>"}. Kept for transition; not used by new ESP firmware.

Configuration

All config via env vars. A .env in the working directory is loaded if present.

Var Required Description
API_KEYS yes Comma-separated list of keys accepted as X-API-Key by the ESP and the legacy endpoint.
WEB_PASSWORD yes Password for the web UI. Empty values rejected — server refuses to start.
FAMTRACK_BASE_URL yes e.g. https://famtrack.mrfluffy.xyz
FAMTRACK_TOKEN yes famtrack API token with the locations:read scope.
FAMTRACK_DEVICES yes device_key=GROUP_UUID/USER_UUID entries, comma-separated. Devices listed here appear on the dashboard.
STATE_PATH no Where to persist per-device state (default ./device_state.json).
SESSIONS_PATH no Where to persist web UI sessions (default ./sessions.json).
CACHE_PATH no Legacy /get/location stale-fallback cache (default ./last_country.json).

FAMTRACK_DEVICES format

FAMTRACK_DEVICES=device42=11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222,alice-esp=11111111-1111-1111-1111-111111111111/33333333-3333-3333-3333-333333333333

Each device_key is a tracked person, not a strip. One strip displays the union of all configured devices' locations.

Getting the UUIDs and token

FAMTRACK=https://famtrack.mrfluffy.xyz

# 1. Log in.
JWT=$(curl -s -X POST $FAMTRACK/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"<you>","password":"<pw>"}' | jq -r .token)

# 2. List groups — pick FAMTRACK_GROUP_ID.
curl -s $FAMTRACK/v1/groups -H "Authorization: Bearer $JWT" | jq

# 3. List group members — pick user UUIDs for FAMTRACK_DEVICES.
curl -s $FAMTRACK/v1/groups/$GID/members -H "Authorization: Bearer $JWT" | jq

# 4. Mint a long-lived API token (save the returned token field — only shown once).
curl -s -X POST $FAMTRACK/v1/auth/api-keys \
  -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
  -d '{"label":"whereAmI bridge","scopes":["locations:read"]}' | jq

Running

nix develop
cargo run --release

Listens on 0.0.0.0:3000.

Behind nginx

location /ws/esp {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
}

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Persistence

Three JSON files written to the working directory by default:

File Contents Surviving a wipe
device_state.json Per-device name, color, brightness, last-known country. UI re-defaults to white/100/no-country; auto-populated again as the poller runs.
sessions.json Active web UI session tokens. All logged-in browsers get bounced back to /login.
last_country.json Legacy /get/location cache. Safe to delete.

Writes are atomic-ish (write-to-tmp + rename).

ESP client

See /home/mrfluffy/Documents/projects/platformIO/whereAmI-esp32. The ESP no longer needs to know about devices or countries — it just opens the WS and renders whatever frames arrive. Strip identity is MAC-derived (esp-<mac>), so reflashing keeps the same ID.

To add a new tracked person:

  1. They register in famtrack and join the relevant group.
  2. Find their user UUID via GET /v1/groups/$GID/members.
  3. Append their-key=$GID/$UID to FAMTRACK_DEVICES and restart the server.
  4. Their card appears on the dashboard with default color/brightness; tweak there.