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

Satellite: Raspberry Pi

Deploy a Raspberry Pi as a BirdNET-NG satellite node for continuous bird audio capture.

Platform: Raspberry Pi — a dedicated, always-on Node.js agent that captures audio via ALSA and uploads to the hub over WSS.

Hardware Requirements

  • Raspberry Pi 3B+ or newer (4 or 5 recommended)
  • USB microphone or I2S MEMS microphone (e.g., Adafruit SPH0645)
  • SD card (16GB+)
  • Power supply
  • Network: Wi-Fi or Ethernet
  • Optional: GPS module (e.g., u-blox NEO-6M, Adafruit Ultimate GPS, BN-880)
  • Optional: weatherproof enclosure for outdoor deployment

Operating System

Recommended: Raspberry Pi OS Lite (64-bit) — the official headless distribution with minimal footprint. Flash it using Raspberry Pi Imager.

Any Debian-based distribution works, including:

  • Raspberry Pi OS Lite (recommended) — official, best hardware support
  • Ubuntu Server for Pi — if you prefer Ubuntu's ecosystem
  • DietPi — ultra-lightweight, good for constrained setups

Requirements: ALSA audio support, apt package manager, systemd for service management.

Tip: When flashing with Raspberry Pi Imager, enable SSH and configure Wi-Fi in the advanced settings to avoid needing a monitor and keyboard for initial setup.

Software Setup

1. Install prerequisites

# Update system
sudo apt update && sudo apt upgrade -y

# Install tools
sudo apt install -y git curl jq alsa-utils vim

# Install Node.js 24 LTS
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
sudo apt install -y nodejs

# Install pnpm
sudo npm install -g pnpm@latest

# Verify versions
node --version   # v24.x
pnpm --version   # 9.x+
git --version

2. Test audio capture

arecord -l                    # List audio devices — find your microphone

This will output something like:

card 0: Headphones [bcm2835 Headphones], device 0: ...
card 1: Microphone [USB Microphone], device 0: ...

The ALSA device name is plughw:<card>,<device> — so card 1, device 0 becomes plughw:1,0. Test it:

arecord -D plughw:1,0 -f S16_LE -r 48000 -c 1 -d 3 test.wav
aplay test.wav                # Verify recording works

Note: The built-in bcm2835 (card 0) is output only. USB microphones are typically card 1 or higher. Use the card number shown by arecord -l for your microphone.

3. Find your GPS coordinates

GPS coordinates are required for audio processing. BirdNET uses location to identify which species are possible in your area. Without coordinates, audio chunks will be queued but not processed — they will be marked as failed in the inference queue.

Ways to find your coordinates:

  • Google Maps: Right-click any location → the coordinates appear at the top of the context menu
  • OpenStreetMap: Browse to your location, the URL contains the coordinates (e.g., map=15/43.4228/4.6310)
  • Phone: Open any maps app, long-press your location, coordinates will be shown
  • Terminal: curl -s ipinfo.io/loc gives a rough estimate based on your IP address

Note: If your Pi has a GPS module (e.g., u-blox NEO-6M), coordinates will be updated automatically via gpsd. You can also set them later in the .env file (step 5).

4. Register the satellite

  1. Log in to the BirdNET-NG web UI
  2. Select your tenant from the dropdown in the sidebar
  3. Navigate to Tenant Admin → Satellites
  4. Click the "+ Register satellite" button
  5. Fill in:
    • Name — a descriptive name (e.g., "Garden Pi", "Rooftop North")
    • Latitude / Longitude (optional) — enter the coordinates from step 3 if available. They will be included in the generated .env configuration.
  6. Click Register
  7. A complete .env configuration will appear with all required fields
  8. Click "Copy to clipboard" and save these somewhere safe — the MQTT password cannot be retrieved later

The satellite will appear in the Satellites table with a "registered" status until it connects for the first time.

Important: Keep these credentials secure. Anyone with them can send audio data to your hub as this satellite.

5. Configure and run

# Clone the repository (replace with your Git server URL)
git clone <your-git-server>/birdnet-ng.git
cd birdnet-ng

# Install dependencies and build only the satellite package
pnpm install --filter @birdnet-ng/satellite --filter @birdnet-ng/shared
pnpm --filter @birdnet-ng/shared build
pnpm --filter @birdnet-ng/satellite build

cd packages/satellite
cat > .env << 'EOF'
# Paste the .env configuration from the registration step here
# If you registered via the web UI, the complete config was provided
# Otherwise, fill in the values below:
SATELLITE_ID=<from registration>
TENANT_ID=<your tenant>
MQTT_BROKER_URL=wss://mqtt.birdnet.example.com
MQTT_USERNAME=<satellite id>
MQTT_PASSWORD=<from registration>
CAPTURE_MODE=alsa
AUDIO_DEVICE=plughw:1,0
# GPS coordinates (REQUIRED for inference — set before recording)
LATITUDE=
LONGITUDE=
RECORDING_PROFILE=continuous
EOF

Tip: If you provided GPS coordinates during registration, the .env from the web UI already includes them. Otherwise, set LATITUDE and LONGITUDE now.

Connection: The satellite connects via WSS (WebSocket Secure) over standard HTTPS port 443. No extra firewall ports needed.

