Skip to content

Commit

Permalink
feat: implement control signal handling and range filter reloading
Browse files Browse the repository at this point in the history
- Added a new control signal monitor in the Processor to handle reload requests for the range filter.
- Introduced a ReloadRangeFilter function to update the species list based on the current date.
- Enhanced the Handlers to include a control channel for sending reload signals when relevant settings change.
- Implemented a check for changes in range filter settings to trigger reloads, improving configuration management.
  • Loading branch information
tphakala committed Jan 10, 2025
1 parent 47789c9 commit 7aca77e
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 39 deletions.
27 changes: 27 additions & 0 deletions internal/analysis/processor/control.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package processor

import "log"

// Control signal types
const (
ReloadRangeFilter = "reload_range_filter"
ReloadBirdNET = "reload_birdnet"
)

// controlSignalMonitor handles various control signals for the processor
func (p *Processor) controlSignalMonitor() {
go func() {
for signal := range p.controlChan {
switch signal {
case ReloadRangeFilter:
if err := p.ReloadRangeFilter(); err != nil {
log.Printf("\033[31m❌ Error handling range filter reload: %v\033[0m", err)
} else {
log.Printf("\033[32m🔄 Range filter reloaded successfully\033[0m")
}
default:
log.Printf("Received unknown control signal: %v", signal)
}
}
}()
}
39 changes: 8 additions & 31 deletions internal/analysis/processor/range_filter.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,26 @@
package processor

import (
"log"
"strings"
"time"
)

