mirror of
https://github.com/sdr-enthusiasts/docker-planefence.git
synced 2025-12-22 11:17:06 +00:00
Thanks to @smkrv in https://github.com/sdr-enthusiasts/docker-planefence/issues/244 for the base code that made the implementation process much easier! --------- Co-authored-by: @smkrv
1293 lines
59 KiB
Bash
Executable File
1293 lines
59 KiB
Bash
Executable File
#!/command/with-contenv bash
|
|
#shellcheck shell=bash
|
|
#shellcheck disable=SC2001,SC2015,SC1091,SC2129,SC2154,SC2155
|
|
#
|
|
# PLANEFENCE - a Bash shell script to render a HTML and CSV table with nearby aircraft
|
|
#
|
|
# Usage: ./planefence.sh
|
|
#
|
|
# Copyright 2020-2025 Ramon F. Kolb (kx1t) - licensed under the terms and conditions
|
|
# of GPLv3. The terms and conditions of this license are included with the Github
|
|
# distribution of this package, and are also available here:
|
|
# https://github.com/sdr-enthusiasts/docker-planefence/
|
|
#
|
|
# The package contains parts of, and modifications or derivatives to the following:
|
|
# Dump1090.Socket30003 by Ted Sluis: https://github.com/tedsluis/dump1090.socket30003
|
|
# These packages may incorporate other software and license terms.
|
|
#
|
|
# Summary of License Terms
|
|
# This program is free software: you can redistribute it and/or modify it under the terms of
|
|
# the GNU General Public License as published by the Free Software Foundation, either version 3
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with this program.
|
|
# If not, see https://www.gnu.org/licenses/.
|
|
# -----------------------------------------------------------------------------------
|
|
# Only change the variables below if you know what you are doing.
|
|
|
|
# all errors will show a line number and the command used to produce the error
|
|
source /scripts/common
|
|
|
|
# We need to define the directory where the config file is located:
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "0. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- started Planefence" || true
|
|
|
|
PLANEFENCEDIR=/usr/share/planefence
|
|
|
|
# Let's see if we must reload the parameters
|
|
if [[ -f "/run/planefence/last-config-change" ]] && [[ -f "/usr/share/planefence/persist/planefence.config" ]]; then
|
|
# if... the date-last-changed of config file on the exposed volume ... is newer than the last time we read it ... then ... rerun the prep routine (which will update the last-config-change)
|
|
[[ "$(stat -c %Y /usr/share/planefence/persist/planefence.config)" -gt "$(</run/planefence/last-config-change)" ]] && /usr/share/planefence/prep-planefence.sh
|
|
fi
|
|
# FENCEDATE will be the date [yymmdd] that we want to process Planefence for.
|
|
# The default value is 'today'.
|
|
|
|
if [[ -n "$1" ]] && [[ "$1" != "reset" ]]; then # $1 contains the date for which we want to run Planefence
|
|
FENCEDATE=$(date --date="$1" '+%y%m%d')
|
|
else
|
|
FENCEDATE=$(date --date="today" '+%y%m%d')
|
|
fi
|
|
|
|
[[ "$TRACKSERVICE" != "flightaware" ]] && TRACKSERVICE="flightaware" || true
|
|
|
|
# -----------------------------------------------------------------------------------
|
|
# Compare the original config file with the one in use, and call
|
|
#
|
|
#
|
|
# -----------------------------------------------------------------------------------
|
|
# Read the parameters from the config file
|
|
if [[ -f "$PLANEFENCEDIR/planefence.conf" ]]; then
|
|
source "$PLANEFENCEDIR/planefence.conf"
|
|
else
|
|
echo $PLANEFENCEDIR/planefence.conf is missing. We need it to run Planefence!
|
|
exit 2
|
|
fi
|
|
|
|
# first get DISTANCE unit:
|
|
DISTUNIT="mi"
|
|
#DISTCONV=1
|
|
if [[ -f "$SOCKETCONFIG" ]]; then
|
|
case "$(grep "^distanceunit=" "$SOCKETCONFIG" |sed "s/distanceunit=//g")" in
|
|
nauticalmile)
|
|
DISTUNIT="nm"
|
|
;;
|
|
kilometer)
|
|
DISTUNIT="km"
|
|
;;
|
|
mile)
|
|
DISTUNIT="mi"
|
|
;;
|
|
meter)
|
|
DISTUNIT="m"
|
|
esac
|
|
fi
|
|
|
|
# get ALTITUDE unit:
|
|
ALTUNIT="ft"
|
|
if [[ -f "$SOCKETCONFIG" ]]; then
|
|
case "$(grep "^altitudeunit=" "$SOCKETCONFIG" |sed "s/altitudeunit=//g")" in
|
|
feet)
|
|
ALTUNIT="ft"
|
|
;;
|
|
meter)
|
|
ALTUNIT="m"
|
|
esac
|
|
fi
|
|
|
|
# Figure out if NOISECAPT is active or not. REMOTENOISE contains the URL of the NoiseCapt container/server
|
|
# and is configured via the $PF_NOISECAPT variable in the .env file.
|
|
# Only if REMOTENOISE contains a URL and we can get the noise log file, we collect noise data
|
|
# replace wget by curl to save memory space. Was: [[ "x$REMOTENOISE" != "x" ]] && [[ "$(wget -q -O /tmp/noisecapt-$FENCEDATE.log $REMOTENOISE/noisecapt-$FENCEDATE.log ; echo $?)" == "0" ]] && NOISECAPT=1 || NOISECAPT=0
|
|
if [[ "x$REMOTENOISE" != "x" ]]; then
|
|
if curl --fail -s "$REMOTENOISE/noisecapt-$FENCEDATE.log" > "/tmp/noisecapt-$FENCEDATE.log"; then
|
|
NOISECAPT=1
|
|
else
|
|
NOISECAPT=0
|
|
fi
|
|
fi
|
|
#
|
|
#
|
|
# Determine the user visible longitude and latitude based on the "fudge" factor we need to add:
|
|
if [[ "$FUDGELOC" != "" ]]; then
|
|
if [[ "$FUDGELOC" == "0" ]]; then
|
|
printf -v LON_VIS "%.0f" "$LON"
|
|
printf -v LAT_VIS "%.0f" "$LAT"
|
|
elif [[ "$FUDGELOC" == "1" ]]; then
|
|
printf -v LON_VIS "%.1f" "$LON"
|
|
printf -v LAT_VIS "%.1f" "$LAT"
|
|
elif [[ "$FUDGELOC" == "2" ]]; then
|
|
printf -v LON_VIS "%.2f" "$LON"
|
|
printf -v LAT_VIS "%.2f" "$LAT"
|
|
else
|
|
# If $FUDGELOC != "" but also != "2", then assume it is "3"
|
|
printf -v LON_VIS "%.3f" "$LON"
|
|
printf -v LAT_VIS "%.3f" "$LAT"
|
|
fi
|
|
# clean up the strings:
|
|
else
|
|
# let's not print more than 5 digits
|
|
printf -v LON_VIS "%.5f" "$LON"
|
|
printf -v LAT_VIS "%.5f" "$LAT"
|
|
fi
|
|
# shellcheck disable=SC2001
|
|
LON_VIS="$(sed 's/^00*\|00*$//g' <<< "$LON_VIS")" # strip any trailing zeros - "41.10" -> "41.1", or "41.00" -> "41."
|
|
LON_VIS="${LON_VIS%.}" # If the last character is a ".", strip it - "41.1" -> "41.1" but "41." -> "41"
|
|
# shellcheck disable=SC2001
|
|
LAT_VIS="$(sed 's/^00*\|00*$//g' <<< "$LAT_VIS")" # strip any trailing zeros - "41.10" -> "41.1", or "41.00" -> "41."
|
|
LAT_VIS="${LAT_VIS%.}" # If the last character is a ".", strip it - "41.1" -> "41.1" but "41." -> "41"
|
|
if (( ALTCORR != 0 )); then ALTREFERENCE="AGL"; else ALTREFERENCE="MSL"; fi
|
|
#
|
|
#
|
|
# Functions
|
|
#
|
|
# Function to write to the log
|
|
LOG ()
|
|
{
|
|
# This reads a string from stdin and stores it in a variable called IN. This enables things like 'echo hello world > LOG'
|
|
while [[ -n "$1" ]] || read -r IN; do
|
|
if [[ -n "$1" ]]; then
|
|
IN="$1"
|
|
fi
|
|
if [[ "$VERBOSE" != "" ]]; then
|
|
if [[ "$LOGFILE" == "logger" ]]; then
|
|
printf "%s-%s[%s]v%s: %s\n" "$(date +"%Y%m%d-%H%M%S")" "$PROCESS_NAME" "$CURRENT_PID" "$VERSION" "$IN" | logger
|
|
else
|
|
printf "%s-%s[%s]v%s: %s\n" "$(date +"%Y%m%d-%H%M%S")" "$PROCESS_NAME" "$CURRENT_PID" "$VERSION" "$IN" >> "$LOGFILE"
|
|
fi
|
|
fi
|
|
if [[ -n "$1" ]]; then
|
|
break
|
|
fi
|
|
done
|
|
}
|
|
|
|
GET_ROUTE () {
|
|
# function to get a route by callsign. Must have a callsign - ICAO won't work
|
|
# Usage: GET_ROUTE <callsign>
|
|
# Uses the adsb.lol API to retrieve the route
|
|
|
|
local route
|
|
|
|
# first let's see if it's in the cache
|
|
if [[ -f /usr/share/planefence/persist/.internal/routecache-$(date +%y%m%d).txt ]]; then
|
|
route="$(awk -F, -v callsign="${1^^}" '$1 == callsign {print $2; exit}' "/usr/share/planefence/persist/.internal/routecache-$(date +%y%m%d).txt")"
|
|
if [[ -n "$route" ]]; then
|
|
if [[ "$route" != "unknown" ]]; then echo "$route"; fi
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if route="$(curl -sSL -X 'POST' 'https://api.adsb.lol/api/0/routeset' \
|
|
-H 'accept: application/json' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"planes": [{"callsign": "'"${1^^}"'","lat": '"$LAT"',"lng": '"$LON"'}] }' \
|
|
| jq -r '.[]._airport_codes_iata')" \
|
|
&& [[ -n "$route" ]] && [[ "$route" != "unknown" ]] && [[ "$route" != "null" ]]
|
|
then
|
|
echo "${1^^},$route" >> "/usr/share/planefence/persist/.internal/routecache-$(date +%y%m%d).txt"
|
|
echo "$route"
|
|
elif [[ "${route,,}" == "unknown" ]] || [[ "${route,,}" == "null" ]]; then
|
|
echo "${1^^},unknown" >> "/usr/share/planefence/persist/.internal/routecache-$(date +%y%m%d).txt"
|
|
fi
|
|
}
|
|
|
|
GET_PS_PHOTO () {
|
|
# Function to get a photo from PlaneSpotters.net
|
|
# Usage: GET_PS_PHOTO ICAO [image|link|thumblink]
|
|
# if [image|link|thumblink] is omitted, "link" is assumed
|
|
# image: the path to the thumbnail image on disk
|
|
# link: a link to the planespotters.net image page (not the image itself!)
|
|
# thumblink: a link to the thumbnail image at planespotters.net's CDN
|
|
|
|
local link
|
|
local json
|
|
local returntype
|
|
local thumb
|
|
|
|
returntype="${2:-link}"
|
|
returntype="${returntype,,}"
|
|
|
|
#echo "returntype=$returntype"
|
|
|
|
# shellcheck disable=SC2076
|
|
if [[ ! " image link thumblink " =~ " $returntype " ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if ! $SHOWIMAGES; then return 0; fi
|
|
|
|
if [[ -f "/usr/share/planefence/persist/planepix/cache/$1.notavailable" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$returntype" == "image" ]] && [[ -f "/usr/share/planefence/persist/planepix/cache/$1.jpg" ]]; then
|
|
#echo in cache
|
|
echo "/usr/share/planefence/persist/planepix/cache/$1.jpg"
|
|
return 0
|
|
elif [[ "$returntype" == "link" ]] && [[ -f "/usr/share/planefence/persist/planepix/cache/$1.link" ]]; then
|
|
#echo in cache
|
|
echo "$(<"/usr/share/planefence/persist/planepix/cache/$1.link")"
|
|
return 0
|
|
elif [[ "$returntype" == "thumblink" ]] && [[ -f "/usr/share/planefence/persist/planepix/cache/$1.thumb.link" ]]; then
|
|
#echo in cache
|
|
echo "$(<"/usr/share/planefence/persist/planepix/cache/$1.thumb.link")"
|
|
return 0
|
|
fi
|
|
|
|
# If we don't have a cached file, let's see if we can get one from PlaneSpotters.net
|
|
if json="$(curl -ssL --fail "https://api.planespotters.net/pub/photos/hex/$1")" && \
|
|
link="$(jq -r 'try .photos[].link | select( . != null )' <<< "$json")" && \
|
|
thumb="$(jq -r 'try .photos[].thumbnail_large.src | select( . != null )' <<< "$json")" && \
|
|
[[ -n "$link" ]] && [[ -n "$thumb" ]]; then
|
|
# If we have a link, let's download the photo
|
|
curl -ssL --fail --clobber "$thumb" -o "/usr/share/planefence/persist/planepix/cache/$1.jpg"
|
|
echo "$link" > "/usr/share/planefence/persist/planepix/cache/$1.link"
|
|
echo "$thumb" > "/usr/share/planefence/persist/planepix/cache/$1.thumb.link"
|
|
touch -d "+$((HISTTIME+1)) days" "/usr/share/planefence/persist/planepix/cache/$1.link" "/usr/share/planefence/persist/planepix/cache/$1.thumb.link"
|
|
#echo newly obtained
|
|
if returntype="image"; then echo "/usr/share/planefence/persist/planepix/cache/$1.jpg"
|
|
elif returntype="link"; then echo "$link"
|
|
elif returntype="thumblink"; then echo "$thumb"
|
|
fi
|
|
return 0
|
|
else
|
|
# If we don't have a link, let's clear the cache and return an empty string
|
|
rm -f "/usr/share/planefence/persist/planepix/cache/$1.*"
|
|
touch "/usr/share/planefence/persist/planepix/cache/$1.notavailable"
|
|
fi
|
|
}
|
|
|
|
CREATE_NOISEPLOT () {
|
|
# usage: CREATE_NOISEPLOT <callsign> <starttime> <endtime> <icao>
|
|
|
|
if [[ -z "$REMOTENOISE" ]]; then return; fi
|
|
|
|
local STARTTIME="$2"
|
|
local ENDTIME="$3"
|
|
local TITLE="Noise plot for $1 at $(date -d "@$2" +"%y%m%d-%H%M%S")"
|
|
local NOWTIME="$(date +%s)"
|
|
local NOISEGRAPHFILE="$OUTFILEDIR"/"noisegraph-$(date -d "@${STARTTIME}" +"%y%m%d-%H%M%S")-$4.png"
|
|
# if the timeframe is less than 30 seconds, extend the ENDTIME to 30 seconds
|
|
if (( ENDTIME - STARTTIME < 15 )); then ENDTIME=$(( STARTTIME + 15 )); fi
|
|
STARTTIME=$(( STARTTIME - 15))
|
|
# check if there are any noise samples
|
|
if (( (NOWTIME - ENDTIME) > (ENDTIME - STARTTIME) )) && \
|
|
[[ -f "/usr/share/planefence/persist/.internal/noisecapt-$FENCEDATE.log" ]] && \
|
|
[[ "$(awk -v s="$STARTTIME" -v e="$ENDTIME" '$1>=s && $1<=e' /usr/share/planefence/persist/.internal/noisecapt-"$FENCEDATE".log | wc -l)" -gt "0" ]]
|
|
then
|
|
gnuplot -e "offset=$(echo "$(date +%z) * 36" | sed 's/+[0]\?//g' | bc); start=$STARTTIME; end=$ENDTIME; infile='/usr/share/planefence/persist/.internal/noisecapt-$FENCEDATE.log'; outfile='$NOISEGRAPHFILE'; plottitle='$TITLE'; margin=60" $PLANEFENCEDIR/noiseplot.gnuplot
|
|
fi
|
|
}
|
|
|
|
CREATE_SPECTROGRAM () {
|
|
# usage: CREATE_SPECTROGRAM <starttime> <endtime>
|
|
# returns the file name of the spectrogram it got
|
|
|
|
if [[ -z "$REMOTENOISE" ]]; then return; fi
|
|
|
|
local STARTTIME="$1"
|
|
local ENDTIME="$2"
|
|
local sf spectrotime
|
|
if (( ENDTIME - STARTTIME < 30 )); then ENDTIME=$(( STARTTIME + 30 )); fi
|
|
|
|
# get the measurement from noisecapt-"$FENCEDATE".log that contains the peak value
|
|
# limited by $STARTTIME and $ENDTIME, and then get the corresponding spectrogram file name
|
|
spectrotime="$(awk -F, -v a="$STARTTIME" -v b="$ENDTIME" 'BEGIN{c=-999; d=0}{if ($1>=0+a && $1<=1+b && $2>0+c) {c=$2; d=$1}} END{print d}' /usr/share/planefence/persist/.internal/noisecapt-"$FENCEDATE".log)"
|
|
sf="noisecapt-spectro-$(date -d "@${spectrotime}" +"%y%m%d-%H%M%S").png"
|
|
|
|
if [[ ! -s "$OUTFILEDIR/$sf" ]]; then
|
|
# we don't have $sf locally, or if it's an empty file, we get it:
|
|
curl -sSL "$REMOTENOISE/$sf" > "$OUTFILEDIR/$sf"
|
|
fi
|
|
# shellcheck disable=SC2012
|
|
if [[ ! -s "$OUTFILEDIR/$sf" ]] || (( $(ls -s1 "$OUTFILEDIR/$sf" | awk '{print $1}') < 10 )); then
|
|
# we don't have $sf (or it's an empty file) and we can't get it; so let's erase it in case it's an empty file:
|
|
rm -f "$OUTFILEDIR/$sf"
|
|
else
|
|
echo "$sf"
|
|
fi
|
|
}
|
|
|
|
CREATE_MP3 () {
|
|
# usage: CREATE_MP3 <starttime> <endtime>
|
|
# returns the file name of the MP3 file it got
|
|
|
|
if [[ -z "$REMOTENOISE" ]]; then return; fi
|
|
|
|
local STARTTIME="$1"
|
|
local ENDTIME="$2"
|
|
local mp3time mp3f
|
|
(( ENDTIME - STARTTIME < 30 )) && ENDTIME=$(( STARTTIME + 30 ))
|
|
|
|
# get the measurement from noisecapt-"$FENCEDATE".log that contains the peak value
|
|
# limited by $STARTTIME and $ENDTIME, and then get the corresponding spectrogram file name
|
|
mp3time="$(awk -F, -v a="$STARTTIME" -v b="$ENDTIME" 'BEGIN{c=-999; d=0}{if ($1>=0+a && $1<=1+b && $2>0+c) {c=$2; d=$1}} END{print d}' /usr/share/planefence/persist/.internal/noisecapt-"$FENCEDATE".log)"
|
|
mp3f="noisecapt-recording-$(date -d "@${mp3time}" +"%y%m%d-%H%M%S").mp3"
|
|
|
|
if [[ ! -s "$OUTFILEDIR/$mp3f" ]]; then
|
|
# we don't have $sf locally, or if it's an empty file, we get it:
|
|
curl -sSL "$REMOTENOISE/$mp3f" > "$OUTFILEDIR/$mp3f"
|
|
fi
|
|
# shellcheck disable=SC2012
|
|
if [[ ! -s "$OUTFILEDIR/$mp3f" ]] || (( $(ls -s1 "$OUTFILEDIR/$mp3f" | awk '{print $1}') < 4 )); then
|
|
# we don't have $mp3f (or it's an empty file) and we can't get it; so let's erase it in case it's an empty file:
|
|
rm -f "$OUTFILEDIR/$mp3f"
|
|
else
|
|
echo "$mp3f"
|
|
fi
|
|
}
|
|
|
|
LOG "-----------------------------------------------------"
|
|
# Function to write an HTML table from a CSV file
|
|
LOG "Defining WRITEHTMLTABLE"
|
|
WRITEHTMLTABLE () {
|
|
# -----------------------------------------
|
|
# Next create an HTML table from the CSV file
|
|
# Usage: WRITEHTMLTABLE INPUTFILE OUTPUTFILE
|
|
LOG "WRITEHTMLTABLE $1 $2"
|
|
|
|
# if the CSV file doesn't exist, then simply return
|
|
if [[ ! -f "$1" ]]; then return 0; fi
|
|
|
|
# read the records from the CSV file into an assoc array called 'records'
|
|
declare -A records
|
|
local counter=0
|
|
local HASNOISE=false
|
|
local HASNOTIFS=false
|
|
local HASROUTE=false
|
|
local INPUTFILE="$(<"$1")"
|
|
|
|
# Replace the map zoom by whatever $HEATMAPZOOM contains
|
|
## shellcheck disable=SC2001
|
|
if [[ -n "$HEATMAPZOOM" ]]; then INPUTFILE=$(sed 's|\(^.*&zoom=\)[0-9]*\(.*\)|\1'"$HEATMAPZOOM"'\2|' <<< "$INPUTFILE"); fi
|
|
|
|
while read -r line; do
|
|
# filling an Associative Array with the following structure: records[$index:key], where:
|
|
# $index is a counter starting at 0 for each of the times
|
|
# icao: ICAO hex ID
|
|
# callsign: flight number or tail number
|
|
# route: route (airport codes)
|
|
# notified: notification has been sent (true/false)
|
|
# firstseen: date/time first seen in secs since epoch
|
|
# lastseen: date/time last seen in secs since epoch
|
|
# altitude: lowest altitude observed
|
|
# distance: minimum distance observed
|
|
# map_link: link to ADSBX or other tar1090 style map
|
|
# fa_link: link to flightaware
|
|
# owner: owner or airline name
|
|
# notif_link: link to notification
|
|
# notif_service: "BlueSky", "Mastodon", or "yes"
|
|
# image_thumblink: link to image thumbnail
|
|
# image_weblink: link to image page at planespotters.net
|
|
# sound_peak: peak sound level (if NoiseCapt is configured)
|
|
# sound_1min: 1 minute sound level (if NoiseCapt is configured)
|
|
# sound_5min: 5 minute sound level (if NoiseCapt is configured)
|
|
# sound_10min: 10 minute sound level (if NoiseCapt is configured)
|
|
# sound_1hour: 1 hour sound level (if NoiseCapt is configured)
|
|
# sound_loudness: loudness level (if NoiseCapt is configured)
|
|
# sound_color: background color corresponding to sound_loudness level
|
|
# noisegraph_file: path of noisegraph file (if NoiseCapt is configured)
|
|
# noisegraph_link: link to noisegraph file (if NoiseCapt is configured)
|
|
# spectro_file: path of spectrogram file (if NoiseCapt is configured)
|
|
# spectro_link: link to spectrogram file (if NoiseCapt is configured)
|
|
# mp3_file: path of mp3 file (if NoiseCapt is configured)
|
|
# mp3_link: link to mp3 file (if NoiseCapt is configured)
|
|
#
|
|
# additionally, the following are local variables:
|
|
# maxindex: highest index number (useful for looping)
|
|
# HASNOISE: true if noise data is present in the array
|
|
# HASNOTIFS: true if notifications have been sent
|
|
# HASROUTE: true if a route is available
|
|
|
|
if [[ -z "$line" ]]; then continue; fi
|
|
readarray -d, -t data <<< "$line"
|
|
index="$((counter++))" # we can't just use the ICAO because there can be multiple observations in a single day
|
|
records[$index:icao]="${data[0]^^}"
|
|
records[$index:callsign]="${data[1]//@/}"
|
|
if [[ "${data[1]:0:1}" == "@" ]]; then
|
|
records[$index:notified]=true
|
|
HASNOTIFS=true
|
|
else
|
|
records[$index:notified]=false
|
|
fi
|
|
|
|
if ! chk_disabled "$CHECKROUTE"; then records[$index:route]="$(GET_ROUTE "${records[$index:callsign]}")"; fi
|
|
if [[ -n "${records[$index:route]}" ]]; then HASROUTE=true; fi
|
|
|
|
records[$index:firstseen]="$(date -d "${data[2]}" +%s)"
|
|
records[$index:lastseen]="$(date -d "${data[3]}" +%s)"
|
|
records[$index:altitude]="$(sed ':a;s/\B[0-9]\{3\}\>/,&/g;ta' <<< "${data[4]//$'\n'/}")"
|
|
records[$index:distance]="${data[5]//$'\n'/}"
|
|
records[$index:map_link]="${data[6]//globe.adsbexchange.com/"$TRACKSERVICE"}"
|
|
records[$index:fa_link]="https://flightaware.com/live/modes/${records[$index:icao]}/ident/${CALLSIGN}/redirect"
|
|
records[$index:owner]="$(/usr/share/planefence/airlinename.sh "${records[$index:callsign]}" "${records[$index:icao]}")"
|
|
records[$index:owner]="${records[$index:owner]:-unknown}"
|
|
records[$index:notif_link]="${data[7]//$'\n'/}" # this will be adjusted if there's noise data
|
|
if [[ ${records[$index:callsign]} =~ ^N[0-9][0-9a-zA-Z]+$ ]] && \
|
|
[[ "${records[$index:callsign]:0:4}" != "NATO" ]] && \
|
|
[[ "${records[$index:icao]:0:1}" == "A" ]]
|
|
then
|
|
records[$index:faa_link]="https://registry.faa.gov/AircraftInquiry/Search/NNumberResult?nNumberTxt=${records[$index:callsign]}"
|
|
fi
|
|
|
|
# get an image links
|
|
records[$index:image_thumblink]="$(GET_PS_PHOTO "${records[$index:icao]}" thumblink)"
|
|
records[$index:image_weblink]="$(GET_PS_PHOTO "${records[$index:icao]}" link)"
|
|
|
|
if [[ -n "$REMOTENOISE" ]] && [[ -z "${data[7]//[0-9.$'\n'-]/}" ]]; then
|
|
# there is sound level information
|
|
HASNOISE=true
|
|
records[$index:sound_peak]="${data[7]//$'\n'/}"
|
|
records[$index:sound_1min]="${data[8]//$'\n'/}"
|
|
records[$index:sound_5min]="${data[9]//$'\n'/}"
|
|
records[$index:sound_10min]="${data[10]//$'\n'/}"
|
|
records[$index:sound_1hour]="${data[11]//$'\n'/}"
|
|
if [[ -n "${records[$index:sound_peak]}" ]]; then records[$index:sound_loudness]="$(( data[7] - data[11] ))"; fi
|
|
records[$index:notif_link]="${data[12]//$'\n'/}"
|
|
{ # get a noise graph if one doesn't exist
|
|
# $NOISEGRAPHFILE is the full file path, NOISEGRAPHLINK is the subset with the filename only
|
|
records[$index:noisegraph_file]="$OUTFILEDIR"/"noisegraph-$(date -d "@${records[$index:firstseen]}" +"%y%m%d-%H%M%S")-${records[$index:icao]}.png"
|
|
records[$index:noisegraph_link]="$(basename "${records[$index:noisegraph_file]}")"
|
|
# If no noisegraph exists, create one:
|
|
if [[ ! -f "${records[$index:noisegraph_file]}" ]]; then
|
|
CREATE_NOISEPLOT "${records[$index:callsign]}" "${records[$index:firstseen]}" "${records[$index:lastseen]}" "${records[$index:icao]}"
|
|
if [[ ! -f "${records[$index:noisegraph_file]}" ]]; then
|
|
unset "${records[$index:noisegraph_file]}" "${records[$index:noisegraph_link]}"
|
|
fi
|
|
fi
|
|
}
|
|
{ # get a spectrogram if one doesn't exist
|
|
records[$index:spectro_file]="$(CREATE_SPECTROGRAM "${records[$index:firstseen]}" "${records[$index:lastseen]}")"
|
|
if [[ -n "${records[$index:spectro_file]}" ]]; then
|
|
records[$index:spectro_link]="$(basename "${records[$index:spectro_file]}")"
|
|
fi
|
|
}
|
|
{ # get a MP3 if one doesn't exist
|
|
records[$index:mp3_file]="$(CREATE_MP3 "${records[$index:firstseen]}" "${records[$index:lastseen]}")"
|
|
if [[ -n "${records[$index:mp3_file]}" ]]; then
|
|
records[$index:mp3_link]="$(basename "${records[$index:mp3_file]}")"
|
|
fi
|
|
}
|
|
{ # determine loudness background color
|
|
if [[ -n "${records[$index:sound_loudness]}" ]]; then
|
|
records[$index:sound_color]="$RED"
|
|
if (( ${records[$index:sound_loudness]} <= YELLOWLIMIT )); then records[$index:sound_color]="$YELLOW"; fi
|
|
if (( ${records[$index:sound_loudness]} <= GREENLIMIT )); then records[$index:sound_color]="$GREEN"; fi
|
|
fi
|
|
}
|
|
fi
|
|
|
|
# get notification service name
|
|
if "${records[$index:notified]}"; then
|
|
records[$index:notif_service]="yes"
|
|
else
|
|
records[$index:notif_service]="no"
|
|
fi
|
|
if [[ -n "${records[$index:notif_link]}" ]]; then
|
|
if [[ "${records[$index:notif_link]}" == "mqtt" ]]; then
|
|
records[$index:notif_service]="MQTT"
|
|
records[$index:notif_link]=""
|
|
elif [[ "${records[$index:notif_link]:0:17}" == "https://bsky.app/" ]]; then records[$index:notif_service]="BlueSky"
|
|
elif [[ "${records[$index:notif_link]:0:13}" == "https://t.me/" ]]; then records[$index:notif_service]="Telegram"
|
|
elif grep -qo "$MASTODON_SERVER" <<< "${records[$index:notif_link]}"; then records[$index:notif_service]="Mastodon"
|
|
fi
|
|
fi
|
|
done <<< "$INPUTFILE"
|
|
maxindex="$((--counter))"
|
|
|
|
# Now write the HTML table header
|
|
# open file for writing as fd 3
|
|
exec 3>>"$2"
|
|
|
|
cat >&3 <<EOF
|
|
<table border="1" class="display planetable" id="mytable" style="width: auto; text-align: left; align: left" align="left">
|
|
<thead border="1">
|
|
<tr>
|
|
<th style="width: auto; text-align: center">No.</th>
|
|
$(${SHOWIMAGES} && echo "<th style=\"width: auto; text-align: center\">Aircraft Image</th>" || true)
|
|
<th style="width: auto; text-align: center">Transponder ID</th>
|
|
<th style="width: auto; text-align: center">Flight</th>
|
|
$(${HASROUTE} && echo "<th style=\"width: auto; text-align: center\">Flight Route</th>" || true)
|
|
<th style="width: auto; text-align: center">Airline or Owner</th>"
|
|
<th style="width: auto; text-align: center">Time First Seen</th>
|
|
<th style="width: auto; text-align: center">Time Last Seen</th>
|
|
<th style="width: auto; text-align: center">Min. Altitude</th>
|
|
<th style="width: auto; text-align: center">Min. Distance</th>
|
|
EOF
|
|
|
|
if "$HASNOISE"; then
|
|
# print the headers for the standard noise columns
|
|
cat >&3 <<EOF
|
|
<th style="width: auto; text-align: center">Loudness</th>
|
|
<th style="width: auto; text-align: center">Peak RMS sound</th>
|
|
<th style="width: auto; text-align: center">1 min avg</th>
|
|
<th style="width: auto; text-align: center">5 min avg</th>
|
|
<th style="width: auto; text-align: center">10 min avg</th>
|
|
<th style="width: auto; text-align: center">1 hr avg</th>
|
|
<th style="width: auto; text-align: center">Spectrogram</th>
|
|
EOF
|
|
fi
|
|
|
|
if "$HASNOTIFS"; then
|
|
# print a header for the Notified column
|
|
printf " <th style=\"width: auto; text-align: center\">Notified</th>\n" >&3
|
|
fi
|
|
printf " </tr></thead>\n<tbody border=\"1\">\n" >&3
|
|
|
|
# Now write the table
|
|
|
|
for (( index=0 ; index<=maxindex ; index++ )); do
|
|
|
|
printf "<tr>\n" >&3
|
|
printf " <td style=\"text-align: center\">%s</td><!-- row 1: index -->\n" "$index" >&3 # table index number
|
|
|
|
if ${SHOWIMAGES} && [[ -n "${records[$index:image_thumblink]}" ]]; then
|
|
printf " <td><a href=\"%s\" target=_blank><img src=\"%s\" style=\"width: auto; height: 75px;\"></a></td><!-- image file and link to planespotters.net -->\n" "${records[$index:image_weblink]}" "${records[$index:image_thumblink]}" >&3
|
|
elif ${SHOWIMAGES}; then
|
|
printf " <td></td><!-- images enabled but no image file available for this entry -->\n" >&3
|
|
fi
|
|
|
|
printf " <td><a href=\"%s\" target=\"_blank\">%s</a></td><!-- ICAO with map link -->\n" "${records[$index:map_link]}" "${records[$index:icao]}" >&3 # ICAO
|
|
|
|
printf " <td><a href=\"%s\" target=\"_blank\">%s</a></td><!-- Flight number/tail with FlightAware link -->\n" "${records[$index:fa_link]}" "${records[$index:callsign]}" >&3 # Flight number/tail with FlightAware link
|
|
|
|
if ${HASROUTE}; then
|
|
printf " <td>%s</td><!-- route -->\n" "${records[$index:route]}" >&3 # route
|
|
fi
|
|
|
|
if [[ -n "${records[$index:faa_link]}" ]]; then
|
|
printf " <td><a href=\"%s\" target=\"_blank\">%s</a></td><!-- owner with FAA link -->\n" "${records[$index:faa_link]}" "${records[$index:owner]}" >&3
|
|
else
|
|
printf " <td>%s</td><!-- owner -->\n" "${records[$index:owner]}" >&3
|
|
fi
|
|
|
|
printf " <td style=\"text-align: center\">%s</td><!-- date/time first seen -->\n" "$(date -d "@${records[$index:firstseen]}" "+${NOTIF_DATEFORMAT:-%F %T %Z}")" >&3 # time first seen
|
|
|
|
printf " <td style=\"text-align: center\">%s</td><!-- date/time last seen -->\n" "$(date -d "@${records[$index:lastseen]}" "+${NOTIF_DATEFORMAT:-%F %T %Z}")" >&3 # time last seen
|
|
|
|
printf " <td>%s %s %s</td><!-- min altitude -->\n" "${records[$index:altitude]}" "$ALTUNIT" "$ALTREFERENCE" >&3 # min altitude
|
|
printf " <td>%s %s</td><!-- min distance -->\n" "${records[$index:distance]}" "$DISTUNIT" >&3 # min distance
|
|
|
|
# Print the noise values if we have determined that there is data
|
|
if "$HASNOISE"; then
|
|
# First the loudness field, which needs a color and a link to a noise graph:
|
|
if [[ -n "${records[$index:noisegraph_link]}" ]]; then
|
|
printf " <td style=\"background-color: %s\"><a href=\"%s\" target=\"_blank\">%s dB</a></td><!-- loudness with noisegraph -->\n" "${records[$index:sound_color]}" "${records[$index:noisegraph_link]}" "${records[$index:sound_loudness]}" >&3
|
|
else
|
|
printf " <td style=\"background-color: %s\">%s dB</td><!-- loudness (no noisegraph available) -->\n" "${records[$index:sound_color]}" "${records[$index:sound_loudness]}" >&3
|
|
fi
|
|
if [[ -n "${records[$index:mp3_link]}" ]]; then
|
|
printf " <td><a href=\"%s\" target=\"_blank\">%s dBFS</td><!-- peak RMS value with MP3 link -->\n" "${records[$index:mp3_link]}" "${records[$index:sound_peak]}" >&3 # print actual value with "dBFS" unit
|
|
else
|
|
printf " <td>%s dBFS</td><!-- peak RMS value (no MP3 recording available) -->\n" "${records[$index:sound_peak]}" >&3 # print actual value with "dBFS" unit
|
|
fi
|
|
printf " <td>%s dBFS</td><!-- 1 minute avg audio levels -->\n" "${records[$index:sound_1min]}" >&3
|
|
printf " <td>%s dBFS</td><!-- 5 minute avg audio levels -->\n" "${records[$index:sound_5min]}" >&3
|
|
printf " <td>%s dBFS</td><!-- 10 minute avg audio levels -->\n" "${records[$index:sound_10min]}" >&3
|
|
printf " <td>%s dBFS</td><!-- 1 hour avg audio levels -->\n" "${records[$index:sound_1hour]}" >&3
|
|
printf " <td><a href=\"%s\" target=\"_blank\">Spectrogram</a></td><!-- spectrogram -->\n" "${records[$index:spectro_link]}" >&3 # print spectrogram
|
|
fi
|
|
|
|
# Print a notification, if there are any:
|
|
if "$HASNOTIFS"; then
|
|
if [[ -n "${records[$index:notif_link]}" ]]; then
|
|
printf " <td><a href=\"%s\" target=\"_blank\">%s</a></td><!-- notification link and service -->\n" "${records[$index:notif_link]}" "${records[$index:notif_service]}" >&3
|
|
else
|
|
printf " <td>%s</td><!-- notified yes or no -->\n" "${records[$index:notif_service]}" >&3
|
|
fi
|
|
fi
|
|
printf "</tr>\n" >&3
|
|
|
|
done
|
|
printf "</tbody>\n</table>\n" >&3
|
|
exec 3>&-
|
|
}
|
|
|
|
# Function to write the Planefence history file
|
|
LOG "Defining WRITEHTMLHISTORY"
|
|
WRITEHTMLHISTORY () {
|
|
# -----------------------------------------
|
|
# Write history file from directory
|
|
# Usage: WRITEHTMLTABLE PLANEFENCEDIRECTORY OUTPUTFILE [standalone]
|
|
LOG "WRITEHTMLHISTORY $1 $2 $3"
|
|
if [[ "$3" == "standalone" ]]; then
|
|
printf "<html>\n<body>\n" >>"$2"
|
|
fi
|
|
|
|
cat <<EOF >>"$2"
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details open>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Historical Data</summary>
|
|
<p>Today: <a href="index.html" target="_top">html</a> - <a href="planefence-$FENCEDATE.csv" target="_top">csv</a>
|
|
EOF
|
|
|
|
# loop through the existing files. Note - if you change the file format, make sure to yodate the arguments in the line
|
|
# right below. Right now, it lists all files that have the planefence-20*.html format (planefence-200504.html, etc.), and then
|
|
# picks the newest 7 (or whatever HISTTIME is set to), reverses the strings to capture the characters 6-11 from the right, which contain the date (200504)
|
|
# and reverses the results back so we get only a list of dates in the format yymmdd.
|
|
|
|
if compgen -G "$1/planefence-??????.html" >/dev/null; then
|
|
# shellcheck disable=SC2012
|
|
for d in $(ls -1 "$1"/planefence-??????.html | tail --lines=$((HISTTIME+1)) | head --lines="$HISTTIME" | rev | cut -c6-11 | rev | sort -r)
|
|
do
|
|
{ printf " | %s" "$(date -d "$d" +%d-%b-%Y): "
|
|
printf "<a href=\"%s\" target=\"_top\">html</a> - " "planefence-$(date -d "$d" +"%y%m%d").html"
|
|
printf "<a href=\"%s\" target=\"_top\">csv</a>" "planefence-$(date -d "$d" +"%y%m%d").csv"
|
|
} >> "$2"
|
|
done
|
|
fi
|
|
{ printf "</p>\n"
|
|
printf "<p>Additional dates may be available by browsing to planefence-yymmdd.html in this directory.</p>"
|
|
printf "</details>\n</article>\n</section>"
|
|
} >> "$2"
|
|
|
|
# and print the footer:
|
|
if [[ "$3" == "standalone" ]]; then
|
|
printf "</body>\n</html>\n" >>"$2"
|
|
fi
|
|
}
|
|
|
|
# file used to store the line progress at the start of the prune interval
|
|
PRUNESTARTFILE=/run/socket30003/.lastprunecount
|
|
# for detecting change of day
|
|
LASTFENCEFILE=/usr/share/planefence/persist/.internal/lastfencedate
|
|
|
|
# Here we go for real:
|
|
LOG "Initiating Planefence"
|
|
LOG "FENCEDATE=$FENCEDATE"
|
|
# First - if there's any command line argument, we need to do a full run discarding all cached items
|
|
if [[ "$1" != "" ]]; then
|
|
rm "$LASTFENCEFILE" 2>/dev/null
|
|
rm "$PRUNESTARTFILE" 2>/dev/null
|
|
rm "$TMPLINES" 2>/dev/null
|
|
rm "$OUTFILEHTML" 2>/dev/null
|
|
rm "$OUTFILECSV" 2>/dev/null
|
|
rm "$OUTFILEBASE-$FENCEDATE"-table.html 2>/dev/null
|
|
rm "$OUTFILETMP" 2>/dev/null
|
|
rm "$TMPDIR"/dump1090-pf* 2>/dev/null
|
|
LOG "File cache reset- doing full run for $FENCEDATE"
|
|
fi
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "1. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- start prune socket30003 data" || true
|
|
|
|
# find out the number of lines previously read
|
|
if [[ -f "$TMPLINES" ]]; then
|
|
read -r READLINES < "$TMPLINES"
|
|
else
|
|
READLINES=0
|
|
fi
|
|
# shellcheck disable=SC2153
|
|
if [[ -f "$TOTLINES" ]]; then
|
|
read -r TOTALLINES < "$TOTLINES"
|
|
else
|
|
TOTALLINES=0
|
|
fi
|
|
if [[ -f "$LASTFENCEFILE" ]]; then
|
|
read -r LASTFENCEDATE < "$LASTFENCEFILE"
|
|
else
|
|
# file is missing, assume we ran last yesterday
|
|
LASTFENCEDATE=$(date --date="yesterday" '+%y%m%d')
|
|
fi
|
|
|
|
# delete some of the existing TMP files, so we don't leave any garbage around
|
|
# this is less relevant for today's file as it will be overwritten below, but this will
|
|
# also delete previous days' files that may have left behind
|
|
rm -f "$TMPLINES"
|
|
rm -f "$OUTFILETMP"
|
|
|
|
# before anything else, let's determine our current line count and write it back to the temp file
|
|
# We do this using 'wc -l', and then strip off all character starting at the first space
|
|
SOCKETFILE="$LOGFILEBASE$FENCEDATE.txt"
|
|
[[ -f "$SOCKETFILE" ]] && CURRCOUNT=$(wc -l "$SOCKETFILE" |cut -d ' ' -f 1) || CURRCOUNT=0
|
|
|
|
if [[ "$READLINES" -gt "$CURRCOUNT" ]]; then
|
|
# Houston, we have a problem. READLINES is an earlier snapshot of the number of records, which should always be GE CURRCOUNT.
|
|
# If it's not, this means most probably that the socket30003 logfile got reset, (again) probably because the container was restarted.
|
|
# In this case, we want to use all lines from the socket30003 logfile.
|
|
# There are some chances that we may process records we've already processed before, but this is improbably and we will take the risk.
|
|
READLINES=0
|
|
fi
|
|
|
|
PRUNEMINS=180 # 3h
|
|
|
|
SOCKETFILEYESTERDAY="$LOGFILEBASE$(date -d yesterday +%y%m%d).txt"
|
|
if [[ -f $SOCKETFILEYESTERDAY ]] && (( $(date -d "1970-01-01 $(date +%T) +0:00" +%s) > PRUNEMINS * 60 ))
|
|
then
|
|
# If we're longer than PRUNEMINS into today, remove yesterday's file
|
|
rm -v -f "$SOCKETFILEYESTERDAY"
|
|
fi
|
|
|
|
# if the PRUNESTARTFILE file doesn't exist
|
|
# note down that we started up, write down 0 for the next prune as nothing will be older than PRUNEMINS
|
|
if [[ ! -f "$PRUNESTARTFILE" ]] || [[ "$LASTFENCEDATE" != "$FENCEDATE" ]]; then
|
|
echo 0 > $PRUNESTARTFILE
|
|
# if PRUNESTARTFILE is older than PRUNEMINS, do the pruning
|
|
elif [[ $(find $PRUNESTARTFILE -mmin +$PRUNEMINS | wc -l) == 1 ]]; then
|
|
read -r CUTLINES < "$PRUNESTARTFILE"
|
|
if (( $(wc -l < "$SOCKETFILE") < CUTLINES )); then
|
|
LOG "PRUNE ERROR: can't retain more lines than $SOCKETFILE has, retaining all lines, regular prune after next interval."
|
|
CUTLINES=0
|
|
fi
|
|
tmpfile=$(mktemp)
|
|
tail --lines=+$((CUTLINES + 1)) "$SOCKETFILE" > "$tmpfile"
|
|
|
|
# restart Socket30003 to ensure that things run smoothly:
|
|
touch /tmp/socket-cleanup # this flags the socket30003 runfile not to complain about the exit and restart immediately
|
|
killall /usr/bin/perl
|
|
sleep .1 # give the script a moment to exit, then move the files
|
|
|
|
mv -f "$tmpfile" "$SOCKETFILE"
|
|
rm -f "$tmpfile"
|
|
|
|
# update line numbers
|
|
(( READLINES -= CUTLINES ))
|
|
(( CURRCOUNT -= CUTLINES ))
|
|
|
|
LOG "pruned $CUTLINES lines from $SOCKETFILE, current lines $CURRCOUNT"
|
|
# socket30003 will start up on its own with a small delay
|
|
|
|
# note the current position in the file, the next prune run will cut everything above that line
|
|
echo $READLINES > $PRUNESTARTFILE
|
|
fi
|
|
|
|
# Now write the $CURRCOUNT back to the TMP file for use next time Planefence is invoked:
|
|
echo "$CURRCOUNT" > "$TMPLINES"
|
|
|
|
if [[ "$LASTFENCEDATE" != "$FENCEDATE" ]]; then
|
|
TOTALLINES=0
|
|
READLINES=0
|
|
fi
|
|
|
|
# update TOTALLINES and write it back to the file
|
|
TOTALLINES=$(( TOTALLINES + CURRCOUNT - READLINES ))
|
|
echo "$TOTALLINES" > "$TOTLINES"
|
|
|
|
LOG "Current run starts at line $READLINES of $CURRCOUNT, with $TOTALLINES lines for today"
|
|
|
|
# Now create a temp file with the latest logs
|
|
tail --lines=+"$READLINES" "$SOCKETFILE" > "$INFILETMP"
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "2. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- invoking planefence.py" || true
|
|
|
|
# First, run planefence.py to create the CSV file:
|
|
LOG "Invoking planefence.py..."
|
|
$PLANEFENCEDIR/planefence.py --logfile="$INFILETMP" --outfile="$OUTFILETMP" --maxalt="$MAXALT" --altcorr="${ALTCORR:-0}" --dist="$DIST" --distunit="$DISTUNIT" --lat="$LAT" --lon="$LON" "$VERBOSE" "$CALCDIST" --trackservice="adsbexchange" | LOG
|
|
LOG "Returned from planefence.py..."
|
|
|
|
# Now we need to combine any double entries. This happens when a plane was in range during two consecutive Planefence runs
|
|
# A real simple solution could have been to use the Linux 'uniq' command, but that won't allow us to easily combine them
|
|
|
|
# Compare the last line of the previous CSV file with the first line of the new CSV file and combine them if needed
|
|
# Only do this is there are lines in both the original and the TMP csv files
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "3. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- returned from planefence.py, start pruning duplicates" || true
|
|
|
|
if [[ -f "$OUTFILETMP" ]] && [[ -f "$OUTFILECSV" ]]; then
|
|
while read -r newline
|
|
do
|
|
IFS="," read -ra newrec <<< "$newline"
|
|
if grep -q "^${newrec[0]}," "$OUTFILECSV"
|
|
then
|
|
#debug echo -n "There is a matching ICAO... ${newrec[1]} "
|
|
# there's a ICAO match between the new record and the existing file
|
|
# grab the last occurrence of the old record
|
|
oldline=$(grep "^${newrec[0]}," "$OUTFILECSV" 2>/dev/null | tail -1)
|
|
IFS="," read -ra oldrec <<< "$oldline"
|
|
if (( $(date -d "${newrec[2]}" +%s) - $(date -d "${oldrec[3]}" +%s) > COLLAPSEWITHIN ))
|
|
then
|
|
# we're outside the collapse window. Write the string to $OUTFILECSV
|
|
echo "$newline" >> "$OUTFILECSV"
|
|
#debug echo "outside COLLAPSE window: old end=${oldrec[3]} new start=${newrec[2]}"
|
|
else
|
|
# we are inside the collapse window and need to collapse the records.
|
|
# Insert newrec's end time into oldrec. Do this ONLY for the line where the ICAO and the start time matches:
|
|
# we also need to take the smallest altitude and distance
|
|
(( $(echo "${newrec[4]} < ${oldrec[4]}" | bc -l) )) && NEWALT=${newrec[4]} || NEWALT=${oldrec[4]}
|
|
(( $(echo "${newrec[5]} < ${oldrec[5]}" | bc -l) )) && NEWDIST=${newrec[5]} || NEWDIST=${oldrec[5]}
|
|
sed -i "s|\(${oldrec[0]}\),\([A-Z0-9@-]*\),\(${oldrec[2]}\),\([0-9 /:]*\),\([0-9]*\),\([0-9\.]*\),\(.*\)|\1,\2,\3,${newrec[3]},$NEWALT,$NEWDIST,\7|" "$OUTFILECSV"
|
|
# ^ ICAO ^ ^ flt/tail ^ ^ starttime ^ ^ endtime ^ ^ alt ^ ^dist^ ^rest^
|
|
# \1 \2 \3 \4 \5 \6 \7
|
|
#sed -i "s|\(${oldrec[0]}\),\([A-Z0-9@-]*\),\(${oldrec[2]}\),\([0-9 /:]*\),\(.*\)|\1,\2,\3,${newrec[3]},\5|" "$OUTFILECSV"
|
|
# ^ ICAO ^ ^ flt/tail ^ ^ starttime ^ ^ endtime ^ ^rest^
|
|
#debug echo "COLLAPSE: inside collapse window: old end=${oldrec[3]} new end=${newrec[3]}"
|
|
#debug echo "sed line:"
|
|
#debug echo "sed -i \"s|\(${oldrec[0]}\),\([A-Z0-9@-]*\),\(${oldrec[2]}\),\([0-9 /:]*\),\([0-9]*\),\([0-9\.]*\),\(.*\)|\1,\2,\3,${newrec[3]},$NEWALT,$NEWDIST,\7|\" \"$OUTFILECSV\""
|
|
fi
|
|
else
|
|
# the ICAO fields did not match and we should write it to the database:
|
|
#debug echo "${newrec[1]}: no matching ICAO / no collapsing considered"
|
|
echo "$newline" >> "$OUTFILECSV"
|
|
fi
|
|
done < "$OUTFILETMP"
|
|
else
|
|
# there's potentially no OUTFILECSV. Move OUTFILETMP to OUTFILECSV if one exists
|
|
[[ -f "$OUTFILETMP" ]] && mv -f "$OUTFILETMP" "$OUTFILECSV"
|
|
fi
|
|
rm -f "$OUTFILETMP"
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "4. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done pruning duplicates, invoking noise2fence" || true
|
|
|
|
# Now check if we need to add noise data to the csv file
|
|
if [[ "$NOISECAPT" == "1" ]]; then
|
|
LOG "Invoking noise2fence!"
|
|
$PLANEFENCEDIR/noise2fence.sh
|
|
else
|
|
LOG "Info: Noise2Fence not enabled"
|
|
fi
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "5. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done invoking noise2fence, applying dirty fixes" || true
|
|
|
|
#Dirty fix -- sometimes the CSV file needs fixing
|
|
$PLANEFENCEDIR/pf-fix.sh "$OUTFILECSV"
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "6. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done applying dirty fixes, applying filters" || true
|
|
|
|
# Ignore list -- first clean up the list to ensure there are no empty lines
|
|
sed -i '/^$/d' "$IGNORELIST" 2>/dev/null
|
|
# now apply the filter
|
|
# shellcheck disable=SC2126
|
|
LINESFILTERED=$(grep -i -f "$IGNORELIST" "$OUTFILECSV" 2>/dev/null | wc -l)
|
|
if (( LINESFILTERED > 0 ))
|
|
then
|
|
grep -v -i -f "$IGNORELIST" "$OUTFILECSV" > /tmp/pf-out.tmp
|
|
mv -f /tmp/pf-out.tmp "$OUTFILECSV"
|
|
fi
|
|
|
|
# rewrite LINESFILTERED to file
|
|
if [[ -f /run/planefence/filtered-$FENCEDATE ]]; then
|
|
read -r i < "/run/planefence/filtered-$FENCEDATE"
|
|
else
|
|
i=0
|
|
fi
|
|
echo $((LINESFILTERED + i)) > "/run/planefence/filtered-$FENCEDATE"
|
|
|
|
# if IGNOREDUPES is ON then remove duplicates
|
|
if [[ "$IGNOREDUPES" == "ON" ]]; then
|
|
LINESFILTERED=$(awk -F',' 'seen[$1 gsub("/@/","", $2)]++' "$OUTFILECSV" 2>/dev/null | wc -l)
|
|
if (( i>0 ))
|
|
then
|
|
# awk prints only the first instance of lines where fields 1 and 2 are the same
|
|
awk -F',' '!seen[$1 gsub("/@/","", $2)]++' "$OUTFILECSV" > /tmp/pf-out.tmp
|
|
mv -f /tmp/pf-out.tmp "$OUTFILECSV"
|
|
fi
|
|
# rewrite LINESFILTERED to file
|
|
if [[ -f /run/planefence/filtered-$FENCEDATE ]]; then
|
|
read -r i < "/run/planefence/filtered-$FENCEDATE"
|
|
else
|
|
i=0
|
|
fi
|
|
echo $((LINESFILTERED + i)) > "/run/planefence/filtered-$FENCEDATE"
|
|
|
|
fi
|
|
|
|
# see if we need to invoke PlaneTweet:
|
|
[[ "$BASETIME" != "" ]] && echo "7. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done applying filters, invoking PlaneTweet" || true
|
|
|
|
if chk_enabled "$PLANETWEET" \
|
|
|| chk_enabled "${PF_DISCORD}" \
|
|
|| chk_enabled "$PF_MASTODON" \
|
|
|| [[ -n "$BLUESKY_HANDLE" ]] \
|
|
|| [[ -n "$RSS_SITELINK" ]] \
|
|
|| chk_enabled "$PF_TELEGRAM_ENABLED" \
|
|
|| [[ -n "$MQTT_URL" ]]; then
|
|
LOG "Invoking planefence_notify.sh for notifications"
|
|
$PLANEFENCEDIR/planefence_notify.sh today "$DISTUNIT" "$ALTUNIT"
|
|
else
|
|
[[ "$1" != "" ]] && LOG "Info: planefence_notify.sh not called because we're doing a manual full run" || LOG "Info: PlaneTweet not enabled"
|
|
fi
|
|
|
|
# run planefence-rss.sh in the background:
|
|
{ timeout 120 /usr/share/planefence/planefence-rss.sh; } &
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "8. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done invoking planefence_notify.sh, invoking PlaneHeat" || true
|
|
|
|
# And see if we need to run PLANEHEAT
|
|
if chk_enabled "$PLANEHEAT" && [[ -f "${PLANEHEATSCRIPT}" ]] # && [[ -f "$OUTFILECSV" ]] <-- commented out to create heatmap even if there's no data
|
|
then
|
|
LOG "Invoking PlaneHeat!"
|
|
"${s6wrap[@]}" echo "Invoking PlaneHeat..."
|
|
$PLANEHEATSCRIPT
|
|
LOG "Returned from PlaneHeat"
|
|
else
|
|
LOG "Skipped PlaneHeat"
|
|
fi
|
|
|
|
# Now let's link to the latest Spectrogram, if one was generated for today:
|
|
[[ "$BASETIME" != "" ]] && echo "9. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done invoking invoking PlaneHeat, getting NoiseCapt stuff" || true
|
|
|
|
if [[ "$NOISECAPT" == "1" ]]; then
|
|
[[ "$BASETIME" != "" ]] && echo "9a. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- getting latest Spectrogram" || true
|
|
# get the latest spectrogram from the remote server
|
|
curl --fail -s "$REMOTENOISE/noisecapt-spectro-latest.png" >"$OUTFILEDIR/noisecapt-spectro-latest.png"
|
|
|
|
# also create a noisegraph for the full day:
|
|
[[ "$BASETIME" != "" ]] && echo "9b. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- creating day-long Noise Graph" || true
|
|
rm -f /tmp/noiselog 2>/dev/null
|
|
[[ -f "/usr/share/planefence/persist/.internal/noisecapt-$(date -d "yesterday" +%y%m%d).log" ]] && cp -f "/usr/share/planefence/persist/.internal/noisecapt-$(date -d "yesterday" +%y%m%d).log" /tmp/noiselog
|
|
[[ -f "/usr/share/planefence/persist/.internal/noisecapt-$(date -d "today" +%y%m%d).log" ]] && cat "/usr/share/planefence/persist/.internal/noisecapt-$(date -d "today" +%y%m%d).log" >> /tmp/noiselog
|
|
gnuplot -e "offset=$(echo "$(date +%z) * 36" | sed 's/+[0]\?//g' | bc); start=$(date -d "yesterday" +%s); end=$(date +%s); infile='/tmp/noiselog'; outfile='/usr/share/planefence/html/noiseplot-latest.jpg'; plottitle='Noise Plot over Last 24 Hours (End date = $(date +%Y-%m-%d))'; margin=60" $PLANEFENCEDIR/noiseplot.gnuplot
|
|
rm -f /tmp/noiselog 2>/dev/null
|
|
|
|
elif (( $(find "$TMPDIR"/noisecapt-spectro*.png -daystart -maxdepth 1 -mmin -1440 -print 2>/dev/null | wc -l ) > 0 ))
|
|
then
|
|
ln -sf "$(find "$TMPDIR"/noisecapt-spectro*.png -daystart -maxdepth 1 -mmin -1440 -print 2>/dev/null | tail -1)" "$OUTFILEDIR"/noisecapt-spectro-latest.png
|
|
else
|
|
rm -f "$OUTFILEDIR"/noisecapt-spectro-latest.png 2>/dev/null
|
|
fi
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "10. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done getting NoiseCapt stuff, invoking plane-alert.sh" || true
|
|
|
|
# Next, we are going to print today's HTML file:
|
|
# Note - all text between 'cat' and 'EOF' is HTML code:
|
|
|
|
"${s6wrap[@]}" echo "Writing Planefence web page..."
|
|
[[ "$BASETIME" != "" ]] && echo "11. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- starting to build the webpage" || true
|
|
|
|
cat <<EOF >"$OUTFILEHTMTMP"
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<!--
|
|
# You are taking an interest in this code! Great!
|
|
# I'm not a professional programmer, and your suggestions and contributions
|
|
# are always welcome. Join me at the GitHub link shown below, or via email
|
|
# at kx1t (at) kx1t (dot) com.
|
|
#
|
|
# Copyright 2020-2025 Ramon F. Kolb, kx1t - licensed under the terms and conditions
|
|
# of GPLv3. The terms and conditions of this license are included with the Github
|
|
# distribution of this package, and are also available here:
|
|
# https://github.com/sdr-enthusiasts/docker-planefence/
|
|
#
|
|
# The package contains contributions from several other packages, that may be licensed
|
|
# under different terms. Attributions and our thanks can be found at
|
|
# https://github.com/sdr-enthusiasts/docker-planefence/blob/main/ATTRIBUTION.md
|
|
#
|
|
# Summary of License Terms
|
|
# This program is free software: you can redistribute it and/or modify it under the terms of
|
|
# the GNU General Public License as published by the Free Software Foundation, either version 3
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with this program.
|
|
# If not, see https://www.gnu.org/licenses/.
|
|
-->
|
|
<head>
|
|
<link rel="icon" href="favicon.ico">
|
|
<meta charset="UTF-8">
|
|
EOF
|
|
|
|
if chk_enabled "${AUTOREFRESH,,}"; then
|
|
REFRESH_INT="$(sed -n 's/\(^\s*PF_INTERVAL=\)\(.*\)/\2/p' /usr/share/planefence/persist/planefence.config)"
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<meta http-equiv="refresh" content="$REFRESH_INT">
|
|
EOF
|
|
fi
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<!-- scripts and stylesheets related to the datatables functionality: -->
|
|
<!-- please note that these scripts and plugins are licensed by their authors and IP owners
|
|
For license terms and copyright ownership, see each linked file -->
|
|
<!-- JQuery itself: -->
|
|
<script src="scripts/jquery-3.7.1.min.js"></script>
|
|
|
|
<!-- DataTables CSS and plugins: -->
|
|
<link href="scripts/dataTables.dataTables.min.css" rel="stylesheet">
|
|
<link href="scripts/buttons.dataTables.min.css" rel="stylesheet">
|
|
<script src="scripts/jszip.min.js"></script>
|
|
<script src="scripts/pdfmake.min.js"></script>
|
|
<script src="scripts/vfs_fonts.js"></script>
|
|
<script src="scripts/dataTables.min.js"></script>
|
|
<script src="scripts/dataTables.buttons.min.js"></script>
|
|
<script src="scripts/buttons.html5.min.js"></script>
|
|
<script src="scripts/buttons.print.min.js"></script>
|
|
|
|
<!-- plugin to make JQuery table columns resizable by the user: -->
|
|
<script src="scripts/colResizable-1.6.min.js"></script>
|
|
|
|
<title>ADS-B 1090 MHz Planefence</title>
|
|
EOF
|
|
|
|
if [[ -f "$PLANEHEATHTML" ]]; then
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<link rel="stylesheet" href="scripts/leaflet.css" />
|
|
<script src="scripts/leaflet.js"></script>
|
|
EOF
|
|
fi
|
|
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<style>
|
|
body { font: 12px/1.4 "Helvetica Neue", Arial, sans-serif;
|
|
EOF
|
|
if chk_enabled "$DARKMODE"; then
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
background-color: black;
|
|
color: white;
|
|
EOF
|
|
else
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
background-image: url('pf_background.jpg');
|
|
background-repeat: no-repeat;
|
|
background-attachment: fixed;
|
|
background-size: cover;
|
|
color: black;
|
|
EOF
|
|
fi
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
}
|
|
a { color: #0077ff; }
|
|
h1 {text-align: center}
|
|
h2 {text-align: center}
|
|
.planetable { border: 1; font: 12px/1.4 "Helvetica Neue", Arial, sans-serif; text-align: center }
|
|
.history { border: none; margin: 0; padding: 0; font: 12px/1.4 "Helvetica Neue", Arial, sans-serif; }
|
|
.footer{ border: none; margin: 0; padding: 0; font: 12px/1.4 "Helvetica Neue", Arial, sans-serif; text-align: center }
|
|
/* Sticky table header */
|
|
table thead tr th tbody, table.dataTable tbody th, table.dataTable tbody td {
|
|
EOF
|
|
if chk_enabled "$DARKMODE"; then
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
background-color: black;
|
|
color: white;
|
|
EOF
|
|
else
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
background-color: #f0f6f6;
|
|
color: black;
|
|
EOF
|
|
fi
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
position: sticky;
|
|
z-index: 100;
|
|
top: 0 !important;
|
|
padding: 2 !important;
|
|
margin-top: 1 !important;
|
|
margin-bottom: 1 !important;
|
|
}
|
|
td, table.dataTable tbody td {
|
|
text-align: center;
|
|
vertical-align: middle;"
|
|
}
|
|
</style>
|
|
$(if [[ -n "$MASTODON_SERVER" ]] && [[ -n "$MASTODON_ACCESS_TOKEN" ]] && [[ -n "$MASTODON_NAME" ]]; then echo "<link href=\"https://$MASTODON_SERVER/@$MASTODON_NAME\" rel=\"me\">"; fi)
|
|
</head>
|
|
|
|
$(if chk_enabled "$DARKMODE"; then echo "<body class=\"dark\">"; else echo "<body>"; fi)
|
|
<script type="text/javascript">
|
|
\$(document).ready(function() {
|
|
\$("#mytable").dataTable( {
|
|
order: [[0, 'desc']],
|
|
pageLength: $TABLESIZE,
|
|
lengthMenu: [10, 25, 50, 100, { label: 'All', value: -1 }],
|
|
layout: { top2Start: { buttons: ['copy', 'csv', 'excel', 'pdf', 'print'] },
|
|
top1Start: { search: { placeholder: 'Type search here' } },
|
|
topEnd: '',
|
|
}
|
|
});
|
|
\$("#mytable").colResizable({
|
|
liveDrag: true,
|
|
gripInnerHtml: "<div class='grip'></div>",
|
|
draggingClass: "dragging",
|
|
resizeMode: 'flex',
|
|
postbackSave: true
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<h1>Planefence</h1>
|
|
<h2>Show aircraft in range of <a href="$MYURL" target="_top">$MY</a> ADS-B station for a specific day</h2>
|
|
${PF_MOTD}
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details open>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Executive Summary</summary>
|
|
<ul>
|
|
<li>Last update: $(date +"%b %d, %Y %R:%S %Z")
|
|
<li>Maximum distance from <a href="https://www.openstreetmap.org/?mlat=$LAT_VIS&mlon=$LON_VIS#map=14/$LAT_VIS/$LON_VIS&layers=H" target=_blank>${LAT_VIS}°N, ${LON_VIS}°E</a>: $DIST $DISTUNIT
|
|
<li>Only aircraft below $(printf "%'.0d" "$MAXALT" | sed ':a;s/\B[0-9]\{3\}\>/,&/g;ta') $ALTUNIT are reported
|
|
<li>Data extracted from $(printf "%'.0d" $TOTALLINES | sed ':a;s/\B[0-9]\{3\}\>/,&/g;ta') <a href="https://en.wikipedia.org/wiki/Automatic_dependent_surveillance_%E2%80%93_broadcast" target="_blank">ADS-B messages</a> received since midnight today
|
|
EOF
|
|
{ [[ -n "$FUDGELOC" ]] && printf " <li> Please note that the reported station coordinates and the center of the circle on the heatmap are rounded for privacy protection. They do not reflect the exact location of the station\n"
|
|
[[ -f "/run/planefence/filtered-$FENCEDATE" ]] && [[ -f "$IGNORELIST" ]] && (( $(grep -c "^[^#;]" "$IGNORELIST") > 0 )) && printf " <li> %d entries were filtered out today because of an <a href=\"ignorelist.txt\" target=\"_blank\">ignore list</a>\n" "$(</run/planefence/filtered-"$FENCEDATE")"
|
|
if [[ -n "$MASTODON_SERVER" ]] && [[ -n "$MASTODON_ACCESS_TOKEN" ]] && [[ -n "$MASTODON_NAME" ]]; then
|
|
printf "<li>Get notified instantaneously of aircraft in range by following <a href=\"https://%s/@%s\" rel=\"me\">@%s@%s</a> on Mastodon" \
|
|
"$MASTODON_SERVER" "$MASTODON_NAME" "$MASTODON_NAME" "$MASTODON_SERVER"
|
|
fi
|
|
if [[ -n "$BLUESKY_HANDLE" ]] && [[ -n "$BLUESKY_APP_PASSWORD" ]]; then printf "<li>Planefence notifications are sent to <a href=\"https://bsky.app/profile/%s\" target=\"_blank\">@%s</a> at BlueSky \n" "$BLUESKY_HANDLE" "$BLUESKY_HANDLE"; fi
|
|
[[ "$PLANETWEET" != "" ]] && printf "<li>Get notified instantaneously of aircraft in range by following <a href=\"http://twitter.com/%s\" target=\"_blank\">@%s</a> on Twitter!\n" "$PLANETWEET" "$PLANETWEET"
|
|
printf "<li> A RSS feed of the aircraft detected with Planefence is available at <a href=\"planefence.rss\">planefence.rss</a>\n"
|
|
[[ -n "$PA_LINK" ]] && printf "<li> Additionally, click <a href=\"%s\" target=\"_blank\">here</a> to visit Plane Alert: a watchlist of aircraft in general range of the station\n" "$PA_LINK"
|
|
} >> "$OUTFILEHTMTMP"
|
|
|
|
# shellcheck disable=SC2129
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
</ul>
|
|
</details>
|
|
</article>
|
|
</section>
|
|
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Click on the triangle next to the header to show/collapse the section </summary>
|
|
</details>
|
|
</article>
|
|
</section>
|
|
|
|
<section style="border: none; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details open>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Flights In Range Table</summary>
|
|
<ul>
|
|
EOF
|
|
|
|
{ printf "<li>Click on the Transponder ID to see the full flight information/history (from <a href=\"https://$TRACKSERVICE/?lat=%s&lon=%s&zoom=11.0\" target=\"_blank\">$TRACKSERVICE</a>)" "$LAT_VIS" "$LON_VIS"
|
|
printf "<li>Click on the Flight Number to see the full flight information/history (from <a href=http://www.flightaware.com\" target=\"_blank\">FlightAware</a>)"
|
|
printf "<li>Click on the Owner Information to see the FAA record for this plane (private, US registered planes only)"
|
|
(( ALTCORR > 0 )) && printf "<li>Minimum altitude is the altitude above local ground level, which is %s %s MSL." "$ALTCORR" "$ALTUNIT" >> "$OUTFILEHTMTMP" || printf "<li>Minimum altitude is the altitude above sea level"
|
|
|
|
[[ "$PLANETWEET" != "" ]] && printf "<li>Click on the word "yes" in the <b>Tweeted</b> column to see the Tweet.\n<li>Note that tweets are issued after a slight delay\n"
|
|
(( $(find "$TMPDIR"/noisecapt-spectro*.png -daystart -maxdepth 1 -mmin -1440 -print 2>/dev/null | wc -l ) > 0 )) && printf "<li>Click on the word "Spectrogram" to see the audio spectrogram of the noisiest period while the aircraft was in range\n"
|
|
chk_enabled "$PLANEALERT" && printf "<li>See a list of aircraft matching the station's Alert List <a href=\"%s\" target=\"_blank\">here</a>\n" "${PA_LINK:-plane-alert}"
|
|
printf "<li> Press the header of any of the columns to sort by that column\n"
|
|
printf "</ul>\n"
|
|
} >> "$OUTFILEHTMTMP"
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "12. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- starting to write the PF table to the website" || true
|
|
|
|
WRITEHTMLTABLE "$OUTFILECSV" "$OUTFILEHTMTMP"
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "13. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done writing the PF table to the website" || true
|
|
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
</details>
|
|
</article>
|
|
</section>
|
|
EOF
|
|
|
|
# Write some extra text if NOISE data is present
|
|
if [[ "$HASNOISE" != "false" ]]; then
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Notes on sound level data</summary>
|
|
<ul>
|
|
<li>This data is for informational purposes only and is of indicative value only. It was collected using a non-calibrated device under uncontrolled circumstances.
|
|
<li>The data unit is "dBFS" (Decibels-Full Scale). 0 dBFS is the loudest sound the device can capture. Lower values, like -99 dBFS, mean very low noise. Higher values, like -10 dBFS, are very loud.
|
|
<li>The system measures the <a href="https://en.wikipedia.org/wiki/Root_mean_square" target="_blank">RMS</a> of the sound level for contiguous periods of 5 seconds.
|
|
<li>'Loudness' is the difference (in dB) between the Peak RMS Sound and the 1 hour average. It provides an indication of how much louder than normal it was when the aircraft flew over.
|
|
<li>Loudness values of greater than $YELLOWLIMIT dB are in red. Values greater than $GREENLIMIT dB are in yellow.
|
|
<li>'Peak RMS Sound' is the highest measured 5-seconds RMS value during the time the aircraft was in the coverage area.
|
|
<li>The subsequent values are 1, 5, 10, and 60 minutes averages of these 5 second RMS measurements for the period leading up to the moment the aircraft left the coverage area.
|
|
<li>One last, but important note: The reported sound levels are general outdoor ambient noise in a suburban environment. The system doesn't just capture airplane noise, but also trucks on a nearby highway, lawnmowers, children playing, people working on their projects, air conditioner noise, etc.
|
|
<ul>
|
|
</details>
|
|
</article>
|
|
</section>
|
|
<hr/>
|
|
EOF
|
|
fi
|
|
|
|
# if $PLANEHEATHTML exists, then add the heatmap
|
|
if chk_enabled "$PLANEHEAT" && [[ -f "$PLANEHEATHTML" ]]; then
|
|
# shellcheck disable=SC2129
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details open>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Heatmap</summary>
|
|
<ul>
|
|
<li>This heatmap reflects passing frequency and does not indicate perceived noise levels
|
|
<li>The heatmap is limited to the coverage area of Planefence, for any aircraft listed in the table above
|
|
$( [[ -d "$OUTFILEDIR/../heatmap" ]] && printf "<li>For a heatmap of all planes in range of the station, please click <a href=\"../heatmap\" target=\"_blank\">here</a>" )
|
|
</ul>
|
|
EOF
|
|
cat "$PLANEHEATHTML" >>"$OUTFILEHTMTMP"
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
</details>
|
|
</article>
|
|
</section>
|
|
<hr/>
|
|
EOF
|
|
fi
|
|
|
|
# If there's a latest spectrogram, show it
|
|
if [[ -f "$OUTFILEDIR/noisecapt-spectro-latest.png" ]]; then
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<section style="border: none; margin: 0; padding: 0; font: 12px/1.4 'Helvetica Neue', Arial, sans-serif;">
|
|
<article>
|
|
<details open>
|
|
<summary style="font-weight: 900; font: 14px/1.4 'Helvetica Neue', Arial, sans-serif;">Latest Spectrogram</summary>
|
|
<ul>
|
|
<li>Latest as of the time of generation of this page
|
|
<li>For spectrograms related to overflying aircraft, see table above
|
|
</ul>
|
|
<a href="noisecapt-spectro-latest.png" target="_blank"><img src="noisecapt-spectro-latest.png"></a>
|
|
$([[ -f "/usr/share/planefence/html/noiseplot-latest.jpg" ]] && echo "<a href=\"noiseplot-latest.jpg\" target=\"_blank\"><img src=\"noiseplot-latest.jpg\"></a>")
|
|
</details>
|
|
</section>
|
|
<hr/>
|
|
EOF
|
|
fi
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "14. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- starting to write the history line to the website" || true
|
|
WRITEHTMLHISTORY "$OUTFILEDIR" "$OUTFILEHTMTMP"
|
|
LOG "Done writing history"
|
|
[[ "$BASETIME" != "" ]] && echo "15. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done writing the history line to the website" || true
|
|
|
|
|
|
cat <<EOF >>"$OUTFILEHTMTMP"
|
|
<div class="footer">
|
|
<hr/>Planefence $VERSION is part of <a href="https://github.com/sdr-enthusiasts/docker-planefence" target="_blank">KX1T's Planefence Open Source Project</a>, available on GitHub. Support is available on the #Planefence channel of the SDR Enthusiasts Discord Server. Click the Chat icon below to join.
|
|
$(if [[ -f /root/.buildtime ]]; then printf " Build: %s" "$([[ -f /usr/share/planefence/branch ]] && cat /usr/share/planefence/branch || cat /root/.buildtime)"; fi)
|
|
<br/>© Copyright 2020-2025 by Ramón F. Kolb, kx1t. Please see <a href="https://github.com/sdr-enthusiasts/docker-planefence/blob/main/ATTRIBUTION.md" target="_blank">here</a> for attributions to our contributors and open source packages used.
|
|
<br/><a href="https://github.com/sdr-enthusiasts/docker-planefence" target="_blank"><img src="https://img.shields.io/github/actions/workflow/status/sdr-enthusiasts/docker-planefence/deploy.yml"></a>
|
|
<a href="https://discord.gg/VDT25xNZzV"><img src="https://img.shields.io/discord/734090820684349521" alt="discord"></a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
|
|
# Last thing we need to do, is repoint INDEX.HTML to today's file
|
|
|
|
[[ "$BASETIME" != "" ]] && echo "16. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- starting final cleanup" || true
|
|
|
|
pushd "$OUTFILEDIR" > /dev/null || true
|
|
mv -f "$OUTFILEHTMTMP" "$OUTFILEHTML"
|
|
ln -sf "${OUTFILEHTML##*/}" index.html
|
|
popd > /dev/null || true
|
|
|
|
# VERY last thing... ensure that the log doesn't overflow:
|
|
if [[ "$VERBOSE" != "" ]] && [[ "$LOGFILE" != "" ]] && [[ "$LOGFILE" != "logger" ]] && [[ -f $LOGFILE ]] && (( $(wc -l < "$LOGFILE") > 8000 ))
|
|
then
|
|
#sed -i -e :a -e '$q;N;8000,$D;ba'
|
|
tail -n 4000 "$LOGFILE" > "$LOGFILE.tmp"
|
|
mv -f "$LOGFILE.tmp" "$LOGFILE"
|
|
fi
|
|
|
|
echo "$FENCEDATE" > "$LASTFENCEFILE"
|
|
|
|
# If $PLANEALERT=on then lets call plane-alert to see if the new lines contain any planes of special interest:
|
|
if chk_enabled "$PLANEALERT"; then
|
|
LOG "Calling Plane-Alert as $PLALERTFILE $INFILETMP"
|
|
"${s6wrap[@]}" echo "Invoking Plane-Alert..."
|
|
$PLALERTFILE "$INFILETMP"
|
|
fi
|
|
|
|
# That's all
|
|
# This could probably have been done more elegantly. If you have changes to contribute, I'll be happy to consider them for addition
|
|
# to the GIT repository! --Ramon
|
|
|
|
# Wait for any background processes to finish
|
|
# Currently, planefence_notify.sh and planefence-rss.sh are the only background processes that are invoked, and those have a time limit of 120 secs
|
|
wait $!
|
|
|
|
LOG "Finishing Planefence... sayonara!"
|
|
[[ "$BASETIME" != "" ]] && echo "17. $(bc -l <<< "$(date +%s.%2N) - $BASETIME")s -- done final cleanup" || true
|
|
"${s6wrap[@]}" echo "Done"
|