Realtime environmental awareness: distributed sound localization + classification + common operating picture.
Point a microphone array — or several, PPS/GPS-synced — at the world, and MinimapPR gives you a live map of what is making noise, where it is, and whether you should care about it. Sounds are localized in 3D, classified (bird species, speech, drones, gunshots, machinery), associated into tracks over time, and pushed to a browser-based common operating picture with alerting rules you write.
Everything runs locally on your own hardware. One command starts the server, one browser tab is the whole UI. No cloud service, no account, no telemetry.
mic array(s) ──▶ ingest ──▶ localize (TDOA/SRP-PHAT) ──▶ classify ──▶ track ──▶ rules ──▶ COP / alerts / integrations
Status: beta (
0.2.1). The core pipeline, UI, and integrations are implemented and tested; interfaces still move between releases. See TODO.md for the full implementation status.
- Live common operating picture. Full-bleed geographic map with entity symbology (MIL-STD-2525-inspired), uncertainty ellipses, velocity leaders, GDOP coverage overlay, zone polygons, a sortable track table, and a filtered live detection feed over WebSocket.
- One node or a whole array. A single Sirith tetrahedral array on a Pi does useful 3D localization on its own. Add more nodes and you get networked TDOA, cross-node SRP-PHAT beamforming, and multi-server federation — same software, same UI.
- Species-level bird ID, placed on the map. BirdNET names the species, YAMNet covers the other 521 classes, and the result is a located, tracked entity rather than a bare label — with an eBird-shaped export when you're done reviewing.
- Speech-to-text with keyword alerting. Transcribe speech in range and fire alerts on matched keywords, with the triggering audio snippet attached as evidence.
- Alerting you actually control. A config-driven rules engine evaluates detections and tracks against your
conditions (class, confidence, zone, time, environment) and emits alerts, Home Assistant state, or effector
commands. Coyote, gunshot, drone, perimeter-intrusion, and a "help me" speech-keyword rule all ship enabled
in
data/rules.json; anything else follows from classifier labels plus your own conditions. Rules hot-reload — no restart to tune. - Zones and exclusions. Draw polygons and suppress expected sounds inside them, or alert only on sounds from them. Per-zone occupancy and sound level are first-class outputs.
- Spatial audio recording. An offline studio render pipeline exports ambisonic/IAMF MP4 so you can go back and listen to a scene spatially after the fact.
- PTZ camera slew-to-track — ONVIF cameras point themselves at a localized sound event.
- BLE device localization — RSSI multilateration puts Bluetooth devices on the same picture as audio tracks.
- Environmental sensing — per-node temperature/humidity feeds live speed-of-sound correction for tighter localization.
- Home Assistant integration — outbound MQTT with auto-discovery: zone occupancy, per-zone SPL, detection impulse sensors, node diagnostics. Nothing to configure on the HA side beyond a shared broker. (Discovery payloads are locked down by golden fixtures and a spec lint, but have not yet been smoke-tested against a live HA instance — see the doc.)
- Analysis views — daily activity rollups, detection heatmaps, and a label browser.
- Review + training loop — confirm/reject detections in the UI or API, promote confirmed clips to training data, capture ground-truth bundles, and replay them through the pipeline as a regression gate.
- Built for the field — store-and-forward buffering across network drops, graceful degradation as nodes fall off (3D → 2D → classify-only → alert-only), BIT health reporting, retention tiers with automatic cleanup, and a Rust fast-path ingest sidecar for high-rate deployments.
Self-hosted, GPL-3.0, SQLite on disk. Your audio never leaves your network.
The one-liner installs uv and then uv tool installs MinimapPR —
cross-platform, no manual Python setup:
# macOS / Linux
curl -LsSf https://minimappr.com/install.sh | sh
# Windows (PowerShell)
irm https://minimappr.com/install.ps1 | iexTo skip the BirdNET/ONVIF/MQTT extras, install the base package instead:
curl -LsSf https://minimappr.com/install.sh | sh -s -- --base
Prefer pip? Python 3.11–3.13, and note TensorFlow is a ~500 MB download:
pip install "minimappr[full]" # BirdNET + ONVIF effectors + HA MQTT
# or just: pip install minimapprminimapprOpen http://127.0.0.1:8080. That's the entire UI — map, detections, tracks, alerts, settings.
Uninstall any time with uv tool uninstall minimappr.
In a second terminal, run the built-in two-node simulator (one point node, one tetrahedral array):
minimappr-demo --server http://127.0.0.1:8080Nodes should appear on the map, detections should populate the feed, and tracks should move.
Set your site origin so the map lands in the right place, then flash nodes to POST at your server:
export MINIMAPPR_SITE_ORIGIN_LAT=37.7749
export MINIMAPPR_SITE_ORIGIN_LON=-122.4194
export MINIMAPPR_SITE_ORIGIN_ALT_M=0.0
minimapprFirmware lives in firmware/ — see firmware/README.md for build and flash instructions, and BETA_SETUP.md for the tested end-to-end deployment path.
Four-channel array (node_type: sirith_tetra); default geometry is a regular tetrahedron with 50 mm edge
(see schematics/). Localizes on its own from a single node — this is the baseline standalone
deployment, and works on a Pi 5 running the full fusion server.
Planar microphone array (node_type: sirith_planar) — same firmware family, different geometry.
Single-channel stream node (node_type: point), ESP32-style. Intended for GPS/PPS-timestamped network
localization across several physically separated nodes.
Firmware targets:
| Path | Target |
|---|---|
| firmware/lib/minimap_node_core | shared node runtime / protocol / transport |
| firmware/lib/minimap_node_runtime | generic node runner (audio source + publisher loop) |
| firmware/lib/minimap_audio_esp32 | ESP32 I2S audio sources |
| firmware/lib/minimap_audio_pico | RP2040/RP2350 Pico TDM audio sources |
| firmware/lib/minimap_transport_cyw43 | Pico W CYW43 WiFi transport |
| firmware/lib/minimap_transport_espc5 | ESP32-C5 transport |
| firmware/nodes/sirith_tetrahedral | Sirith tetrahedral node (Pico SDK / CMake) |
| firmware/nodes/sirith_planar | Sirith planar array node (Pico SDK / CMake) |
| firmware/nodes/point_single_mic | reference point node (PlatformIO) |
Most settings are editable live in the UI under Settings, and persist as a sparse YAML overlay at
data/config.yml. Environment variables override the file and are the right choice for deployment scripts.
The Settings → Pipeline view renders the live processing DAG with each stage's config attached, which is
usually the fastest way to find the knob you want.
| Variable | Default | Purpose |
|---|---|---|
MINIMAPPR_HOST / MINIMAPPR_PORT |
0.0.0.0 / 8080 |
bind address |
MINIMAPPR_DB_PATH |
data/minimappr.db |
SQLite database |
MINIMAPPR_SITE_ORIGIN_SOURCE |
auto |
derive site origin from node GPS midpoint, else the fallback coords below |
MINIMAPPR_SITE_ORIGIN_LAT / _LON / _ALT_M |
44.987 / -93.258 / 0.0 |
fallback reference point for local ↔ geographic conversion |
MINIMAPPR_COORDINATE_MODE |
flat |
flat (local XY meters) or geodetic |
MINIMAPPR_CLASSIFIER_ROUTING_CONFIG_PATH |
data/classifier_routing.json |
which models run on which audio context |
MINIMAPPR_CLASSIFICATION_AUDIO_SOURCE |
beamformed |
beamformed (localized render) or omni |
MINIMAPPR_TRIGGER_RMS |
0.001 |
event trigger threshold |
MINIMAPPR_SNIPPET_RETENTION_SECONDS |
3600 |
how long detection audio is kept |
Classification is always on and per-context, driven by data/classifier_routing.json
(override the path with MINIMAPPR_CLASSIFIER_ROUTING_CONFIG_PATH). The file names classifier
instances, says which of them run for each audio context, and wires chained stages:
{
"version": 1,
"classifiers": { "<member_id>": { "backend": "yamnet|birdnet|drone_head|stt" } },
"contexts": { "<context>": { "run": ["<member_id>", ...] } },
"chains": [ { "id": "...", "after": "<member_id>", "input": "audio|embedding" } ],
"triggers": [ { "id": "...", "on": "<member_id>", "action": "speech_capture", "labels": [...] } ]
}The three contexts are detection_trigger (the cheap RMS/cooldown admission gate — empty by
default, not an inference context), localized_render (the beamformed track render), and
omni_continuous (a normalized sum of each node's synchronized mic windows). Shipped default:
YAMNet and BirdNET on localized_render, BirdNET and t3t4_alarm on omni_continuous, the drone
head chained off YAMNet's embeddings, and STT triggered on speech labels.
Available backends:
yamnet— general 521-class audio classifier, shipped as a validated local TensorFlow SavedModel underminimappr/assets/yamnet. Nothing is fetched from TF Hub at startup. Apache-2.0, with provenance and checksums recorded alongside the asset.birdnet— bird species classifier, with a site-specific allow-list built from your site origin coordinates.drone_head— a chained head that rides YAMNet's per-frame embeddings (input: "embedding"), so it costs nothing extra wherever YAMNet already runs.stt— Moonshine speech-to-text, feeding transcripts and keyword alerting.heuristic— dependency-free baseline labels (bird_like,speech_like,impulse,machine_hum,ambient). Also the automatic fallback if routing resolves zero members for a context.t3t4_alarm— temporal alarm-pattern detector for repeating alert tones.
Per-backend kill switches, if you'd rather not edit the routing file:
export MINIMAPPR_BIRDNET_ENABLED=false
export MINIMAPPR_DRONE_HEAD_ENABLED=false
export MINIMAPPR_STT_ENABLED=false
export MINIMAPPR_OMNI_SCAN_ENABLED=falseSee docs/classifier_routing.md for the full schema.
Migrating from ≤0.1.x:
MINIMAPPR_CLASSIFIER,MINIMAPPR_MODEL_CHAIN_CONFIG_PATH, andMINIMAPPR_RUNTIME_PROFILEwere removed, and startup fails loudly if any of them is still set — silently ignoring them would change which models run on live audio. The error message lists the exact replacement variables.
Full environment variable reference
Environment variables override data/config.yml, which in turn overrides the built-in defaults.
Server & storage
MINIMAPPR_HOST(default0.0.0.0)MINIMAPPR_PORT(default8080)MINIMAPPR_CONFIG_PATH(defaultdata/config.yml)MINIMAPPR_DB_PATH(defaultdata/minimappr.db)MINIMAPPR_SNIPPET_DIR(defaultdata/snippets)MINIMAPPR_SNIPPET_RETENTION_SECONDS(default3600)MINIMAPPR_RETENTION_TRACK_UPDATES_SECONDS(default604800,-1disables cleanup)MINIMAPPR_RETENTION_ALERTS_SECONDS(default2592000,-1disables cleanup)MINIMAPPR_RETENTION_ENVIRONMENT_SECONDS(default604800,-1disables cleanup)MINIMAPPR_RETENTION_DROPPED_TRACKS_SECONDS(default604800,-1disables cleanup)
Site geometry
MINIMAPPR_SITE_ORIGIN_SOURCE(autodefault; uses the midpoint of active nodes with GPSposition_geowhen available, otherwise the configured fallback coordinates)MINIMAPPR_SITE_ORIGIN_LAT(default44.98698840878797)MINIMAPPR_SITE_ORIGIN_LON(default-93.2579197515542)MINIMAPPR_SITE_ORIGIN_ALT_M(default0.0)MINIMAPPR_COORDINATE_MODE(flatorgeodetic; defaultflat)
Triggering & windows
MINIMAPPR_TRIGGER_RMS(default0.001)MINIMAPPR_TRIGGER_COOLDOWN_SECONDS(default0.8)MINIMAPPR_LOCALIZATION_WINDOW_SECONDS(default0.08)MINIMAPPR_CLASSIFICATION_WINDOW_SECONDS(default30.0)MINIMAPPR_REPORTING_WINDOW_SECONDS(canonical detection dedupe window; default30)MINIMAPPR_EVENT_STALE_SECONDS(default30.0)
Localization
MINIMAPPR_LOCALIZATION_ALGORITHM(gcc_phatdefault; alsosrp_phat,music,esprit)MINIMAPPR_LOCALIZATION_STRATEGY(geometry_awaredefault, orfixed)MINIMAPPR_LOCALIZATION_BAND_MIN_HZ/MINIMAPPR_LOCALIZATION_BAND_MAX_HZ(optional localization-only bandpass;0disables)MINIMAPPR_LOCALIZATION_SINGLE_NODE_SOLVER(python_cartesiandefault — re-homes the single-node tetrahedral position solve onto Python's Cartesian TDOA solver using the Rust sidecar's pairwise TDOAs + bearing, falling back to the sidecar's own estimate if TDOAs are missing; setrustto trust the sidecar's own SRP-PHAT position/confidence directly, the legacy behavior)MINIMAPPR_SKIP_LOCALIZATION_FOR_CLASSIFICATION(falsedefault)
Classification
MINIMAPPR_CLASSIFIER_ROUTING_CONFIG_PATH(defaultdata/classifier_routing.json)MINIMAPPR_CLASSIFICATION_AUDIO_SOURCE(beamformeddefault, oromni)MINIMAPPR_BIRDNET_ENABLED/MINIMAPPR_DRONE_HEAD_ENABLED/MINIMAPPR_STT_ENABLED/MINIMAPPR_OMNI_SCAN_ENABLED(alltrueby default)MINIMAPPR_BIRDNET_TRIGGER_MIN_CONFIDENCE(default0.40; minimum confidence before BirdNET detections are emitted or BirdNET chain stages fire)MINIMAPPR_BIRDNET_GEO_MIN_CONFIDENCE(default0.01; BirdNET geo-model occurrence threshold used to build the site-specific species allow-list fromMINIMAPPR_SITE_ORIGIN_LAT/LON)MINIMAPPR_DETECTION_MIN_CONFIDENCE(default0.4; hides lower-confidence detections from detection APIs/UI and soundscape rendering)MINIMAPPR_OMNI_SCAN_INTERVAL_SECONDS(default30.0)MINIMAPPR_STT_MODEL_ID(defaultonnx-community/moonshine-base-ONNX)
Environment / speed of sound
MINIMAPPR_DEFAULT_TEMPERATURE_C(default20.0)MINIMAPPR_DEFAULT_HUMIDITY(default0.5)MINIMAPPR_ENVIRONMENT_READING_MAX_AGE_SECONDS(default300.0,0disables the staleness cutoff)
Tracking
MINIMAPPR_TRACKING_FILTER(kalmandefault, orlinear)MINIMAPPR_KALMAN_PROCESS_NOISE(default2.0)MINIMAPPR_KALMAN_MEASUREMENT_NOISE(default1.5)MINIMAPPR_KALMAN_INITIAL_POSITION_VARIANCE(default4.0)MINIMAPPR_KALMAN_INITIAL_VELOCITY_VARIANCE(default16.0)
Pipeline & node health
MINIMAPPR_FUSION_WORKER_COUNT(default2)MINIMAPPR_FUSION_EVENT_QUEUE_SIZE(default512)MINIMAPPR_NODE_DEGRADED_AFTER_SECONDS(default15.0)MINIMAPPR_NODE_OFFLINE_AFTER_SECONDS(default45.0)
Rules & ingest
MINIMAPPR_RULES_CONFIG_PATH(defaultdata/rules.json)MINIMAPPR_DIRECT_INGEST_ENABLED(truedefault; setfalseto force firmware batch ingest through the Rust sidecar)MINIMAPPR_INGEST_SPOOL_DIR(defaultdata/spool)MINIMAPPR_INGEST_SPOOL_READY_TTL_SECONDS(default60)
Federation
MINIMAPPR_FEDERATION_ENABLED(falsedefault)MINIMAPPR_FEDERATION_SERVER_ID(srv-localdefault)MINIMAPPR_FEDERATION_PEERS_CONFIG_PATH(defaultdata/federation_peers.json)MINIMAPPR_FEDERATION_PEERS_JSON(optional inline JSON peer config override)MINIMAPPR_FEDERATION_AUTH_TOKEN(optional shared token / fallback peer auth token)MINIMAPPR_FEDERATION_PUBLISH_INTERVAL_SECONDS(default1.0)MINIMAPPR_FEDERATION_HEARTBEAT_INTERVAL_SECONDS(default2.0)MINIMAPPR_FEDERATION_LINK_TIMEOUT_SECONDS(default8.0)MINIMAPPR_FEDERATION_REQUEST_TIMEOUT_SECONDS(default2.5)MINIMAPPR_FEDERATION_TRACK_TTL_SECONDS(default20.0)MINIMAPPR_FEDERATION_DECONFLICT_MAHALANOBIS_GATE(default4.5)MINIMAPPR_FEDERATION_TQI_HYSTERESIS(default0.05)
Removed (startup fails if set): MINIMAPPR_CLASSIFIER, MINIMAPPR_MODEL_CHAIN_CONFIG_PATH,
MINIMAPPR_RUNTIME_PROFILE. See the migration note above.
Either way it's a single minimappr command; the difference is whether firmware posts directly to the Python
API or to the Rust ingest sidecar.
minimapprFirmware posts to POST /api/v1/ingest/frame or /api/v1/ingest/binary on :8080. No Rust process runs.
export MINIMAPPR_DIRECT_INGEST_ENABLED=false
minimapprPython launches and supervises the sidecar; firmware posts high-rate batch ingest to it on :8081. Requires
the binary at dist/minimappr-ingest-sidecar (build with scripts/build_rust.sh --all). The sidecar's
SRP-PHAT pairwise TDOAs feed Python's Cartesian solver for the position estimate
(MINIMAPPR_LOCALIZATION_SINGLE_NODE_SOLVER=python_cartesian, the default).
Run the sidecar as its own independent process with its own lifecycle (e.g. on a separate host):
# Terminal 1: Python UI/API and spool consumer
minimappr
# Terminal 2: Rust fast-path proxy
MINIMAPPR_INGEST_SPOOL_DIR=data/spool ./dist/minimappr-ingest-sidecarThe sidecar accepts POST /api/v1/ingest/binary and /api/v1/ingest/store-forward, streams bodies to
data/spool/tmp/, atomically publishes complete items to data/spool/ready/, then returns 202 Accepted.
Python drains ready/, drops items older than MINIMAPPR_INGEST_SPOOL_READY_TTL_SECONDS, and moves
parse/delivery failures to data/spool/failed/. Point firmware at port 8081.
If you're running BirdNET over long omni windows rather than short localized impulses, this is the
settings group that matters (these were the old birdnet_hybrid_production profile):
export MINIMAPPR_BIRDNET_ENABLED=true
export MINIMAPPR_LOCALIZATION_ALGORITHM=srp_phat
export MINIMAPPR_LOCALIZATION_STRATEGY=fixed
export MINIMAPPR_CLASSIFICATION_AUDIO_SOURCE=omni
export MINIMAPPR_BIRDNET_CHUNKED_DISPATCH_ENABLED=true
export MINIMAPPR_BIRDNET_CHUNK_OVERLAP_SECONDS=2.0
export MINIMAPPR_CLASSIFICATION_WINDOW_SECONDS=30.0
export MINIMAPPR_MAX_SENSOR_BUFFER_SECONDS=32.0
export MINIMAPPR_LOCALIZATION_BAND_MIN_HZ=300.0
export MINIMAPPR_LOCALIZATION_BAND_MAX_HZ=3500.0
export MINIMAPPR_REPORTING_WINDOW_SECONDS=30.0python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[full,train]"Run the backend with live reload:
uvicorn minimappr.main:app --host 0.0.0.0 --port 8080 --reloadsource .venv/bin/activate
pytest -q5-minute soak harness:
python scripts/run_soak.py --duration 300The operator UI lives in minimappr-frontend/ (Rust → WASM via Leptos 0.8 + Trunk). End users don't need a Rust toolchain — the pre-built WASM bundle ships in the wheel. Only contributors editing the UI do.
# one-time
cargo install trunk
rustup target add wasm32-unknown-unknown
# dev loop, terminal 1: backend on :8000 (Trunk.toml proxies /api and /ws there)
MINIMAPPR_PORT=8000 .venv/bin/python -m minimappr
# dev loop, terminal 2: Trunk dev server with live reload
cd minimappr-frontend && trunk serve
# open http://localhost:8080Release build (required before python -m build):
scripts/build_frontend.sh # → minimappr/frontend/{index.html,*.js,*.wasm,*.css}
scripts/build_rust.sh --all # also builds dist/minimappr-ingest-sidecarPre-publish check:
ls minimappr/frontend/*.wasm # must exist before packaging
python -m build
unzip -l dist/minimappr-*.whl | grep frontend| Path | Contents |
|---|---|
| minimappr/main.py | FastAPI app, HTTP endpoints, /ws/live |
| minimappr/core/ | buffering, localization, beamforming, tracking, zones, rules, federation, fusion orchestration |
| minimappr/classifiers/ | classifier interface, heuristic/YAMNet/BirdNET/STT backends, chaining and routing |
| minimappr/storage/db.py | SQLite schema + persistence |
| minimappr/models.py | Pydantic v2 models for the whole system |
| minimappr/sim/run_demo.py | realtime two-node simulator |
| minimappr-frontend/ | Leptos/WASM operator UI |
| minimappr-ingest-sidecar/ | Rust firmware-facing ingest + DSP fast path |
| firmware/ | shared embedded node runtime + Sirith/point firmware targets |
| tests/ | pipeline, localization, classifier, and integration tests |
Contributor conventions live in AGENTS.md — notably §2.5: any new pipeline stage or config key
must be registered in both core/pipeline_graph.py and core/config_groups.py.
- docs/ui_architecture.md — frontend structure
- docs/classifier_routing.md — how audio reaches which classifier
- docs/home_assistant_integration.md — MQTT entity contract
- docs/distributed_localization_verification.md — multi-node accuracy
- BETA_SETUP.md — tested end-to-end deployment path
- TODO.md — implementation status and roadmap
- Ingest timestamped audio frames.
- Append channel streams to rolling per-sensor buffers.
- Trigger candidate events from frame RMS threshold.
- Enqueue trigger candidates to fusion workers.
- Build synchronized multi-sensor windows.
- Run TDOA measurement (GCC-PHAT / SRP-PHAT / MUSIC / ESPRIT via dispatch) and nonlinear 3D solve.
- Classify event audio (beamformed or omni, with optional model chaining).
- Associate/update track.
- Persist detection + track, evaluate rules, emit live WebSocket event.
- Save mono snippet for the retention window; periodic cleanup removes expired snippets.
POST /api/v1/ingest/frame
{
"node": {
"id": "point-node-01",
"node_type": "point",
"position_m": [0.0, 0.0, 2.0],
"sensor_offsets_m": [[0.0, 0.0, 0.0]],
"capabilities": ["audio", "gps_pps"],
"metadata": {}
},
"frame": {
"start_time_ns": 1739810000000000000,
"sample_rate_hz": 16000,
"channels": 1,
"encoding": "pcm16le",
"samples_b64": "...",
"sequence": 42
},
"environment": {
"temperature_c": 21.4,
"humidity_fraction": 0.52,
"pressure_pa": 101325.0,
"source": "onboard_sensor"
}
}- audio payload is interleaved
pcm16le, base64 encoded frame.channelsmust matchlen(node.sensor_offsets_m)(enforced by a model validator)- a node may report
node.position_geo(lat/lon/alt) instead ofnode.position_m; geographic positions are converted to local offsets against the site origin on registration - timestamps are per-frame start timestamps in
ns - optional per-frame timing quality metadata:
time_quality,toa_ns,tor_ns - optional environmental payload:
environment.temperature_c(minimum), humidity/pressure/wind/lux optional - firmware-compatible fallback:
node.metadata.temperature_cis ingested intoenvironmenteven without an explicitenvironmentobject - response
triggered=truemeans an event candidate was queued for fusion workers; detection emission is asynchronous
GET /healthGET /api/v1/configGET /api/v1/config/structuredGET /api/v1/pipeline/graphGET /api/v1/fusion/statusGET /api/v1/federation/statusGET /api/v1/context/currentPOST /api/v1/ingest/frameGET /api/v1/nodesGET /api/v1/nodes/{node_id}/audio/recent?seconds=10GET /api/v1/detections?limit=100GET /api/v1/detections/{detection_id}GET /api/v1/detections/{detection_id}/audioPATCH /api/v1/detections/{detection_id}/reviewGET /api/v1/tracks?limit=200&include_standby=falseGET /api/v1/zones/occupancyGET /api/v1/cop/statusGET /api/v1/alerts?limit=100GET /api/v1/environment?limit=500&node_id=...GET /api/v1/environment/current?x=...&y=...&z=...GET /api/v1/exports/ebird?format=json|csv&limit=500&since_hours=24GET /api/v1/transcriptsand/api/v1/transcripts/{transcript_id}/audioGET /api/v1/analytics/daily,/api/v1/analytics/heatmap,/api/v1/analytics/labels,/api/v1/analytics/classifiersGET|POST /api/v1/zones,GET|POST /api/v1/rules,GET|POST /api/v1/overlaysGET|POST /api/v1/recordingsand/api/v1/recordings/{session_id}/downloadPOST /api/v1/capture/start//api/v1/capture/{session_id}/stop(calibration bundles)GET /api/v1/classifier-routingGET /api/v1/ble/devices,POST /api/v1/ingest/ble,POST /api/v1/ingest/envGET /api/v1/nodes/{node_id}/bit(built-in test reports)POST /api/v1/nodes/{node_id}/effector/aim|arm|disarmandGET .../effector/snapshot.jpgGET /api/v1/integrations/hass/statusandPOST .../republish-discoveryGET /api/v1/system/diagnostics,/api/v1/system/logs,/api/v1/debug/selftestPOST /api/v1/federation/heartbeat(peer-to-peer)POST /api/v1/federation/snapshot(peer-to-peer)WS /ws/live
The full surface is browsable at /docs (FastAPI's generated OpenAPI UI) on a running server.
The v1 bird workflow is review-driven rather than classifier-final:
- Inspect detections with
GET /api/v1/detections(or the UI). - Review with
PATCH /api/v1/detections/{detection_id}/review. - Export confirmed detections with
GET /api/v1/exports/ebird.
The review mutation accepts review_state (unreviewed / confirmed / rejected), review_label,
review_label_category, review_notes, and promote_to_training (confirmed reviews only).
curl -X PATCH "http://127.0.0.1:8080/api/v1/detections/det-123/review" \
-H "Content-Type: application/json" \
-d '{
"review_state": "confirmed",
"review_label": "song_sparrow",
"review_label_category": "bird",
"review_notes": "confirmed by operator",
"promote_to_training": true
}'
curl "http://127.0.0.1:8080/api/v1/exports/ebird?format=json&since_hours=24"
curl "http://127.0.0.1:8080/api/v1/exports/ebird?format=csv&since_hours=24" --output ebird_export.csvWhen detections are absent, verify audio ingest is healthy before touching classifier settings:
- Open the COP dashboard and use the Node Audio Debug panel.
- Click Listen on a node to request the most recent buffered clip.
- If it doesn't sound reasonable, inspect node health and ingest transport first.
curl "http://127.0.0.1:8080/api/v1/nodes/http-node-1/audio/recent?seconds=10" --output node_recent.wavGroundwork is in place for:
- additional sensor modalities and multi-modal fusion
- richer model chaining (speech/STT → Home Assistant automation)
- federated fusion-server topologies at larger scale
- richer COP layers (advanced zones, alerting policies, coverage planning)
- multi-hypothesis tracking and JPDA association
See TODO.md for per-item status.
GPL-3.0. See LICENSE.