You are viewing archived documentation for v0.5. Go to latest →

MQTT Protocol

BirdNET-NG uses MQTT for all satellite-to-hub communication. The broker is Mosquitto 2.x with the built-in Dynamic Security Plugin.

Topic Structure

birdnet/{tenant_id}/{satellite_id}/{channel}

Channels

Channel Direction QoS Description
audio Satellite → Hub 1 (at-least-once) Audio chunk upload (base64 WAV)
telemetry Satellite → Hub 1 (at-least-once) Battery, storage, CPU, GPS
heartbeat Satellite → Hub 1 (at-least-once) Lightweight keepalive every 30s
config Hub → Satellite 2 (exactly-once) Recording profile + filter settings push
ack Hub → Satellite 1 (at-least-once) Chunk acknowledgment

::: tip Why QoS 1 for telemetry? Telemetry was originally QoS 0 (fire-and-forget), but lost telemetry packets caused satellites to appear offline. Upgraded to QoS 1 for reliable delivery. :::

Message Formats

Audio Chunk

{
  "chunkId": "uuid",
  "satelliteId": "uuid",
  "tenantId": "uuid",
  "recordedAt": "2026-03-22T10:30:00Z",
  "durationMs": 3000,
  "sampleRate": 48000,
  "latitude": 43.5659,
  "longitude": 3.905,
  "audio": "<base64-encoded WAV>"
}

3-second WAV at 48kHz mono, base64-encoded. The hub stores the audio in MinIO, inserts a DB record, and enqueues an inference job.

Telemetry

{
  "satelliteId": "uuid",
  "tenantId": "uuid",
  "batteryLevel": 85,
  "storageFreeBytes": 1073741824,
  "cpuTemp": 42.5,
  "networkLatencyMs": null,
  "packetLossPct": null,
  "uptimeSeconds": 3600,
  "timestamp": "2026-03-22T10:30:00Z"
}
  • batteryLevel: percentage (0–100) or null for mains-powered devices
  • storageFreeBytes: available disk space, null if unavailable
  • cpuTemp: degrees Celsius, null on mobile (not accessible)
  • All fields except satelliteId, tenantId, timestamp are nullable

Heartbeat

{
  "satelliteId": "uuid",
  "tenantId": "uuid",
  "uptimeSeconds": 3600,
  "state": "recording",
  "noiseFloorRms": 0.0012,
  "version": "0.4.6",
  "timestamp": "2026-03-22T10:30:00Z"
}
  • noiseFloorRms: current adaptive noise floor level, used for filter calibration
  • version: (optional) satellite package version string; stored on the satellites table by the hub

State values:

State Meaning
recording Actively capturing audio
paused User paused recording (mobile)
scheduled_off Outside recording window (scheduler)
error Capture or connection error

The hub updates satellites.last_seen_at and satellites.heartbeat_state on each heartbeat. This decouples online status from audio chunk sending — a satellite filtering all chunks (silence) still shows as online.

Sent at a configurable interval (default 30 seconds, adjustable in tenant settings) by both Pi satellites and mobile app.

Config Push

{
  "satelliteId": "uuid",
  "tenantId": "uuid",
  "recordingProfile": {
    "type": "dawn_chorus",
    "schedule": [
      { "start": "-30", "end": "120", "reference": "sunrise" }
    ],
    "sampleRate": 48000,
    "chunkDurationMs": 3000,
    "overlapMs": 0,
    "gain": 1.0
  },
  "retentionHours": 48,
  "heartbeatIntervalMs": 30000,
  "filter": {
    "enabled": true,
    "minRms": 0.003,
    "peakSnr": 1.5,
    "birdBandCheck": true,
    "noiseFloorAlpha": 0.02
  }
}

Schedule references:

  • sunrise / sunset: start/end are minutes relative to sun event (negative = before)
  • absolute: start/end are HH:MM 24-hour format

The satellite resolves relative times locally using its GPS coordinates and the NOAA solar algorithm.

Config sync: The hub pushes config to all online satellites when tenant settings change. Satellites also apply the latest config on reconnect. Filter settings (filter.*) control the adaptive noise floor and spectral peak SNR detection pipeline on each satellite.

Acknowledgment

{
  "chunkId": "uuid",
  "status": "stored"
}

Status: stored (success) or error (with error field containing message).

Connection

Client Protocol URL
Pi Satellite WSS wss://mqtt.example.com
Mobile App WSS wss://mqtt.example.com
  • WSS: WebSocket over TLS, uses standard HTTPS port 443 — works through firewalls without extra configuration
  • Client ID: satellite-{satelliteId} or mobile-{satelliteId}
  • Auth: username = satellite ID, password = generated at registration

Security

  • Mosquitto Dynamic Security Plugin manages per-satellite credentials
  • Each satellite can only publish/subscribe to its own topics (ACL via %u username substitution)
  • Hub ingester subscribes to birdnet/+/+/audio, birdnet/+/+/telemetry, birdnet/+/+/heartbeat
  • Credentials are provisioned at registration (POST /api/satellites) and revoked on deletion

Offline Behavior

  • The satellite offline timeout is configurable per tenant (default: 5 minutes)
  • SatelliteMonitor (hub) checks every 60 seconds for satellites where last_seen_at is older than the timeout
  • Heartbeat ensures the satellite stays online even when no audio passes the pre-filter
  • When offline, the satellite queues chunks in its local SQLite outbox and drains them on reconnect