# adacam-api A small, readable **Python/Flask on-device API** for dashcams in the Hivemapper Bee (HDC-S) family. It is a clean-room, open replacement for the device's stock `odc-api` — a large Node.js service — reduced to a few hundred lines of Python that are easy to audit, modify, and self-host. > Independent open-source project. Not affiliated with, endorsed by, or > supported by Hivemapper. "Hivemapper" and "Bee" are used only to describe > hardware compatibility. The API runs locally on the device and exposes a small HTTP surface that a companion app (or any HTTP client) can use to read GPS, list recent landmark detections, check device status, configure WiFi/SSH, and optionally forward detections to a map-ingest backend. ## Features - **Landmark store + forwarder** — detections are written to a local SQLite database and forwarded to a configurable map-ingest API. Forwarding is resilient: anything that fails while the device is offline is queued and retried by a background thread. - **GPS** — reads the latest fix from the device's on-disk recording database (`odc-api.db`, `framekms` table), with a Redis fallback for live fixes. - **Device control** — WiFi client connect/status, SSH enable/disable, and device status/identity endpoints. - **WiGLE** — optional wardriving status, stats, and configuration endpoints. - **Token auth + pairing** — control endpoints require a bearer token. The token is a high-entropy random secret stored on the device; clients obtain it through a one-shot, physically-gated pairing window (see [Authentication](#authentication)). ## Endpoints | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/1/landmarks/last/{N}` | no | Last N detections | | POST | `/api/1/landmarks` | yes | Ingest a new detection | | GET | `/api/1/gnssConcise/latestValid` | no | Current GPS fix | | GET | `/api/1/status` | no | Device status (firmware, uptime, GPS lock) | | GET | `/api/1/deviceinfo` | no | Device identity | | GET | `/api/1/recording/frames/latest` | no | Path to the most recent frame | | GET | `/api/1/info` | no | Device serial + AP info | | GET | `/pair` | no* | One-shot pairing — returns the API token while the window is open | | GET | `/api/1/wifi/status` | no | WiFi client status | | POST | `/api/1/wifi/connect` | yes | Connect to a WiFi network | | GET | `/api/1/ssh/status` | no | SSH daemon status | | POST | `/api/1/ssh/toggle` | yes | Enable/disable SSH | | GET | `/api/1/wigle/status` | no | WiGLE service status | | GET | `/api/1/wigle/stats` | no | WiGLE statistics | | GET | `/api/1/wigle/config` | no | WiGLE config (secrets masked) | | POST | `/api/1/wigle/config` | yes | Update WiGLE config | | GET | `/api/1/debug/redis-keys` | yes | List Redis keys (GPS troubleshooting) | `*` `/pair` only returns the token while a pairing window is open; otherwise it returns `403`. > Note: there is intentionally no remote command-execution endpoint. The stock > firmware's `/api/1/cmd` (the source of a known CVE) is deliberately not > implemented here. ## Quick start Requirements: - Python 3.8+ - Redis (optional — used as a live-GPS fallback) - `flask`, `redis`, `requests` (see `requirements.txt`) Run it locally: ```bash pip install -r requirements.txt python main.py # serves on 0.0.0.0:5000 ``` Install on a device (copies files to `/opt/adacam`, installs the pairing helper, and enables a systemd service): ```bash ./install.sh ``` ## Configuration Config lives at `/data/adacam/config.json` and is created with defaults on first start. A device ID (UUID) is generated automatically. ```json { "device_id": "auto-generated UUID", "adamaps_key": "", "adamaps_api": "https://api.adamaps.org", "ap_interface": "wlp1s0f0", "tunnel_host": "", "tunnel_user": "", "tunnel_port": 2222 } ``` - `adamaps_api` / `adamaps_key` — the upstream map-ingest endpoint and its API key. Point these at your own backend; detections are POSTed to `/api/ingest` with the key in an `X-AdaMaps-Key` header. - `ap_interface` — the device's access-point network interface. The API key can be required from the environment instead of being stored inline; in that case the service refuses to start until it is set. ## Authentication Read-only endpoints are open; control endpoints (WiFi connect, SSH toggle, WiGLE config, landmark ingest) require an `Authorization: Bearer ` header. The token is **not** derived from any public identifier. On first use the device generates a random `secrets.token_urlsafe(32)` value, stores it at `/data/adacam/api_token` (mode `600`), and compares it in constant time. Clients obtain the token through a **one-shot pairing window**: ``` device: adacam-pair # opens the window (physical/SSH presence) client: GET /pair -> { serial, token, ... } # window closes on success client: stores token; sends it on all control calls ``` `install.sh` opens the window once on a fresh provision so the first pair works out of the box. To re-pair (new client, reset), run `adacam-pair` on the device again. ## Development The code is a standard Flask app factory (`adacam_api/app.py`) with route blueprints under `adacam_api/routes/`. There are no build steps — edit and restart. ``` adacam_api/ app.py # app factory + pairing/wifi/ssh/debug routes auth.py # bearer token + pairing window config.py # JSON config load/save db.py # SQLite: landmarks + offline forward queue forwarder.py # background map-ingest forwarder with retry redis_client.py # GPS reader (SQLite primary, Redis fallback) routes/ # landmarks, gnss, status, frames, wigle blueprints ``` ## Contributing Issues and pull requests are welcome. Please keep changes small and focused, match the existing style, and avoid adding heavy dependencies — the goal is a service small enough to read in one sitting. ## License AGPL-3.0-or-later — see [LICENSE](LICENSE).