Verify the .env file and replace any remaining placeholder values:

vim .env

Make sure all <from registration>, <your tenant>, and <satellite id> placeholders have been replaced with real values. The LATITUDE and LONGITUDE should match your deployment location from step 4. Save and exit (:wq).

Then start the satellite:

node dist/index.js

6. Run as a systemd service (recommended)

Replace <user> with your Linux username (e.g., pi, deurk) and adjust the path if you cloned elsewhere:

sudo tee /etc/systemd/system/birdnet-satellite.service << EOF
[Unit]
Description=BirdNET-NG Satellite
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$USER
WorkingDirectory=$(pwd)
ExecStart=/usr/bin/node dist/index.js
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now birdnet-satellite

Note: Run this command from the packages/satellite directory so $(pwd) resolves correctly. The .env file is loaded automatically by the application via dotenv — no EnvironmentFile directive needed.

Verify the service is running:

sudo systemctl status birdnet-satellite
journalctl -u birdnet-satellite -f    # Follow live logs

GPS Setup

For live GPS positioning instead of static coordinates:

sudo apt install -y gpsd gpsd-clients

Edit /etc/default/gpsd:

DEVICES="/dev/ttyUSB0"    # or /dev/serial0 for GPIO UART
GPSD_OPTIONS="-n"
START_DAEMON="true"
sudo systemctl enable --now gpsd
cgps                         # Verify GPS fix

Add to .env:

GPS_MODE=gpsd
GPSD_HOST=localhost
GPSD_PORT=2947

When GPS mode is gpsd, the satellite reads live coordinates from the GPS daemon and updates the hub via telemetry. Falls back to static coordinates if GPS has no fix.

Recording Profiles

Profiles control when the satellite captures audio. They are pushed from the hub via MQTT and can be changed on the Satellites page.

Profile Schedule Description
continuous 24/7 Always recording
dawn_chorus sunrise-30min to sunrise+2h Peak bird activity
night_migration sunset+30min to sunset+12h Nocturnal flight calls
low_power sunrise+/-30min, sunset+/-30min Dawn and dusk only

Sunrise and sunset times are calculated automatically from the satellite's GPS coordinates using the NOAA solar algorithm. Times update daily as day length changes with seasons.

When outside a recording window, the satellite:

  • Pauses the capture loop (no CPU/disk usage)
  • Continues sending heartbeats (stays "online" on the hub)
  • Reports state as scheduled_off in the heartbeat
  • Re-checks the schedule every 5 minutes

Heartbeat

The satellite sends a lightweight heartbeat at a configurable interval (default 30 seconds, set from hub tenant settings) via MQTT QoS 1. This keeps the satellite showing as "online" on the hub even when all audio chunks are filtered out (silence).

The heartbeat includes the satellite's current state (recording, scheduled_off, error) and noise_floor_rms for filter calibration, visible on the Satellites page.

On-Device Audio Filtering

The satellite uses adaptive noise floor + spectral peak SNR detection to filter audio before upload:

  1. Adaptive noise floor: tracks ambient noise level over time (smoothing alpha: 0.02)
  2. Minimum RMS energy: rejects silence below threshold (default 0.003)
  3. Spectral peak SNR: requires signal-to-noise ratio above threshold (default 1.5)
  4. Bird-band frequency check: confirms energy in the 1-10kHz bird frequency range

All filter settings are configurable from hub tenant settings (filter_enabled, filter_min_rms, filter_peak_snr, filter_bird_band_check, filter_noise_floor_alpha) and pushed to satellites via the MQTT config channel when changed. Satellites also apply the latest settings on reconnect.

The satellite reports noise_floor_rms in heartbeat telemetry for monitoring.

This typically filters 70-90% of chunks, reducing bandwidth and hub processing load. Disable with filter_enabled=false in tenant settings (or AUDIO_FILTER=false in the satellite .env as a local override).

Filtering is automatically disabled for simulate and replay capture modes.

Updating

Manual update

Run the included update script:

cd ~/birdnet-ng
./packages/satellite/scripts/birdnet-update.sh

This will:

  1. Check for new versions on origin/main
  2. Show pending changes and ask for confirmation
  3. Stop the systemd service
  4. Pull code, install dependencies, rebuild
  5. Restart the service

Use --force to skip the confirmation prompt (useful for automation).

Remote update from hub

Admins can trigger an update from the web UI:

  1. Go to Satellites page
  2. Expand the satellite with an outdated version (shows ⚠)
  3. Click the Update button next to the version

This sends an MQTT command to the satellite, which runs the update script automatically. The satellite will briefly go offline during the update and reconnect with the new version.

Git credentials

To avoid entering credentials on every git pull:

git config --global credential.helper store

Next time you authenticate, credentials are saved in ~/.git-credentials. For GitHub, use a personal access token as the password.

Troubleshooting

Symptom Check
Satellite shows offline Verify MQTT credentials, check journalctl -u birdnet-satellite
No detections Check if all chunks are filtered (low audio activity), verify worker is running
GPS unavailable Check cgps for fix, verify /dev/ttyUSB0 permissions
High CPU temp Normal for Pi 4 under load; add heatsink or fan
Storage filling up Increase RETENTION_HOURS or check outbox drain