alexbelgium / hassio-addons

My homeassistant addons
MIT License
1.54k stars 217 forks source link

🐛 [BirdNET-Pi] MQTT is not showing all detections #1515

Closed UlrichThiess closed 2 months ago

UlrichThiess commented 2 months ago

Description

I have a running BirdNET-Pi AddOn. It uses two mics via rtsp. This works perfectly. BirdNET-Pi counts till now 243 detections with 19 species. In HA i have a table to show the birds from https://community.home-assistant.io/t/displaying-birdnet-go-detections/713611 But in HA i count only 76 detections with 13 species. If i look with MQTT Explorer i could see, that not all detections will be send by mqtt. I enabled eMail: Works for all detections.

Reproduction steps

1. running BirdNET-Pi AddOn
2. Table to show detections in HA
3. MQTT Explorer

Addon Logs

no logs needed

Architecture

No response

OS

No response

alexbelgium commented 2 months ago

I'll probably rewrite the logic to hook in the detection py (as for apprise) instead of reading the log

alexbelgium commented 2 months ago

And currently it only works for mp3 not other formats

alexbelgium commented 2 months ago

Rewriting the logic to have a python hook in the https://github.com/Nachtzuster/BirdNETPi/blob/main/scripts/utils/reporting.py (like sendAppriseNotifications) instead of blindly reading the log would be the right way to go but it is more complex than I anticipated.

I probably won't be able to look at it in the next 2 weeks

alexbelgium commented 2 months ago

I've created a branch here to modify the code : https://github.com/alexbelgium/hassio-addons/tree/mqtt_hook

alexbelgium commented 2 months ago

New logic in the PR :

alexbelgium commented 2 months ago

I've tried merging to test

UlrichThiess commented 2 months ago

small improvements: change print to logging.info change syslog = open('/proc/1/fd/1', 'r') to syslog = open('/proc/1/fd/1', 'r', buffering=1) change

def file_row_generator(s):
...

to

def file_row_generator(s):
    for line in iter(s.readline, ''):
        yield line

as i see all new detections will be published could say more tommorrow

UlrichThiess commented 2 months ago

hm - not perfect but better - i work on it

alexbelgium commented 2 months ago

I've tried pushing a new logic with a hook in birdnet_analysis instead of reading the log. Not sure if it fully works though

UlrichThiess commented 2 months ago

should i reinstall the AddOn?

alexbelgium commented 2 months ago

Nono it is just in the incremental updates (v81 is building now)

UlrichThiess commented 2 months ago

Installed v81.

LOG: ERROR:utils.birdnet_to_mqtt:Cannot post mqtt: '<=' not supported between instances of 'str' and 'int'

UlrichThiess commented 2 months ago
sed: -e expression #1, char 15: Unmatched ( or \(
Error : /etc/cont-init.d/33-mqtt.sh exiting 1
UlrichThiess commented 2 months ago

change in 33-mqtt.sh

sed -i "/write_to_db\(/a\            automatic_mqtt_publish\(file, detections, os.path.basename\(detection.file_name_extr\)\)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py

