Skip to content

Commit

Permalink
config in yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
mzakharo committed Feb 5, 2024
1 parent b58224b commit 5141e95
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 63 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__
/MANIFEST
/birdNet
/build
/config.yaml
18 changes: 4 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,9 @@ BirdNET App for Raspberry Pi/BeagleBone
```bash
git clone --recurse-submodules https://github.com/mzakharo/birdnetapp.git
```
- In the `birdnetapp/birdnetapp` folder, create a `secrets.py` with the following contents:
```python
TELEGRAM_TOKEN = 'from_botfather'
TELEGRAM_CHATID = '#######'

INFLUX_URL = "http://host:PORT"
INFLUX_TOKEN= "XXXXXX"
INFLUX_ORG = "my_org"
INFLUX_BUCKET = "my_bucket"
```

- Copy `config.example.yaml` to `config.yaml` and fill in the details according to your hardware and environment


### Optional: reduce SD card wear
- setup `/tmp` as ramdisk:
```bash
Expand All @@ -67,9 +59,7 @@ RuntimeMaxUse=64M
- for BeagleBone: `pip3 install beaglebone/tflite_runtime-2.16.0-cp39-cp39-linux_armv7l.whl`
- Install dependencies via `pip3 install -r requirements.txt`
- Run the server: `cd $HOME/birdnetapp/BirdNET-Analyzer && python3 server.py`
- Run the app: `cd $HOME/birdnetapp && python3 main.py --lat <latitude> --lon <longitude> --notification_delay 15`
- Note: the app will probably complain that the mic is not configured properly and give options on possible configurations. adjust `--card --channels --rate` parameters according to your microphone capabilities

- Run the app: `cd $HOME/birdnetapp && python3 main.py`

- Optional: install systemd services to run on startup via `birdnet_main.service` and `birdnet_server.service`
```
Expand Down
58 changes: 32 additions & 26 deletions birdnetapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
from influxdb_client import Point, WritePrecision
from influxdb_client.client.warnings import MissingPivotFunction
warnings.simplefilter("ignore", MissingPivotFunction)
from .secrets import TELEGRAM_TOKEN, TELEGRAM_CHATID, INFLUX_BUCKET, INFLUX_ORG
from .config import *
from .clean import cleanup
from dataclasses import dataclass
import yaml


_LOGGER = logging.getLogger(__name__)

Expand All @@ -35,43 +37,47 @@
}


def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument('--min_confidence', type=float, default=CONF_THRESH, help='minimum confidence threshold')
parser.add_argument('--dry', action='store_true', help='do not upload to influx, send notifications')
parser.add_argument('--debug', action='store_true', help='enable debug logs')
parser.add_argument('--card', default=CARD, help='microphone card to look for')
parser.add_argument('--channels', default=CHANNELS, type=int, help='microphone number of channels')
parser.add_argument('--rate', default=RATE, type=int, help='microphone sampling rate (Hz)')
parser.add_argument('--stride_seconds', default=RECORD_SECONDS // 2, help='buffer stride (in seconds) -> increase for RPi-3', type=int)
parser.add_argument('--notification_delay', type=int, default=NOTIFICATION_DELAY_SECONDS, help='notificaiton delay')
parser.add_argument('--min_notification_count', type=int, default=MIN_NOTIFICATION_COUNT, help='minimum detection count threshold for sending telegram notification')
parser.add_argument('--latitude', type=float, default=LAT, help='latitude for zone based result filtering')
parser.add_argument('--longtitude', type=float, default=LON, help='longtitude for zone based result filtering')
return parser
@dataclass
class Config:
influx_token: str
influx_url: str
influx_org: str
influx_bucket: str
telegram_token: str
telegram_chatid: str
latitude: float
longitude: float
microphone: str
channels: int
rate: int

@classmethod
def load(cls, path: str = os.path.abspath("config.yaml")) -> "Config":
with open(path, "r") as f:
config_dict = yaml.safe_load(f)
return cls(**config_dict)




def send_telegram(msg, dry=False, min_notification_count=1):
def send_telegram(token, chatid, msg, dry=False):
filename = msg['fname']
sci_result = msg['sci']
result = msg['name']
conf = msg['conf']
count = msg['count']
if count < min_notification_count:
_LOGGER.info(f'skipping telegram message for {result}')
return
_LOGGER.info(f'sending telegram message for {result}')
with open(filename, 'rb') as audio:
linkname = sci_result.replace(' ', '+')
all_species_name = result.replace(' ', '_')
tb = telebot.TeleBot(TELEGRAM_TOKEN, parse_mode='MARKDOWN')
tb = telebot.TeleBot(token, parse_mode='MARKDOWN')
title = f'Confidence: {int(conf * 100)}%'
caption = f'''{title}
Count: {count}
[All About Birds](https://allaboutbirds.org/guide/{all_species_name})
[Wikimedia](https://commons.wikimedia.org/w/index.php?search={linkname}&title=Special:MediaSearch&go=Go)'''
if not dry:
tb.send_audio(TELEGRAM_CHATID, audio, performer=sci_result, title=result, caption=caption)
tb.send_audio(chatid, audio, performer=sci_result, title=result, caption=caption)
else:
_LOGGER.info(f'telegram {result} {sci_result} {caption}')

Expand Down Expand Up @@ -149,7 +155,7 @@ def upload_result(self, ts, filename, savedir, res):
return
result, conf = results[0]
sci_result, result = result.split('_')
if conf < self.args.min_confidence:
if conf < CONF_THRESH:
return

dir_path = os.path.join(savedir, result)
Expand All @@ -167,7 +173,7 @@ def upload_result(self, ts, filename, savedir, res):
query = f'''
import "influxdata/influxdb/schema"
schema.fieldKeys(
bucket: "{INFLUX_BUCKET}",
bucket: "{self.args.influx_bucket}",
predicate: (r) => r["_measurement"] == "birdnet",
start: -{SEEN_TIME},
)'''
Expand Down Expand Up @@ -199,7 +205,7 @@ def upload_result(self, ts, filename, savedir, res):
point = Point("birdnet") \
.field(result, conf) \
.time(ts_utc, WritePrecision.NS)
self.write_api.write(INFLUX_BUCKET, INFLUX_ORG, point)
self.write_api.write(self.args.influx_bucket, self.args.influx_org, point)
return out


Expand Down Expand Up @@ -237,9 +243,9 @@ def post_process(self, ts):
continue
self.futures.remove(f)
res = f.result()
msg = send_notification_delayed(self.delayed_notifications, ts, res, delay=self.args.notification_delay)
msg = send_notification_delayed(self.delayed_notifications, ts, res, delay=NOTIFICATION_DELAY_SECONDS)
if msg is not None:
self.futures.append(self.exc.submit(send_telegram, msg, self.args.dry, self.args.min_notification_count))
self.futures.append(self.exc.submit(send_telegram, self.args.telegram_token, self.args.telegram_chatid, msg, False))
return msg

def work(self, ts, data):
Expand Down
10 changes: 3 additions & 7 deletions birdnetapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#Audio Card sampling rate
RATE = 48000
#Number of channels to use
CHANNELS = 2
CHANNELS = 1
#Card name as it appears in 'arecord -l'
CARD = 'PCH'
CARD = 'Microphone'

#Files saved here
SAVEDIR = f'{HOME}/birdNet'
Expand All @@ -22,7 +22,7 @@
#how long to wait before sending a notification
#used to gather multiple recordings and choose the best
# to send over telegram
NOTIFICATION_DELAY_SECONDS = 60*5
NOTIFICATION_DELAY_SECONDS = 15

#segment length for analysis
RECORD_SECONDS = 6
Expand All @@ -47,10 +47,6 @@
#time window of how long the bird must be not seen to trigger a telegram
SEEN_TIME = '14d'

# minimum detection count threshold for sending telegram notification
# setting to higher than 1 reduces false alarm rate
MIN_NOTIFICATION_COUNT = 1

#time window of app result fetch
APP_WINDOW = '14d'

16 changes: 8 additions & 8 deletions birdnetapp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from influxdb_client.client.write_api import SYNCHRONOUS
import datetime

from .secrets import INFLUX_URL, INFLUX_TOKEN, INFLUX_ORG
from .app import Worker, get_parser, MDATA
from .app import Worker, Config, MDATA

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -53,7 +52,7 @@ def __del__(self):


def runner(args, stream):
influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
influx_client = InfluxDBClient(url=args.influx_url, token=args.influx_token, org=args.influx_org)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)
query_api = influx_client.query_api()
with ThreadPoolExecutor(max_workers=1) as exc:
Expand All @@ -73,19 +72,20 @@ def runner(args, stream):
future.cancel()

