mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-10 09:51:02 +01:00
battybirdnet-pi
This commit is contained in:
3
battybirdnet-pi/CHANGELOG.md
Normal file
3
battybirdnet-pi/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
## 0.1 (28-04-2024)
|
||||
- Initial build
|
||||
205
battybirdnet-pi/Dockerfile
Normal file
205
battybirdnet-pi/Dockerfile
Normal file
@@ -0,0 +1,205 @@
|
||||
#============================#
|
||||
# ALEXBELGIUM'S DOCKERFILE #
|
||||
#============================#
|
||||
# _.------.
|
||||
# _.-` ('>.-`"""-.
|
||||
# '.--'` _'` _ .--.)
|
||||
# -' '-.-';` `
|
||||
# ' - _.' ``'--.
|
||||
# '---` .-'""`
|
||||
# /`
|
||||
#=== Home Assistant Addon ===#
|
||||
|
||||
#################
|
||||
# 1 Build Image #
|
||||
#################
|
||||
|
||||
ARG BUILD_VERSION
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
ENV DEBIAN_FRONTEND="noninteractive" \
|
||||
BIRDNET_USER="pi" \
|
||||
USER="pi" \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
HOME="/home/pi" \
|
||||
XDG_RUNTIME_DIR="/run/user/1000" \
|
||||
PYTHON_VIRTUAL_ENV="/home/pi/BirdNET-Pi/birdnet/bin/python3" \
|
||||
my_dir=/home/pi/BirdNET-Pi/scripts
|
||||
|
||||
# Global LSIO modifications
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_lsio.sh" "/ha_lsio.sh"
|
||||
ARG CONFIGLOCATION="/config"
|
||||
RUN chmod 744 /ha_lsio.sh && if grep -qr "lsio" /etc; then /ha_lsio.sh "$CONFIGLOCATION"; fi && rm /ha_lsio.sh
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# hadolint ignore=DL3015,SC2016
|
||||
RUN \
|
||||
# Install dependencies
|
||||
echo "Install dependencies" && \
|
||||
apt-get update -y && apt-get install curl gcc python3-dev git jq sudo php-mbstring procps -y && \
|
||||
\
|
||||
# Correct for systemctl
|
||||
curl -f -L -s -S https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl3.py -o /bin/systemctl && \
|
||||
chmod a+x /bin/systemctl && \
|
||||
\
|
||||
# Change user to pi and create /home/pi
|
||||
echo "setting users" && \
|
||||
if id abc >/dev/null 2>&1; then groupmod -o -g 101 abc && usermod -o -u 101 abc; fi && \
|
||||
groupadd --non-unique -g 1000 "$USER" && \
|
||||
useradd --non-unique --uid 1000 --gid 1000 -m "$USER" && \
|
||||
\
|
||||
# Ensure permissions
|
||||
echo "setting permissions" && \
|
||||
echo "$USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
|
||||
mkdir -p /home/"$USER"/.config/matplotlib && \
|
||||
chown -R "$USER":"$USER" /home/"$USER" && \
|
||||
git config --global --add safe.directory '*' && \
|
||||
\
|
||||
# Download installer
|
||||
curl -f -L -s -S "https://raw.githubusercontent.com/rdz-oss/BattyBirdNET-Pi/main/newinstaller.sh" -o /newinstaller.sh && \
|
||||
chmod 777 /newinstaller.sh && \
|
||||
\
|
||||
# Use installer to modify other scripts
|
||||
#######################################
|
||||
# Define file
|
||||
sed -i "1a /./newinstallermod.sh" /newinstaller.sh && \
|
||||
echo '#!/bin/bash' >> /newinstallermod.sh && \
|
||||
# Remove all instances of sudo from all other scripts
|
||||
echo 'for file in $(grep -srl "sudo" $HOME/BirdNET-Pi/scripts); do sed -i "s|sudo ||" "$file"; done' >> /newinstallermod.sh && \
|
||||
echo 'for file in $(grep -srl "my_dir" $HOME/BirdNET-Pi/scripts); do sed -i "s|\$my_dir|/config|" "$file"; done' >> /newinstallermod.sh && \
|
||||
# Disable pulseaudio
|
||||
echo 'for file in $(grep -srl "pulseaudio --start" $HOME/BirdNET-Pi/scripts); do sed -i "/pulseaudio --start/d" "$file"; done' >> /newinstallermod.sh && \
|
||||
# Set permission
|
||||
chmod +x /newinstallermod.sh && \
|
||||
\
|
||||
# Modify installer
|
||||
##################
|
||||
# Avoid rebooting at end of installation
|
||||
sed -i "/reboot/d" /newinstaller.sh && \
|
||||
# Use apt-get as without user action
|
||||
sed -i "s|apt |apt-get |g" /newinstaller.sh && \
|
||||
# Ensure chmod
|
||||
sed -i "/git clone/a chown -R 1000:1000 $HOME" /newinstaller.sh && \
|
||||
# Remove all instances of sudo from the newinstaller
|
||||
sed -i -e "s|== 0|== 7|g" -e "s|sudo -n true|true|g" -e "s|sudo -K|true|g" /newinstaller.sh && \
|
||||
\
|
||||
# Execute installer
|
||||
/./newinstaller.sh && \
|
||||
\
|
||||
# Install dateparser
|
||||
$PYTHON_VIRTUAL_ENV /usr/bin/pip3 install dateparser && \
|
||||
\
|
||||
# Adapt for lsio usage of /app
|
||||
if [ -d /app ]; then rm -r /app; fi && \
|
||||
ln -s /home/"$USER" /app && \
|
||||
chown -R "$USER":"$USER" /home/"$USER" /app && \
|
||||
\
|
||||
# Give access to caddy for files owned by the user, to allow files modification
|
||||
groupmod -o -g 1000 caddy && usermod -o -u 1000 caddy && \
|
||||
\
|
||||
# Cleanup
|
||||
apt-get clean all && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
##################
|
||||
|
||||
# Set S6 wait time
|
||||
ENV S6_CMD_WAIT_FOR_SERVICES=1 \
|
||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||
S6_SERVICES_GRACETIME=0
|
||||
|
||||
##################
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
# Add rootfs
|
||||
COPY rootfs/ /
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
# hadolint ignore=DL4005
|
||||
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
|
||||
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
|
||||
|
||||
# Modules
|
||||
ARG MODULES="00-local_mounts.sh 00-smb_mounts.sh"
|
||||
|
||||
# Automatic modules download
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_automodules.sh" "/ha_automodules.sh"
|
||||
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
|
||||
|
||||
# Manual apps
|
||||
ENV PACKAGES="alsa-utils libasound2-plugins"
|
||||
|
||||
# Automatic apps & bashio
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_autoapps.sh" "/ha_autoapps.sh"
|
||||
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
|
||||
|
||||
################
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
# Add entrypoint
|
||||
ENV S6_STAGE2_HOOK=/ha_entrypoint.sh
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_entrypoint.sh" "/ha_entrypoint.sh"
|
||||
|
||||
# Entrypoint modifications
|
||||
ADD "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_entrypoint_modif.sh" "/ha_entrypoint_modif.sh"
|
||||
RUN chmod 777 /ha_entrypoint.sh /ha_entrypoint_modif.sh && /ha_entrypoint_modif.sh && rm /ha_entrypoint_modif.sh
|
||||
|
||||
# Avoid config.yaml interference
|
||||
WORKDIR /config
|
||||
#ENTRYPOINT ["/lib/systemd/systemd"]
|
||||
#ENTRYPOINT [ "/usr/bin/env" ]
|
||||
#CMD [ "/ha_entrypoint.sh" ]
|
||||
#SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Allow a dockerfile independent from HA
|
||||
EXPOSE 80
|
||||
RUN mkdir -p /data /config
|
||||
|
||||
############
|
||||
# 5 Labels #
|
||||
############
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_DESCRIPTION
|
||||
ARG BUILD_NAME
|
||||
ARG BUILD_REF
|
||||
ARG BUILD_REPOSITORY
|
||||
ARG BUILD_VERSION
|
||||
LABEL \
|
||||
io.hass.name="${BUILD_NAME}" \
|
||||
io.hass.description="${BUILD_DESCRIPTION}" \
|
||||
io.hass.arch="${BUILD_ARCH}" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION} \
|
||||
maintainer="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.title="${BUILD_NAME}" \
|
||||
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
|
||||
org.opencontainers.image.vendor="Home Assistant Add-ons" \
|
||||
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.url="https://github.com/alexbelgium" \
|
||||
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
|
||||
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
|
||||
org.opencontainers.image.created=${BUILD_DATE} \
|
||||
org.opencontainers.image.revision=${BUILD_REF} \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
#################
|
||||
# 6 Healthcheck #
|
||||
#################
|
||||
|
||||
ENV HEALTH_PORT="80" \
|
||||
HEALTH_URL=""
|
||||
HEALTHCHECK \
|
||||
--interval=5s \
|
||||
--retries=5 \
|
||||
--start-period=30s \
|
||||
--timeout=25s \
|
||||
CMD curl --fail "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1
|
||||
159
battybirdnet-pi/README.md
Normal file
159
battybirdnet-pi/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Home assistant add-on: battybirdnet-pi
|
||||
|
||||
[![Donate][donation-badge]](https://www.buymeacoffee.com/alexbelgium)
|
||||
[![Donate][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA)
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
[](https://www.codacy.com/gh/alexbelgium/hassio-addons/dashboard?utm_source=github.com&utm_medium=referral&utm_content=alexbelgium/hassio-addons&utm_campaign=Badge_Grade)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/weekly-supelinter.yaml)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/onpush_builder.yaml)
|
||||
|
||||
[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee%20(no%20paypal)-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white
|
||||
[paypal-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee%20with%20Paypal-0070BA?logo=paypal&style=flat&logoColor=white
|
||||
|
||||
_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_
|
||||
|
||||
[](https://github.com/alexbelgium/hassio-addons/stargazers)
|
||||
|
||||

|
||||
|
||||
## About
|
||||
|
||||
---
|
||||
|
||||
[battybirdnet-pi](https://github.com/Nachtzuster/BirdNET-Pi) is an AI solution for continuous avian monitoring and identification originally developed by @mcguirepr89 on github (https://github.com/mcguirepr89/BirdNET-Pi), whose work is continued by @Nachtzuster and other developers on an active fork (https://github.com/Nachtzuster/BirdNET-Pi)
|
||||
|
||||
Features of the addon :
|
||||
- Robust base image provided by [linuxserver](https://github.com/linuxserver/docker-baseimage-debian)
|
||||
- Working docker system thanks to https://github.com/gdraheim/docker-systemctl-replacement
|
||||
- Uses HA pulseaudio server
|
||||
- Uses HA tmpfs to store temporary files in ram and avoid disk wear
|
||||
- Exposes all config files to /config to allow remanence and easy access
|
||||
- Allows to modify the location of the stored bird songs (preferably to an external hdd)
|
||||
- Supports ingress, to allow secure remote access without exposing ports
|
||||
|
||||
## Configuration
|
||||
|
||||
---
|
||||
|
||||
Install, then start the addon a first time
|
||||
Webui can be found by two ways :
|
||||
- Ingress from HA (no password but some functions don't work)
|
||||
- Direct access with <http://homeassistant:port>, port being the one defined in the birdnet.conf. The username when asked for a password is `birdnet`, the password is the one that you can define in the birdnet.con (blank by default). This is different than the password from the addon options, which is the one that must be used to access the web terminal
|
||||
|
||||
Web terminal access : uesrname `pi`, password : as defined in the addon options
|
||||
|
||||
You'll need a microphone : either use one connected to HA or the audio stream of a rstp camera.
|
||||
|
||||
Options can be configured through three ways :
|
||||
|
||||
- Addon options
|
||||
|
||||
```yaml
|
||||
BIRDSONGS_FOLDER: folder to store birdsongs file # It should be an ssd if you want to avoid clogging of analysis
|
||||
MQTT_DISABLED : if true, disables automatic mqtt publishing. Only valid if there is a local broker already available
|
||||
LIVESTREAM_BOOT_ENABLED: start livestream from boot, or from settings
|
||||
SPECIES_CONVERTER_ENABLED: true/false. if enabled, will create a new setting in the birdnet options where you can specify birds to convert. It will convert on the fly the specie when detected
|
||||
PROCESSED_FOLDER_ENABLED : if enabled, you need to set in the birdnet.conf (or the setting of birdnet) the number of last wav files that will be saved in the temporary folder "/tmp/Processed" within the tmpfs (so no disk wear) in case you want to retrieve them. This amount can be adapted from the addon options
|
||||
TZ: Etc/UTC specify a timezone to use, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
||||
pi_password: set the user password to access the web terminal
|
||||
localdisks: sda1 #put the hardware name of your drive to mount separated by commas, or its label. ex. sda1, sdb1, MYNAS...
|
||||
networkdisks: "//SERVER/SHARE" # optional, list of smb servers to mount, separated by commas
|
||||
cifsusername: "username" # optional, smb username, same for all smb shares
|
||||
cifspassword: "password" # optional, smb password
|
||||
cifsdomain: "domain" # optional, allow setting the domain for the smb share
|
||||
```
|
||||
|
||||
- Config.yaml
|
||||
Additional variables can be configured using the config.yaml file found in /config/db21ed7f_battybirdnet-pi/config.yaml using the Filebrowser addon
|
||||
|
||||
- Config_env.yaml
|
||||
Additional environment variables can be configured there
|
||||
|
||||
## Installation
|
||||
|
||||
---
|
||||
|
||||
The installation of this add-on is pretty straightforward and not different in comparison to installing any other add-on.
|
||||
|
||||
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
1. Install this add-on.
|
||||
1. Click the `Save` button to store your configuration.
|
||||
1. Set the add-on options to your preferences
|
||||
1. Start the add-on.
|
||||
1. Check the logs of the add-on to see if everything went well.
|
||||
1. Open the webUI and adapt the software options
|
||||
|
||||
## Integration with HA
|
||||
|
||||
---
|
||||
### Apprise
|
||||
|
||||
You can use apprise to send notifications with mqtt, then act on those using HomeAssistant
|
||||
Further informations : https://wander.ingstar.com/projects/birdnetpi.html
|
||||
|
||||
### Automatic mqtt
|
||||
|
||||
If mqtt is installed, the addon automatically updates the birdnet topic with each detected species
|
||||
|
||||
## Using ssl
|
||||
|
||||
---
|
||||
|
||||
Option 1 : Install let's encrypt addon, generate certificates. They are by default certfile.pem and keyfile.pem stored in /ssl. Just enable ssl from the addon option and it will work.
|
||||
|
||||
Option 2 : enable port 80, define your battybirdnet-pi URL as https. Certificate will be automatically generated by caddy
|
||||
|
||||
## Improve detections
|
||||
|
||||
---
|
||||
|
||||
### Gain for card
|
||||
|
||||
Using alsamixer in the Terminal tab, make sure that the sound level is high enough but not too high (not in the red part)
|
||||
https://github.com/mcguirepr89/BirdNET-Pi/wiki/Adjusting-your-sound-card
|
||||
|
||||
### Ferrite
|
||||
|
||||
Adding ferrite beads lead in my case to worst noise
|
||||
|
||||
### Aux to usb adapters
|
||||
|
||||
Based on my test, only adapters using KT0210 (such as Ugreen's) work. I couldn't get adapters based on ALC to be detected.
|
||||
|
||||
### Microphone comparison
|
||||
|
||||
Recommended microphones ([full discussion here](https://github.com/mcguirepr89/BirdNET-Pi/discussions/39)):
|
||||
- Clippy EM272 (https://www.veldshop.nl/en/smart-clippy-em272z1-mono-omni-microphone.html) + ugreen aux to usb connector : best sensitivity with lavalier tech
|
||||
- Boya By-LM40 : best quality/price
|
||||
- Hyperx Quadcast : best sensitivity with cardioid tech
|
||||
|
||||
Conclusion, using mic from Dahua is good enough, EM272 is optimal, but Boya by-lm40 is a very good compromise as birndet model analysis the 0-15000Hz range
|
||||
|
||||

|
||||
|
||||
### Denoise ([Full discussion here](https://github.com/mcguirepr89/BirdNET-Pi/discussions/597))
|
||||
|
||||
Denoise is frowned upon by serious researchers. However it does seem to significantly increase quality of detection ! Here is how to do it in HA :
|
||||
- Using Portainer addon, go in the hassio_audio container, and modify the file /etc/pulse/system.pa to add the line `load-module module-echo-cancel`
|
||||
- Go in the Terminal addon, and type `ha audio restart`
|
||||
- Select the echo cancelled device as input device in the addon options
|
||||
|
||||
### High pass
|
||||
|
||||
Should be avoided as the model uses the whole 0-15khz range
|
||||
|
||||
## Common issues
|
||||
|
||||
Not yet available
|
||||
|
||||
## Support
|
||||
|
||||
Create an issue on github
|
||||
|
||||
---
|
||||
|
||||
66
battybirdnet-pi/apparmor.txt
Normal file
66
battybirdnet-pi/apparmor.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <tunables/global>
|
||||
|
||||
profile battybirdnet-pi_addon flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
capability,
|
||||
file,
|
||||
signal,
|
||||
mount,
|
||||
umount,
|
||||
remount,
|
||||
network udp,
|
||||
network tcp,
|
||||
network dgram,
|
||||
network stream,
|
||||
network inet,
|
||||
network inet6,
|
||||
network netlink raw,
|
||||
network unix dgram,
|
||||
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
capability sys_admin,
|
||||
capability dac_read_search,
|
||||
# capability dac_override,
|
||||
# capability sys_rawio,
|
||||
|
||||
# S6-Overlay
|
||||
/init ix,
|
||||
/run/{s6,s6-rc*,service}/** ix,
|
||||
/package/** ix,
|
||||
/command/** ix,
|
||||
/run/{,**} rwk,
|
||||
/dev/tty rw,
|
||||
/bin/** ix,
|
||||
/usr/bin/** ix,
|
||||
/usr/lib/bashio/** ix,
|
||||
/etc/s6/** rix,
|
||||
/run/s6/** rix,
|
||||
/etc/services.d/** rwix,
|
||||
/etc/cont-init.d/** rwix,
|
||||
/etc/cont-finish.d/** rwix,
|
||||
/init rix,
|
||||
/var/run/** mrwkl,
|
||||
/var/run/ mrwkl,
|
||||
/dev/i2c-1 mrwkl,
|
||||
# Files required
|
||||
/dev/fuse mrwkl,
|
||||
/dev/sda1 mrwkl,
|
||||
/dev/sdb1 mrwkl,
|
||||
/dev/nvme0 mrwkl,
|
||||
/dev/nvme1 mrwkl,
|
||||
/dev/mmcblk0p1 mrwkl,
|
||||
/dev/* mrwkl,
|
||||
/tmp/** mrkwl,
|
||||
|
||||
# Data access
|
||||
/data/** rw,
|
||||
|
||||
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
|
||||
ptrace (trace,read) peer=docker-default,
|
||||
|
||||
# docker daemon confinement requires explict allow rule for signal
|
||||
signal (receive) set=(kill,term) peer=/usr/bin/docker,
|
||||
|
||||
}
|
||||
6
battybirdnet-pi/build.yaml
Normal file
6
battybirdnet-pi/build.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
build_from:
|
||||
aarch64: ghcr.io/linuxserver/baseimage-debian:arm64v8-bookworm
|
||||
amd64: ghcr.io/linuxserver/baseimage-debian:amd64-bookworm
|
||||
codenotary:
|
||||
signer: alexandrep.github@gmail.com
|
||||
126
battybirdnet-pi/config.json
Normal file
126
battybirdnet-pi/config.json
Normal file
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"arch": [
|
||||
"aarch64",
|
||||
"amd64"
|
||||
],
|
||||
"audio": true,
|
||||
"backup": "cold",
|
||||
"codenotary": "alexandrep.github@gmail.com",
|
||||
"description": "A realtime acoustic bat & bird classification system for the Raspberry Pi 4/5 built on BattyBirdNET-Analyzer",
|
||||
"devices": [
|
||||
"/dev/dri",
|
||||
"/dev/dri/card0",
|
||||
"/dev/dri/card1",
|
||||
"/dev/dri/renderD128",
|
||||
"/dev/vchiq",
|
||||
"/dev/video10",
|
||||
"/dev/video11",
|
||||
"/dev/video12",
|
||||
"/dev/video13",
|
||||
"/dev/video14",
|
||||
"/dev/video15",
|
||||
"/dev/video16",
|
||||
"/dev/ttyUSB0",
|
||||
"/dev/sda",
|
||||
"/dev/sdb",
|
||||
"/dev/sdc",
|
||||
"/dev/sdd",
|
||||
"/dev/sde",
|
||||
"/dev/sdf",
|
||||
"/dev/sdg",
|
||||
"/dev/nvme",
|
||||
"/dev/nvme0n1p1",
|
||||
"/dev/nvme0n1p2",
|
||||
"/dev/mmcblk",
|
||||
"/dev/fuse",
|
||||
"/dev/sda1",
|
||||
"/dev/sdb1",
|
||||
"/dev/sdc1",
|
||||
"/dev/sdd1",
|
||||
"/dev/sde1",
|
||||
"/dev/sdf1",
|
||||
"/dev/sdg1",
|
||||
"/dev/sda2",
|
||||
"/dev/sdb2",
|
||||
"/dev/sdc2",
|
||||
"/dev/sdd2",
|
||||
"/dev/sde2",
|
||||
"/dev/sdf2",
|
||||
"/dev/sdg2",
|
||||
"/dev/sda3",
|
||||
"/dev/sdb3",
|
||||
"/dev/sda4",
|
||||
"/dev/sdb4",
|
||||
"/dev/sda5",
|
||||
"/dev/sda6",
|
||||
"/dev/sda7",
|
||||
"/dev/sda8",
|
||||
"/dev/nvme0",
|
||||
"/dev/nvme1",
|
||||
"/dev/nvme2"
|
||||
],
|
||||
"image": "ghcr.io/alexbelgium/battybirdnet-pi-{arch}",
|
||||
"ingress": true,
|
||||
"ingress_stream": true,
|
||||
"init": false,
|
||||
"map": [
|
||||
"addon_config:rw",
|
||||
"media:rw",
|
||||
"share:rw",
|
||||
"ssl"
|
||||
],
|
||||
"name": "BattyBirdNET-Pi",
|
||||
"options": {
|
||||
"BIRDSONGS_FOLDER": "/config/BirdSongs",
|
||||
"LIVESTREAM_BOOT_ENABLED": false,
|
||||
"TZ": "Europe/Paris",
|
||||
"certfile": "fullchain.pem",
|
||||
"keyfile": "privkey.pem",
|
||||
"ssl": false
|
||||
},
|
||||
"panel_admin": false,
|
||||
"panel_icon": "mdi:bird",
|
||||
"ports": {
|
||||
"80/tcp": null,
|
||||
"8081/tcp": 8081
|
||||
},
|
||||
"ports_description": {
|
||||
"80/tcp": "Optional : set to 80 to use caddy's automatic ssl",
|
||||
"8081/tcp": "Web ui"
|
||||
},
|
||||
"privileged": [
|
||||
"SYS_ADMIN",
|
||||
"DAC_READ_SEARCH"
|
||||
],
|
||||
"schema": {
|
||||
"BIRDSONGS_FOLDER": "str?",
|
||||
"LIVESTREAM_BOOT_ENABLED": "bool",
|
||||
"MQTT_DISABLED": "bool?",
|
||||
"MQTT_HOST_manual": "str?",
|
||||
"MQTT_PASSWORD_manual": "password?",
|
||||
"MQTT_PORT_manual": "int?",
|
||||
"MQTT_USER_manual": "str?",
|
||||
"PROCESSED_FOLDER_ENABLED": "bool?",
|
||||
"SPECIES_CONVERTER_ENABLED": "bool?",
|
||||
"TZ": "str?",
|
||||
"certfile": "str",
|
||||
"cifsdomain": "str?",
|
||||
"cifspassword": "str?",
|
||||
"cifsusername": "str?",
|
||||
"keyfile": "str",
|
||||
"localdisks": "str?",
|
||||
"networkdisks": "str?",
|
||||
"pi_password": "password",
|
||||
"ssl": "bool"
|
||||
},
|
||||
"services": [
|
||||
"mqtt:want"
|
||||
],
|
||||
"slug": "battybirdnet-pi",
|
||||
"tmpfs": true,
|
||||
"udev": true,
|
||||
"url": "https://github.com/alexbelgium/hassio-addons/tree/master/battybirdnet-pi",
|
||||
"usb": true,
|
||||
"version": "0.1",
|
||||
"video": true
|
||||
}
|
||||
BIN
battybirdnet-pi/icon.png
Normal file
BIN
battybirdnet-pi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
BIN
battybirdnet-pi/logo.png
Normal file
BIN
battybirdnet-pi/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
12
battybirdnet-pi/rootfs/custom-services.d/00-php_pfm.sh
Normal file
12
battybirdnet-pi/rootfs/custom-services.d/00-php_pfm.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Correct /config permissions after startup
|
||||
chown pi:pi /config
|
||||
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
echo "Starting service: php pfm"
|
||||
exec /usr/sbin/php-fpm* -F
|
||||
9
battybirdnet-pi/rootfs/custom-services.d/01-avahi.sh
Normal file
9
battybirdnet-pi/rootfs/custom-services.d/01-avahi.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
echo "Starting service: avahi daemon"
|
||||
exec \
|
||||
avahi-daemon --no-chroot
|
||||
21
battybirdnet-pi/rootfs/custom-services.d/02-caddy.sh
Normal file
21
battybirdnet-pi/rootfs/custom-services.d/02-caddy.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Dependencies
|
||||
sockfile="empty"
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]] && [[ -e "$sockfile" ]]; do
|
||||
sleep 1s
|
||||
sockfile="$(find /run/php -name "*.sock")"
|
||||
done
|
||||
|
||||
# Correct fpm.sock
|
||||
chown caddy:caddy /run/php/php*-fpm.sock
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /helpers/caddy_ingress.sh
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
|
||||
# Update caddyfile with password
|
||||
/."$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh &>/dev/null || true
|
||||
|
||||
echo "Starting service: caddy"
|
||||
/usr/bin/caddy run --config /etc/caddy/Caddyfile
|
||||
6
battybirdnet-pi/rootfs/custom-services.d/02-nginx.sh
Normal file
6
battybirdnet-pi/rootfs/custom-services.d/02-nginx.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
echo "Starting service: nginx"
|
||||
nginx
|
||||
92
battybirdnet-pi/rootfs/custom-services.d/30-monitoring.sh
Normal file
92
battybirdnet-pi/rootfs/custom-services.d/30-monitoring.sh
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
echo "Starting service: throttlerecording"
|
||||
touch "$HOME"/BirdSongs/StreamData/analyzing_now.txt
|
||||
|
||||
# variables for readability
|
||||
srv="birdnet_recording"
|
||||
analyzing_now="."
|
||||
counter=10
|
||||
set +u
|
||||
# shellcheck disable=SC1091
|
||||
source /config/birdnet.conf 2>/dev/null
|
||||
|
||||
# Ensure folder exists
|
||||
ingest_dir="$RECS_DIR/StreamData"
|
||||
|
||||
# Check permissions
|
||||
mkdir -p "$ingest_dir"
|
||||
chown -R pi:pi "$ingest_dir"
|
||||
chmod -R 755 "$ingest_dir"
|
||||
ingest_dir="$(readlink -f "$ingest_dir")" || true
|
||||
mkdir -p "$ingest_dir"
|
||||
chown -R pi:pi "$ingest_dir"
|
||||
chmod -R 755 "$ingest_dir"
|
||||
|
||||
function apprisealert() {
|
||||
# Set failed check so it only runs once
|
||||
touch "$HOME"/BirdNET-Pi/failed_servicescheck
|
||||
NOTIFICATION=""
|
||||
STOPPEDSERVICE="<br><b>Stopped services:</b> "
|
||||
services=(birdnet_analysis
|
||||
chart_viewer
|
||||
spectrogram_viewer
|
||||
icecast2
|
||||
birdnet_recording
|
||||
birdnet_log
|
||||
birdnet_stats)
|
||||
for i in "${services[@]}"; do
|
||||
if [[ "$(sudo systemctl is-active "${i}".service)" == "inactive" ]]; then
|
||||
STOPPEDSERVICE+="${i}; "
|
||||
fi
|
||||
done
|
||||
NOTIFICATION+="$STOPPEDSERVICE"
|
||||
NOTIFICATION+="<br><b>Additional informations</b>: "
|
||||
NOTIFICATION+="<br><b>Since:</b> ${LASTCHECK:-unknown}"
|
||||
NOTIFICATION+="<br><b>System:</b> ${SITE_NAME:-$(hostname)}"
|
||||
NOTIFICATION+="<br>Available disk space: $(df -h "$(readlink -f "$HOME/BirdSongs")" | awk 'NR==2 {print $4}')"
|
||||
if [ -n "$BIRDNETPI_URL" ]; then
|
||||
NOTIFICATION+="<br> <a href=\"$BIRDNETPI_URL\">Access your battybirdnet-pi</a>"
|
||||
fi
|
||||
TITLE="BirdNET-Analyzer stopped"
|
||||
"$HOME"/BirdNET-Pi/birdnet/bin/apprise -vv -t "$TITLE" -b "${NOTIFICATION}" --input-format=html --config="$HOME/BirdNET-Pi/apprise.txt"
|
||||
}
|
||||
|
||||
while true; do
|
||||
sleep 61
|
||||
|
||||
# Restart analysis if clogged
|
||||
############################
|
||||
|
||||
if ((counter <= 0)); then
|
||||
latest="$(cat "$ingest_dir"/analyzing_now.txt)"
|
||||
if [[ "$latest" == "$analyzing_now" ]]; then
|
||||
echo "$(date) WARNING no change in analyzing_now for 10 iterations, restarting services"
|
||||
/."$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
fi
|
||||
counter=10
|
||||
analyzing_now=$(cat "$ingest_dir"/analyzing_now.txt)
|
||||
fi
|
||||
|
||||
# Pause recorder to catch-up
|
||||
############################
|
||||
|
||||
wavs="$(find "$ingest_dir" -maxdepth 1 -name '*.wav' | wc -l)"
|
||||
state="$(systemctl is-active "$srv")"
|
||||
|
||||
bashio::log.green "$(date) INFO ${wavs} wav files waiting in $ingest_dir, $srv state is $state"
|
||||
|
||||
if ((wavs > 100)); then
|
||||
bashio::log.red "$(date) WARNING too many files in queue, pausing $srv"
|
||||
sudo systemctl stop "$srv"
|
||||
sudo systemctl restart birdnet_analysis
|
||||
if [ -s "$HOME/BirdNET-Pi/apprise.txt" ]; then apprisealert; fi
|
||||
elif [[ "$state" != "active" ]]; then
|
||||
bashio::log.yellow "$(date) INFO started $srv service"
|
||||
sudo systemctl start $srv
|
||||
sudo systemctl restart birdnet_analysis
|
||||
fi
|
||||
|
||||
((counter--))
|
||||
done
|
||||
18
battybirdnet-pi/rootfs/etc/cont-finish.d/savestreamdata.sh
Normal file
18
battybirdnet-pi/rootfs/etc/cont-finish.d/savestreamdata.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
if [ -d "$HOME"/BirdSongs/StreamData ]; then
|
||||
bashio::log.fatal "Container stopping, saving temporary files"
|
||||
|
||||
# Stop the services in parallel
|
||||
systemctl stop birdnet_analysis &
|
||||
systemctl stop birdnet_recording
|
||||
|
||||
# Check if there are files in StreamData and move them to /data/StreamData
|
||||
mkdir -p /data/StreamData
|
||||
if [ "$(ls -A "$HOME"/BirdSongs/StreamData)" ]; then
|
||||
mv -v "$HOME"/BirdSongs/StreamData/* /data/StreamData/
|
||||
fi
|
||||
|
||||
bashio::log.fatal "... files safe, allowing container to stop"
|
||||
fi
|
||||
87
battybirdnet-pi/rootfs/etc/cont-init.d/01-structure.sh
Normal file
87
battybirdnet-pi/rootfs/etc/cont-init.d/01-structure.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
###############
|
||||
# SET /CONFIG #
|
||||
###############
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Ensuring the file structure is correct :"
|
||||
|
||||
# Define structure
|
||||
echo "... creating default files"
|
||||
touch /config/include_species_list.txt # Should be null
|
||||
for files in apprise.txt exclude_species_list.txt IdentifiedSoFar.txt disk_check_exclude.txt confirmed_species_list.txt blacklisted_images.txt; do
|
||||
if [ ! -f /config/"$files" ]; then
|
||||
echo "" > /config/"$files"
|
||||
fi
|
||||
done
|
||||
|
||||
# Get BirdSongs folder locations
|
||||
BIRDSONGS_FOLDER="/config/BirdSongs"
|
||||
if bashio::config.has_value "BIRDSONGS_FOLDER"; then
|
||||
BIRDSONGS_FOLDER_OPTION="$(bashio::config "BIRDSONGS_FOLDER")"
|
||||
echo "... BIRDSONGS_FOLDER set to $BIRDSONGS_FOLDER_OPTION"
|
||||
mkdir -p "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be created"
|
||||
chown -R pi:pi "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be given permissions for 1000:1000"
|
||||
if [ -d "$BIRDSONGS_FOLDER_OPTION" ] && [ "$(stat -c '%u:%g' "$BIRDSONGS_FOLDER_OPTION")" == "1000:1000" ]; then
|
||||
BIRDSONGS_FOLDER="$BIRDSONGS_FOLDER_OPTION"
|
||||
else
|
||||
bashio::log.yellow "BIRDSONGS_FOLDER reverted to /config/BirdSongs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create BirdSongs folder
|
||||
echo "... creating default folders ; it is highly recommended to store those on a ssd"
|
||||
mkdir -p "$BIRDSONGS_FOLDER"/By_Date
|
||||
mkdir -p "$BIRDSONGS_FOLDER"/Charts
|
||||
|
||||
# If tmpfs is installed, use it
|
||||
if df -T /tmp | grep -q "tmpfs"; then
|
||||
echo "... tmpfs detected, using it for StreamData and Processed to reduce disk wear"
|
||||
mkdir -p /tmp/StreamData
|
||||
mkdir -p /tmp/Processed
|
||||
rm -r "$HOME"/BirdSongs/StreamData
|
||||
rm -r "$HOME"/BirdSongs/Processed
|
||||
sudo -u pi ln -fs /tmp/StreamData "$HOME"/BirdSongs/StreamData
|
||||
sudo -u pi ln -fs /tmp/Processed "$HOME"/BirdSongs/Processed
|
||||
fi
|
||||
|
||||
# Permissions for created files and folders
|
||||
echo "... set permissions to user pi"
|
||||
chown -R pi:pi /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
chmod -R 755 /config /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
|
||||
# Save default birdnet.conf to perform sanity check
|
||||
cp "$HOME"/BirdNET-Pi/birdnet.conf "$HOME"/BirdNET-Pi/birdnet.bak
|
||||
|
||||
# Symlink files
|
||||
echo "... creating symlink"
|
||||
for files in "$HOME/BirdNET-Pi/birdnet.conf" "$HOME/BirdNET-Pi/blacklisted_images.txt" "$HOME/BirdNET-Pi/scripts/birds.db" "$HOME/BirdNET-Pi/BirdDB.txt" "$HOME/BirdNET-Pi/scripts/disk_check_exclude.txt" "$HOME/BirdNET-Pi/apprise.txt" "$HOME/BirdNET-Pi/exclude_species_list.txt" "$HOME/BirdNET-Pi/include_species_list.txt" "$HOME/BirdNET-Pi/IdentifiedSoFar.txt" "$HOME/BirdNET-Pi/confirmed_species_list.txt"; do
|
||||
filename="${files##*/}"
|
||||
if [ ! -f /config/"$filename" ]; then
|
||||
if [ -f "$files" ]; then
|
||||
echo "... copying $filename" && sudo -u pi mv "$files" /config/
|
||||
else
|
||||
touch /config/"$filename"
|
||||
fi
|
||||
fi
|
||||
if [ -e "$files" ]; then rm "$files"; fi
|
||||
sudo -u pi ln -fs /config/"$filename" "$files" || bashio::log.fatal "Symlink creation failed for $filename"
|
||||
sudo -u pi ln -fs /config/"$filename" /etc/birdnet/"$filename" || bashio::log.fatal "Symlink creation failed for $filename"
|
||||
done
|
||||
|
||||
# Symlink folders
|
||||
for folders in By_Date Charts; do
|
||||
echo "... creating symlink for $BIRDSONGS_FOLDER/$folders"
|
||||
rm -r "$HOME/BirdSongs/Extracted/${folders:?}"
|
||||
sudo -u pi ln -fs "$BIRDSONGS_FOLDER"/"$folders" "$HOME/BirdSongs/Extracted/$folders"
|
||||
done
|
||||
|
||||
# Permissions for created files and folders
|
||||
echo "... check permissions"
|
||||
chmod -R 755 /config/*
|
||||
chmod 777 /config
|
||||
|
||||
echo " "
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Check if there are files in "$HOME"/BirdSongs/StreamData and move them to /data/StreamData
|
||||
if [ -d /data/StreamData ] && [ "$(ls -A /data/StreamData/)" ]; then
|
||||
|
||||
bashio::log.warning "Container was stopped while files were still being analysed, restoring them"
|
||||
|
||||
# Copy files
|
||||
if [ "$(ls -A /data/StreamData)" ]; then
|
||||
mv -v /data/StreamData/* "$HOME"/BirdSongs/StreamData/
|
||||
fi
|
||||
echo "... done"
|
||||
echo ""
|
||||
|
||||
# Setting permissions
|
||||
chown -R pi:pi "$HOME"/BirdSongs
|
||||
chmod -R 755 "$HOME"/BirdSongs
|
||||
|
||||
# Cleaning folder
|
||||
rm -r /data/StreamData
|
||||
|
||||
fi
|
||||
|
||||
54
battybirdnet-pi/rootfs/etc/cont-init.d/31-checks.sh
Normal file
54
battybirdnet-pi/rootfs/etc/cont-init.d/31-checks.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
######################
|
||||
# CHECK BIRDNET.CONF #
|
||||
######################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Checking your birndet.conf file integrity"
|
||||
|
||||
# Set variables
|
||||
configcurrent="$HOME"/BirdNET-Pi/birdnet.conf
|
||||
configtemplate="$HOME"/BirdNET-Pi/birdnet.bak
|
||||
|
||||
# Extract variable names from config template and read each one
|
||||
grep -o '^[^#=]*=' "$configtemplate" | sed 's/=//' | while read -r var; do
|
||||
# Check if the variable is in configcurrent, if not, append it
|
||||
if ! grep -q "^$var=" "$configcurrent"; then
|
||||
# At which line was the variable in the initial file
|
||||
bashio::log.yellow "...$var was missing from your birdnet.conf file, it was re-added"
|
||||
grep "^$var=" "$configtemplate" >> "$configcurrent"
|
||||
fi
|
||||
# Check for duplicates
|
||||
if [ "$(grep -c "^$var=" "$configcurrent")" -gt 1 ]; then
|
||||
bashio::log.error "Duplicate variable $var found in $configcurrent, all were commented out expect for the first one"
|
||||
awk -v var="$var" '{ if ($0 ~ "^[[:blank:]]*"var && c++ > 0) print "#" $0; else print $0; }' "$configcurrent" > temp && mv temp "$configcurrent"
|
||||
fi
|
||||
done
|
||||
|
||||
################
|
||||
# CHECK AMIXER #
|
||||
################
|
||||
|
||||
# If default capture is set at 0%, increase it to 50%
|
||||
# current_volume="$(amixer sget Capture | grep -oP '\[\d+%]' | tr -d '[]%' | head -1)" 2>/dev/null || true
|
||||
# current_volume="${current_volume:-100}"
|
||||
|
||||
# Set the default microphone volume to 50% if it's currently at 0%
|
||||
# if [[ "$current_volume" -eq 0 ]]; then
|
||||
# amixer sset Capture 70%
|
||||
# bashio::log.warning "Microphone was off, volume set to 70%."
|
||||
# fi
|
||||
|
||||
##############
|
||||
# CHECK PORT #
|
||||
##############
|
||||
|
||||
if [[ "$(bashio::addon.port "80")" == 3000 ]]; then
|
||||
bashio::log.fatal "This is crazy but your port is set to 3000 and streamlit doesn't accept this port! You need to change it from the addon options and restart. Thanks"
|
||||
sleep infinity
|
||||
fi
|
||||
|
||||
echo " "
|
||||
47
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal file
47
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
if bashio::services.available 'mqtt' && ! bashio::config.true 'MQTT_DISABLED' ; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT addon is active on your system! battybirdnet-pi is now automatically configured to send its ouptut to MQTT"
|
||||
bashio::log.blue "MQTT user : $(bashio::services "mqtt" "username")"
|
||||
bashio::log.blue "MQTT password : $(bashio::services "mqtt" "password")"
|
||||
bashio::log.blue "MQTT broker : tcp://$(bashio::services "mqtt" "host"):$(bashio::services "mqtt" "port")"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply MQTT settings
|
||||
sed -i "s|%%mqtt_server%%|$(bashio::services "mqtt" "host")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_port%%|$(bashio::services "mqtt" "port")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_user%%|$(bashio::services "mqtt" "username")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_pass%%|$(bashio::services "mqtt" "password")|g" /helpers/birdnet_to_mqtt.py
|
||||
|
||||
# Copy script
|
||||
cp /helpers/birdnet_to_mqtt.py /usr/bin/birdnet_to_mqtt.py
|
||||
cp /helpers/birdnet_to_mqtt.sh /custom-services.d
|
||||
chmod 777 /usr/bin/birdnet_to_mqtt.py
|
||||
chmod 777 /custom-services.d/birdnet_to_mqtt.sh
|
||||
elif bashio::config.has_value "MQTT_HOST_manual" && bashio::config.has_value "MQTT_PORT_manual"; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT is manually configured in the addon options"
|
||||
bashio::log.blue "battybirdnet-pi is now automatically configured to send its ouptut to MQTT"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply MQTT settings
|
||||
sed -i "s|%%mqtt_server%%|$(bashio::config "MQTT_HOST_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_port%%|$(bashio::config "MQTT_PORT_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_user%%|$(bashio::config "MQTT_USER_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_pass%%|$(bashio::config "MQTT_PASSWORD_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
|
||||
# Copy script
|
||||
cp /helpers/birdnet_to_mqtt.py /usr/bin/birdnet_to_mqtt.py
|
||||
cp /helpers/birdnet_to_mqtt.sh /custom-services.d
|
||||
chmod +x /usr/bin/birdnet_to_mqtt.py
|
||||
chmod +x /custom-services.d/birdnet_to_mqtt.sh
|
||||
fi
|
||||
127
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal file
127
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
################
|
||||
# ADD FEATURES #
|
||||
################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adding optional features"
|
||||
|
||||
# Denoiser
|
||||
#if bashio::config.true "DENOISER_ANALYSIS_ENABLED"; then
|
||||
# sed -i "s|ar 48000|ar 48000 -af \"arnndn=m=sample.rnnn\"|g" "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh
|
||||
# sed -i "s|ar 48000|ar 48000 -af afftdn=nr=30:nt=w:om=o|g" "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh
|
||||
#fi
|
||||
|
||||
# Add species conversion system
|
||||
###############################
|
||||
if bashio::config.true "SPECIES_CONVERTER_ENABLED"; then
|
||||
echo "... adding feature of SPECIES_CONVERTER, a new tab is added to your Tools"
|
||||
touch /config/convert_species_list.txt
|
||||
chown pi:pi /config/convert_species_list.txt
|
||||
sudo -u pi ln -fs /config/convert_species_list.txt "$HOME"/BirdNET-Pi/
|
||||
sudo -u pi ln -fs /config/convert_species_list.txt "$HOME"/BirdNET-Pi/scripts/
|
||||
# Not useful
|
||||
sed -i "/exclude_species_list.txt/a sudo -u pi ln -fs /config/convert_species_list.txt $HOME/BirdNET-Pi/scripts/" "$HOME"/BirdNET-Pi/scripts/clear_all_data.sh
|
||||
sed -i "/exclude_species_list.txt/a sudo -u pi ln -fs /config/convert_species_list.txt $HOME/BirdNET-Pi/scripts/" "$HOME"/BirdNET-Pi/scripts/install_services.sh
|
||||
# Modify views.php if not already done
|
||||
if ! grep -q "Converted" "$HOME"/BirdNET-Pi/homepage/views.php; then
|
||||
# Add button
|
||||
# shellcheck disable=SC2016
|
||||
sed -i '/Excluded Species List/a\ <button type=\\"submit\\" name=\\"view\\" value=\\"Converted\\" form=\\"views\\">Converted Species List</button>' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Flag to indicate whether we've found the target line
|
||||
found_target=false
|
||||
# Read the original file line by line
|
||||
while IFS= read -r line; do
|
||||
if [[ $line == *"if(\$_GET['view'] == \"File\"){"* ]]; then
|
||||
found_target=true
|
||||
fi
|
||||
if $found_target; then
|
||||
echo "$line" >> "$HOME"/BirdNET-Pi/homepage/views.php.temp
|
||||
fi
|
||||
done < "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Remove the extracted lines from the original file
|
||||
# shellcheck disable=SC2016
|
||||
sed -i '/if(\$_GET\['\''view'\''\] == "File"){/,$d' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Add new text
|
||||
cat "/helpers/views.add" >> "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
cat "$HOME"/BirdNET-Pi/homepage/views.php.temp >> "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Clean up: Remove the temporary file
|
||||
rm "$HOME"/BirdNET-Pi/homepage/views.php.temp
|
||||
fi
|
||||
|
||||
# Add the converter script
|
||||
if [ ! -f "$HOME"/BirdNET-Pi/scripts/convert_list.php ]; then
|
||||
mv -f /helpers/convert_list.php "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
chown pi:pi "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
chmod 664 "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
fi
|
||||
|
||||
# Change server
|
||||
if ! grep -q "converted_entry" "$HOME"/BirdNET-Pi/scripts/server.py; then
|
||||
sed -i "/INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST/c INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST = (None, None, None, None, None)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/global INCLUDE_LIST, EXCLUDE_LIST/c\ global INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST, CONVERT_DICT" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/exclude_species_list.txt/a\ CONVERT_DICT = {row.split(';')[0]: row.split(';')[1] for row in CONVERT_LIST}" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/exclude_species_list.txt/a\ CONVERT_LIST = loadCustomSpeciesList(os.path.expanduser(\"~/BirdNET-Pi/convert_species_list.txt\"))" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s|entry\[0\]|converted_entry|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s|if converted_entry in|if entry\[0\] in|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ converted_entry = entry[0]" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ else :" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ log.info('WARNING : ' + entry[0] + ' converted to ' + converted_entry)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ converted_entry = CONVERT_DICT.get(entry[0], entry[0])" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ if entry[0] in CONVERT_DICT:" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ if entry[1] >= conf.getfloat('CONFIDENCE'):" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/converted_entry in INCLUDE_LIST or len(INCLUDE_LIST)/c\ if ((converted_entry in INCLUDE_LIST or len(INCLUDE_LIST) == 0)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s| d = Detection| d = Detection|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s| confident_detections| confident_detections|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
fi
|
||||
fi || true
|
||||
|
||||
# Enable the Processed folder
|
||||
#############################
|
||||
|
||||
if bashio::config.true "PROCESSED_FOLDER_ENABLED" && ! grep -q "processed_size" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py; then
|
||||
echo "... Enabling the Processed folder : the last 15 wav files will be stored there"
|
||||
# Adapt config.php
|
||||
sed -i "/GET\[\"info_site\"\]/a\ \$processed_size = \$_GET\[\"processed_size\"\];" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\$contents = file_get_contents/a\ \$contents = preg_replace\(\"/PROCESSED_SIZE=\.\*/\", \"PROCESSED_SIZE=\$processed_size\", \$contents\);" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <table class=\"settingstable\"><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <h2>Processed folder management </h2>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <label for=\"processed_size\">Amount of files to keep after analysis :</label>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <input name=\"processed_size\" type=\"number\" style=\"width:6em;\" max=\"90\" min=\"0\" step=\"1\" value=\"<\?php print(\$config\['PROCESSED_SIZE'\]);?>\"/>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i </td></tr><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i Processed is the directory where the formerly 'Analyzed' files are moved after extractions, mostly for troubleshooting purposes.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i This value defines the maximum amount of files that are kept before replacement with new files.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i </td></tr></table>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i\ <br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
# Adapt birdnet_analysis.py - move_to_processed
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ os.remove(files.pop(0))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ while len(files) > processed_size:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ files.sort(key=os.path.getmtime)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ files = glob.glob(os.path.join(processed_dir, '*'))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ os.rename(file_name, os.path.join(processed_dir, os.path.basename(file_name)))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ processed_dir = os.path.join(get_settings()['RECS_DIR'], 'Processed')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\def move_to_processed(file_name, processed_size):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Adapt birdnet_analysis.py - get_processed_size
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ return 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ except (ValueError, TypeError):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ return processed_size if isinstance(processed_size, int) else 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ processed_size = get_settings().getint('PROCESSED_SIZE')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ try:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\def get_processed_size():" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Modify calls
|
||||
sed -i "/from subprocess import CalledProcessError/a\import glob" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/from subprocess import CalledProcessError/a\import time" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Modify main code
|
||||
sed -i "/os.remove(file.file_name)/i\ processed_size = get_processed_size()" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ if processed_size > 0:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ move_to_processed(file.file_name, processed_size)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ else:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/c\ os.remove(file.file_name)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
fi || true
|
||||
|
||||
echo " "
|
||||
66
battybirdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh
Normal file
66
battybirdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
################
|
||||
# MODIFY WEBUI #
|
||||
################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adapting webui"
|
||||
|
||||
# Remove services tab
|
||||
echo "... removing System Controls from webui as should be used from HA"
|
||||
sed -i '/>System Controls/d' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
|
||||
# Remove services tab
|
||||
echo "... removing Ram drive from webui as it is handled from HA"
|
||||
sed -i '/Ram drive/{n;s/center"/center" style="display: none;"/;}' "$HOME"/BirdNET-Pi/scripts/service_controls.php
|
||||
sed -i '/Ram drive/d' "$HOME"/BirdNET-Pi/scripts/service_controls.php
|
||||
|
||||
# Correct services to start as user pi
|
||||
echo "... correct services to start as pi"
|
||||
for file in $(find "$HOME"/BirdNET-Pi/templates/birdnet*.service -print0 | xargs -0 basename -a) livestream.service chart_viewer.service chart_viewer.service spectrogram_viewer.service; do
|
||||
if [[ "$file" != "birdnet_log.service" ]]; then
|
||||
sed -i "s|ExecStart=|ExecStart=/usr/bin/sudo -u pi |g" "$HOME/BirdNET-Pi/templates/$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Send services log to container logs
|
||||
echo "... send services log to container logs"
|
||||
for file in $(find "$HOME"/BirdNET-Pi/templates/birdnet*.service -print0 | xargs -0 basename -a) livestream.service chart_viewer.service chart_viewer.service spectrogram_viewer.service; do
|
||||
sed -i "/Service/a StandardError=append:/proc/1/fd/1" "$HOME/BirdNET-Pi/templates/$file"
|
||||
sed -i "/Service/a StandardOutput=append:/proc/1/fd/1" "$HOME/BirdNET-Pi/templates/$file"
|
||||
done
|
||||
|
||||
# Avoid preselection in include and exclude lists
|
||||
echo "... avoid preselecting options in include and exclude lists"
|
||||
sed -i "s|option selected|option disabled|g" "$HOME"/BirdNET-Pi/scripts/include_list.php
|
||||
sed -i "s|option selected|option disabled|g" "$HOME"/BirdNET-Pi/scripts/exclude_list.php
|
||||
|
||||
# Correct log services to show /proc/1/fd/1
|
||||
echo "... show container logs in /logs"
|
||||
sed -i "/User=pi/d" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
sed -i "s|birdnet_log.sh|cat /proc/1/fd/1|g" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
|
||||
# Make sure config is correctly formatted.
|
||||
echo "... caddyfile modifications"
|
||||
#Correct instructions
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
#Change port to leave 80 free for certificate requests
|
||||
sed -i "s|http://|http://:8081|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://|http://:8081|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
#Remove default file that blocks 80
|
||||
if [ -f /etc/caddy/Caddyfile.original ]; then rm /etc/caddy/Caddyfile.original; fi
|
||||
|
||||
# Improve webui paths to facilitate ingress
|
||||
echo "... correcting webui paths"
|
||||
sed -i "s|/stats|/stats/|g" "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
sed -i "s|/log|/log/|g" "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
|
||||
# If port 80 is enabled, make sure it is still 80
|
||||
if [ -n "$(bashio::addon.port 80)" ] && [ "$(bashio::addon.port 80)" != 80 ]; then
|
||||
bashio::log.fatal "The port 80 is enabled, but should still be 80 if you want the automatic ssl certificates generation to work"
|
||||
fi
|
||||
|
||||
echo " "
|
||||
37
battybirdnet-pi/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Normal file
37
battybirdnet-pi/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# NGINX SETTING #
|
||||
#################
|
||||
|
||||
declare ingress_interface
|
||||
declare ingress_port
|
||||
declare ingress_entry
|
||||
|
||||
# Variables
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
ingress_entry=$(bashio::addon.ingress_entry)
|
||||
|
||||
# Quits if ingress not active
|
||||
if [ -z "$ingress_entry" ]; then exit 0; fi
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adapting for ingress"
|
||||
echo "... setting up nginx"
|
||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf
|
||||
|
||||
echo "... ensuring restricted area access"
|
||||
echo "${ingress_entry}" > /ingress_url
|
||||
sed -i "/function is_authenticated/a if (strpos(\$_SERVER['HTTP_REFERER'], '/api/hassio_ingress') !== false && strpos(\$_SERVER['HTTP_REFERER'], trim(file_get_contents('/ingress_url'))) !== false) { \$ret = true; return \$ret; }" "$HOME"/BirdNET-Pi/scripts/common.php
|
||||
|
||||
echo "... adapt Caddyfile for ingress"
|
||||
chmod +x /helpers/caddy_ingress.sh
|
||||
/./helpers/caddy_ingress.sh
|
||||
sed -i "/sudo caddy fmt --overwrite/i /./helpers/caddy_ingress.sh" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
|
||||
echo " "
|
||||
20
battybirdnet-pi/rootfs/etc/cont-init.d/92-ssl.sh
Normal file
20
battybirdnet-pi/rootfs/etc/cont-init.d/92-ssl.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
###############
|
||||
# SSL SETTING #
|
||||
###############
|
||||
|
||||
if bashio::config.true 'ssl'; then
|
||||
bashio::log.info "Ssl is enabled using addon options, setting up nginx"
|
||||
bashio::config.require.ssl
|
||||
certfile=$(bashio::config 'certfile')
|
||||
keyfile=$(bashio::config 'keyfile')
|
||||
sed -i "2a\ tls /ssl/${certfile} /ssl/${keyfile}" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://:8081|https://:8081|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://:8081|https://:8081|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
sed -i "/https:/a tls /ssl/${certfile} /ssl/${keyfile}" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
fi
|
||||
|
||||
echo " "
|
||||
72
battybirdnet-pi/rootfs/etc/cont-init.d/99-run.sh
Normal file
72
battybirdnet-pi/rootfs/etc/cont-init.d/99-run.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##############
|
||||
# SET SYSTEM #
|
||||
##############
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Setting password for the user pi"
|
||||
echo "pi:$(bashio::config "pi_password")" | sudo chpasswd
|
||||
echo "... done"
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Starting system services"
|
||||
|
||||
# Set TZ
|
||||
if bashio::config.has_value 'TZ'; then
|
||||
TIMEZONE=$(bashio::config 'TZ')
|
||||
echo "... setting timezone to $TIMEZONE"
|
||||
ln -snf /usr/share/zoneinfo/"$TIMEZONE" /etc/localtime
|
||||
echo "$TIMEZONE" >/etc/timezone
|
||||
fi || (bashio::log.fatal "Error : $TIMEZONE not found. Here is a list of valid timezones : https://manpages.ubuntu.com/manpages/focal/man3/DateTime::TimeZone::Catalog.3pm.html")
|
||||
|
||||
# Correcting systemctl
|
||||
echo "... correcting systemctl"
|
||||
mv /helpers/systemctl3.py /bin/systemctl
|
||||
chmod a+x /bin/systemctl
|
||||
|
||||
# Correcting systemctl
|
||||
echo "... correcting datetimectl"
|
||||
mv /helpers/timedatectl /usr/bin/timedatectl
|
||||
chmod a+x /usr/bin/timedatectl
|
||||
|
||||
# Correct language labels
|
||||
export "$(grep "^DATABASE_LANG" /config/birdnet.conf)"
|
||||
# Saving default of en
|
||||
cp "$HOME"/BirdNET-Pi/model/labels.txt "$HOME"/BirdNET-Pi/model/labels.bak
|
||||
# Adapt to new language
|
||||
echo "... adapting labels according to birdnet.conf file to $DATABASE_LANG"
|
||||
/."$HOME"/BirdNET-Pi/scripts/install_language_label_nm.sh -l "$DATABASE_LANG"
|
||||
|
||||
echo "... starting cron"
|
||||
systemctl start cron
|
||||
|
||||
# Starting dbus
|
||||
echo "... starting dbus"
|
||||
service dbus start
|
||||
|
||||
# Starting journald
|
||||
# echo "... starting journald"
|
||||
# systemctl start systemd-journald
|
||||
|
||||
# Starting services
|
||||
echo ""
|
||||
bashio::log.info "Starting battybirdnet-pi services"
|
||||
chmod +x "$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
"$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
|
||||
if bashio::config.true LIVESTREAM_BOOT_ENABLED; then
|
||||
echo "... starting livestream"
|
||||
sudo systemctl enable icecast2
|
||||
sudo systemctl start icecast2.service
|
||||
sudo systemctl enable --now livestream.service
|
||||
fi
|
||||
|
||||
# Correct the phpsysinfo for the correct gotty service
|
||||
gottyservice="$(pgrep -l "gotty" | awk '{print $NF}' | head -n 1)"
|
||||
echo "... using $gottyservice in phpsysinfo"
|
||||
sed -i "s/,gotty,/,${gottyservice:-gotty},/g" "$HOME"/BirdNET-Pi/templates/phpsysinfo.ini
|
||||
|
||||
echo " "
|
||||
96
battybirdnet-pi/rootfs/etc/nginx/includes/mime.types
Normal file
96
battybirdnet-pi/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
16
battybirdnet-pi/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
16
battybirdnet-pi/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
1
battybirdnet-pi/rootfs/etc/nginx/includes/resolver.conf
Normal file
1
battybirdnet-pi/rootfs/etc/nginx/includes/resolver.conf
Normal file
@@ -0,0 +1 @@
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
@@ -0,0 +1,6 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
@@ -0,0 +1,9 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
3
battybirdnet-pi/rootfs/etc/nginx/includes/upstream.conf
Normal file
3
battybirdnet-pi/rootfs/etc/nginx/includes/upstream.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:80;
|
||||
}
|
||||
78
battybirdnet-pi/rootfs/etc/nginx/nginx.conf
Normal file
78
battybirdnet-pi/rootfs/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
# Run nginx in foreground.
|
||||
daemon off;
|
||||
|
||||
# This is run inside Docker.
|
||||
user root;
|
||||
|
||||
# Pid storage location.
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
# Set number of worker processes.
|
||||
worker_processes auto;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Write error log to Hass.io add-on log.
|
||||
error_log /proc/1/fd/1 error;
|
||||
|
||||
# Load allowed environment vars
|
||||
env HASSIO_TOKEN;
|
||||
|
||||
# Load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Max num of simultaneous connections by a worker process.
|
||||
events {
|
||||
worker_connections 8192;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
# https://emby.media/community/index.php?/topic/93074-how-to-emby-with-nginx-with-windows-specific-tips-and-csp-options/
|
||||
server_names_hash_bucket_size 64;
|
||||
gzip_disable "msie6";
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1100;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_proxied any;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/js
|
||||
text/xml
|
||||
text/javascript
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/xml
|
||||
application/rss+xml
|
||||
image/svg+xml;
|
||||
proxy_connect_timeout 1h;
|
||||
|
||||
log_format hassio '[$time_local] $status '
|
||||
'$http_x_forwarded_for($remote_addr) '
|
||||
'$request ($http_user_agent)';
|
||||
|
||||
access_log /proc/1/fd/1 hassio;
|
||||
client_max_body_size 4G;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/resolver.conf;
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
47
battybirdnet-pi/rootfs/etc/nginx/servers/ingress.conf
Normal file
47
battybirdnet-pi/rootfs/etc/nginx/servers/ingress.conf
Normal file
@@ -0,0 +1,47 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
proxy_buffering off;
|
||||
auth_basic_user_file /home/pi/.htpasswd;
|
||||
|
||||
location /log {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location /stats {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location /terminal {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location / {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
|
||||
# Next three lines allow websockets
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Correct base_url
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once off;
|
||||
sub_filter_types *;
|
||||
sub_filter /spectrogram %%ingress_entry%%/spectrogram;
|
||||
sub_filter /By_Date/ %%ingress_entry%%/By_Date/;
|
||||
sub_filter /Charts/ %%ingress_entry%%/Charts/;
|
||||
sub_filter /todays %%ingress_entry%%/todays;
|
||||
sub_filter href=\"/ href=\"%%ingress_entry%%/;
|
||||
sub_filter src=\"/ src=\"%%ingress_entry%%/;
|
||||
sub_filter hx-get=\"/ hx-get=\"%%ingress_entry%%/;
|
||||
sub_filter action=\"/ action=\"%%ingress_entry%%/;
|
||||
}
|
||||
|
||||
}
|
||||
124
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.py
Normal file
124
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#! /usr/bin/env python3
|
||||
# birdnet_to_mqtt.py
|
||||
#
|
||||
# Adapted from : https://gist.github.com/deepcoder/c309087c456fc733435b47d83f4113ff
|
||||
# Adapted from : https://gist.github.com/JuanMeeske/08b839246a62ff38778f701fc1da5554
|
||||
#
|
||||
# monitor the records in the syslog file for info from the birdnet system on birds that it detects
|
||||
# publish this data to mqtt
|
||||
#
|
||||
|
||||
import time
|
||||
import re
|
||||
import dateparser
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import paho.mqtt.client as mqtt
|
||||
import subprocess
|
||||
|
||||
# Setup basic configuration for logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# this generator function monitors the requested file handle for new lines added at its end
|
||||
# the newly added line is returned by the function
|
||||
def file_row_generator(s):
|
||||
while True :
|
||||
line = s.readline()
|
||||
if not line:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
yield line
|
||||
|
||||
# mqtt server
|
||||
mqtt_server = "%%mqtt_server%%" # server for mqtt
|
||||
mqtt_user = "%%mqtt_user%%" # Replace with your MQTT username
|
||||
mqtt_pass = "%%mqtt_pass%%" # Replace with your MQTT password
|
||||
mqtt_port = %%mqtt_port%% # port for mqtt
|
||||
|
||||
# mqtt topic for bird heard above threshold will be published
|
||||
mqtt_topic_confident_birds = 'birdnet'
|
||||
|
||||
# url base for website that will be used to look up info about bird
|
||||
bird_lookup_url_base = 'http://en.wikipedia.org/wiki/'
|
||||
|
||||
# regular expression patters used to decode the records from birdnet
|
||||
re_high_clean = re.compile(r'(?<=^\[birdnet_analysis\]\[INFO\] ).*?(?=\.mp3$)')
|
||||
|
||||
syslog = open('/proc/1/fd/1', 'r')
|
||||
|
||||
def on_connect(client, userdata, flags, rc, properties=None):
|
||||
""" Callback for when the client receives a CONNACK response from the server. """
|
||||
if rc == 0:
|
||||
logging.info("Connected to MQTT Broker!")
|
||||
else:
|
||||
logging.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()
|
||||
|
||||
# Extract the array from the PHP file
|
||||
array_str = re.search(r'\$ebirds = \[(.*?)\];', data, re.DOTALL).group(1)
|
||||
|
||||
# Convert the PHP array to a Python dictionary
|
||||
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 the corresponding value for the given bird's scientific name
|
||||
return bird_dict.get(scientific_name)
|
||||
|
||||
# this little hack is to make each received record for the all birds section unique
|
||||
# the date and time that the log returns is only down to the 1 second accuracy, do
|
||||
# you can get multiple records with same date and time, this will make Home Assistant not
|
||||
# think there is a new reading so we add a incrementing tenth of second to each record received
|
||||
ts_noise = 0.0
|
||||
|
||||
#try :
|
||||
# connect to MQTT server
|
||||
mqttc = mqtt.Client('birdnet_mqtt') # Create instance of client with client ID
|
||||
mqttc.username_pw_set(mqtt_user, mqtt_pass) # Use credentials
|
||||
mqttc.connect(mqtt_server, mqtt_port) # Connect to (broker, port, keepalive-time)
|
||||
mqttc.on_connect = on_connect
|
||||
mqttc.loop_start()
|
||||
|
||||
# call the generator function and process each line that is returned
|
||||
for row in file_row_generator(syslog):
|
||||
# bird found above confidence level found, process it
|
||||
if re_high_clean.search(row) :
|
||||
|
||||
# this slacker regular expression work, extracts the data about the bird found from the log line
|
||||
# I do the parse in two passes, because I did not know the re to do it in one!
|
||||
|
||||
raw_high_bird = re.search(re_high_clean, row)
|
||||
raw_high_bird = raw_high_bird.group(0)
|
||||
|
||||
# the fields we want are separated by semicolons, so split
|
||||
high_bird_fields = raw_high_bird.split(';')
|
||||
|
||||
# build a structure in python that will be converted to json
|
||||
bird = {}
|
||||
|
||||
# human time in this record is in two fields, date and time. They are human format
|
||||
# combine them together separated by a space and they turn the human data into a python
|
||||
# timestamp
|
||||
raw_ts = high_bird_fields[0] + ' ' + high_bird_fields[1]
|
||||
|
||||
#bird['ts'] = str(datetime.datetime.timestamp(dateparser.parse(raw_ts)))
|
||||
bird['Date'] = high_bird_fields[0]
|
||||
bird['Time'] = high_bird_fields[1]
|
||||
bird['ScientificName'] = high_bird_fields[2]
|
||||
bird['CommonName'] = high_bird_fields[3]
|
||||
bird['Confidence'] = high_bird_fields[4]
|
||||
bird['SpeciesCode'] = get_bird_code(high_bird_fields[2])
|
||||
bird['ClipName'] = high_bird_fields[11]
|
||||
|
||||
# build a url from scientific name of bird that can be used to lookup info about bird
|
||||
bird['url'] = bird_lookup_url_base + high_bird_fields[2].replace(' ', '_')
|
||||
|
||||
# convert to json string we can sent to mqtt
|
||||
json_bird = json.dumps(bird)
|
||||
|
||||
print('Posted to MQTT : ok')
|
||||
|
||||
mqttc.publish(mqtt_topic_confident_birds, json_bird, 1)
|
||||
5
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.sh
Normal file
5
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
echo "Starting service: mqtt automated publish"
|
||||
"$PYTHON_VIRTUAL_ENV" /usr/bin/birdnet_to_mqtt.py &>/proc/1/fd/1
|
||||
24
battybirdnet-pi/rootfs/helpers/caddy_ingress.sh
Normal file
24
battybirdnet-pi/rootfs/helpers/caddy_ingress.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Get values
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
# Create ingress configuration for Caddyfile
|
||||
cat << EOF >> /etc/caddy/Caddyfile
|
||||
:8082 {
|
||||
root * ${EXTRACTED}
|
||||
file_server browse
|
||||
handle /By_Date/* {
|
||||
file_server browse
|
||||
}
|
||||
handle /Charts/* {
|
||||
file_server browse
|
||||
}
|
||||
reverse_proxy /stream localhost:8000
|
||||
php_fastcgi unix//run/php/php-fpm.sock
|
||||
reverse_proxy /log* localhost:8080
|
||||
reverse_proxy /stats* localhost:8501
|
||||
reverse_proxy /terminal* localhost:8888
|
||||
}
|
||||
EOF
|
||||
116
battybirdnet-pi/rootfs/helpers/convert_list.php
Normal file
116
battybirdnet-pi/rootfs/helpers/convert_list.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<p><strong>This tool will allow to convert on-the-fly species to compensate for model errors. It SHOULD NOT BE USED except if you know what you are doing, instead the model errors should be reported to the owner. However, it is still convenient for systematic biases that are confirmed through careful listening of samples, while waiting for the models to be updated.</strong></p>
|
||||
|
||||
<div class="customlabels column1">
|
||||
<form action="" method="GET" id="add">
|
||||
<input type="hidden" id="species" name="species">
|
||||
<h3>Specie to convert from :</h3>
|
||||
<!-- Input box to filter options in the first table -->
|
||||
<input type="text" id="species1Search" onkeyup="filterOptions('species1')" placeholder="Search for species...">
|
||||
<select name="species1" id="species1" size="25">
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors',1);
|
||||
|
||||
$filename = './scripts/labels.txt';
|
||||
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
|
||||
|
||||
foreach($eachline as $lines){echo
|
||||
"<option value=\"".$lines."\">$lines</option>";}
|
||||
?>
|
||||
</select>
|
||||
<br><br> <!-- Added a space between the two tables -->
|
||||
<h3>Specie to convert to :</h3>
|
||||
<!-- Input box to filter options in the second table -->
|
||||
<input type="text" id="species2Search" onkeyup="filterOptions('species2')" placeholder="Search for species...">
|
||||
<select name="species2" id="species2" size="25">
|
||||
<?php
|
||||
foreach($eachline as $lines){echo
|
||||
"<option value=\"".$lines."\">$lines</option>";}
|
||||
?>
|
||||
</select>
|
||||
<input type="hidden" name="add" value="add">
|
||||
</form>
|
||||
<div class="customlabels smaller">
|
||||
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="customlabels column2">
|
||||
<table><td>
|
||||
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
|
||||
<br><br>
|
||||
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
|
||||
</td></table>
|
||||
</div>
|
||||
|
||||
<div class="customlabels column3" style="margin-top: 0;"> <!-- Removed the blank space above the table -->
|
||||
<form action="" method="GET" id="del">
|
||||
<h3>Converted Species List</h3>
|
||||
<select name="species[]" id="value2" multiple size="25">
|
||||
<?php
|
||||
$filename = './scripts/convert_species_list.txt'; // Changed the file path
|
||||
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
|
||||
foreach($eachline as $lines){
|
||||
echo
|
||||
"<option value=\"".$lines."\">$lines</option>";
|
||||
}?>
|
||||
</select>
|
||||
<input type="hidden" name="del" value="del">
|
||||
</form>
|
||||
<div class="customlabels smaller">
|
||||
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="hiddenSpecies" name="hiddenSpecies">
|
||||
|
||||
<script>
|
||||
document.getElementById("add").addEventListener("submit", function(event) {
|
||||
var speciesSelect1 = document.getElementById("species1");
|
||||
var speciesSelect2 = document.getElementById("species2");
|
||||
if (speciesSelect1.selectedIndex < 0 || speciesSelect2.selectedIndex < 0) {
|
||||
alert("Please select a species from both lists.");
|
||||
document.querySelector('.views').style.opacity = 1;
|
||||
event.preventDefault();
|
||||
} else {
|
||||
var selectedSpecies1 = speciesSelect1.options[speciesSelect1.selectedIndex].value;
|
||||
var selectedSpecies2 = speciesSelect2.options[speciesSelect2.selectedIndex].value;
|
||||
document.getElementById("species").value = selectedSpecies1 + ";" + selectedSpecies2;
|
||||
}
|
||||
});
|
||||
|
||||
// Store the original list of options in a variable
|
||||
var originalOptions = {};
|
||||
|
||||
// Function to filter options in a select element
|
||||
function filterOptions(id) {
|
||||
var input = document.getElementById(id + "Search");
|
||||
var filter = input.value.toUpperCase();
|
||||
var select = document.getElementById(id);
|
||||
var options = select.getElementsByTagName("option");
|
||||
|
||||
// If the original list of options for this select element hasn't been stored yet, store it
|
||||
if (!originalOptions[id]) {
|
||||
originalOptions[id] = Array.from(options).map(option => option.value);
|
||||
}
|
||||
|
||||
// Clear the select element
|
||||
while (select.firstChild) {
|
||||
select.removeChild(select.firstChild);
|
||||
}
|
||||
|
||||
// Populate the select element with the filtered labels
|
||||
originalOptions[id].forEach(label => {
|
||||
if (label.toUpperCase().indexOf(filter) > -1) {
|
||||
let option = document.createElement('option');
|
||||
option.value = label;
|
||||
option.text = label;
|
||||
select.appendChild(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
70
battybirdnet-pi/rootfs/helpers/spectral_analysis.py
Normal file
70
battybirdnet-pi/rootfs/helpers/spectral_analysis.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import numpy as np
|
||||
import scipy.io.wavfile as wavfile
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import glob
|
||||
import sys # Import the sys module
|
||||
|
||||
from utils.helpers import get_settings
|
||||
|
||||
# Dependencies /usr/bin/pip install numpy scipy matplotlib
|
||||
|
||||
# Define the directory containing the WAV files
|
||||
conf = get_settings()
|
||||
input_directory = os.path.join(conf['RECS_DIR'], 'StreamData')
|
||||
output_directory = os.path.join(conf['RECS_DIR'], 'Extracted/Charts')
|
||||
|
||||
# Ensure the output directory exists
|
||||
if not os.path.exists(output_directory):
|
||||
os.makedirs(output_directory)
|
||||
|
||||
# Check if a command-line argument is provided
|
||||
if len(sys.argv) > 1:
|
||||
# If an argument is provided, use it as the file to analyze
|
||||
wav_files = [sys.argv[1]]
|
||||
else:
|
||||
# If no argument is provided, analyze all WAV files in the directory
|
||||
wav_files = glob.glob(os.path.join(input_directory, '*.wav'))
|
||||
|
||||
# Process each file
|
||||
for file_path in wav_files:
|
||||
# Load the WAV file
|
||||
sample_rate, audio_data = wavfile.read(file_path)
|
||||
|
||||
# If stereo, select only one channel
|
||||
if len(audio_data.shape) > 1:
|
||||
audio_data = audio_data[:, 0]
|
||||
|
||||
# Apply the Hamming window to the audio data
|
||||
hamming_window = np.hamming(len(audio_data))
|
||||
windowed_data = audio_data * hamming_window
|
||||
|
||||
# Compute the FFT of the windowed audio data
|
||||
audio_fft = np.fft.fft(windowed_data)
|
||||
audio_fft = np.abs(audio_fft)
|
||||
|
||||
# Compute the frequencies associated with the FFT values
|
||||
frequencies = np.fft.fftfreq(len(windowed_data), d=1/sample_rate)
|
||||
|
||||
# Select the range of interest
|
||||
idx = np.where((frequencies >= 150) & (frequencies <= 15000))
|
||||
|
||||
# Calculate the saturation threshold based on the bit depth
|
||||
bit_depth = audio_data.dtype.itemsize * 8
|
||||
max_amplitude = 2**(bit_depth - 1) - 1
|
||||
saturation_threshold = 0.8 * max_amplitude
|
||||
|
||||
# Plot the spectrum with a logarithmic Y-axis
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.semilogy(frequencies[idx], audio_fft[idx], label='Spectrum')
|
||||
plt.axhline(y=saturation_threshold, color='r', linestyle='--', label='Saturation Threshold')
|
||||
plt.xlabel("Frequency (Hz)")
|
||||
plt.ylabel("Amplitude (Logarithmic)")
|
||||
plt.title(f"Frequency Spectrum (150 - 15000 Hz) - {os.path.basename(file_path)}")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
|
||||
# Save the plot as a PNG file
|
||||
output_filename = os.path.basename(file_path).replace('.wav', '_spectrum.png')
|
||||
plt.savefig(os.path.join(output_directory, output_filename))
|
||||
plt.close() # Close the figure to free memory
|
||||
62
battybirdnet-pi/rootfs/helpers/spectral_analysis.sh
Normal file
62
battybirdnet-pi/rootfs/helpers/spectral_analysis.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# Performs the recording from the specified RTSP stream or soundcard
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
# Read the logging level from the configuration option
|
||||
LOGGING_LEVEL="${LogLevel_BirdnetRecordingService}"
|
||||
# If empty for some reason default to log level of error
|
||||
[ -z "$LOGGING_LEVEL" ] && LOGGING_LEVEL='error'
|
||||
# Additionally if we're at debug or info level then allow printing of script commands and variables
|
||||
if [ "$LOGGING_LEVEL" == "info" ] || [ "$LOGGING_LEVEL" == "debug" ];then
|
||||
# Enable printing of commands/variables etc to terminal for debugging
|
||||
set -x
|
||||
fi
|
||||
|
||||
[ -z "$RECORDING_LENGTH" ] && RECORDING_LENGTH=15
|
||||
[ -d "$RECS_DIR"/StreamData ] || mkdir -p "$RECS_DIR"/StreamData
|
||||
|
||||
filename="Spectrum_$(date "+%Y-%m-%d_%H:%M").wav"
|
||||
|
||||
if [ ! -z "$RTSP_STREAM" ];then
|
||||
# Explode the RSPT steam setting into an array so we can count the number we have
|
||||
RTSP_STREAMS_EXPLODED_ARRAY=("${RTSP_STREAM//,/ }")
|
||||
|
||||
while true;do
|
||||
|
||||
# Initially start the count off at 1 - our very first stream
|
||||
RTSP_STREAMS_STARTED_COUNT=1
|
||||
FFMPEG_PARAMS=""
|
||||
|
||||
# Loop over the streams
|
||||
for i in "${RTSP_STREAMS_EXPLODED_ARRAY[@]}"
|
||||
do
|
||||
# Map id used to map input to output (first stream being 0), this is 0 based in ffmpeg so decrement our counter (which is more human readable) by 1
|
||||
MAP_ID="$((RTSP_STREAMS_STARTED_COUNT-1))"
|
||||
# Build up the parameters to process the RSTP stream, including mapping for the output
|
||||
FFMPEG_PARAMS+="-vn -thread_queue_size 512 -i ${i} -map ${MAP_ID}:a:0 -t ${RECORDING_LENGTH} -acodec pcm_s16le -ac 2 -ar 48000 file:${RECS_DIR}/StreamData/$filename "
|
||||
# Increment counter
|
||||
((RTSP_STREAMS_STARTED_COUNT += 1))
|
||||
done
|
||||
|
||||
# Make sure were passing something valid to ffmpeg, ffmpeg will run interactive and control our loop by waiting ${RECORDING_LENGTH} between loops because it will stop once that much has been recorded
|
||||
if [ -n "$FFMPEG_PARAMS" ];then
|
||||
ffmpeg -hide_banner -loglevel "$LOGGING_LEVEL" -nostdin "$FFMPEG_PARAMS"
|
||||
fi
|
||||
|
||||
done
|
||||
else
|
||||
if pgrep arecord &> /dev/null ;then
|
||||
echo "Recording"
|
||||
else
|
||||
if [ -z "${REC_CARD}" ];then
|
||||
arecord -f S16_LE -c"${CHANNELS}" -r48000 -t wav --max-file-time "${RECORDING_LENGTH}"\
|
||||
--use-strftime "${RECS_DIR}"/StreamData/"$filename"
|
||||
else
|
||||
arecord -f S16_LE -c"${CHANNELS}" -r48000 -t wav --max-file-time "${RECORDING_LENGTH}"\
|
||||
-D "${REC_CARD}" --use-strftime "${RECS_DIR}"/StreamData/"$filename"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create the spectral analysis
|
||||
"$PYTHON_VIRTUAL_ENV" "$HOME"/BirdNET-Pi/scripts/spectral_analysis.py
|
||||
6851
battybirdnet-pi/rootfs/helpers/systemctl3.py
Normal file
6851
battybirdnet-pi/rootfs/helpers/systemctl3.py
Normal file
File diff suppressed because it is too large
Load Diff
72
battybirdnet-pi/rootfs/helpers/timedatectl
Normal file
72
battybirdnet-pi/rootfs/helpers/timedatectl
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to show the current timezone, with two alternative methods
|
||||
show_timezone() {
|
||||
# Check if the /etc/timezone file exists
|
||||
if [ -f /etc/timezone ]; then
|
||||
timezone=$(cat /etc/timezone)
|
||||
elif [ -f /etc/localtime ]; then
|
||||
timezone=$(readlink /etc/localtime)
|
||||
timezone=${timezone/\/usr\/share\/zoneinfo\//}
|
||||
else
|
||||
timezone="Cannot determine timezone."
|
||||
fi
|
||||
echo "$timezone"
|
||||
}
|
||||
|
||||
# Function to set the timezone
|
||||
set_timezone() {
|
||||
new_timezone="$1"
|
||||
echo "$new_timezone" | sudo tee /etc/timezone >/dev/null
|
||||
sudo ln -sf /usr/share/zoneinfo/"$new_timezone" /etc/localtime
|
||||
if [ -f /etc/environment ]; then sudo sed -i "/TZ/c\TZ=$new_timezone" /etc/environment; fi
|
||||
if [ -d /var/run/s6/container_environment ]; then echo "$new_timezone" | sudo tee /var/run/s6/container_environment/TZ > /dev/null; fi
|
||||
echo "$new_timezone"
|
||||
}
|
||||
|
||||
# Main script
|
||||
case "$1" in
|
||||
"set-ntp")
|
||||
case "$2" in
|
||||
"false")
|
||||
sudo systemctl stop systemd-timesyncd
|
||||
sudo systemctl disable systemd-timesyncd
|
||||
echo "NTP disabled"
|
||||
;;
|
||||
"true")
|
||||
sudo systemctl start systemd-timesyncd
|
||||
sudo systemctl enable systemd-timesyncd
|
||||
echo "NTP enabled"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument for set-ntp. Use 'false' or 'true'."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"show")
|
||||
show_timezone
|
||||
;;
|
||||
"set-timezone")
|
||||
set_timezone "$2"
|
||||
;;
|
||||
*)
|
||||
# Get values
|
||||
local_time="$(date)"
|
||||
utc_time="$(date -u)"
|
||||
time_zone="$(show_timezone)"
|
||||
# Check if NTP is used
|
||||
if sudo systemctl status systemd-timesyncd | grep -q " active"; then
|
||||
ntp_status="yes"
|
||||
ntp_service="active"
|
||||
else
|
||||
ntp_status="no"
|
||||
ntp_service="inactive"
|
||||
fi
|
||||
# Print the information
|
||||
echo "Local time: $local_time"
|
||||
echo "Universal time: $utc_time"
|
||||
echo "Time zone: $time_zone"
|
||||
echo "Network time on: $ntp_status"
|
||||
echo "NTP service: $ntp_service"
|
||||
;;
|
||||
esac
|
||||
27
battybirdnet-pi/rootfs/helpers/views.add
Normal file
27
battybirdnet-pi/rootfs/helpers/views.add
Normal file
@@ -0,0 +1,27 @@
|
||||
if($_GET['view'] == "Converted"){
|
||||
ensure_authenticated();
|
||||
if(isset($_GET['species']) && isset($_GET['add'])){
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
|
||||
file_put_contents("$file", "$str");
|
||||
// Write $_GET['species'] to the file
|
||||
file_put_contents("./scripts/convert_species_list.txt", htmlspecialchars_decode($_GET['species'], ENT_QUOTES)."\n", FILE_APPEND);
|
||||
} elseif (isset($_GET['species']) && isset($_GET['del'])){
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace('/^\h*\v+/m', '', $str);
|
||||
file_put_contents("$file", "$str");
|
||||
foreach($_GET['species'] as $selectedOption) {
|
||||
$content = file_get_contents("./scripts/convert_species_list.txt");
|
||||
$newcontent = str_replace($selectedOption, "", "$content");
|
||||
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$content");
|
||||
file_put_contents("./scripts/convert_species_list.txt", "$newcontent");
|
||||
}
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace('/^\h*\v+/m', '', $str);
|
||||
file_put_contents("$file", "$str");
|
||||
}
|
||||
include('./scripts/convert_list.php');
|
||||
}
|
||||
BIN
battybirdnet-pi/stats.png
Normal file
BIN
battybirdnet-pi/stats.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
8
battybirdnet-pi/updater.json
Normal file
8
battybirdnet-pi/updater.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"last_update": "22-06-2024",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "battybattybirdnet-pi",
|
||||
"source": "github",
|
||||
"upstream_repo": "rdz-oss/Battybattybirdnet-pi",
|
||||
"upstream_version": "0.1"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"last_update": "22-06-2024",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "birdnet-go",
|
||||
"slug": "birdnet-pi",
|
||||
"source": "github",
|
||||
"upstream_repo": "Nachtzuster/BirdNET-Pi",
|
||||
"upstream_version": "0.1"
|
||||
|
||||
Reference in New Issue
Block a user