into (escaping of ( is not necassary)

sed -i "/write_to_db(/a\ \ \ \ \ \ \ \ automatic_mqtt_publish(file, detections, os.path.basename(detection.file_name_extr))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
UlrichThiess commented 2 months ago

thanks for the corrections

log:

INFO:birdnet_analysis:2024-08-15;09:03:51;Passer domesticus;Haussperling;0.828;47.9379;7.6354;0.7;33;1.25;0.0;Haussperling-83-2024-08-15-birdnet-RTSP_1-09:03:51.mp3
[birdnet_analysis][INFO] 2024-08-15;09:03:51;Passer domesticus;Haussperling;0.828;47.9379;7.6354;0.7;33;1.25;0.0;Haussperling-83-2024-08-15-birdnet-RTSP_1-09:03:51.mp3
INFO:utils.birdnet_to_mqtt:Posted to MQTT: ok
[utils.birdnet_to_mqtt][INFO] Posted to MQTT: ok

but nothing is posted

changes in birdnet_to_mqtt.py won't be recognized immediately, which service or process could i restart/kill?

alexbelgium commented 2 months ago

You need to do systemctl restart birdnet_analysis to do it manually but it is automatically restarted at boot. Mmh it seems it's due to the ClipName which doesn't work yet. Perhaps I'll remove it for the moment

UlrichThiess commented 2 months ago

can't update the addon : "manifest error"

Die Aktion update/install konnte nicht ausgeführt werden. Error updating BirdNET-pi: Can't install ghcr.io/alexbelgium/birdnet-pi-amd64:0.13-83: 500 Server Error for http+docker://localhost/v1.45/images/create?tag=0.13-83&fromImage=ghcr.io%2Falexbelgium%2Fbirdnet-pi-amd64&platform=linux%2Famd64: Internal Server Error ("manifest unknown")
UlrichThiess commented 2 months ago

got it (i deleted all my old messages)

33-mqtt.sh line 14 should be

    sed -i '/write_to_db(/a\                automatic_mqtt_publish(file, detection)' "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py

birdnet_to_mqtt.py

#! /usr/bin/env python3
# birdnet_to_mqtt.py

import time
import re
import datetime
import json
import logging
import paho.mqtt.client as mqtt
import requests
import sys
import os

sys.path.append('/home/pi/BirdNET-Pi/scripts/utils')
from helpers import get_settings

# Setup basic configuration for logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)

# Used in flickrimage
flickr_images = {}
conf = get_settings()
settings_dict = dict(conf)

# MQTT server configuration
mqtt_server = "%%mqtt_server%%"
mqtt_user = "%%mqtt_user%%"
mqtt_pass = "%%mqtt_pass%%"
mqtt_port = %%mqtt_port%%
mqtt_topic = 'birdnet'
bird_lookup_url_base = 'http://en.wikipedia.org/wiki/'

def on_connect(client, userdata, flags, rc ): #, properties=None):
    """ Callback for when the client receives a CONNACK response from the server. """
    if rc == 0:
        log.info("Connected to MQTT Broker!")
    else:
        log.error(f"Failed to connect, return code {rc}\n")

def get_bird_code(scientific_name):
    with open('/home/pi/BirdNET-Pi/scripts/ebird.php', 'r') as file:
        data = file.read()

    array_str = re.search(r'\$ebirds = \[(.*?)\];', data, re.DOTALL).group(1)

    bird_dict = {re.search(r'"(.*?)"', line).group(1): re.search(r'=> "(.*?)"', line).group(1)
                 for line in array_str.split('\n') if '=>' in line}

    return bird_dict.get(scientific_name)

def automatic_mqtt_publish(file, detection, path):
    bird = {}
    bird['Date'] = file.date
    bird['Time'] = file.time
    bird['ScientificName'] = detection.scientific_name.replace('_', ' ')
    bird['CommonName'] = detection.common_name
    bird['Confidence'] = detection.confidence
    bird['SpeciesCode'] = get_bird_code(detection.scientific_name)
    bird['ClipName'] = path
    bird['url'] = bird_lookup_url_base + detection.scientific_name.replace(' ', '_')

    # Flickimage
    image_url = ""
    common_name = detection.common_name
    if len(settings_dict.get('FLICKR_API_KEY')) > 0:
        if common_name not in flickr_images:
            try:
                headers = {'User-Agent': 'Python_Flickr/1.0'}
                url = ('https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=' + str(settings_dict.get('FLICKR_API_KEY')) +
                       '&text=' + str(common_name) + ' bird&sort=relevance&per_page=5&media=photos&format=json&license=2%2C3%2C4%2C5%2C6%2C9&nojsoncallback=1')
                resp = requests.get(url=url, headers=headers, timeout=10)

                resp.encoding = "utf-8"
                data = resp.json()["photos"]["photo"][0]

                image_url = 'https://farm'+str(data["farm"])+'.static.flickr.com/'+str(data["server"])+'/'+str(data["id"])+'_'+str(data["secret"])+'_n.jpg'
                flickr_images[common_name] = image_url
            except Exception as e:
                print("FLICKR API ERROR: "+str(e))
                image_url = ""
        else:
            image_url = flickr_images[common_name]

        bird['FlickrImage'] = image_url

        json_bird = json.dumps(bird)
        mqttc.reconnect()
        mqttc.publish(mqtt_topic, json_bird, 1) 
        log.info("Posted to MQTT: ok")

mqttc = mqtt.Client('birdnet_mqtt')
mqttc.username_pw_set(mqtt_user, mqtt_pass)
mqttc.on_connect = on_connect

try:
    mqttc.connect(mqtt_server, mqtt_port)
    mqttc.loop_start()

    # Assuming `file` and `detections` are provided from somewhere
    # automatic_mqtt_publish(file, detections)

except Exception as e:
    log.error("Cannot post mqtt: %s", e)

finally:
    mqttc.loop_stop()
    mqttc.disconnect()
alexbelgium commented 2 months ago

Thanks so much!!! I'll correct it now

alexbelgium commented 2 months ago

I've pushed a v84, it shows "manifest unknown" until the build is completed and pushed to github through the automatic action. Thanks again for the collaborative effort! Now with this logic it should for sure push every detections to mqtt

alexbelgium commented 2 months ago

I've pushed a v85 to remove path in automatic_mqtt_publish(file, detection, path)

UlrichThiess commented 2 months ago

and remove also line 61 in birdnet_to_mqtt.py bird['ClipName'] = path

does anyone uses the ClipName in Home Assistant?

alexbelgium commented 2 months ago

I would say the ClipName is usually used as unique identifier for detections. Now, do people actually use it... In 2 weeks I'll have more time so I can see how to re-implement it. Thanks very much for all the help on this mqtt code!

UlrichThiess commented 2 months ago

to reimplement ClipName:

change 33-mqtt.sh line 14 into

    sed -i '/write_to_db(/a\                automatic_mqtt_publish(file, detection,os.path.basename(detection.file_name_extr))' "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py

change birdnet_to_mqtt.py line 52 into

def automatic_mqtt_publish(file, detection, path):

and remove line 53

UlrichThiess commented 2 months ago

as i could see: BirdNET has 192 detections and Home Assistant shows 192 detections

perfect

thank you very much it was my pleasure