def main():
parser = get_parser()
args = parser.parse_args()

args = Config.load()
print('App CONFIG:', args)
MDATA['lat'] = args.latitude
MDATA['lon'] = args.longtitude
MDATA['lon'] = args.longitude
print('birdNet settings', MDATA)

if args.debug:
debug = False
if debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)

stream = MicStream(args.rate, args.channels, args.rate, args.card)
stream = MicStream(args.rate, args.channels, args.rate, args.microphone)
stream.open()
try:
runner(args, stream)
Expand Down
18 changes: 18 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#InfluxDB access configuration
influx_token: 'xxxx'
influx_url: 'http://localhost:8086'
influx_bucket: 'xxxx'
influx_org: 'xxxx'


#Telegram
telegram_token: 'xxxx'
telegram_chatid: '-000000000000'


#Environment
latitude: -1
longitude: -1
microphone: 'Microphone'
channels: 1
rate: 48000
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ scipy
influxdb-client
pyTelegramBotAPI
pydub
pyyaml

12 changes: 7 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from birdnetapp.app import Worker, send_notification_delayed, send_telegram, get_parser
from birdnetapp.app import Worker, send_notification_delayed, send_telegram
import datetime
from copy import deepcopy
import pandas as pd
from concurrent.futures import Future, Executor
from threading import Lock
import pytest



Expand Down Expand Up @@ -49,7 +50,7 @@ def shutdown(self, wait=True):
with self._shutdownLock:
self._shutdown = True


@pytest.mark.skip(reason="no way of currently testing this")
def test1():

q = Query()
Expand Down Expand Up @@ -127,11 +128,12 @@ def test1():
assert so is None


class Config: pass
@pytest.mark.skip(reason="no way of currently testing this")
def test_worker():
q = Query()
args = get_parser().parse_args("")
args.dry = True
args.notification_delay = 0
args = Config()

stream = MocStream()
exc = DummyExecutor()
w = Worker(args, stream, exc, q, q)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_telegram.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from birdnetapp.app import send_telegram
from birdnetapp.app import send_telegram, Config
from pydub import AudioSegment

def test1(dry=True):
Expand All @@ -11,8 +11,8 @@ def test1(dry=True):
sci_result, result = result.split('_')
count = 1
msg = {'fname' : export_filename,'sci' : sci_result, 'name' : result, 'conf' : conf, 'count' : count }
send_telegram(msg, dry=dry)
send_telegram(msg, dry=dry, min_notification_count=2)
send_telegram('', '', msg, dry=dry)
send_telegram('', '', msg, dry=dry)

if __name__ == '__main__':
test1(dry=False)

0 comments on commit 5141e95

Please sign in to comment.