6.2 KiB
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,framekmstable), 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(seerequirements.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/ingestwith the key in anX-AdaMaps-Keyheader.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.