This release focuses on closing gaps in Google Perch v2 and BattyBirdNET bat model support introduced in the previous release. BirdNET Geomodel v3.0 replaces the embedded v2.4 range filter with a 12,012-species geographic model covering birds, mammals, insects, amphibians, and reptiles. Perch v2 common name coverage jumps from 42% to approximately 85% using taxonomy data from the geomodel. Bat detection gains a nighttime scheduler that pauses inference during daylight hours and a post-detection ultrasonic validation filter that flags likely false positives. The rest is bug fixes across notifications, streaming, timezone handling, and the settings UI.
New Features
BirdNET Geomodel v3.0 Extended Range Filter
The geographic range filter now uses BirdNET Geomodel v3.0, a dedicated ONNX model covering 12,012 species across birds, mammals, insects, amphibians, and reptiles. It replaces the embedded BirdNET v2.4 range filter with broader species coverage and per-classifier mapping, so each installed bird classifier (BirdNET, Perch) gets range filtering matched to its own label set. Bat classifiers are excluded from range filtering since BattyBirdNET already ships as region-specific models. The geomodel downloads automatically from HuggingFace as a shared companion when you install or reinstall Perch v2 from the Model Gallery. If you already have Perch v2 installed, use the reinstall button to get the geomodel files. A redesigned status panel in Analysis settings shows per-classifier coverage and a toggle for passing species that have no geographic range data (#3031, #3032, #3035, #3037, #3075).
Perch v2 Taxonomy-Based Species Names
Google Perch v2 species names now resolve through the BirdNET Geomodel v3.0 taxonomy file (13,361 species, 29 languages) as a fallback when the BirdNET label set has no match. This raises Perch v2 common name coverage from 42% to approximately 85% across all supported locales. The taxonomy file downloads alongside the geomodel and the resolver registers automatically when the file is present on disk (#3042).
Bat Detection: Nighttime Scheduling
Bat detection can now be restricted to nighttime hours using civil dusk and dawn boundaries calculated from your configured location. The scheduler is enabled by default and pauses bat model inference during daylight, resuming at dusk. If location is not configured or sun calculation fails, the scheduler fails open and keeps bat detection active. The hot path is a single atomic boolean load per analysis cycle, so non-bat models pay zero cost (#3069).
Bat Detection: Ultrasonic Validation Filter
A post-detection filter measures temporal variability of ultrasonic energy in the captured audio to distinguish real bat echolocation from false positives caused by audible noise. Detections with low ultrasonic variability (flat spectral content) are tagged as "unlikely" rather than discarded, preserving all data for review. An "unlikely" badge appears on detection cards and detail pages, and a localized explanatory comment is automatically added to flagged detections. The filter is enabled by default with a coefficient of variation threshold of 0.15, validated across 24 audio files and 13 bat species (#3072, #3074, #3082).
Bug Fixes
Configuration
- Zero-valued settings rejected instead of normalized - when a parent YAML key exists but child sections are absent, Viper drops nested defaults. LiveStream bitrate, segment length, sample rate, weather poll interval, and session duration are now normalized to compile-time defaults before range validation instead of being rejected (#3034).
- Southern hemisphere season dates wrong - Viper defaults always populated Northern Hemisphere season dates, making the hemisphere detection dead code. Season dates are now corrected based on latitude at both config load and save time (#3063, fixes #3003).
Dashboard & UI
- Missing save button on Analysis settings page - the tab bar suppressed action controls; now follows default behavior so the save button appears when settings are modified (#3023 by @ModerateWinGuy).
- Daily activity grid confused by timezone differences - sunrise/sunset icons landed in wrong columns when server and browser timezones differed. The date picker also blocked navigation to the server's current date when the server was in a timezone ahead of the browser. Both now use the server's IANA timezone (#3061, fixes #3005).
- Weather emoji icons swapped for rain conditions - shower rain and steady rain had their day emojis reversed. Also expanded weather condition translations from 15 to 43 base keys covering all 83 yr.no weather symbols, fixing "Unknown" tooltips for conditions like heavy rain (#3058, fixes #3018).
Audio & Streaming
- Model list changes not hot-reloaded - adding or removing a classifier on a stream or audio source persisted to disk but the running orchestrator never rebuilt its model bindings until restart. The settings comparator now checks the per-stream/source Models list (#3045 by @jdevalk).
- Stream health showing "Unknown" status - the stream health API used a credential-stripped copy of the source URL that had an empty connection string. A new registry method returns the raw connection string for health checks, still sanitized at the API boundary (#3049, fixes #3038).
- Stream health SSE race conditions - added engine nil guard to the SSE endpoint, cached raw URL in health snapshots for stream_removed events, and added read lock to prevent races with hot-reload settings updates (#3060).
Notifications & Alerts
- Mark All Read not persisting - the button only marked the locally visible notifications while the alert system kept creating new identical unread entries. A new bulk endpoint marks all unread non-toast notifications atomically (#3048, fixes #3046).
- MQTT Home Assistant sensors stuck on "unknown" - HA autodiscovery value templates used
this.stateas fallback for non-matching sources, which returns "unknown" at startup and is rejected by numeric sensors. Changed toNoneso HA skips the update entirely (#3057, fixes #3052). - Gotify notifications timing out over HTTP - the Shoutrrr library defaults to HTTPS for Gotify URLs, causing TLS handshakes to hang against plain HTTP instances. Added a protocol selector in the notification provider form that appends
?disabletls=yeswhen HTTP is selected (#3059, fixes #3050). - Push alert action silently ignored - the alert dispatcher only handled the "bell" target. Rules configured with the "push" action (offered in the UI) fell through to a warning log with no notification created or broadcast (#3062, fixes #3021).
Database
- Hardcoded table names break MySQL v1-to-v2 migration - four raw SQL queries used hardcoded table names without the
v2_prefix required during the migration window. Fixed all four to read the table prefix from the manager (#3054 by @jdevalk).
Thread Safety
- SunCalc cache stampede and unbounded growth - added double-check locking to reduce redundant calculations under concurrent access and capped the cache at 400 entries. Also fixed a data race in the yr.no weather provider's lastModified field (#3064).
- Frontend date validation rejecting server's timezone - dates from servers in far-ahead timezones were rejected as "future dates" by the browser. Added a +2 day tolerance covering the UTC+14 to UTC-12 gap (#3064).
Internationalization
- Missing translation keys - added missing keys to Danish, Hungarian, Italian, Latvian, Slovak, and Swedish locale files (#3076).