Clean Python Flask replacement for odc-api (Hivemapper Bee liberation)
Find a file
2026-06-28 21:14:14 -07:00
.forgejo/workflows docs: rewrite README for public release 2026-06-28 12:53:13 -07:00
adacam_api audit follow-ups: deps floor, LICENSE, gate /debug/redis-keys 2026-05-27 09:22:12 -07:00
systemd Initial commit: adacam-api v1.0.0 2026-03-14 08:13:04 -07:00
.gitignore Initial commit: adacam-api v1.0.0 2026-03-14 08:13:04 -07:00
install.sh fix: pre-release review — frames dir, wigle config GET, debug redis-keys endpoint 2026-03-14 17:59:08 -07:00
LICENSE license: relicense to AGPL-3.0-or-later 2026-06-28 21:14:14 -07:00
main.py Initial commit: adacam-api v1.0.0 2026-03-14 08:13:04 -07:00
README.md license: relicense to AGPL-3.0-or-later 2026-06-28 21:14:14 -07:00
requirements.txt audit follow-ups: deps floor, LICENSE, gate /debug/redis-keys 2026-05-27 09:22:12 -07:00

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).

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:

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):

./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.

{
  "device_id": "auto-generated UUID",
  "adamaps_key": "<map-ingest API 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 <adamaps_api>/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 <token> 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.