// Add or update the updateIncludedSpecies function
func (p *Processor) updateIncludedSpecies(date time.Time) {
speciesScores, err := p.Bn.GetProbableSpecies(date, 0.0)
func (p *Processor) ReloadRangeFilter() error {
today := time.Now().Truncate(24 * time.Hour)

// Update location based species list
speciesScores, err := p.Bn.GetProbableSpecies(today, 0.0)
if err != nil {
log.Printf("Failed to get probable species: %s", err)
return
return err
}

// Convert the speciesScores slice to a slice of species labels
var includedSpecies []string
for _, speciesScore := range speciesScores {
includedSpecies = append(includedSpecies, speciesScore.Label)
}

p.Settings.UpdateIncludedSpecies(includedSpecies)
p.Settings.BirdNET.RangeFilter.LastUpdated = date

// Update dynamic thresholds if enabled
if p.Settings.Realtime.DynamicThreshold.Enabled {
p.updateDynamicThresholds()
}
}

// Add a new function to update dynamic thresholds
func (p *Processor) updateDynamicThresholds() {
newDynamicThresholds := make(map[string]*DynamicThreshold)
for _, species := range p.Settings.BirdNET.RangeFilter.Species {
speciesLowercase := strings.ToLower(species)
if dt, exists := p.DynamicThresholds[speciesLowercase]; exists {
newDynamicThresholds[speciesLowercase] = dt
} else {
newDynamicThresholds[speciesLowercase] = &DynamicThreshold{
Level: 0,
CurrentValue: float64(p.Settings.BirdNET.Threshold),
Timer: time.Now(),
HighConfCount: 0,
ValidHours: p.Settings.Realtime.DynamicThreshold.ValidHours,
}
}
}
p.DynamicThresholds = newDynamicThresholds
return nil
}
4 changes: 3 additions & 1 deletion internal/httpcontroller/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Handlers struct {
SunCalc *suncalc.SunCalc // SunCalc instance for calculating sun event times
AudioLevelChan chan myaudio.AudioLevelData // Channel for audio level updates
OAuth2Server *security.OAuth2Server
controlChan chan string
}

// HandlerError is a custom error type that includes an HTTP status code and a user-friendly message.
Expand Down Expand Up @@ -71,7 +72,7 @@ func (bh *baseHandler) logInfo(message string) {
}

// New creates a new Handlers instance with the given dependencies.
func New(ds datastore.Interface, settings *conf.Settings, dashboardSettings *conf.Dashboard, birdImageCache *imageprovider.BirdImageCache, logger *log.Logger, sunCalc *suncalc.SunCalc, audioLevelChan chan myaudio.AudioLevelData, oauth2Server *security.OAuth2Server) *Handlers {
func New(ds datastore.Interface, settings *conf.Settings, dashboardSettings *conf.Dashboard, birdImageCache *imageprovider.BirdImageCache, logger *log.Logger, sunCalc *suncalc.SunCalc, audioLevelChan chan myaudio.AudioLevelData, oauth2Server *security.OAuth2Server, controlChan chan string) *Handlers {
if logger == nil {
logger = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}
Expand All @@ -89,6 +90,7 @@ func New(ds datastore.Interface, settings *conf.Settings, dashboardSettings *con
SunCalc: sunCalc,
AudioLevelChan: audioLevelChan,
OAuth2Server: oauth2Server,
controlChan: controlChan,
}
}

Expand Down
30 changes: 23 additions & 7 deletions internal/httpcontroller/handlers/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,31 +48,34 @@ func (h *Handlers) GetAudioDevices(c echo.Context) error {
func (h *Handlers) SaveSettings(c echo.Context) error {
settings := conf.Setting()
if settings == nil {
// Return an error if settings are not initialized
return h.NewHandlerError(fmt.Errorf("settings is nil"), "Settings not initialized", http.StatusInternalServerError)
}

// Store old settings for comparison
oldSettings := *settings

formParams, err := c.FormParams()
if err != nil {
// Return an error if form parameters cannot be parsed
return h.NewHandlerError(err, "Failed to parse form", http.StatusBadRequest)
}

// Store old equalizer settings
oldEqualizerSettings := settings.Realtime.Audio.Equalizer

// Update settings from form parameters
if err := updateSettingsFromForm(settings, formParams); err != nil {
// Add more detailed logging for debugging
log.Printf("Debug: Form parameters for species config: %+v", formParams["realtime.species.config"])
return h.NewHandlerError(err, "Error updating settings", http.StatusInternalServerError)
}

// Check if range filter related settings have changed
if rangeFilterSettingsChanged(oldSettings, *settings) {
log.Println("Range filter settings changed, sending reload signal")
h.controlChan <- "reload_range_filter"
}

// Check the authentication settings and update if needed
h.updateAuthenticationSettings(settings)

// Check if audio equalizer settings have changed
if equalizerSettingsChanged(oldEqualizerSettings, settings.Realtime.Audio.Equalizer) {
if equalizerSettingsChanged(settings.Realtime.Audio.Equalizer, settings.Realtime.Audio.Equalizer) {
log.Println("Debug (SaveSettings): Equalizer settings changed, reloading audio filters")
if err := myaudio.UpdateFilterChain(settings); err != nil {
h.SSE.SendNotification(Notification{
Expand Down Expand Up @@ -560,3 +563,16 @@ func parseIntFromForm(formValues map[string][]string, key string) (int, error) {
func equalizerSettingsChanged(oldSettings, newSettings conf.EqualizerSettings) bool {
return !reflect.DeepEqual(oldSettings, newSettings)
}

// rangeFilterSettingsChanged checks if any settings that require a range filter reload have changed
func rangeFilterSettingsChanged(old, new conf.Settings) bool {

Check failure on line 568 in internal/httpcontroller/handlers/settings.go

View workflow job for this annotation

GitHub Actions / golangci / lint

param new has same name as predeclared identifier (predeclared)
// Check for changes in species include/exclude lists
if !reflect.DeepEqual(old.Realtime.Species.Include, new.Realtime.Species.Include) {
return true
}
if !reflect.DeepEqual(old.Realtime.Species.Exclude, new.Realtime.Species.Exclude) {
return true
}

return false
}

0 comments on commit 7aca77e

Please sign in to comment.