Skip to content

Commit

Permalink
Merge pull request #509 from tphakala/birdweather-dynamic-restart
Browse files Browse the repository at this point in the history
feat: Birdweather dynamic restart on reconfigure
  • Loading branch information
tphakala authored Mar 2, 2025
2 parents ff091a7 + 67e9433 commit 8043a45
Show file tree
Hide file tree
Showing 14 changed files with 1,740 additions and 42 deletions.
14 changes: 14 additions & 0 deletions assets/js/components/multiStageOperation.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ document.addEventListener('alpine:init', () => {
});
}

// Always complete the "Starting Test" stage when any other stage begins
// This ensures we don't have a stuck "Starting Test" if the first real stage fails
if (result.stage !== "Starting Test" && !isProgress) {
const startingTestIndex = this.results.findIndex(r => r.stage === "Starting Test");
if (startingTestIndex >= 0 && this.results[startingTestIndex].state === "running") {
this.results[startingTestIndex] = {
...this.results[startingTestIndex],
state: 'completed',
isProgress: false,
message: "Initialization complete"
};
}
}

// Sort results according to stage order
this.results.sort((a, b) =>
this.stageOrder.indexOf(a.stage) - this.stageOrder.indexOf(b.stage)
Expand Down
37 changes: 37 additions & 0 deletions internal/analysis/control_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/tphakala/birdnet-go/internal/analysis/processor"
"github.com/tphakala/birdnet-go/internal/birdnet"
"github.com/tphakala/birdnet-go/internal/birdweather"
"github.com/tphakala/birdnet-go/internal/conf"
"github.com/tphakala/birdnet-go/internal/httpcontroller/handlers"
"github.com/tphakala/birdnet-go/internal/mqtt"
Expand Down Expand Up @@ -71,6 +72,8 @@ func (cm *ControlMonitor) handleControlSignal(signal string) {
cm.handleReconfigureMQTT()
case "reconfigure_rtsp_sources":
cm.handleReconfigureRTSP()
case "reconfigure_birdweather":
cm.handleReconfigureBirdWeather()
default:
log.Printf("Received unknown control signal: %v", signal)
}
Expand Down Expand Up @@ -176,6 +179,40 @@ func (cm *ControlMonitor) handleReconfigureRTSP() {
cm.notifySuccess("Audio capture reconfigured successfully")
}

// handleReconfigureBirdWeather reconfigures the BirdWeather integration
func (cm *ControlMonitor) handleReconfigureBirdWeather() {
log.Printf("\033[32m🔄 Reconfiguring BirdWeather integration...\033[0m")
settings := conf.Setting()

if cm.proc == nil {
log.Printf("\033[31m❌ Error: Processor not available\033[0m")
cm.notifyError("Failed to reconfigure BirdWeather", fmt.Errorf("processor not available"))
return
}

// First, safely disconnect any existing client
cm.proc.DisconnectBwClient()

// Create new BirdWeather client with updated settings
if settings.Realtime.Birdweather.Enabled {
bwClient, err := birdweather.New(settings)
if err != nil {
log.Printf("\033[31m❌ Error creating BirdWeather client: %v\033[0m", err)
cm.notifyError("Failed to create BirdWeather client", err)
return
}

// Update the processor's BirdWeather client using the thread-safe setter
cm.proc.SetBwClient(bwClient)
log.Printf("\033[32m✅ BirdWeather integration configured successfully\033[0m")
cm.notifySuccess("BirdWeather integration configured successfully")
} else {
// If BirdWeather is disabled, client is already set to nil by DisconnectBwClient
log.Printf("\033[32m✅ BirdWeather integration disabled\033[0m")
cm.notifySuccess("BirdWeather integration disabled")
}
}

// notifySuccess sends a success notification
func (cm *ControlMonitor) notifySuccess(message string) {
cm.notificationChan <- handlers.Notification{
Expand Down
13 changes: 12 additions & 1 deletion internal/analysis/processor/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ func (a *BirdWeatherAction) Execute(data interface{}) error {
return nil
}

// Early check if BirdWeather is still enabled in settings
if !a.Settings.Realtime.Birdweather.Enabled {
return nil // Silently exit if BirdWeather was disabled after this action was created
}

// Add threshold check here
if a.Note.Confidence < float64(a.Settings.Realtime.Birdweather.Threshold) {
if a.Settings.Debug {
Expand All @@ -208,11 +213,17 @@ func (a *BirdWeatherAction) Execute(data interface{}) error {
return nil
}

// Safe check for nil BwClient
if a.BwClient == nil {
return fmt.Errorf("BirdWeather client is not initialized")
}

if err := a.BwClient.Publish(&a.Note, a.pcmData); err != nil {
// Copy data locally to reduce lock duration if needed
note := a.Note
pcmData := a.pcmData

// Try to publish with appropriate error handling
if err := a.BwClient.Publish(&note, pcmData); err != nil {
log.Printf("error uploading to BirdWeather: %s\n", err)
return err
} else if a.Settings.Debug {
Expand Down
47 changes: 39 additions & 8 deletions internal/analysis/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Processor struct {
Ds datastore.Interface
Bn *birdnet.BirdNET
BwClient *birdweather.BwClient
bwClientMutex sync.RWMutex // Mutex to protect BwClient access
MqttClient mqtt.Client
mqttMutex sync.RWMutex // Mutex to protect MQTT client access
BirdImageCache *imageprovider.BirdImageCache
Expand Down Expand Up @@ -104,9 +105,11 @@ func New(settings *conf.Settings, ds datastore.Interface, bn *birdnet.BirdNET, m
// Initialize BirdWeather client if enabled in settings
if settings.Realtime.Birdweather.Enabled {
var err error
p.BwClient, err = birdweather.New(settings)
bwClient, err := birdweather.New(settings)
if err != nil {
log.Printf("failed to create Birdweather client: %s", err)
} else {
p.SetBwClient(bwClient) // Use setter for thread safety
}
}

Expand Down Expand Up @@ -495,13 +498,16 @@ func (p *Processor) getDefaultActions(detection *Detections) []Action {
}

// Add BirdWeatherAction if enabled and client is initialized
if p.Settings.Realtime.Birdweather.Enabled && p.BwClient != nil {
actions = append(actions, &BirdWeatherAction{
Settings: p.Settings,
EventTracker: p.EventTracker,
BwClient: p.BwClient,
Note: detection.Note,
pcmData: detection.pcmData3s})
if p.Settings.Realtime.Birdweather.Enabled {
bwClient := p.GetBwClient() // Use getter for thread safety
if bwClient != nil {
actions = append(actions, &BirdWeatherAction{
Settings: p.Settings,
EventTracker: p.EventTracker,
BwClient: bwClient,
Note: detection.Note,
pcmData: detection.pcmData3s})
}
}

// Add MQTT action if enabled and client is available
Expand Down Expand Up @@ -531,3 +537,28 @@ func (p *Processor) getDefaultActions(detection *Detections) []Action {

return actions
}

// GetBwClient safely returns the current BirdWeather client
func (p *Processor) GetBwClient() *birdweather.BwClient {
p.bwClientMutex.RLock()
defer p.bwClientMutex.RUnlock()
return p.BwClient
}

// SetBwClient safely sets a new BirdWeather client
func (p *Processor) SetBwClient(client *birdweather.BwClient) {
p.bwClientMutex.Lock()
defer p.bwClientMutex.Unlock()
p.BwClient = client
}

// DisconnectBwClient safely disconnects and removes the BirdWeather client
func (p *Processor) DisconnectBwClient() {
p.bwClientMutex.Lock()
defer p.bwClientMutex.Unlock()
// Call the Close method if the client exists
if p.BwClient != nil {
p.BwClient.Close()
p.BwClient = nil
}
}
Loading

0 comments on commit 8043a45

Please sign in to comment.