From 7aca77e372f86ee546e0a9c5db536a659e61689a Mon Sep 17 00:00:00 2001 From: "Tomi P. Hakala" Date: Fri, 10 Jan 2025 22:23:42 +0200 Subject: [PATCH] feat: implement control signal handling and range filter reloading - 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. --- internal/analysis/processor/control.go | 27 ++++++++++++++ internal/analysis/processor/range_filter.go | 39 ++++---------------- internal/httpcontroller/handlers/handlers.go | 4 +- internal/httpcontroller/handlers/settings.go | 30 +++++++++++---- 4 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 internal/analysis/processor/control.go diff --git a/internal/analysis/processor/control.go b/internal/analysis/processor/control.go new file mode 100644 index 00000000..5e9659c4 --- /dev/null +++ b/internal/analysis/processor/control.go @@ -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) + } + } + }() +} diff --git a/internal/analysis/processor/range_filter.go b/internal/analysis/processor/range_filter.go index 1c712f24..6767eeac 100644 --- a/internal/analysis/processor/range_filter.go +++ b/internal/analysis/processor/range_filter.go @@ -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 } diff --git a/internal/httpcontroller/handlers/handlers.go b/internal/httpcontroller/handlers/handlers.go index cbb5ea7b..08c8f77a 100644 --- a/internal/httpcontroller/handlers/handlers.go +++ b/internal/httpcontroller/handlers/handlers.go @@ -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. @@ -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) } @@ -89,6 +90,7 @@ func New(ds datastore.Interface, settings *conf.Settings, dashboardSettings *con SunCalc: sunCalc, AudioLevelChan: audioLevelChan, OAuth2Server: oauth2Server, + controlChan: controlChan, } } diff --git a/internal/httpcontroller/handlers/settings.go b/internal/httpcontroller/handlers/settings.go index d6bb6835..4b4f390e 100644 --- a/internal/httpcontroller/handlers/settings.go +++ b/internal/httpcontroller/handlers/settings.go @@ -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{ @@ -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 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 +}