From 12e6d9a18168bda0410ce2a81bf54f15754190b1 Mon Sep 17 00:00:00 2001 From: "Tomi P. Hakala" Date: Wed, 8 Jan 2025 23:33:31 +0200 Subject: [PATCH] feat: refactor species action configuration from thresholds to a unified config structure - Renamed ExecuteScriptAction to ExecuteCommandAction and updated its implementation to execute commands instead of scripts. - Changed species action configuration from using thresholds to a new config structure, allowing for more flexible action definitions. - Updated related components to reflect the new configuration structure, including UI adjustments in species settings for command execution. - Enhanced the handling of species actions in the processor and rendering logic to accommodate the new configuration format. --- internal/analysis/processor/execute.go | 10 +-- internal/analysis/processor/workers.go | 10 +-- internal/birdnet/range_filter.go | 2 +- internal/conf/config.go | 27 ++++---- internal/httpcontroller/handlers/settings.go | 30 ++++----- internal/httpcontroller/template_renderers.go | 4 +- views/settings/speciesSettings.html | 67 ++++++++++--------- 7 files changed, 75 insertions(+), 75 deletions(-) diff --git a/internal/analysis/processor/execute.go b/internal/analysis/processor/execute.go index 07781201..e9f3b5d5 100644 --- a/internal/analysis/processor/execute.go +++ b/internal/analysis/processor/execute.go @@ -9,15 +9,15 @@ import ( "github.com/tphakala/birdnet-go/internal/datastore" ) -type ExecuteScriptAction struct { - ScriptPath string - Params map[string]interface{} +type ExecuteCommandAction struct { + Command string + Params map[string]interface{} } // A map to store the action configurations for different species //var speciesActionsMap map[string]SpeciesActionConfig -func (a ExecuteScriptAction) Execute(data interface{}) error { +func (a ExecuteCommandAction) Execute(data interface{}) error { //log.Println("Executing script:", a.ScriptPath) // Type assertion to check if data is of type Detections detection, ok := data.(Detections) @@ -39,7 +39,7 @@ func (a ExecuteScriptAction) Execute(data interface{}) error { } // Executing the script with the provided arguments - cmd := exec.Command(a.ScriptPath, args...) + cmd := exec.Command(a.Command, args...) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error executing script: %w, output: %s", err, string(output)) diff --git a/internal/analysis/processor/workers.go b/internal/analysis/processor/workers.go index 6ac96a25..f8336175 100644 --- a/internal/analysis/processor/workers.go +++ b/internal/analysis/processor/workers.go @@ -54,7 +54,7 @@ func (p *Processor) getActionsForItem(detection Detections) []Action { speciesName := strings.ToLower(detection.Note.CommonName) // Check if species has custom configuration - if speciesConfig, exists := p.Settings.Realtime.Species.Thresholds[speciesName]; exists { + if speciesConfig, exists := p.Settings.Realtime.Species.Config[speciesName]; exists { if p.Settings.Debug { log.Println("Species config exists for custom actions") } @@ -64,11 +64,11 @@ func (p *Processor) getActionsForItem(detection Detections) []Action { // Add custom actions from the new structure for _, actionConfig := range speciesConfig.Actions { switch actionConfig.Type { - case "ExecuteScript": + case "ExecuteCommand": if len(actionConfig.Parameters) > 0 { - actions = append(actions, ExecuteScriptAction{ - ScriptPath: actionConfig.Parameters[0], - Params: parseScriptParams(actionConfig.Parameters[1:], detection), + actions = append(actions, ExecuteCommandAction{ + Command: actionConfig.Command, + Params: parseScriptParams(actionConfig.Parameters, detection), }) } case "SendNotification": diff --git a/internal/birdnet/range_filter.go b/internal/birdnet/range_filter.go index dc89a21d..caa022cd 100644 --- a/internal/birdnet/range_filter.go +++ b/internal/birdnet/range_filter.go @@ -81,7 +81,7 @@ func (bn *BirdNET) GetProbableSpecies(date time.Time, week float32) ([]SpeciesSc } // Process species with configured actions - for species := range bn.Settings.Realtime.Species.Thresholds { + for species := range bn.Settings.Realtime.Species.Config { bn.Debug("Processing species with actions: %s", species) addSpeciesWithMaxScore(bn, &speciesScores, species, processedSpecies) } diff --git a/internal/conf/config.go b/internal/conf/config.go index c44fbc8d..1ae700a3 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -165,23 +165,24 @@ type RealtimeSettings struct { Weather WeatherSettings // Weather provider related settings } -// SpeciesSettings represents all species-related configuration -type SpeciesSettings struct { - Include []string // Species to always include regardless of range filter - Exclude []string // Species to always exclude from detection - Thresholds map[string]SpeciesThreshold // Per-species configuration including threshold and actions +// SpeciesAction represents a single action configuration +type SpeciesAction struct { + Type string `yaml:"type"` // Type of action (ExecuteCommand, etc) + Command string `yaml:"command"` // Path to the command to execute + Parameters []string `yaml:"parameters"` // Action parameters } -// SpeciesThreshold represents per-species configuration -type SpeciesThreshold struct { - Confidence float32 // Detection confidence threshold - Actions []SpeciesAction // List of actions for this species +// SpeciesConfig represents configuration for a specific species +type SpeciesConfig struct { + Threshold float64 `yaml:"threshold"` // Confidence threshold + Actions []SpeciesAction `yaml:"actions"` // List of actions to execute } -// SpeciesAction represents a single action configuration -type SpeciesAction struct { - Type string // Type of action (ExecuteScript, SendNotification, etc) - Parameters []string // Action parameters +// RealtimeSpeciesSettings contains all species-specific settings +type SpeciesSettings struct { + Include []string `yaml:"include"` // Always include these species + Exclude []string `yaml:"exclude"` // Always exclude these species + Config map[string]SpeciesConfig `yaml:"config"` // Per-species configuration } // ActionConfig holds configuration details for a specific action. diff --git a/internal/httpcontroller/handlers/settings.go b/internal/httpcontroller/handlers/settings.go index 04dee05b..d5a8bb24 100644 --- a/internal/httpcontroller/handlers/settings.go +++ b/internal/httpcontroller/handlers/settings.go @@ -28,8 +28,6 @@ var fieldsToSkip = map[string]bool{ "output.file.enabled": true, "output.file.path": true, "output.file.type": true, - //"realtime.species.threshold": true, - "realtime.species.actions": true, } // GetAudioDevices handles the request to list available audio devices @@ -207,14 +205,14 @@ func updateStructFromForm(v reflect.Value, formValues map[string][]string, prefi return err } } - } else if fieldType.Type == reflect.TypeOf(conf.SpeciesThreshold{}) { - // Special handling for SpeciesThreshold - if thresholdJSON, exists := formValues[fullName]; exists && len(thresholdJSON) > 0 { - var threshold conf.SpeciesThreshold - if err := json.Unmarshal([]byte(thresholdJSON[0]), &threshold); err != nil { - return fmt.Errorf("error unmarshaling species threshold for %s: %w", fullName, err) + } else if fieldType.Type == reflect.TypeOf(conf.SpeciesConfig{}) { + // Special handling for SpeciesConfig + if configJSON, exists := formValues[fullName]; exists && len(configJSON) > 0 { + var config conf.SpeciesConfig + if err := json.Unmarshal([]byte(configJSON[0]), &config); err != nil { + return fmt.Errorf("error unmarshaling species config for %s: %w", fullName, err) } - field.Set(reflect.ValueOf(threshold)) + field.Set(reflect.ValueOf(config)) } } else { //log.Println("Debug (updateStructFromForm): Updating struct field:", fullName) @@ -307,14 +305,14 @@ func updateStructFromForm(v reflect.Value, formValues map[string][]string, prefi } case reflect.Map: // Handle map fields - if fieldType.Type == reflect.TypeOf(map[string]conf.SpeciesThreshold{}) { - // Special handling for species thresholds map - if thresholdsJSON, exists := formValues[fullName]; exists && len(thresholdsJSON) > 0 { - var thresholds map[string]conf.SpeciesThreshold - if err := json.Unmarshal([]byte(thresholdsJSON[0]), &thresholds); err != nil { - return fmt.Errorf("error unmarshaling species thresholds for %s: %w", fullName, err) + if fieldType.Type == reflect.TypeOf(map[string]conf.SpeciesConfig{}) { + // Special handling for species config map + if configJSON, exists := formValues[fullName]; exists && len(configJSON) > 0 { + var configs map[string]conf.SpeciesConfig + if err := json.Unmarshal([]byte(configJSON[0]), &configs); err != nil { + return fmt.Errorf("error unmarshaling species configs for %s: %w", fullName, err) } - field.Set(reflect.ValueOf(thresholds)) + field.Set(reflect.ValueOf(configs)) } } else { return fmt.Errorf("unsupported map type for %s", fullName) diff --git a/internal/httpcontroller/template_renderers.go b/internal/httpcontroller/template_renderers.go index 45214dab..689b1bd5 100644 --- a/internal/httpcontroller/template_renderers.go +++ b/internal/httpcontroller/template_renderers.go @@ -134,8 +134,8 @@ func (s *Server) renderSettingsContent(c echo.Context) (template.HTML, error) { // For thresholds, we need to handle the map specially var thresholdStrings []string - for species, threshold := range s.Settings.Realtime.Species.Thresholds { - thresholdStrings = append(thresholdStrings, fmt.Sprintf("[%s: %f]", species, threshold)) + for species, threshold := range s.Settings.Realtime.Species.Config { + thresholdStrings = append(thresholdStrings, fmt.Sprintf("[%s: %f]", species, threshold.Threshold)) } } diff --git a/views/settings/speciesSettings.html b/views/settings/speciesSettings.html index abac0262..256fde68 100644 --- a/views/settings/speciesSettings.html +++ b/views/settings/speciesSettings.html @@ -5,11 +5,11 @@ speciesSettings: { Include: {{if .Settings.Realtime.Species.Include}}{{.Settings.Realtime.Species.Include | toJSON}}{{else}}[]{{end}}, Exclude: {{if .Settings.Realtime.Species.Exclude}}{{.Settings.Realtime.Species.Exclude | toJSON}}{{else}}[]{{end}}, - Thresholds: {{if .Settings.Realtime.Species.Thresholds}}{{.Settings.Realtime.Species.Thresholds | toJSON}}{{else}}{}{{end}}, + Config: {{if .Settings.Realtime.Species.Config}}{{.Settings.Realtime.Species.Config | toJSON}}{{else}}{}{{end}}, }, newIncludeSpecies: '', newExcludeSpecies: '', - newThresholdSpecies: '', + newSpeciesConfig: '', newThreshold: 0.5, editMode: null, editValue: '', @@ -20,7 +20,7 @@ speciesSettingsOpen: false, showActionsModal: false, currentSpecies: '', - currentAction: { type: 'ExecuteScript', parameters: '' }, + currentAction: { type: 'ExecuteCommand', parameters: '' }, resetChanges() { this.hasChanges = false; }, @@ -37,38 +37,39 @@ this.hasChanges = true; }, addConfig() { - if (this.newThresholdSpecies && !this.speciesSettings.Thresholds[this.newThresholdSpecies]) { - this.speciesSettings.Thresholds[this.newThresholdSpecies] = { - Confidence: this.newThreshold, + if (this.newSpeciesConfig && !this.speciesSettings.Config[this.newSpeciesConfig]) { + this.speciesSettings.Config[this.newSpeciesConfig] = { + Threshold: this.newThreshold, Actions: [] }; - this.newThresholdSpecies = ''; + this.newSpeciesConfig = ''; this.newThreshold = 0.5; this.hasChanges = true; } }, removeConfig(species) { - delete this.speciesSettings.Thresholds[species]; + delete this.speciesSettings.Config[species]; this.hasChanges = true; }, openActionsModal(species) { this.currentSpecies = species; - this.currentAction = this.speciesSettings.Thresholds[species]?.Actions?.[0] || { type: 'ExecuteScript', parameters: '' }; + this.currentAction = this.speciesSettings.Config[species]?.Actions?.[0] || { type: 'ExecuteCommand', parameters: '' }; this.showActionsModal = true; }, closeActionsModal() { this.showActionsModal = false; }, saveAction() { - if (!this.speciesSettings.Thresholds[this.currentSpecies]) { - this.speciesSettings.Thresholds[this.currentSpecies] = { - Confidence: 0.5, + if (!this.speciesSettings.Config[this.currentSpecies]) { + this.speciesSettings.Config[this.currentSpecies] = { + Threshold: 0.5, Actions: [] }; } - this.speciesSettings.Thresholds[this.currentSpecies].Actions.push({ + this.speciesSettings.Config[this.currentSpecies].Actions.push({ Type: this.currentAction.type, + CommandPath: this.currentAction.commandPath, Parameters: this.currentAction.parameters.split(',').map(p => p.trim()) }); @@ -197,18 +198,18 @@
+ x-show="showTooltip === 'customConfig'" x-cloak> Set custom confidence thresholds for specific species. These will override the global threshold for the specified species.
- +
@@ -219,13 +220,13 @@
-