diff --git a/Birders_Guide_Installer.sh b/Birders_Guide_Installer.sh index 81b4f7a..5c06af3 100755 --- a/Birders_Guide_Installer.sh +++ b/Birders_Guide_Installer.sh @@ -141,6 +141,12 @@ DB_PWD=changeme ICE_PWD=changeme +#--------------------- BirdWeather Station Information -----------------------# +#_____________The variable below can be set to have your BirdNET-Pi____________# +#__________________also act as a BirdWeather listening station_________________# + +BIRDWEATHER_ID=${BIRDWEATHER_ID} + #----------------------- Web-hosting/Caddy File-server -----------------------# #________The four variables below can be set to enable internet access_________# #____________to your data,(e.g., extractions, raw data, live___________________# diff --git a/Birders_Guide_Installer_Configuration.txt b/Birders_Guide_Installer_Configuration.txt index b4aa1cd..1bbf484 100644 --- a/Birders_Guide_Installer_Configuration.txt +++ b/Birders_Guide_Installer_Configuration.txt @@ -1,18 +1,19 @@ # Birders Guide Installer Configuration File -########################################################################### +################################################################################ # Fill this out in order to install BirdNET-Lite # Follow the instructions for each section. -########################################################################### +################################################################################ # 1. To find your latitude and longitude, you can go to https://maps.google.com # 1a. Find the location on the map where you will be putting the BirdNET-Lite -# 1b. Right-click the location to view the latitude and longitude and click them to -# copy them to your clip-board. -# 2. Enter the latitude and longitude here. Be certain not to mix them up and -# only go to the thousandth's decimal place. The first number is the latitude, -# and the second is longitude. See the example below. +# 1b. Right-click the location to view the latitude and longitude and click +# them to copy them to your clip-board. +# 2. Enter the latitude and longitude here. Be certain not to mix them up. +# The first number is the latitude, and the second is longitude. See the +# example below. +# # Example: these coordinates would indicate the Eiffel Tower in Paris, France. # LATITUDE=48.858 # LONGITUDE=2.294 @@ -22,17 +23,17 @@ LONGITUDE= # 1. CADDY_PWD is the password you will use to access your live audio stream and # maintenance tools. You can set this to anything, but keep it alpha-numeric. -# You will probably also want to write this one down if you ever plan to listen -# to the live stream. -# 2. ICE_PWD is the password that IceCast2 will use to authenticate the stream source. -# You can set this to anything also, but keep it alpha-numeric. You will never use -# this again, so just set it to your favorite word. +# You will probably also want to write this one down if you ever plan to +# listen to the live stream. +# 2. ICE_PWD is the password that IceCast2 will use to authenticate the stream +# source.You can set this to anything also, but keep it alpha-numeric. You +# will never use this again, so just set it to your favorite word. CADDY_PWD= ICE_PWD= -# DB_PWD is the password that the system will use to access the database. Set this to -# anything you want. +# DB_PWD is the password that the system will use to access the database. Set +# this to anything you want. DB_PWD= @@ -49,3 +50,10 @@ PUSHED_APP_KEY= BIRDNETPI_URL= BIRDNETLOG_URL= EXTRACTIONLOG_URL= + +# If you would like to POST your BirdNET-Pi detections to +# https://app.birdweather.com, reach out to github:@timsterc for a unique and +# confidential BirdWeather ID. Place that here if you have one already. +# You can always add this later and the system will immediately start POSTing. + +BIRDWEATHER_ID= diff --git a/LICENSE b/LICENSE index 2820ecf..01cecb6 100644 --- a/LICENSE +++ b/LICENSE @@ -570,11 +570,11 @@ Caddy, and Adminer use the Apache License Version 2.0 limitations under the License. ============================================================================= -GoTTY use the MIT License +GoTTY and autoAP scripts use the MIT License ============================================================================= MIT License -BirdNET - Copyright (c) 2019 Stefan Kahl +autoAP - Copywrite (c) 2021 Benn GoTTY - Copyright (c) 2015-2017 Iwasaki Yudai Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 6420714..547a171 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Check out birds from around the world * Web interface access to all data and logs * Automatic extraction of detected data (creating audio clips of detected bird sounds) * Spectrograms available for all extractions +* [BirdWeather](https://app.birdweather.com) integration (you will need to be issued a BirdWeather ID -- for now, request that from [@timsterc here](https://github.com/mcguirepr89/BirdNET-Pi/discussions/82)) * MariaDB integration * NoMachine remote desktop (for personal use only) * Live audio stream diff --git a/analyze.py b/analyze.py index 4177070..b90038f 100644 --- a/analyze.py +++ b/analyze.py @@ -1,3 +1,5 @@ +# BirdWeather edits by @timsterc +# Other edits by @CaiusX and @mcguirepr89 import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' os.environ['CUDA_VISIBLE_DEVICES'] = '' @@ -13,10 +15,17 @@ import librosa import numpy as np import math import time +from decimal import Decimal +import json ############################################################################### +import requests import mysql.connector -############################################################################### -from datetime import date, datetime +############################################################################### +import datetime +import pytz +from tzlocal import get_localzone +from pathlib import Path + def loadModel(): global INPUT_LAYER_INDEX @@ -196,6 +205,7 @@ def main(): parser.add_argument('--sensitivity', type=float, default=1.0, help='Detection sensitivity; Higher values result in higher sensitivity. Values in [0.5, 1.5]. Defaults to 1.0.') parser.add_argument('--min_conf', type=float, default=0.1, help='Minimum confidence threshold. Values in [0.01, 0.99]. Defaults to 0.1.') parser.add_argument('--custom_list', default='', help='Path to text file containing a list of species. Not used if not provided.') + parser.add_argument('--birdweather_id', default='99999', help='Private Station ID for BirdWeather.') args = parser.parse_args() @@ -208,75 +218,128 @@ def main(): else: WHITE_LIST = [] + birdweather_id = args.birdweather_id # Read audio data audioData = readAudioData(args.i, args.overlap) - # Process audio data and get detections - week = max(1, min(args.week, 48)) + # Get Date/Time from filename in case Pi gets behind + #now = datetime.now() + full_file_name = args.i + file_name = Path(full_file_name).stem + file_date = file_name.split('-birdnet-')[0] + file_time = file_name.split('-birdnet-')[1] + date_time_str = file_date + ' ' + file_time + date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S') + #print('Date:', date_time_obj.date()) + #print('Time:', date_time_obj.time()) + print('Date-time:', date_time_obj) + now = date_time_obj + current_date = now.strftime("%Y/%m/%d") + current_time = now.strftime("%H:%M:%S") + current_iso8601 = now.astimezone(get_localzone()).isoformat() + + week_number = int(now.strftime("%V")) + week = max(1, min(week_number, 48)) + sensitivity = max(0.5, min(1.0 - (args.sensitivity - 1.0), 1.5)) + + # Process audio data and get detections detections = analyzeAudioData(audioData, args.lat, args.lon, week, sensitivity, args.overlap, interpreter) # Write detections to output file min_conf = max(0.01, min(args.min_conf, 0.99)) writeResultsToFile(detections, min_conf, args.o) - now = datetime.now() ############################################################################### ############################################################################### - - + + soundscape_uploaded = False # Write detections to Database for i in detections: - print("\n", detections[i][0],"\n") + print("\n", detections[i][0],"\n") with open('BirdDB.txt', 'a') as rfile: - for d in detections: - print("\n", "Database Entry", "\n") - for entry in detections[d]: - if entry[1] >= min_conf and (entry[0] in WHITE_LIST or len(WHITE_LIST) == 0): - current_date = now.strftime("%Y/%m/%d") - current_time = now.strftime("%H:%M:%S") - rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' \ - + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' \ - + str(sensitivity) +';' + str(args.overlap) + '\n') + for d in detections: + print("\n", "Database Entry", "\n") + for entry in detections[d]: + if entry[1] >= min_conf and (entry[0] in WHITE_LIST or len(WHITE_LIST) == 0): + rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' \ + + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' \ + + str(sensitivity) +';' + str(args.overlap) + '\n') - def insert_variables_into_table(Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap): - try: - connection = mysql.connector.connect(host='localhost', - database='birds', - user='birder', - password='databasepassword') - cursor = connection.cursor() - mySql_insert_query = """INSERT INTO detections (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ + def insert_variables_into_table(Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap): + try: + connection = mysql.connector.connect(host='localhost', + database='birds', + user='birder', + password='databasepassword') + cursor = connection.cursor() + mySql_insert_query = """INSERT INTO detections (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ + + record = (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) + + cursor.execute(mySql_insert_query, record) + connection.commit() + print("Record inserted successfully into detections table") + + + except mysql.connector.Error as error: + print("Failed to insert record into detections table {}".format(error)) - record = (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) + finally: + if connection.is_connected(): + connection.close() + print("MySQL connection is closed") - cursor.execute(mySql_insert_query, record) - connection.commit() - print("Record inserted successfully into detections table") + species = entry[0] + sci_name,com_name = species.split('_') + insert_variables_into_table(str(current_date), str(current_time), sci_name, com_name, \ + str(entry[1]), str(args.lat), str(args.lon), str(min_conf), str(week), \ + str(args.sensitivity), str(args.overlap)) - - except mysql.connector.Error as error: - print("Failed to insert record into detections table {}".format(error)) - - finally: - if connection.is_connected(): - connection.close() - print("MySQL connection is closed") + print(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' + str(args.sensitivity) +';' + str(args.overlap) + '\n') - species = entry[0] - sci_name,com_name = species.split('_') - insert_variables_into_table(str(current_date), str(current_time), sci_name, com_name, \ - str(entry[1]), str(args.lat), str(args.lon), str(min_conf), str(week), \ - str(args.sensitivity), str(args.overlap)) + if birdweather_id != "99999": - print(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' + str(args.sensitivity) +';' + str(args.overlap) + '\n') + if soundscape_uploaded is False: + # POST soundscape to server + soundscape_url = "https://app.birdweather.com/api/v1/stations/" + birdweather_id + "/soundscapes" + "?timestamp=" + current_iso8601 - time.sleep(3) + with open(args.i, 'rb') as f: + wav_data = f.read() + response = requests.post(url=soundscape_url, data=wav_data, headers={'Content-Type': 'application/octet-stream'}) + print("Soundscape POST Response Status - ", response.status_code) + sdata = response.json() + soundscape_id = sdata['soundscape']['id'] + soundscape_uploaded = True - now = datetime.now() + # POST detection to server + detection_url = "https://app.birdweather.com/api/v1/stations/" + birdweather_id + "/detections" + start_time = d.split(';')[0] + end_time = d.split(';')[1] + post_begin = "{ " + now_p_start = now + datetime.timedelta(seconds=float(start_time)) + current_iso8601 = now_p_start.astimezone(get_localzone()).isoformat() + post_timestamp = "\"timestamp\": \"" + current_iso8601 + "\"," + post_lat = "\"lat\": " + str(args.lat) + "," + post_lon = "\"lon\": " + str(args.lon) + "," + post_soundscape_id = "\"soundscapeId\": " + str(soundscape_id) + "," + post_soundscape_start_time = "\"soundscapeStartTime\": " + start_time + "," + post_soundscape_end_time = "\"soundscapeEndTime\": " + end_time + "," + post_commonName = "\"commonName\": \"" + entry[0].split('_')[1] + "\"," + post_scientificName = "\"scientificName\": \"" + entry[0].split('_')[0] + "\"," + post_algorithm = "\"algorithm\": " + "\"alpha\"" + "," + post_confidence = "\"confidence\": " + str(entry[1]) + post_end = " }" + + post_json = post_begin + post_timestamp + post_lat + post_lon + post_soundscape_id + post_soundscape_start_time + post_soundscape_end_time + post_commonName + post_scientificName + post_algorithm + post_confidence + post_end + print(post_json) + response = requests.post(detection_url, json=json.loads(post_json)) + print("Detection POST Response Status - ", response.status_code) + + #time.sleep(3) ############################################################################### ############################################################################### diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index a20c045..751ff8b 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -53,6 +53,12 @@ DB_PWD=changeme ICE_PWD=changeme +#--------------------- BirdWeather Station Information -----------------------# +#_____________The variable below can be set to have your BirdNET-Pi____________# +#__________________also act as a BirdWeather listening station_________________# + +BIRDWEATHER_ID= + #----------------------- Web-hosting/Caddy File-server -----------------------# #________The four variables below can be set to enable internet access_________# #____________to your data,(e.g., extractions, raw data, live___________________# diff --git a/homepage/index.html b/homepage/index.html index 9d2922c..a1294b0 100644 --- a/homepage/index.html +++ b/homepage/index.html @@ -1,45 +1,42 @@ - - -BirdNET-Pi - - - - + + + + - - - - - - - - - + + + + + + + + - --> + + <p>This section (everything between the 'noframes' tags) will only be displayed if the users' browser doesn't support frames. You can provide a link to a non-frames version of the website here. Feel free to use HTML tags within this section.</p> + - - <p>This section (everything between the 'noframes' tags) will only be displayed if the users' browser doesn't support frames. You can provide a link to a non-frames version of the website here. Feel free to use HTML tags within this section.</p> - - - + diff --git a/homepage/menu.html b/homepage/menu.html index 6feb129..05c937c 100644 --- a/homepage/menu.html +++ b/homepage/menu.html @@ -1,74 +1,79 @@ - -HTML Frames Example - Menu - - - -
Extractions
-

