Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish bird images in mqtt events #219

Merged
merged 4 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions internal/analysis/processor/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/tphakala/birdnet-go/internal/birdweather"
"github.com/tphakala/birdnet-go/internal/conf"
"github.com/tphakala/birdnet-go/internal/datastore"
"github.com/tphakala/birdnet-go/internal/imageprovider"
"github.com/tphakala/birdnet-go/internal/mqtt"
"github.com/tphakala/birdnet-go/internal/myaudio"
"github.com/tphakala/birdnet-go/internal/observation"
Expand Down Expand Up @@ -55,10 +56,11 @@ type BirdWeatherAction struct {
}

type MqttAction struct {
Settings *conf.Settings
Note datastore.Note
MqttClient *mqtt.Client
EventTracker *EventTracker
Settings *conf.Settings
Note datastore.Note
BirdImageCache *imageprovider.BirdImageCache
MqttClient *mqtt.Client
EventTracker *EventTracker
}

type UpdateRangeFilterAction struct {
Expand Down Expand Up @@ -151,15 +153,29 @@ func (a BirdWeatherAction) Execute(data interface{}) error {
return nil // return an error if the action fails
}

type NoteWithBirdImage struct {
datastore.Note
BirdImage imageprovider.BirdImage
}

// Execute sends the note to the MQTT broker
func (a MqttAction) Execute(data interface{}) error {
// Validate MQTT settings
if a.Settings.Realtime.MQTT.Topic == "" {
return errors.New("MQTT topic is not specified")
}

// Get bird image of detected bird
birdImage, err := a.BirdImageCache.Get(a.Note.ScientificName)
if err != nil {
birdImage = imageprovider.BirdImage{}
}

// Wrap note with bird image
noteWithBirdImage := NoteWithBirdImage{Note: a.Note, BirdImage: birdImage}

// Create a JSON representation of the note
noteJson, err := json.Marshal(a.Note)
noteJson, err := json.Marshal(noteWithBirdImage)
if err != nil {
log.Printf("error marshalling note to JSON: %s\n", err)
return err
Expand Down
5 changes: 4 additions & 1 deletion internal/analysis/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/tphakala/birdnet-go/internal/birdweather"
"github.com/tphakala/birdnet-go/internal/conf"
"github.com/tphakala/birdnet-go/internal/datastore"
"github.com/tphakala/birdnet-go/internal/imageprovider"
"github.com/tphakala/birdnet-go/internal/mqtt"
"github.com/tphakala/birdnet-go/internal/observation"
"github.com/tphakala/birdnet-go/internal/telemetry"
Expand All @@ -24,6 +25,7 @@ type Processor struct {
Bn *birdnet.BirdNET
BwClient *birdweather.BwClient
MqttClient *mqtt.Client
BirdImageCache *imageprovider.BirdImageCache
EventTracker *EventTracker
DogBarkFilter DogBarkFilter
SpeciesConfig SpeciesConfig
Expand Down Expand Up @@ -71,11 +73,12 @@ var PendingDetections map[string]PendingDetection = make(map[string]PendingDetec
var mutex sync.Mutex

// func New(settings *conf.Settings, ds datastore.Interface, bn *birdnet.BirdNET, audioBuffers map[string]*myaudio.AudioBuffer, metrics *telemetry.Metrics) *Processor {
func New(settings *conf.Settings, ds datastore.Interface, bn *birdnet.BirdNET, metrics *telemetry.Metrics) *Processor {
func New(settings *conf.Settings, ds datastore.Interface, bn *birdnet.BirdNET, metrics *telemetry.Metrics, birdImageCache *imageprovider.BirdImageCache) *Processor {
p := &Processor{
Settings: settings,
Ds: ds,
Bn: bn,
BirdImageCache: birdImageCache,
EventTracker: NewEventTracker(),
IncludedSpecies: new([]string),
Metrics: metrics,
Expand Down
2 changes: 1 addition & 1 deletion internal/analysis/processor/workers.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (p *Processor) getDefaultActions(detection Detections) []Action {
log.Println("MQTT client is not initialized, skipping MQTT action")
return actions
}
actions = append(actions, MqttAction{Settings: p.Settings, MqttClient: p.MqttClient, EventTracker: p.EventTracker, Note: detection.Note})
actions = append(actions, MqttAction{Settings: p.Settings, MqttClient: p.MqttClient, EventTracker: p.EventTracker, Note: detection.Note, BirdImageCache: p.BirdImageCache})
}

// Check if UpdateRangeFilterAction needs to be executed for the day
Expand Down
43 changes: 41 additions & 2 deletions internal/analysis/realtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/shirou/gopsutil/v3/host"
"github.com/tphakala/birdnet-go/internal/imageprovider"

"github.com/tphakala/birdnet-go/internal/analysis/processor"
"github.com/tphakala/birdnet-go/internal/analysis/queue"
Expand Down Expand Up @@ -109,11 +110,14 @@ func RealtimeAnalysis(settings *conf.Settings) error {
log.Fatalf("Error initializing metrics: %v", err)
}

// Intialize bird image cache
birdImageCache := initBirdImageCache(dataStore)

// Start worker pool for processing detections
processor.New(settings, dataStore, bn, metrics)
processor.New(settings, dataStore, bn, metrics, birdImageCache)

// Start http server
httpcontroller.New(settings, dataStore)
httpcontroller.New(settings, dataStore, birdImageCache)

// Initialize the wait group to wait for all goroutines to finish
var wg sync.WaitGroup
Expand Down Expand Up @@ -255,3 +259,38 @@ func clipCleanupMonitor(wg *sync.WaitGroup, dataStore datastore.Interface, quitC
}
}
}

func initBirdImageCache(ds datastore.Interface) *imageprovider.BirdImageCache {

birdImageCache, err := imageprovider.CreateDefaultCache()
if err != nil {
log.Fatalf("Failed to create image cache: %v", err)
}

// Initialize the image cache by fetching all detectes species in database
go func() {
speciesList, err := ds.GetAllDetectedSpecies()
if err != nil {
log.Printf("Failed to get detected species: %v", err)
return
}

var wg sync.WaitGroup

for _, species := range speciesList {
wg.Add(1)

go func(speciesName string) {
defer wg.Done()
_, err := birdImageCache.Get(speciesName)
if err != nil {
fmt.Printf("Failed to get image for species %s: %v\n", speciesName, err)
}
}(species.ScientificName)
}

wg.Wait()
}()

return birdImageCache
}
36 changes: 3 additions & 33 deletions internal/httpcontroller/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"html/template"
"io"
"log"
"sync"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"golang.org/x/crypto/acme/autocert"

"github.com/tphakala/birdnet-go/internal/conf"
"github.com/tphakala/birdnet-go/internal/datastore"
"github.com/tphakala/birdnet-go/internal/httpcontroller/imageprovider"
"github.com/tphakala/birdnet-go/internal/imageprovider"
"github.com/tphakala/birdnet-go/internal/logger"
)

Expand All @@ -37,20 +36,15 @@ type Server struct {
}

// New initializes a new HTTP server with given context and datastore.
func New(settings *conf.Settings, dataStore datastore.Interface) *Server {
func New(settings *conf.Settings, dataStore datastore.Interface, birdImageCache *imageprovider.BirdImageCache) *Server {
// Default port configuration
configureDefaultSettings(settings)

cache, err := imageprovider.CreateDefaultCache()
if err != nil {
log.Fatal(err)
}

s := &Server{
Echo: echo.New(),
ds: dataStore,
Settings: settings,
BirdImageCache: cache,
BirdImageCache: birdImageCache,
}

// Server initialization
Expand Down Expand Up @@ -106,7 +100,6 @@ func (s *Server) initializeServer() {
s.initLogger()
s.configureMiddleware()
s.initRoutes()
go s.initBirdImageCache()
}

// handleServerError listens for server errors and handles them.
Expand Down Expand Up @@ -152,26 +145,3 @@ func (s *Server) initLogger() {
},
}))
}

func (s *Server) initBirdImageCache() {
speciesList, err := s.ds.GetAllDetectedSpecies()
if err != nil {
s.Echo.Logger.Fatal(err)
}

var wg sync.WaitGroup

for _, species := range speciesList {
wg.Add(1)

go func(speciesName string) {
defer wg.Done()
_, err := s.BirdImageCache.Get(speciesName)
if err != nil {
s.Echo.Logger.Error(err)
}
}(species.ScientificName)
}

wg.Wait()
}
1 change: 0 additions & 1 deletion views/fragments/birdsTableHTML.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
<th scope="row" class="py-1 px-6">
<!-- Bird thumbnail -->
<img loading="lazy" width="100%" src="{{thumbnail .ScientificName}}" class="h-auto rounded-md">
<!-- TODO add credits back in -->
<div style="font-size:0.5em;" >
{{thumbnailAttribution .ScientificName}}
</div>
Expand Down
Loading