diff --git a/birdnet-pi/CHANGELOG.md b/birdnet-pi/CHANGELOG.md index f7f78d797c..2496f01bdb 100644 --- a/birdnet-pi/CHANGELOG.md +++ b/birdnet-pi/CHANGELOG.md @@ -1,3 +1,5 @@ +- Fix: an unreachable audio source (RTSP host switched off, missing soundcard) no longer causes a restart loop. The monitoring service treated a static `analyzing_now.txt` as a hung analyzer and ran `restart_services.sh` every two minutes, which stacked up a second `daily_plot.py` and a second recording loop each time, so RAM kept growing. It now reports the silent source instead of restarting when there is nothing queued to analyze, and never restarts more than once every 30 minutes +- Fix: the recording retry loop no longer restarts `ffmpeg` once per second while the audio source is unreachable (100%+ CPU). The retry delay now grows from 1s to 64s, and resets as soon as `ffmpeg` has run for more than two minutes, so a healthy stream that hiccups is still picked up straight away - Local disks: `localdisks` can now mount a single folder of a disk with `disk/folder` (e.g. `sda1/public` or `NAS/public`, mounted at `/mnt//`). Existing values are unchanged. - Migrate legacy add-on configuration map names to current app configuration terminology. ## 2026.08.15 (15-08-2026) diff --git a/birdnet-pi/rootfs/custom-services.d/30-monitoring.sh b/birdnet-pi/rootfs/custom-services.d/30-monitoring.sh index 4503b4dc7e..e3a35a374c 100755 --- a/birdnet-pi/rootfs/custom-services.d/30-monitoring.sh +++ b/birdnet-pi/rootfs/custom-services.d/30-monitoring.sh @@ -60,6 +60,11 @@ DISK_USAGE_THRESHOLD=95 # "Analyzing" file check variables same_file_counter=0 SAME_FILE_THRESHOLD=2 +# Minimum delay between two automatic service restarts (seconds). Restarting +# every couple of minutes has never recovered a stuck analyzer, and each restart +# leaves processes of the previous run behind. +RESTART_MIN_INTERVAL=1800 +last_restart_time=0 if [[ -f "$ANALYZING_NOW_FILE" ]]; then analyzing_now=$(< "$ANALYZING_NOW_FILE") else @@ -157,9 +162,34 @@ check_analyzing_now() { fi if ((same_file_counter >= SAME_FILE_THRESHOLD)); then + # birdnet_analysis only rewrites 'analyzing_now' when it picks up a new + # recording, so the file also stops changing when there is nothing to + # analyze at all - an unreachable RTSP source, a silent or missing + # soundcard. With an empty queue the analyzer is idle, not stuck, and + # restarting the services cannot bring the audio source back: it only + # leaves a second daily_plot.py and a second recording loop behind every + # couple of minutes. Report it, but do not restart. + local wav_waiting + wav_waiting=$(find -L "$INGEST_DIR" -maxdepth 1 -name '*.wav' | wc -l) + if ((wav_waiting == 0)); then + log_red "$(date) INFO: no recordings to analyze - the audio source looks silent or unreachable. Not restarting." + apprisealert "No new recordings: the audio source looks silent or unreachable" + same_file_counter=0 + return 1 + fi + + local now + now=$(date +%s) + if ((now - last_restart_time < RESTART_MIN_INTERVAL)); then + log_yellow "$(date) INFO: 'analyzing_now' still unchanged, but services were already restarted $(((now - last_restart_time) / 60)) minutes ago. Waiting." + same_file_counter=0 + return 1 + fi + log_red "$(date) INFO: 'analyzing_now' file unchanged for $SAME_FILE_THRESHOLD iterations." apprisealert "No change in analyzing_now for ${SAME_FILE_THRESHOLD} iterations" "$HOME/BirdNET-Pi/scripts/restart_services.sh" + last_restart_time=$now same_file_counter=0 return 1 else diff --git a/birdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh b/birdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh index 3cc047d6a2..00324f2c70 100755 --- a/birdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh +++ b/birdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh @@ -89,6 +89,31 @@ fi echo "... allow pulseaudio as root as backup" sed -i 's#pulseaudio --start#pulseaudio --start || pulseaudio --system#g' "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh +# Back off the recording retry loop +# loop_ffmpeg() retries a failed ffmpeg after a fixed "sleep 1". When the audio +# source is unreachable ffmpeg exits immediately ("No route to host"), so that is +# one ffmpeg start per second for as long as the source is down - enough to keep +# a 2-core host at 100% CPU. Grow the delay 1s -> 64s instead, and reset it once +# ffmpeg has managed to run for more than 2 minutes, so a healthy stream that +# hiccups is still picked up straight away. The sleep only runs when ffmpeg +# fails, so a working source is unaffected. +RECORDING_SCRIPT="$HOME/BirdNET-Pi/scripts/birdnet_recording.sh" +if grep -q '^[[:space:]]*sleep 1$' "$RECORDING_SCRIPT"; then + echo "... backing off the recording retry loop" + sed 's|^\([[:space:]]*\)sleep 1$|\1RETRY_DELAY=$(( SECONDS - ${RETRY_LAST:-0} > 120 ? 1 : ${RETRY_DELAY:-1} ))\n\1RETRY_LAST=$SECONDS\n\1sleep "$RETRY_DELAY"\n\1RETRY_DELAY=$(( RETRY_DELAY < 64 ? RETRY_DELAY * 2 : 64 ))|' \ + "$RECORDING_SCRIPT" > "$RECORDING_SCRIPT.backoff" + # Only keep the patched script if it still parses, a broken recording script + # would take down recording altogether + if bash -n "$RECORDING_SCRIPT.backoff"; then + chown --reference="$RECORDING_SCRIPT" "$RECORDING_SCRIPT.backoff" + chmod --reference="$RECORDING_SCRIPT" "$RECORDING_SCRIPT.backoff" + mv "$RECORDING_SCRIPT.backoff" "$RECORDING_SCRIPT" + else + rm -f "$RECORDING_SCRIPT.backoff" + bashio::log.warning "Could not add a backoff to the recording retry loop, leaving it unchanged" + fi +fi + # Send services log to container logs echo "... redirecting services logs to container logs" while IFS= read -r file; do