-By Date
-By Common Name
-By Scientific Name
-Processed
+body::-webkit-scrollbar { + display:none +} + + + -

-
-
System Links
-

-Tools
+

Extractions
+ - By Date
+ - By Common Name
+ - By Scientific Name
+ - Processed
-System Info
+
-BirdNET-Lite Log
+
System Links
+ - Tools
+ - System Info
+ - BirdNET-Lite Log
+ - Extraction Log
-Extraction Log
+
-

-
-
External Links
-

-Repository
-Wiki Help Page
-Wttr.in Report
-Cornell Lab of Ornithology
-

-
-
Other
BirdNET-Pis
-United States, -South Africa, -Germany - - +
External Links
+ - BirdWeather
+ - Repository
+ - Wiki Help Page
+ - Wttr.in Report
+ - Cornell Lab of Ornithology
+
+ +
Other
BirdNET-Pis
+ - United States
+ - South Africa
+ - Germany + + + diff --git a/homepage/mobile.html b/homepage/mobile.html index 355ec31..90986c6 100644 --- a/homepage/mobile.html +++ b/homepage/mobile.html @@ -324,9 +324,21 @@ footer {
+

+ + 🐦 Overview + +

+
+

+ + 🐦 Today's Top 10 + +

+

- 🐦 Recognized Birds + 🐦 View the Database


diff --git a/homepage/style.css b/homepage/style.css index 5ef8516..e4dc235 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -1,18 +1,34 @@ /* style sheet */ * { + font-family: 'Arial', 'Gill Sans', 'Gill Sans MT', + ' Calibri', 'Trebuchet MS', 'sans-serif'; box-sizing: border-box; } -.row { - display: flex; +/* Create two equal columns that floats next to each other */ +.column2 { + margin-left: auto; + margin-right: auto; + width: 75%; + padding: 5px; } +/* Create two equal columns that floats next to each other */ .column { - flex: 50%; - padding-right: 5px; + float: left; + width: 50%; + padding: 5px; +} + +/* Clear floats after the columns */ +.row:after { + content: ""; + display: table; + clear: both; } table { + background-color: rgb(219, 255, 235); margin: 0 auto; font-size: large; border-collapse: collapse; @@ -22,82 +38,59 @@ table { } h1 { - text-align: center; - color: black; - font-size: xx-large; - font-family: 'Gill Sans', 'Gill Sans MT', - ' Calibri', 'Trebuchet MS', 'sans-serif'; + text-align: center; + color: black; + font-size: xx-large; } h2 { - text-align: center; - color: black; - font-size: large; - font-family: 'Gill Sans', 'Gill Sans MT', - ' Calibri', 'Trebuchet MS', 'sans-serif'; + margin-left: -150px; + text-align: center; + color: black; + font-size: large; +} + +h3 { + text-align: center; + color: black; + font-size: large; } td { - background-color: rgb(119, 196, 135); - border: 1px solid black; + background-color: rgb(219, 255, 235); + font-weight: lighter; + border: 1px solid black; + padding: 10px; + text-align: center; } -th, -td { - font-weight: bold; - border: 1px solid black; - padding: 10px; - text-align: center; - background-color: rgb(219, 296, 235); +th { + background-color: rgb(219, 255, 235); + font-weight: bold; + border: 1px solid black; + padding: 10px; + text-align: center; } -@media screen and (max-width: 800px) { - .column { - float: none; - width: 100%; - } - table { - margin: 0 auto; - font-size: medium; - border-collapse: collapse; - border-spacing: 1; - width: 100%; - border: 1px solid black; - } - - h1 { - text-align: center; - color: black; - font-size: large; - font-family: 'Gill Sans', 'Gill Sans MT', - ' Calibri', 'Trebuchet MS', 'sans-serif'; - } - +@media screen and (max-width: 600px) { h2 { - text-align: center; - color: black; - font-size: large; - font-family: 'Gill Sans', 'Gill Sans MT', - ' Calibri', 'Trebuchet MS', 'sans-serif'; + margin-left: 0; + text-align: center; + color: black; + font-size: large; } - - td { - background-color: rgb(119, 196, 135); - border: 1px solid black; - } - - th, - td { - font-weight: bold; - border: 1px solid black; - padding: 10px; - text-align: center; - } -} -td { - font-weight: lighter; + .column { + width: 100%; + } + /* Create two equal columns that floats next to each other */ + .column2 { + float: center; + width: 100%; + padding: 0px; + } + + } body::-webkit-scrollbar { - display:none -} + display:none diff --git a/homepage/top.html b/homepage/top.html index 009bf50..8308b52 100644 --- a/homepage/top.html +++ b/homepage/top.html @@ -1,45 +1,50 @@ - - BirdNET-Pi - - - -

BirdNET-Pi

- -

- Database View | - Spectrogram View -

- + + + + + BirdNET-Pi + +

BirdNET-Pi

+ +

+ Overview | + Database View | + Today View | + Spectrogram View +

+ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..acbb665 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,41 @@ +appdirs +audioread +certifi +cffi +charset-normalizer +colorama +cycler +decorator +idna +joblib +kiwisolver +librosa +llvmlite +matplotlib +mysql-connector-python +numba +numpy +packaging +pandas +Pillow +pip +pkg_resources +pooch +protobuf +pycparser +pyparsing +python-dateutil +pytz +requests +resampy +scikit-learn +scipy +seaborn +setuptools +six +SoundFile +tflite-runtime +threadpoolctl +tzlocal +urllib3 +wheel diff --git a/scripts/birdnet_analysis.sh b/scripts/birdnet_analysis.sh index f7d6ff0..80f49c2 100755 --- a/scripts/birdnet_analysis.sh +++ b/scripts/birdnet_analysis.sh @@ -91,18 +91,18 @@ run_analysis() { if [ "${RECORDING_LENGTH}" == "01:00" ];then until [ "$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" == "${RECORDING_LENGTH}" ];do sleep 1 - [ $a -ge 60 ] && sudo rm -f ${1}/${i} && break - a=$((a+1)) + [ $a -ge 60 ] && sudo rm -f ${1}/${i} && break + a=$((a+1)) done else until [ "$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" == "00:${RECORDING_LENGTH}" ];do sleep 1 - [ $a -ge ${RECORDING_LENGTH} ] && sudo rm -f ${1}/${i} && break - a=$((a+1)) + [ $a -ge ${RECORDING_LENGTH} ] && sudo rm -f ${1}/${i} && break + a=$((a+1)) done fi - if [ -f ${1}/${i} ] && [ ! -f ${CUSTOM_LIST} ];then + if [ -f ${1}/${i} ] && [ ! -f ${CUSTOM_LIST} ] && [ -z $BIRDWEATHER_ID ];then echo "python3 analyze.py \ --i "${1}/${i}" \ --o "${1}/${i}.csv" \ @@ -119,9 +119,9 @@ run_analysis() { --lon "${LONGITUDE}" \ --week "${WEEK}" \ --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ + --sensitivity "${SENSITIVITY}" \ --min_conf "${CONFIDENCE}" - elif [ -f ${1}/${i} ] && [ -f ${CUSTOM_LIST} ];then + elif [ -f ${1}/${i} ] && [ -f ${CUSTOM_LIST} ] && [ -z $BIRDWEATHER_ID ];then echo "python3 analyze.py \ --i "${1}/${i}" \ --o "${1}/${i}.csv" \ @@ -139,10 +139,54 @@ run_analysis() { --lon "${LONGITUDE}" \ --week "${WEEK}" \ --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ + --sensitivity "${SENSITIVITY}" \ --min_conf "${CONFIDENCE}" \ - --custom_list "${CUSTOM_LIST}" - fi + --custom_list "${CUSTOM_LIST}" + elif [ -f ${1}/${i} ] && [ ! -f ${CUSTOM_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then + echo "python3 analyze.py \ +--i "${1}/${i}" \ +--o "${1}/${i}.csv" \ +--lat "${LATITUDE}" \ +--lon "${LONGITUDE}" \ +--week "${WEEK}" \ +--overlap "${OVERLAP}" \ +--sensitivity "${SENSITIVITY}" \ +--min_conf "${CONFIDENCE}" \ +--birdweather_id IN_USE" + "${VENV}"/bin/python analyze.py \ + --i "${1}/${i}" \ + --o "${1}/${i}.csv" \ + --lat "${LATITUDE}" \ + --lon "${LONGITUDE}" \ + --week "${WEEK}" \ + --overlap "${OVERLAP}" \ + --sensitivity "${SENSITIVITY}" \ + --min_conf "${CONFIDENCE}" \ + --birdweather_id "${BIRDWEATHER_ID}" + elif [ -f ${1}/${i} ] && [ -f ${CUSTOM_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then + echo "python3 analyze.py \ +--i "${1}/${i}" \ +--o "${1}/${i}.csv" \ +--lat "${LATITUDE}" \ +--lon "${LONGITUDE}" \ +--week "${WEEK}" \ +--overlap "${OVERLAP}" \ +--sensitivity "${SENSITIVITY}" \ +--min_conf "${CONFIDENCE}" \ +--custom_list "${CUSTOM_LIST}" \ +--birdweather_id IN_USE" + "${VENV}"/bin/python analyze.py \ + --i "${1}/${i}" \ + --o "${1}/${i}.csv" \ + --lat "${LATITUDE}" \ + --lon "${LONGITUDE}" \ + --week "${WEEK}" \ + --overlap "${OVERLAP}" \ + --sensitivity "${SENSITIVITY}" \ + --min_conf "${CONFIDENCE}" \ + --custom_list "${CUSTOM_LIST}" \ + --birdweather_id "${BIRDWEATHER_ID}" + fi done } diff --git a/scripts/clear_all_data.sh b/scripts/clear_all_data.sh index 5617702..9e93202 100755 --- a/scripts/clear_all_data.sh +++ b/scripts/clear_all_data.sh @@ -12,12 +12,13 @@ sudo systemctl stop birdnet_recording.service echo "Removing all data . . . " sudo rm -drf "${RECS_DIR}" sudo rm -f "${IDFILE}" +sudo rm -f $(dirname ${my_dir})/BirdDB.txt echo "Recreating necessary directories" -echo "Creating necessary directories" [ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED} [ -d ${EXTRACTED}/By_Date ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Date [ -d ${EXTRACTED}/By_Common_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Common_Name [ -d ${EXTRACTED}/By_Scientific_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Scientific_Name +[ -d ${EXTRACTED}/Charts ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/Charts [ -d ${PROCESSED} ] || sudo -u ${USER} mkdir -p ${PROCESSED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/homepage/* ${EXTRACTED} @@ -49,13 +50,20 @@ if [ ! -z ${BIRDNETPI_URL} ];then fi sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/spectrogram.php ${EXTRACTED} +sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/overview.php ${EXTRACTED} +sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewday.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewdb.php ${EXTRACTED} sudo -u ${USER} ln -fs ${HOME}/phpsysinfo ${EXTRACTED} sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/phpsysinfo.ini ${HOME}/phpsysinfo/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/green_bootstrap.css ${HOME}/phpsysinfo/templates/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/index_bootstrap.html ${HOME}/phpsysinfo/templates/html +echo "Setting Wttr.in URL to "${LATITUDE}", "${LONGITUDE}"" +sudo -u${USER} sed -i "s/https:\/\/v2.wttr.in\//https:\/\/v2.wttr.in\/"${LATITUDE},${LONGITUDE}"/g" $(dirname ${my_dir})/homepage/menu.html +echo "Generating BirdDB.txt" +[ -f $(dirname ${my_dir})/BirdDB.txt ] || sudo -u ${USER} touch $(dirname ${my_dir})/BirdDB.txt +echo "Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap" | sudo -u ${BIRDNET_USER} tee -a $(dirname ${my_dir})/BirdDB.txt sudo -u ${BIRDNET_USER} cp -f ${HOME}/BirdNET-Pi/homepage/index.html ${EXTRACTED}/ echo "Dropping and re-creating database" diff --git a/scripts/createdb_bullseye.sh b/scripts/createdb_bullseye.sh index 9f73eee..e23f24b 100755 --- a/scripts/createdb_bullseye.sh +++ b/scripts/createdb_bullseye.sh @@ -39,5 +39,7 @@ GRANT ALL ON birds.* TO 'birder'@'localhost' IDENTIFIED BY '${DB_PWD}' WITH GRAN FLUSH PRIVILEGES; exit EOF -sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py -sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/overview.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewday.php diff --git a/scripts/createdb_buster.sh b/scripts/createdb_buster.sh index 8f18992..0ebcdb1 100755 --- a/scripts/createdb_buster.sh +++ b/scripts/createdb_buster.sh @@ -40,5 +40,7 @@ GRANT ALL ON birds.* TO 'birder'@'localhost' IDENTIFIED BY '${DB_PWD}' WITH GRAN FLUSH PRIVILEGES; exit EOF -sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py -sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewday.php +sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/overview.php diff --git a/scripts/daily_plot.py b/scripts/daily_plot.py new file mode 100755 index 0000000..f4741c1 --- /dev/null +++ b/scripts/daily_plot.py @@ -0,0 +1,154 @@ +#!/home/pi/BirdNET-Pi/birdnet/bin/python3 +import pandas as pd +import seaborn as sns +# import numpy as np +import matplotlib.pyplot as plt +from matplotlib.colors import LogNorm +from datetime import datetime +import textwrap + + + +#Read database into Pandas dataframe +df = pd.read_csv('~/BirdNET-Pi/BirdDB.txt', sep=';') + +#Convert Date and Time Fields to Panda's format +df['Date']=pd.to_datetime(df['Date']) +df['Time']=pd.to_datetime(df['Time']) + +#Add round hours to dataframe +df['Hour of Day'] = [r.hour for r in df.Time] + +#Create separate dataframes for separate locations +df_jhb=df[df.Lat > -32] +df_ec = df[df.Lat < -32] + +#Get todays readings for Joburg +now = datetime.now() +df_jhb_today = df_jhb[df_jhb['Date']==now.strftime("%Y-%m-%d")] + +# Definition to start getting top N detections - work in process +def filter_by_freq(df: pd.DataFrame, column: str, min_freq: int) -> pd.DataFrame: + """Filters the DataFrame based on the value frequency in the specified column. + + :param df: DataFrame to be filtered. + :param column: Column name that should be frequency filtered. + :param min_freq: Minimal value frequency for the row to be accepted. + :return: Frequency filtered DataFrame. + """ + # Frequencies of each value in the column. + freq = df[column].value_counts() + # Select frequent values. Value is in the index. + frequent_values = freq[freq >= min_freq].index + # Return only rows with value frequency above threshold. + return df[df[column].isin(frequent_values)] + +#Get top readings today +min_valuecounts = 2 + + +jhb_gt_min = filter_by_freq (df_jhb_today,'Com_Name', min_valuecounts) + +jhb_gt_min_counts = jhb_gt_min['Com_Name'].value_counts() +print(jhb_gt_min_counts) + + +jhb_top10_today = (df_jhb_today['Com_Name'].value_counts()[:10]) +df_jhb_top10_today = df_jhb_today[df_jhb_today.Com_Name.isin(jhb_top10_today.index)] + +#Get bottom 10 today +jhb_bot10_today=(df_jhb_today['Com_Name'].value_counts()[-10:]) +df_jhb_bot10_today = df_jhb_today[df_jhb_today.Com_Name.isin(jhb_bot10_today.index)] + +#Set Palette for graphics +pal = "Greens" + +#Set up plot axes and titles +f, axs = plt.subplots(1, 2, figsize = (10, 4), gridspec_kw=dict(width_ratios=[3, 5])) +plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=None) + + +#Generate frequency plot +plot=sns.countplot(y='Com_Name', data = df_jhb_top10_today, palette = pal+"_r", order=pd.value_counts(df_jhb_top10_today['Com_Name']).iloc[:20].index, ax=axs[0]) + +#Try plot grid lines between bars - problem at the moment plots grid lines on bars - want between bars +# plot.grid(True, axis='y') + +plot.set_yticklabels(['\n'.join(textwrap.wrap(ticklabel.get_text(),15)) for ticklabel in plot.get_yticklabels()]) +plot.set(ylabel=None) +plot.set(xlabel="Detections") + +#Generate crosstab matrix for heatmap plot + +heat = pd.crosstab(df_jhb_top10_today['Com_Name'],df_jhb_top10_today['Hour of Day']) +#Order heatmap Birds by frequency of occurrance +heat.index = pd.CategoricalIndex(heat.index, categories = pd.value_counts(df_jhb_top10_today['Com_Name']).iloc[:10].index) +heat.sort_index(level=0, inplace=True) + + +hours_in_day = pd.Series(data = range(0,24)) +heat_frame = pd.DataFrame(data=0, index=heat.index, columns = hours_in_day) +heat=(heat+heat_frame).fillna(0) + +#Generatie heatmap plot +plot = sns.heatmap(heat, norm=LogNorm(), annot=True, annot_kws={"fontsize":7}, cmap = pal , square = False, cbar=False, linewidths = 0.5, linecolor = "Grey", ax=axs[1], yticklabels = False) + +# Set heatmap border +for _, spine in plot.spines.items(): + spine.set_visible(True) + +plot.set(ylabel=None) +plot.set(xlabel="Hour of Day") +#Set combined plot layout and titles +plt.tight_layout() +f.subplots_adjust(top=0.9) +plt.suptitle("Last Updated: "+ str(now.strftime("%B, %d at %I:%M%P"))) + +#Save combined plot +savename='/home/pi/BirdSongs/Extracted/Charts/Combo-'+str(now.strftime("%d-%m-%Y"))+'.png' +plt.savefig(savename) +plt.close() + + +#Get bottom 10 today +jhb_bot10_today=(df_jhb_today['Com_Name'].value_counts()[-10:]) +df_jhb_bot10_today = df_jhb_today[df_jhb_today.Com_Name.isin(jhb_bot10_today.index)] + +#Set Palette for graphics +pal = "Reds" + +#Set up plot axes and titles +f, axs = plt.subplots(1, 2, figsize = (8, 4), gridspec_kw=dict(width_ratios=[3, 5])) + +#Generate frequency plot +plot=sns.countplot(y='Com_Name', data = df_jhb_bot10_today, palette = pal+"_r", order=pd.value_counts(df_jhb_bot10_today['Com_Name']).iloc[:10].index, ax=axs[0]) +plot.set_yticklabels(['\n'.join(textwrap.wrap(ticklabel.get_text(),17)) for ticklabel in plot.get_yticklabels()]) +plot.set(ylabel=None) +plot.set(xlabel="no. of detections") +#Generate crosstab matrix for heatmap plot +heat = pd.crosstab(df_jhb_bot10_today['Com_Name'],df_jhb_bot10_today['Hour of Day']) + +#Order heatmap Birds by frequency of occurrance +heat.index = pd.CategoricalIndex(heat.index, categories = pd.value_counts(df_jhb_bot10_today['Com_Name']).iloc[:10].index) +heat.sort_index(level=0, inplace=True) +heat_frame = pd.DataFrame(data=0, index=heat.index, columns = hours_in_day) +heat=(heat+heat_frame).fillna(0) + +#Generate heatmap plot +plot = sns.heatmap(heat, norm=LogNorm(), annot=True, annot_kws={"fontsize":7}, cmap = pal , square = False, cbar=False, linewidths = 0.5, linecolor = "Grey", ax=axs[1], yticklabels = False) + +# Set heatmap border +for _, spine in plot.spines.items(): + spine.set_visible(True) +plot.set(ylabel=None) + +#Set combined plot layout and titles +plt.tight_layout() +f.subplots_adjust(top=0.9) +plt.suptitle("Bottom 10 Detected: "+ str(now.strftime("%d-%h-%Y %H:%M"))) +plot.set(xlabel="Hour of Day") +#Save combined plot +savename='/home/pi/BirdSongs/Extracted/Charts/Combo2-'+str(now.strftime("%d-%m-%Y"))+'.png' +plt.savefig(savename) + +plt.close() diff --git a/scripts/install_birdnet.sh b/scripts/install_birdnet.sh index 6d00d4d..3e7b49f 100755 --- a/scripts/install_birdnet.sh +++ b/scripts/install_birdnet.sh @@ -52,7 +52,6 @@ install_birdnet() { source ./birdnet/bin/activate echo "Upgrading pip, wheel, and setuptools" pip3 install --upgrade pip wheel setuptools -set -x python_version="$(awk -F. '{print $2}' <(ls -l $(which /usr/bin/python3)))" echo "python_version=${python_version}" # TFLite Pre-built binaires from https://github.com/PINTO0309/TensorflowLite-bin @@ -67,13 +66,14 @@ set -x echo "Installing the TFLite bin wheel" pip3 install --upgrade tflite_runtime-2.6.0-cp39-none-linux_aarch64.whl fi -set +x echo "Installing colorama==0.4.4" pip3 install colorama==0.4.4 echo "Installing librosa" pip3 install librosa echo "Installing mysql-connector-python" pip3 install mysql-connector-python + echo "Making sure everything else is installed" + pip3 install -U -r /home/pi/BirdNET-Pi/requirements.txt } read -sp "\ diff --git a/scripts/install_noip2.sh b/scripts/install_noip2.sh index 565d340..b23a71e 100755 --- a/scripts/install_noip2.sh +++ b/scripts/install_noip2.sh @@ -5,5 +5,6 @@ tar -vzxf noip-duc-linux.tar.gz cd noip-2* make make install -sed '/^exit 0$/i \/usr\/local\/bin\/noip2' /etc/rc.local +chmod a+wr /usr/local/etc/no-ip2.conf +sed -i '/^exit 0$/i \/usr\/local\/bin\/noip2' /etc/rc.local noip2 -S diff --git a/scripts/install_services.sh b/scripts/install_services.sh index 48be958..0446a43 100755 --- a/scripts/install_services.sh +++ b/scripts/install_services.sh @@ -36,9 +36,9 @@ install_mariadb() { echo "Initializing the database" source /etc/os-release if [[ "${VERSION_CODENAME}" == "buster" ]];then - ${my_dir}/createdb_buster.sh + USER=${USER} ${my_dir}/createdb_buster.sh elif [[ "${VERSION_CODENAME}" == "bullseye" ]];then - ${my_dir}/createdb_bullseye.sh + USER=${USER} ${my_dir}/createdb_bullseye.sh fi } @@ -113,6 +113,7 @@ create_necessary_dirs() { [ -d ${EXTRACTED}/By_Date ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Date [ -d ${EXTRACTED}/By_Common_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Common_Name [ -d ${EXTRACTED}/By_Scientific_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Scientific_Name + [ -d ${EXTRACTED}/Charts ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/Charts [ -d ${PROCESSED} ] || sudo -u ${USER} mkdir -p ${PROCESSED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/homepage/* ${EXTRACTED} @@ -144,15 +145,29 @@ create_necessary_dirs() { fi sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/spectrogram.php ${EXTRACTED} + sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewday.php ${EXTRACTED} + sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/overview.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewdb.php ${EXTRACTED} sudo -u ${USER} ln -fs ${HOME}/phpsysinfo ${EXTRACTED} sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/phpsysinfo.ini ${HOME}/phpsysinfo/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/green_bootstrap.css ${HOME}/phpsysinfo/templates/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/index_bootstrap.html ${HOME}/phpsysinfo/templates/html + echo "Setting Wttr.in URL to "${LATITUDE}", "${LONGITUDE}"" + sudo -u${USER} sed -i "s/https:\/\/v2.wttr.in\//https:\/\/v2.wttr.in\/"${LATITUDE},${LONGITUDE}"/g" $(dirname ${my_dir})/homepage/menu.html } +generate_BirdDB() { + echo "Generating BirdDB.txt" + if ! [ -f $(dirname ${my_dir})/BirdDB.txt ];then + sudo -u ${USER} touch $(dirname ${my_dir})/BirdDB.txt + echo "Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap" | sudo -u ${USER} tee -a $(dirname ${my_dir})/BirdDB.txt + elif ! grep Date $(dirname ${my_dir})/BirdDB.txt;then + sudo -u ${USER} sed -i '1 i\Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap' $(dirname ${my_dir})/BirdDB.txt + fi +} + install_alsa() { echo "Checking for alsa-utils and pulseaudio" if which arecord &> /dev/null ;then @@ -302,6 +317,24 @@ EOF systemctl enable spectrogram_viewer.service } +install_chart_viewer_service() { + echo "Installing the chart_viewer.service" + cat << EOF > /etc/systemd/system/chart_viewer.service +[Unit] +Description=BirdNET-Pi Chart Viewer Service + +[Service] +Restart=always +RestartSec=300 +Type=simple +User=pi +ExecStart=/usr/local/bin/daily_plot.py +[Install] +WantedBy=multi-user.target +EOF + systemctl enable chart_viewer.service +} + install_gotty_logs() { echo "Installing GoTTY logging" if ! which gotty &> /dev/null;then @@ -492,6 +525,7 @@ install_selected_services() { install_sox install_mariadb install_spectrogram_service + install_chart_viewer_service install_edit_birdnet_conf install_pushed_notifications @@ -505,6 +539,7 @@ install_selected_services() { fi create_necessary_dirs + generate_BirdDB install_cleanup_cron } diff --git a/scripts/overview.php b/scripts/overview.php new file mode 100644 index 0000000..c6e5754 --- /dev/null +++ b/scripts/overview.php @@ -0,0 +1,128 @@ +connect_error) { + die('Connect Error (' . + $mysqli->connect_errno . ') '. + $mysqli->connect_error); +} + +// SQL query to select data from database +$sql0 = "SELECT * FROM detections"; +$fulltable = $mysqli->query($sql0); +$totalcount = mysqli_num_rows($fulltable); + +$sql1 = "SELECT Com_Name, Date, Time FROM detections + ORDER BY Date DESC, Time DESC LIMIT 1"; +$mostrecent = $mysqli->query($sql1); + +$sql2 = "SELECT * FROM detections + WHERE Date = CURDATE()"; +$todaystable = $mysqli->query($sql2); +$todayscount = mysqli_num_rows($todaystable); + + +$sql3 = "SELECT * FROM detections + WHERE Date = CURDATE() + AND Time >= DATE_SUB(NOW(),INTERVAL 1 HOUR)"; +$lasthourtable = $mysqli->query($sql3); +$lasthourcount = mysqli_num_rows($lasthourtable); + +$sql4 = "SELECT Com_Name, Date, Time, MAX(Confidence) + FROM detections + WHERE Date = CURDATE() + GROUP BY Com_Name + ORDER BY MAX(Confidence) DESC"; +$specieslist = $mysqli->query($sql4); +$speciescount = mysqli_num_rows($specieslist); + +$mysqli->close(); +?> + + + + + + + + Overview + + + + + + + +

Overview

+
+
+fetch_assoc()) +{ +?> + + + + + + + +
Most Recent Detection
+
+
+ + +
+
+ + + + + + + + + + + + + +
TotalTodayLast Hour
Number of Detections
+
+
+ + + + + +
Species Detected Today
+
+
+

Today's Top 10 Species

+ +

Currently Analyzing

+ + diff --git a/scripts/spectrogram.php b/scripts/spectrogram.php index 6b43be7..57b9496 100644 --- a/scripts/spectrogram.php +++ b/scripts/spectrogram.php @@ -1,5 +1,5 @@ "; -echo ""; ?> + diff --git a/scripts/stop_core_services.sh b/scripts/stop_core_services.sh index 2f2c8f4..a2ce92c 100755 --- a/scripts/stop_core_services.sh +++ b/scripts/stop_core_services.sh @@ -4,7 +4,9 @@ services=(birdnet_recording.service birdnet_analysis.service -extraction.timer) +chart_viewer.service +extraction.timer +spectrogram_viewer.service) for i in "${services[@]}";do sudo systemctl stop ${i} diff --git a/scripts/update_db_pwd_bullseye.sh b/scripts/update_db_pwd_bullseye.sh index dae2656..d7097b8 100755 --- a/scripts/update_db_pwd_bullseye.sh +++ b/scripts/update_db_pwd_bullseye.sh @@ -9,3 +9,5 @@ git -C /home/pi/BirdNET-Pi checkout -f analyze.py git -C /home/pi/BirdNET-Pi checkout -f scripts/viewdb.php sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/overview.php +sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewday.php diff --git a/scripts/update_db_pwd_buster.sh b/scripts/update_db_pwd_buster.sh index eba6dd0..e26a377 100755 --- a/scripts/update_db_pwd_buster.sh +++ b/scripts/update_db_pwd_buster.sh @@ -12,3 +12,5 @@ git -C /home/pi/BirdNET-Pi checkout -f analyze.py git -C /home/pi/BirdNET-Pi checkout -f scripts/viewdb.php sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/analyze.py sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewdb.php +sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/overview.php +sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/viewday.php diff --git a/scripts/update_services.sh b/scripts/update_services.sh index df4d35b..a4f5ca2 100755 --- a/scripts/update_services.sh +++ b/scripts/update_services.sh @@ -7,7 +7,7 @@ USER=pi HOME=/home/pi my_dir=${HOME}/BirdNET-Pi/scripts tmpfile=$(mktemp) -nomachine_url="https://download.nomachine.com/download/7.6/Arm/nomachine_7.6.2_3_arm64.deb" +nomachine_url="https://download.nomachine.com/download/7.7/Arm/nomachine_7.7.4_1_arm64.deb" gotty_url="https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_linux_arm.tar.gz" config_file="$(dirname ${my_dir})/birdnet.conf" @@ -35,9 +35,9 @@ install_mariadb() { fi source /etc/os-release if [[ "${VERSION_CODENAME}" == "buster" ]];then - ${my_dir}/update_db_pwd_buster.sh + USER=${USER} ${my_dir}/update_db_pwd_buster.sh elif [[ "${VERSION_CODENAME}" == "bullseye" ]];then - ${my_dir}/update_db_pwd_bullseye.sh + USER=${USER} ${my_dir}/update_db_pwd_bullseye.sh fi } @@ -112,6 +112,7 @@ create_necessary_dirs() { [ -d ${EXTRACTED}/By_Date ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Date [ -d ${EXTRACTED}/By_Common_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Common_Name [ -d ${EXTRACTED}/By_Scientific_Name ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Scientific_Name + [ -d ${EXTRACTED}/Charts ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/Charts [ -d ${PROCESSED} ] || sudo -u ${USER} mkdir -p ${PROCESSED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/homepage/* ${EXTRACTED} @@ -143,12 +144,26 @@ create_necessary_dirs() { fi sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/spectrogram.php ${EXTRACTED} + sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewday.php ${EXTRACTED} + sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/overview.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewdb.php ${EXTRACTED} sudo -u ${USER} ln -fs ${HOME}/phpsysinfo ${EXTRACTED} sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/phpsysinfo.ini ${HOME}/phpsysinfo/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/green_bootstrap.css ${HOME}/phpsysinfo/templates/ sudo -u ${USER} cp -f $(dirname ${my_dir})/templates/index_bootstrap.html ${HOME}/phpsysinfo/templates/html + echo "Setting Wttr.in URL to "${LATITUDE}", "${LONGITUDE}"" + sudo -u${USER} sed -i "s/https:\/\/v2.wttr.in\//https:\/\/v2.wttr.in\/"${LATITUDE},${LONGITUDE}"/g" $(dirname ${my_dir})/homepage/menu.html +} + +generate_BirdDB() { + echo "Generating BirdDB.txt" + if ! [ -f $(dirname ${my_dir})/BirdDB.txt ];then + sudo -u ${USER} touch $(dirname ${my_dir})/BirdDB.txt + echo "Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap" | sudo -u ${USER} tee -a $(dirname ${my_dir})/BirdDB.txt + elif ! grep Date $(dirname ${my_dir})/BirdDB.txt;then + sudo -u ${USER} sed -i '1 i\Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap' $(dirname ${my_dir})/BirdDB.txt + fi } install_alsa() { @@ -300,6 +315,24 @@ EOF systemctl enable spectrogram_viewer.service } +install_chart_viewer_service() { + echo "Installing the chart_viewer.service" + cat << EOF > /etc/systemd/system/chart_viewer.service +[Unit] +Description=BirdNET-Pi Chart Viewer Service + +[Service] +Restart=always +RestartSec=300 +Type=simple +User=pi +ExecStart=/usr/local/bin/daily_plot.py +[Install] +WantedBy=multi-user.target +EOF + systemctl enable chart_viewer.service +} + install_gotty_logs() { echo "Installing GoTTY logging" if ! which gotty &> /dev/null;then @@ -489,6 +522,7 @@ install_selected_services() { install_sox install_mariadb install_spectrogram_service + install_chart_viewer_service install_edit_birdnet_conf install_pushed_notifications @@ -502,6 +536,7 @@ install_selected_services() { fi create_necessary_dirs + generate_BirdDB install_cleanup_cron } diff --git a/scripts/viewday.php b/scripts/viewday.php new file mode 100644 index 0000000..50c1c63 --- /dev/null +++ b/scripts/viewday.php @@ -0,0 +1,98 @@ +connect_error) { + die('Connect Error (' . + $mysqli->connect_errno . ') '. + $mysqli->connect_error); +} + +// SQL query to select data from database + +$sql1 = "SELECT * FROM detections + WHERE Date = CURDATE() + ORDER BY Date DESC, Time DESC"; +$mosttable = $mysqli->query($sql1); + +$sql2 = "SELECT * FROM detections + WHERE Date = CURDATE()"; +$todaystable = $mysqli->query($sql2); +$todayscount=mysqli_num_rows($todaystable); + + +$sql3 = "SELECT * FROM detections + WHERE Date = CURDATE() + AND Time >= DATE_SUB(NOW(),INTERVAL 1 HOUR)"; +$lasthourtable = $mysqli->query($sql3); +$lasthourcount=mysqli_num_rows($lasthourtable); + +$sql4 = "SELECT Com_Name, Date, Time, MAX(Confidence) + FROM detections + WHERE Date = CURDATE() + GROUP BY Com_Name + ORDER BY MAX(Confidence) DESC"; +$specieslist = $mysqli->query($sql4); +$speciescount=mysqli_num_rows($specieslist); + +$mysqli->close(); +?> + + + + + + + + Today's View + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + +
TodayLast HourNumber of Unique Species Today
Number of Detections
+ +
+ +
+
+ + + diff --git a/scripts/viewdb.php b/scripts/viewdb.php index eb4a9f6..b93e23a 100644 --- a/scripts/viewdb.php +++ b/scripts/viewdb.php @@ -10,45 +10,45 @@ $servername='localhost'; $mysqli = new mysqli($servername, $user, $password, $database); if ($mysqli->connect_error) { - die('Connect Error (' . - $mysqli->connect_errno . ') '. - $mysqli->connect_error); + die('Connect Error (' . + $mysqli->connect_errno . ') '. + $mysqli->connect_error); } // SQL query to select data from database $sql = "SELECT * FROM detections - ORDER BY Date DESC, Time DESC"; + ORDER BY Date DESC, Time DESC"; $fulltable = $mysqli->query($sql); $totalcount=mysqli_num_rows($fulltable); $sql1 = "SELECT * FROM detections - WHERE Date = CURDATE() - ORDER BY Date DESC, Time DESC"; + WHERE Date = CURDATE() + ORDER BY Date DESC, Time DESC"; $mosttable = $mysqli->query($sql1); $sql2 = "SELECT * FROM detections - WHERE Date = CURDATE()"; + WHERE Date = CURDATE()"; $todaystable = $mysqli->query($sql2); $todayscount=mysqli_num_rows($todaystable); $sql3 = "SELECT * FROM detections - WHERE Date = CURDATE() - AND Time >= DATE_SUB(NOW(),INTERVAL 1 HOUR)"; + WHERE Date = CURDATE() + AND Time >= DATE_SUB(NOW(),INTERVAL 1 HOUR)"; $lasthourtable = $mysqli->query($sql3); $lasthourcount=mysqli_num_rows($lasthourtable); $sql4 = "SELECT Com_Name, Date, Time, MAX(Confidence) - FROM detections - GROUP BY Com_Name - ORDER BY MAX(Confidence) DESC"; + FROM detections + GROUP BY Com_Name + ORDER BY MAX(Confidence) DESC"; $specieslist = $mysqli->query($sql4); $speciescount=mysqli_num_rows($specieslist); $sql5 = "SELECT Com_Name,COUNT(*) - AS Total - FROM detections - GROUP BY Com_Name - ORDER BY Total DESC"; + AS Total + FROM detections + GROUP BY Com_Name + ORDER BY Total DESC"; $speciestally = $mysqli->query($sql5); $mysqli->close(); @@ -58,122 +58,117 @@ $mysqli->close(); - - - BirdNET-Pi DB - - - + + + BirdNET-Pi DB + - + -
+
+

Number of Detections

-
-

Number of Detections

- - - - - - - - - - - - - -
TotalTodayLast HourNumber of Unique Species
+
+ + + + + + + + + + + + + +
TotalTodayLast HourNumber of Unique Species
-
-
-

Detected Species

- - - - - - - -fetch_assoc()) -{ -?> - - - - - - - - -
SpeciesDateTimeMax Confidence Score
-
-
-

Species Statistics

- - - - - -fetch_assoc()) -{ -?> - - - - - -
SpeciesDetections
-
-
-

Today's Detections

- - - - - - - - - - - - - - - +

Today's Detections

+ +
TimeScientific NameCommon NameConfidenceLatLonCutoffWeekSensOverlap
+ + + + + + + + + + + + + fetch_assoc()) { ?> - - - - - - - - - - - - - + + + + + + + + + + + + + -
TimeScientific NameCommon NameConfidenceLatLonCutoffWeekSensOverlap
-
+ +
+
+

Detected Species

+ + + + + + + +fetch_assoc()) +{ +?> + + + + + + + + +
SpeciesDateTimeMax Confidence Score
+
+
+

Species Statistics

+ + + + + +fetch_assoc()) +{ +?> + + + + + + + diff --git a/scripts/welcome_wizard.sh b/scripts/welcome_wizard.sh index 3f88c03..beff850 100755 --- a/scripts/welcome_wizard.sh +++ b/scripts/welcome_wizard.sh @@ -28,7 +28,7 @@ overview() { 6) Updating the system to use your new birdnet.conf file" --no-wrap --icon-name=red-cardinal if [ $? -eq 0 ];then - exit 1 + exit 0 elif [ $? -eq 1 ];then open_rpi_configuration && change_password fi diff --git a/templates/phpsysinfo.ini b/templates/phpsysinfo.ini index 97617ca..79b725e 100644 --- a/templates/phpsysinfo.ini +++ b/templates/phpsysinfo.ini @@ -57,8 +57,7 @@ SUDO_COMMANDS=false ; Example : BLOCKS="vitals,hardware,memory,filesystem,network,voltage,current,temperature,fans,power,other,ups" or BLOCKS=true //default order ; BLOCKS=false //hide all blocks ; -BLOCKS=true - +BLOCKS="filesystem,memory,vitals,hardware,network,temperature,voltage,current,fans,power,other,ups" ; Maximum time in seconds a script is allowed to run before it is terminated by the parser ; ;MAX_TIMEOUT=30 @@ -105,7 +104,7 @@ BLOCKS=true ; - Docker - show docker stats ; - Viewer - show output of any command or file viewer.tmp contents ; -PLUGINS=false +PLUGINS="Viewer,PSStatus" ; ******************************** @@ -248,7 +247,7 @@ SHOW_DEVICES_SERIAL=false ; - "UTC" shown as UTC string ; - "locale" shown as Locale string ; -DATETIME_FORMAT="UTC" +DATETIME_FORMAT="locale" ; ******************************** @@ -336,7 +335,7 @@ SHOW_INODES=true ; Hide mounts ; Example : HIDE_MOUNTS="/home,/usr" ; -HIDE_MOUNTS="" +HIDE_MOUNTS="/boot,/run,/run/lock,/run/user/1000,/dev/shm" ; Filesystem usage warning threshold in percent @@ -398,7 +397,7 @@ HIDE_TOTALS=false ; Example : HIDE_NETWORK_INTERFACE="eth0,sit0" ; HIDE_NETWORK_INTERFACE=true //hide all network interfaces ; -HIDE_NETWORK_INTERFACE="" +HIDE_NETWORK_INTERFACE="lo" ; Use a regular expression in the name of a hidden network interface (e.g. HIDE_NETWORK_INTERFACE="docker.*") @@ -786,7 +785,7 @@ USE_REGEX=false ; ; string contains a list of process names that are checked, names are seperated by a comma (on WinNT names must end with '.exe') ; -PROCESSES="mysqld, sshd, explorer.exe" +PROCESSES="avahi-daemon,gotty,sshd,mariadbd,ffmpeg,vncserver-x11-serviced,nxd,bash" [quotas] @@ -929,11 +928,13 @@ ACCESS="command" ; - "systeminfo" systeminfo command is run everytime the block gets refreshed or build (WinNT) ; -COMMAND="" +COMMAND="curl" +#COMMAND="cat" ; define COMMAND parameters (for command access) ; -PARAMS="" +PARAMS="-s4 ifconfig.co" +#PARAMS="/home/pi/BirdNET-Pi/thisrun.txt" [pingtest] ; PingTest Plugin configuration diff --git a/version.md b/version.md index 372f859..d8a0d2e 100644 --- a/version.md +++ b/version.md @@ -1,3 +1,13 @@ +# main v.10 & pre-installed image notes +- New "BirdWeather" Chromium App (Pre-install image) +- New Infographics _chart_viewer.service_ (courtesy of @CaiusX) +- New "Overview" +- BirdWeather Support +- Bug Fix for systemd-networkd-wait-online.service +- Bug Fix for `install_noip2.sh` for NoIP DUC Support +- New `disk_check.sh` utitlity/crontab entry to `stop_core_services.sh` + when disk space is greater than 95% + # main v0.9 -- pre-installed image - Bug fix for Auto Access Point - Improved Welcome Wizard
SpeciesDetections