Skip to content

Instantly share code, notes, and snippets.

@netscylla
Created October 10, 2024 06:46
Show Gist options
  • Save netscylla/3a06e7a8abb31b71799e8531fcd3859a to your computer and use it in GitHub Desktop.
Save netscylla/3a06e7a8abb31b71799e8531fcd3859a to your computer and use it in GitHub Desktop.
modified pistar-lastqso to function on WPSD
#!/bin/bash
# shellcheck source=/dev/null disable=SC2206,SC2034
#
# pistar-lastqso
# Written by: Ken Cormack (KE8DPF), [email protected]
# Copyright: © 2024, Ken Cormack
# github: https://github.com/kencormack/pistar-lastqso
# QRZ page: https://www.qrz.com/db/KE8DPF
# Modified 2024-10-08 Andy (2W0APD) for WPSD
###########################################################
# INITIALIZATION
###########################################################
set -o allexport
VERSION=3.26
#----------
if [[ "${EUID}" -eq 0 ]]
then
echo
echo "ERROR:" >&2
echo " Execution as \"root\" not required." >&2
echo " Please run as the normal \"pi-star\" userid." >&2
echo
exit
fi
#----------
# This condition occurs during development only. A typical
# user will never encounter this.
if [[ -n "$(pgrep -o "build_cache")" ]]
then
echo
echo "ERROR:" >&2
echo " A cache build is in progress." >&2
echo
exit
fi
#----------
# Turn on privileged mode. In this mode, the $BASH_ENV and $ENV files
# are not processed, shell functions are not inherited from the
# environment, and the SHELLOPTS, BASHOPTS, CDPATH and GLOBIGNORE
# variables, if they appear in the environment, are ignored.
set -p
# Make globs that don't get expanded cause errors, rather than getting
# passed to the command with the * intact.
shopt -s failglob
# Ensure that ERR traps are inherited by functions, command substitutions,
# and subshell environments.
set -o errtrace
# Ensure that DEBUG and RETURN traps are inherited by functions, command substitutions,
# and subshell environments. (The -E causes errors within functions to bubble up.)
set -E -o functrace
#----------
# Save the current terminal configuration
STTY="$(stty -g)"
# Turn off character/word/line erase, and
# turn off echo of chars as they are typed
stty cbreak -echo
#----------
# NOTE #1: Originally written for pi-star >= 4.1.4, for DMR mode ONLY.
# I developed this first for DMR, using a TYT MD-UV390GPS. I later
# obtained a Yaesu FT-70DR and added support for YSF. Then, by simply
# configuring pi-star and watching the logs, network-traffic-only (no RF
# traffic) was added for NXDN, D-Star, and P25. When a D-Star user sent
# me a sample of his logs, showing D-Star RF log entries, D-Star RF
# support was added. At this writing, support for NXDN, P25, and M17 RF
# voice traffic have also been added.
# NOTE #2: I've tried to write this script with readability in mind.
# A year after having last touched a script, I like to look at it and
# quickly understand what it's doing, without a lot of head scratching.
# Comments are applied liberally throughout, and "clever but cryptic"
# one-liners that are not immediately understandable without reading
# (and then re-reading) various man pages, are generally avoided. The
# priority is understandability, rather than clever elegance or raw
# performance, within reason. I did, however, undertake a performance
# minded sweep through the code, replacing well over a hundred calls
# to external tools like awk, cut, grep and such, with bash variable
# expansion mechanisms. This saves tremendous overhead in loops such
# as this script, particularly on a Pi Zero.
# NOTE #3: I cut my teeth on Sequent hardware and SVR3, 35+ years ago.
# Over three decades, I've worked on nearly all commercial *nix variants.
# Until Linux, none of them came with BASH, and KSH was my defacto shell.
# At heart, I'm a KSH guy. Consequently, you may find "old fashioned"
# ksh-like syntax in here (out of habbit), where a "modern"(?) bash-ism
# might exist, but that I simply forgot to use.
# NOTE #4: This was an effort to A.) eliminate the necessity of a second
# computer from which to simply monitor traffic via use of an HDMI
# console on the pi-star node - although it works terrifically via SSH,
# B.) provide a great deal more information about the traffic being
# observed than what pi-star's own dashboard provides, C.) be as well
# documented as possible to allow anyone to customize the code as they
# wish, and finally, D.) have my stuff be independent of Andy's great
# work (ie: not steal any of his dashboard code.)
#----------
# NOTES ON GETTING RID OF SOME CUTS
#${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
#${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
#${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
#${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
# VAR="${VAR#*=}" same as: cut -f2 -d"="
# VAR="${VAR%,*}" same as: cut -f1 -d","
# If a string contains multiple fields, these do not extract *just* a single "hit".
#
# For that, make use of an array...
# IFS='/'; CUT_ARRAY=(${STRING TO PICK APART}); unset IFS; #IFS is the field separator
# SPECIFIC_NEEDED_DATA="${CUT_ARRAY[0]}" #[0] is first field, [1] is second, [2] is third, etc.
# unset CUT_ARRAY
# This allows selection of a single field, without contaminating that field with
# overlap from another field
#----------
# POSSIBLE FUTURE ENHANCEMENTS TO CACHE UPDATING
# Fast in-place edits using sed:
# deleting records...
# sed -i '/^${callsign}:/d' ${file}
# updating records...
# sed -i "s/^${callsign}:.*/${callsign}:${new_data}/" "${file}"
# The potential complication is whether multiple instances
# of the script are running, each with it's own cache files,
# which currently then get re-merged and saved in $HOME. This
# would put stale records, as well as the updated records,
# back into the saved/merged copies, resulting in multiple
# records with no way to determine which record is then correct.
# Some thinking will be required. Easiest fix would be to
# disallow multiple concurrent instances of the script.
#----------
# For whatever reason, "umask 0077" isn't locking down permissions
# on files written to /tmp, yet symbolically setting it does.
# I'll need to look into that a bit more.
umask u=rw,g=,o=
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
# Start the Elapsed Time clock
SECONDS="0"
#----------
# Get the full path to this script
# for the -i|--info section
MY_PID="$(pgrep -o -f "${BASH} $(readlink -f "${0}")")"
#----------
# For bash's built-in "time" command
# This gives us just the "real" time,
# in seconds, for the "time" command
# that clocks dxcc.pl execution time.
TIMEFORMAT='%R'
#----------
# Any equal signs passed on the cmdline are replaced w/ spaces,
# so that an option, and it's parameter (if any), are two separate
# elements in an array. The array gets passed to fnPARSE_CMDLINE.
#
# Gather what's passed on the cmdline...
# Strip any "=" signs separating an option and it's parameter...
# Place the groomed elements into an array
MY_CMDLINE="${*//=/ }"
read -r -a ARG_LIST <<< "${MY_CMDLINE}"
ARG_LIST_LENGTH="${#ARG_LIST[@]}"
#----------
# Get the tty we're using. Tmp filenames will be suffixed to keep
# multiple concurrent sessions from stomping on each other's files.
MY_TTY="$(tty | sed -u -e 's/\///g' -e 's/dev//')"
#----------
# SET UP THE DEBUG FILE DESCRIPTOR
# If it doesn't already exist, setup the file descriptor for debugging
[[ -e /proc/self/fd/3 ]] || exec 3> /dev/null
# If no redirection has been applied by the user, "${DEBUG_LOG}" will be "/dev/null".
# But if the user has redirected fd 3 to a file, then "${DEBUG_LOG}" will contain the
# name (with full path), to the file.
DEBUG_LOG="$(readlink /proc/self/fd/3 2>/dev/null)"
echo "LASTQSO v${VERSION} - DEBUGGING LOG" >&3
echo -e "==================================\n" >&3
# FOR DEBUGGING: THE DEBUG TRAP
# This trap writes each line to be executed, just before it is executed, to the debug log.
trap 'echo -e "line#: ${LINENO}...\t${BASH_COMMAND}" >&3' DEBUG
# FOR DEBUGGING: THE ERR TRAP
# This trap will log all non-0 return codes.
trap 'echo -e "NON-0: LINE ${LINENO}: RETURN CODE: ${?}\t${BASH_COMMAND}" >&3' ERR
# FOR DEBUGGING: THE RETURN TRAP
# This trap logs the completion of each function, upon return.
trap 'echo -e "leave: ${FUNCNAME[0]} -> back to ${FUNCNAME[1]}\n" >&3' RETURN
#----------
# SET UP THE PROFILING FILE DESCRIPTOR
# For a rough profiling of this script
[[ -e /proc/self/fd/4 ]] || exec 4> /dev/null
# If no redirection has been applied by the user, "${PROFILING_LOG}" will be "/dev/null".
# But if the user has redirected fd 4 to a file, then "${PROFILING_LOG}" will contain the
# name (with full path), to the file.
PROFILING_LOG="$(readlink /proc/self/fd/4 2>/dev/null)"
echo "LASTQSO v${VERSION} - PROFILING LOG" >&4
echo -e "==================================\n" >&4
BASH_XTRACEFD="4"
# Using PS4, "\011" is a tab, and "\t" is a current hh:mm:ss timestamp
# (I don't need subsecond accuracy here.)
PS4='+\011\t ${LINENO}\011'
# Enclose any section of code we want to profile with "set -x"..."set +x".
# Profiling data will be sent to the fd whenever "set -x" is in effect.
# Turning it on now, for example, would basically profile everything from
# here down. Use sparingly, as it can seriously impact performance
# (and don't forget to turn it off when done).
# set -x
#----------
# Defaults that can be overridden with cmdline options...
#
# Timestamps now default to 24Hr format. To go back
# to 12Hr am/pm timestamps, use "-12|--12hr" option.
MIL_TIME="1"
#
# Don't force immediate download of user.csv file.
# Can be overridden with "-c|--csv" cmdline option
GET_CSV_NOW="0"
#
# Don't force immediate download of cty.dat file.
# Can be overridden with "-d|--dat" cmdline option
GET_DAT_NOW="0"
#
# Call upon dxcc.pl to resolve QTH country when
# the callsign can't be found in either the DMR
# user.csv or NXDN NXDN.csv files. Can be overridden
# with "-D|--DXCC" cmdline option.
USE_DXCC="1"
#
# Show errors, whenever they appear in the MMDVM log.
# Can be overridden with "-e|--errors" cmdline option
SHOW_LOG_ERRORS="1"
#
# Maidenhead lookups on US callsigns disabled by default.
# Can be activated with "-g|--grid" cmdline option.
GRID="0"
#
# If callsign is US-based, FCC database queries to
# determine if the license is Novice, Technician, General,
# Advanced, or Extra levels, are disabled by default.
# Can be activated with "-F|--FCC" cmdline option.
FCC="0"
#
# *IF* your display is greater than 130 columns wide,
# *AND* you use the "-t|--top" option, *AND* you add
# the "-i|--info" option, a few helpful details are
# added to the top-right corner of the display. This
# is helpful if you are modifying this script. Other
# than that, it serves little use.
SHOW_DEV_INFO="0"
#
# By default, display logo screen.
# Can be disabled with "-l|--logo" cmdline option
LOGO="1"
#
# Display Callsigns & TGs/DG-IDs, etc., in large font.
# Can be overridden with "-n|--nobig" cmdline option
USE_BANNER="1"
#
# By default, the script will query Brandmeister ONLY ONCE
# at startup, to determine currently linked talkgroups.
# This "-p|--poll" option tells the script to frequently
# poll Brandmeister to refresh the linked TG list. This is
# most helpful to people who like to bounce around between
# dynamically linked TGs, but can slow the script down a bit
# between each QSO, as that is when the polling to refresh
# the list will occur. This delay will be more noticable on
# Raspberry Pi Zero-based hotspots. The quad-core models
# (including the new Pi Zero 2) won't generally notice a delay.
BM_POLL="0"
#
# By default, the script will parse the current "live" log
# using "tail". But if testing, "-r|--replay" will use the
# "cat" command instead, to replay a user-specified log.
REPLAY="0"
#
# Don't display the config and history section at top of screen.
# Can be overridden with "-t|--top" cmdline option
TOP="0"
#
# Allow figlet to auto-wrap as needed.
# Can be overridden with "-w|--wrap" cmdline option
NO_WRAP="0"
#----------
# Define some ways to make things bold, use different colors, control
# scrolling and cursor positioning, and so on. See "man tput" for
# descriptions of each tput option used here. (tput is much more
# predictable across terminal types than cryptic ANSI escape sequences.)
BELL="$(tput bel)" # Sound a bell
FLASH="$(tput flash)" # Flash the screen
# Not all term types (including the pi console's "TERM=linux") support
# tput's alternate screen buffers. If not, just clear the screen.
if infocmp -1 | grep -q "smcup=" && infocmp -1 | grep -q "rmcup="
then
SMCUP="$(tput smcup)" # Set alternate screen buffer
RMCUP="$(tput rmcup)" # Restore primary screen buffer
HAS_BUFFER="1"
else
SMCUP="$(tput clear)"
RMCUP="$(tput clear)"
HAS_BUFFER="0"
fi
CUR_HOME=$(tput cup 0 0) # Send the cursor home, to line 0, column 0
CUR_SC="$(tput sc)" # Save cursor position
CUR_RC="$(tput rc)" # Restore cursor position
CUR_OFF="$(tput civis)" # Turn cursor off
CUR_ON="$(tput cnorm)" # Turn cursor on
CLR_EL="$(tput el)" # Clear to end of line
CLR_ED="$(tput ed)" # Clear to end of display
INS_LINE="$(tput il1)" # Insert a line above current row (pushes everything down)
#DEL_LINE="$(tput dl1)" # Delete a line below current row (pulls everything up)
SGR0="$(tput sgr0)" # Undo text attributes (bold, etc.)
BOLD="$(tput bold)" # Make text bold
DIM="$(tput dim)" # Half-intensity
REV="$(tput rev)" # Reverse text fg/bg colors
SMUL="$(tput smul)" # Start Underline text
RMUL="$(tput rmul)" # End Underline text
#BLINK="$(tput blink)" # Blink text
CUR_UP="$(tput cuu1)" # Cursor up, one line
#CUR_DN="$(tput cud1)" # Cursor down, one line
WRAP_OFF="$(tput rmam)" # Disable line-wrap
WRAP_ON="$(tput smam)" # Enable line-wrap
# Forground colors
BLA="$(tput setaf 0)" # Black
RED="$(tput setaf 1)" # Red
GRE="$(tput setaf 2)" # Green
YEL="$(tput setaf 3)" # Yellow
BLU="$(tput setaf 4)" # Blue
MAG="$(tput setaf 5)" # Magenta
CYA="$(tput setaf 6)" # Cyan
WHI="$(tput setaf 7)" # White
DEF="$(tput setaf 9)" # Default
# Background colors
# Reserved for possible future use
# BG_BLA="$(tput setab 0)" # Black
# BG_RED="$(tput setab 1)" # Red
# BG_GRE="$(tput setab 2)" # Green
# BG_YEL="$(tput setab 3)" # Yellow
# BG_BLU="$(tput setab 4)" # Blue
# BG_MAG="$(tput setab 5)" # Magenta
# BG_CYA="$(tput setab 6)" # Cyan
# BG_WHI="$(tput setab 7)" # White
# BG_DEF="$(tput setab 9)" # Default
DMR_COLOR="${BOLD}${WHI}"
YSF_COLOR="${BOLD}${YEL}"
NXDN_COLOR="${BOLD}${CYA}"
DSTAR_COLOR="${BOLD}${GRE}"
P25_COLOR="${BOLD}${MAG}"
M17_COLOR="${BOLD}${RED}"
OTHER_COLOR="${BOLD}${BLU}"
TICK="✓"
CROSS="✗"
#----------
# ANSI STUFF - for things that tput can't address.
# Not every terminal emulator or term type may support ANSI
# escape sequences, though most common ones do. YMMV
# The ESCape character
ESC=$(echo -n $'\e' 2>/dev/null)
# "tput sgr0" fails to restore terminal settings under certain
# circumstances. This may(?) be a better choice, upon exit.
TERM_RESET="$(echo -ne "${ESC}[0m" 2>/dev/null)"
# Double-height, double-width character set.
# 3 is top half of charset, 4 is bottom half.
# Not every terminal type/emulation supports these.
LARGE_TOP="$(echo -n "${ESC}#3" 2>/dev/null)"
LARGE_BOT="$(echo -n "${ESC}#4" 2>/dev/null)"
# 5 sets line single-width (lines are set this way when cleared by ESC [ J)
NORM_FONT="$(echo -n "${ESC}"#5 2>/dev/null)"
# Single-height, double-width character set
# 6 sets line double-width, with normal height
WIDE_FONT="$(echo -n "${ESC}"#6 2>/dev/null)"
#----------
# Stuff dealing with pi-star files
#
MY_CALLSIGN="$(grep -A 8 "^\[General\]" /etc/mmdvmhost | grep "^Callsign")"
MY_CALLSIGN="${MY_CALLSIGN#*=}"
# Some DMR-related files we'll be consulting...
# These are supplied with, and updated by, pistar
DMRIDS="$(grep -A 2 "^\[DMR Id Lookup\]" /etc/mmdvmhost | grep "^File")"
DMRIDS="${DMRIDS#*=}"
TGLIST="$(grep "^TGLISTBM=" /usr/local/sbin/wpsd-hostfile-update)"
TGLIST="${TGLIST#*=}"
# Unlike the two files above, all of pi-star's references to this file are
# hard-coded, so I am safe to set this variable directly to the filename.
DMRHOSTS="/usr/local/etc/DMR_Hosts.txt"
# Users can create this next file to contain any DMR talkgroup
# names that are blank/empty in the pistar-supplied TGLIST above.
# If all your talk groups are covered, then you don't need to
# worry about this file.
MY_LIST="/usr/local/etc/MY_LIST.txt"
# This is the master DMR user.csv file from radioid.net.
# We download this, and update it if older than 7 days.
USERCSV="/usr/local/etc/user.csv"
# This is the NXDN.csv file supplied with, and updated by pi-star.
# It is identical in structure to the DMR user.csv file
NXDNCSV="/usr/local/etc/NXDN.csv"
# This is the cty.dat file from www.country-files.com.
# It is used by dxcc.pl, to determine the country that issued
# a callsign, based on the callsign's prefix. We download
# and update it if older than 30 days.
CTYDAT="/usr/local/etc/cty.dat"
#----------
if [[ "${REPLAY}" -eq 1 ]]
then
WORKING_LOG="${REPLAY_LOG}"
else
# Find the latest log
LOGDIR="$(grep -A 4 "^\[Log\]" /etc/mmdvmhost | grep "^FilePath=")"
LOGDIR="${LOGDIR#*=}"
LOGROOT="$(grep -A 4 "^\[Log\]" /etc/mmdvmhost | grep "^FileRoot=")"
LOGROOT="${LOGROOT#*=}"
CANDIDATE_FILES=("${LOGDIR}/${LOGROOT}"*.log) NEWEST=${CANDIDATE_FILES[0]}
for F in "${CANDIDATE_FILES[@]}"
do
if [[ "${F}" -nt "${NEWEST}" ]]
then
NEWEST="${F}"
fi
done
WORKING_LOG="${NEWEST}"
fi
#----------
# BM_API_KEY="$(grep "^apikey=" /etc/bmapi.key 2>/dev/null | cut -f2 -d"=")"
BM_API_KEY="$(grep "^apikey=" /etc/bmapi.key 2>/dev/null)"
BM_API_KEY="${BM_API_KEY#*=}"
HOTSPOT_ID="$(grep -A 13 "^\[DMR\]" /etc/mmdvmhost | grep "^Id=")"
HOTSPOT_ID="${HOTSPOT_ID#*=}"
#----------
# This tmpfile preserves the counters manipulated in functions
# and loops, that are needed by (but not otherwise "handed up" to)
# calling functions and processes.
COUNT_FILE="/tmp/lastqso-count-${MY_TTY}"
# The various counters that will be stored and recalled
# to/from the above file
DMR_COUNT="0"
YSF_COUNT="0"
NXDN_COUNT="0"
DSTAR_COUNT="0"
P25_COUNT="0"
M17_COUNT="0"
QSO_COUNT="0"
KERCHUNK_COUNT="0"
ERROR_COUNT="0"
WARNING_COUNT="0"
# This tracks when logging is cycled
LOG_RESTARTS="0"
#----------
HISTORY_FILE="/tmp/lastqso-history-${MY_TTY}"
# When we query Brandmeister to learn a hotspot's TGs,
# the findings are written to this tmpfile.
BM_TG_FILE="/tmp/lastqso-tgs-${MY_TTY}"
# We make sure any file left behind by a previous run is
# deleted, so that we build a fresh copy with this run.
rm "${BM_TG_FILE}" 2>/dev/null || :
# When dxcc.pl is called to find the country that issued
# a callsign, the result will be stored in this cache
# file. Subsequent appearances of that callsign can then
# quickly be recalled from cache, saving 3-5 seconds each
# time that callsign appears. Unlike the counter-tracking,
# history, and talk-group temp files employed by this
# script, this file will NOT be deleted upon program exit.
# This makes an already populated file immediately available,
# if the script is stopped by the user and then restarted.
DXCC_CACHE_FILE="/tmp/lastqso-dxcc-cache-${MY_TTY}"
touch "${DXCC_CACHE_FILE}"
# Lookups for the Maidenhead Grid Squares of US-based
# callsigns are performed via an online query to a
# remote host. These queries are normally very fast.
# But there's little point in continually querying the
# remote server each time we see the same callsign.
# So, we'll cache any learned results, in a manner
# similar to caching of the dxcc lookups.
GRID_CACHE_FILE="/tmp/lastqso-grid-cache-${MY_TTY}"
touch "${GRID_CACHE_FILE}"
# Ditto, for the FCC license class.
LICENSE_CACHE_FILE="/tmp/lastqso-license-cache-${MY_TTY}"
touch "${LICENSE_CACHE_FILE}"
#----------
# Finding a way to get the screen width of a window that can
# be dynamically re-sized is funky, in bash. Neither tput,
# stty, bash's own $LINES and $COLUMNS, or other such tools
# work to read a terminal's screen size, from within a script.
# So, I have to call on a few lines of python.
#
# The advantage to using the "here doc" is that I don't need
# to host, bundle, distribute and install a trivial external
# python script alongside pistar-lastqso. Everything needed
# stays within the bash script.
#
# Note to self:
# My 40" 1080P screen is...
# 61 lines x 228 columns - HDMI-connected to Pi
# 45 lines x 157 columns - SSH (PuTTY Max Windowed-mode)
# 49 lines x 160 columns - SSH (Putty Full Screen-mode)
# My Windows laptop screen with PuTTY is...
# 44 lines x 168 columns - SSH (PuTTY Max Windowed Mode)
# 48 lines x 170 columns - SSH (Putty Full Screen-mode)
export LINES COLUMNS
PY3_SCRIPT=$(cat <<'EOF'
import os
ts = os.get_terminal_size(2)
ts.lines
ts.columns
print (ts.lines, ts.columns, sep=' ')
EOF
)
# The above is called like this, whenever needed...
read -r LINES COLUMNS <<< "$(python3 -c "${PY3_SCRIPT}")" 2>/dev/null
#----------
# MISC stuff...
# For grepping stuff that includes tabs
GTAB=$'\t'
# This padding is appended to most of the displayed values at top of
# the "-t" screen, and then truncated to an appropriate length. This
# acts to clear out any artifacts of previous data that are some-
# times left behind when the user changes the size of an SSH window
# or the data in that field changes.
#TOP_PAD=".........................................................."
TOP_PAD=" "
# Disable line-wrap
# Prevents unexpectedly long fields from busting up the screen.
# (There's one entry in user.csv that is over 170 characters long!)
echo -en "${WRAP_OFF}"
###########################################################
# FUNCTIONS
###########################################################
#----------
fnCHOP_CALL()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Call with:
# VAR_TO_CHOP="$(fnCHOP_CALL "${VAR_TO_CHOP}")"
# Attempt to isolate the call when we see things
# like "XX0XXX/BILL", reducing it to "XX0XXX".
# Any non-alphanumeric separator the person has
# inserted to append their name (often seen in
# YSF, such as a slash, dash, underscore or
# whatever), is changed to a known value we can
# parse on. In this case, I change it to an
# underscore. Then, I chop off that underscore
# along with anything that follows it, leaving
# only the callsign.
CHOP_CALL="${1}"
CHOP_CALL="${CHOP_CALL//[^[:alnum:]]/_}"
CHOP_CALL="${CHOP_CALL%%_*}"
echo "${CHOP_CALL}"
# TODO: Someday, I need to figure out a way to
# handle cases where no non-alphanumeric char
# was used, and the person just appended their
# name directly onto the call ("XX0XXXBILL").
# But that's for another day (maybe).
return
}
#----------
fnSECONDS2TIME()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
local T="${SECONDS}"
local D=$((T/60/60/24))
local H=$((T/60/60%24))
local M=$((T/60%60))
local S=$((T%60))
[[ $D -ge 0 ]] && printf '%02d days ' $D
[[ $H -ge 0 ]] && printf '%02d hrs ' $H
[[ $M -ge 0 ]] && printf '%02d mins ' $M
# [[ $D > 0 || $H > 0 || $M > 0 ]] && printf 'and '
# printf '%d secs\n' $S
return
}
#----------
fnWRITE_COUNTER_FILE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
echo "#
DMR_COUNT=${DMR_COUNT}
YSF_COUNT=${YSF_COUNT}
NXDN_COUNT=${NXDN_COUNT}
DSTAR_COUNT=${DSTAR_COUNT}
P25_COUNT=${P25_COUNT}
M17_COUNT=${M17_COUNT}
#
QSO_COUNT=${QSO_COUNT}
KERCHUNK_COUNT=${KERCHUNK_COUNT}
#
ERROR_COUNT=${ERROR_COUNT}
WARNING_COUNT=${WARNING_COUNT}" \
> "${COUNT_FILE}"
return
}
#-----------------------------------
fnCOPY_CACHE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
#----------
# Combine any current caches with anything previously saved.
# Put the groomed file in place as the live cache.
# Save a copy to the home directory. Do this, only if the
# active and saved files are different. If they are the
# same, skip the merge/sort/write/copy/delete operations.
if ! cmp --silent -- "${DXCC_CACHE_FILE}" /home/pi-star/.lastqso-dxcc-cache 2>/dev/null
then
cat "${DXCC_CACHE_FILE}" /home/pi-star/.lastqso-dxcc-cache 2>/dev/null \
| sed 's/United States of America/United States/' \
| LC_ALL=C sort -u >/tmp/.cache_tmp 2>/dev/null
cat /tmp/.cache_tmp > "${DXCC_CACHE_FILE}" 2>/dev/null
sudo mount -o remount,rw / 2>/dev/null
cat /tmp/.cache_tmp > /home/pi-star/.lastqso-dxcc-cache 2>/dev/null
rm /tmp/.cache_tmp 2>/dev/null
fi
if ! cmp --silent -- "${GRID_CACHE_FILE}" /home/pi-star/.lastqso-grid-cache 2>/dev/null
then
cat "${GRID_CACHE_FILE}" /home/pi-star/.lastqso-grid-cache 2>/dev/null \
| grep -F -v "Unknown" \
| LC_ALL=C sort -u >/tmp/.cache_tmp 2>/dev/null
cat /tmp/.cache_tmp > "${GRID_CACHE_FILE}" 2>/dev/null
sudo mount -o remount,rw / 2>/dev/null
cat /tmp/.cache_tmp > /home/pi-star/.lastqso-grid-cache 2>/dev/null
rm /tmp/.cache_tmp 2>/dev/null
fi
if ! cmp --silent -- "${LICENSE_CACHE_FILE}" /home/pi-star/.lastqso-license-cache 2>/dev/null
then
cat "${LICENSE_CACHE_FILE}" /home/pi-star/.lastqso-license-cache 2>/dev/null \
| LC_ALL=C sort -u >/tmp/.cache_tmp 2>/dev/null
cat /tmp/.cache_tmp > "${LICENSE_CACHE_FILE}" 2>/dev/null
sudo mount -o remount,rw / 2>/dev/null
cat /tmp/.cache_tmp > /home/pi-star/.lastqso-license-cache 2>/dev/null
rm /tmp/.cache_tmp 2>/dev/null
fi
# When I'm testing/developing, occasionally the script spots log
# entries it denotes as "unparsed". Save those for me to look
# at, to determine if the script should parse or ignore them.
if [[ -n "${IS_DEV}" ]]
then
sudo mount -o remount,rw / 2>/dev/null
mv /tmp/unparsed_DMR /home/pi-star 2>/dev/null || :
mv /tmp/unparsed_YSF /home/pi-star 2>/dev/null || :
mv /tmp/unparsed_NXDN /home/pi-star 2>/dev/null || :
mv /tmp/unparsed_DSTAR /home/pi-star 2>/dev/null || :
mv /tmp/unparsed_P25 /home/pi-star 2>/dev/null || :
mv /tmp/unparsed_M17 /home/pi-star 2>/dev/null || :
fi
#----------
sync;sync;sync
return
}
# For possible future use
#-----------------------------------
fnCENTER()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
read -r LINES COLUMNS <<< "$(python3 -c "${PY3_SCRIPT}")" 2>/dev/null
LENGTH=${#STRING}
tput cup $((LINES / 2)) $(((COLUMNS / 2) - (LENGTH / 2)))
echo "${STRING}"
return
}
# Look up Maidenhead Grid Square for US Callsigns,
# based on address of record, in the FCC database.
#-----------------------------------
fnGET_US_GRIDSQUARE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
US_CALL="${1}"
US_CALL="$(fnCHOP_CALL "${US_CALL}")"
# Search the cache.
GRIDSQUARE="$(grep "^${US_CALL}:" "${GRID_CACHE_FILE}")"
if [[ -n "${GRIDSQUARE}" ]]
then
# If the cache delivered, we're done.
GRIDSQUARE="${GRIDSQUARE^^}"
GRIDSQUARE=" (${GRIDSQUARE#*:})"
else
# Otherwise, we need to query the remote host.
read -r GRIDSQUARE <<< "$(curl -s --fail --max-time 2 "https://callook.info/${US_CALL}/json" 2>/dev/null | jq -r '.location.gridsquare' 2>/dev/null | tr '\n' ' ')"
GRIDSQUARE="${GRIDSQUARE^^}"
# Dont cache an unknown or null response
if [[ "${GRIDSQUARE}" =~ [A-Z][A-Z][0-9][0-9][A-Z][A-Z] ]]
then
echo "${US_CALL}:${GRIDSQUARE}" >> "${GRID_CACHE_FILE}"
GRIDSQUARE=" (${GRIDSQUARE})"
fi
fi
return
}
# Look up FCC License level for US Callsigns
#-----------------------------------
fnGET_US_LICENSE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
US_CALL="${1}"
US_CALL="$(fnCHOP_CALL "${US_CALL}")"
# Search the cache.
LICENSE="$(grep "^${US_CALL}:" "${LICENSE_CACHE_FILE}")"
if [[ -n "${LICENSE}" ]]
then
# If the cache delivered, we're done.
LICENSE="${LICENSE^^}"
LICENSE="${LICENSE#*:}"
else
# Otherwise, we need to query the remote host.
read -r TYPE CLASS <<< "$(curl --silent --fail --max-time 2 "https://callook.info/${US_CALL}/json" 2>/dev/null \
| grep -v "^<" | jq -r '.type, .current.operClass' 2>/dev/null \
| tr '\n' ' ')"
if [[ "${TYPE}" =~ "CLUB" ]]
then
CLASS="CLUB"
fi
# Dont cache an "Unknown" or null response
LICENSE="${CLASS^^}"
case "${LICENSE}" in
NOVICE|TECHNICIAN|GENERAL|ADVANCED|EXTRA|CLUB)
echo "${US_CALL}:${LICENSE}" >> "${LICENSE_CACHE_FILE}"
;;
*)
LICENSE=""
;;
esac
fi
return
}
#-----------------------------------
fnGET_MODES()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
#----------
# Primary modes
DMR_ENABLED="$(grep -A 2 "^\[DMR\]" /etc/mmdvmhost | grep "^Enable")"
DMR_ENABLED="${DMR_ENABLED#*=}"
YSF_ENABLED="$(grep -A 2 "^\[System Fusion\]" /etc/mmdvmhost | grep "^Enable")"
YSF_ENABLED="${YSF_ENABLED#*=}"
DSTAR_ENABLED="$(grep -A 2 "^\[D-Star\]" /etc/mmdvmhost | grep "^Enable")"
DSTAR_ENABLED="${DSTAR_ENABLED#*=}"
NXDN_ENABLED="$(grep -A 2 "^\[NXDN\]" /etc/mmdvmhost | grep "^Enable")"
NXDN_ENABLED="${NXDN_ENABLED#*=}"
P25_ENABLED="$(grep -A 2 "^\[P25\]" /etc/mmdvmhost | grep "^Enable")"
P25_ENABLED="${P25_ENABLED#*=}"
M17_ENABLED="$(grep -A 2 "^\[M17\]" /etc/mmdvmhost | grep "^Enable")"
M17_ENABLED="${M17_ENABLED#*=}"
#POCSAG_ENABLED="$(grep -A 2 "^\[POCSAG\]" /etc/mmdvmhost | grep "^Enable")"
#POCSAG_ENABLED="${POCSAG_ENABLED#*=}"
#----------
# Cross-modes
DMR2YSF_ENABLED="$(grep -A 1 "^\[Enabled\]" /etc/dmr2ysf | grep "^Enabled")"
DMR2YSF_ENABLED="${DMR2YSF_ENABLED#*=}"
DMR2NXDN_ENABLED="$(grep -A 1 "^\[Enabled\]" /etc/dmr2nxdn | grep "^Enabled")"
DMR2NXDN_ENABLED="${DMR2NXDN_ENABLED#*=}"
YSF2DMR_ENABLED="$(grep -A 1 "^\[Enabled\]" /etc/ysf2dmr | grep "^Enabled")"
YSF2DMR_ENABLED="${YSF2DMR_ENABLED#*=}"
YSF2NXDN_ENABLED="$(grep -A 1 "^\[Enabled\]" /etc/ysf2nxdn | grep "^Enabled")"
YSF2NXDN_ENABLED="${YSF2NXDN_ENABLED#*=}"
YSF2P25_ENABLED="$(grep -A 1 "^\[Enabled\]" /etc/ysf2p25 | grep "^Enabled")"
YSF2P25_ENABLED="${YSF2P25_ENABLED#*=}"
return
}
#-----------------------------------
fnCLEANUP()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Delete everything except the dxcc and grid cache files
rm "${BM_TG_FILE}" \
"${COUNT_FILE}" \
"${HISTORY_FILE}" 2>/dev/null || :
fnCOPY_CACHE
return
}
#-----------------------------------
fnCLOSE_FDS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Close the debugging and profiling file descriptors
exec 3>&- 2>/dev/null
set +x 2>/dev/null
exec 4>&- 2>/dev/null
return
}
#-----------------------------------
# Double high, double wide character set
fnLARGE_FONT()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
echo -n "${SGR0}"
case ${MY_FONT} in
5)
echo -e "${LARGE_TOP}${*}\n${LARGE_BOT}${*}"
;;
6)
echo -e "${WIDE_FONT}${*}"
;;
*)
echo "${*}"
;;
esac
echo -n "${CLR_ED}"
return
}
#-----------------------------------
# Because bash can't do simple floating-point comparisons....
fnCOMPARE_FLOAT_LT()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Look for this to return "<" if FLOAT1 is less than FLOAT2
awk -v FLOAT1="${1}" -v FLOAT2="${2}" 'BEGIN {printf "%s " (FLOAT1<FLOAT2?"<":">=") " %s\n", FLOAT1, FLOAT2}'
return
}
#-----------------------------------
# Because bash can't do simple floating-point comparisons....
fnCOMPARE_FLOAT_GT()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Look for this to return ">" if FLOAT1 is greater than FLOAT2
awk -v FLOAT1="${1}" -v FLOAT2="${2}" 'BEGIN {printf "%s " (FLOAT1>FLOAT2?">":"<=") " %s\n", FLOAT1, FLOAT2}'
return
}
#-----------------------------------
# Gratuitous eye-candy
# UTF-8 has richer, more predictable box-drawing characters
# than old fashioned terminfo acsc characters. The caveat is
# that the terminal must support UTF-8.
#
fnDEFINE_BOXCHARS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Unicode box-drawing characters. We'll use a few of these.
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# U+250x ─ ━ │ ┃ ┌ ┏
# U+251x ┐ ┓ └ ┗ ┘ ┛ ├ ┝
# U+252x ┠ ┣ ┤ ┥ ┨ ┫ ┬ ┯
# U+253x ┰ ┳ ┴ ┷ ┸ ┻ ┼ ┿
# U+254x ╂ ╋
# U+255x ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
# U+256x ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬
# Unicode block elements.
# 0 1 2 3 4 5 6 7 8 9 A B C D E F
# U+258x ▀ ▄ █ ▌
# U+259x ▐ ░ ▒ ▓
if [[ "$(locale charmap)" =~ "UTF-8" ]]
then
# UTF-8 single-line box-drawing characters
SBOX_BR="$(printf "\u2518")" # ┘ bottom-right corner
SBOX_TR="$(printf "\u2510")" # ┐ top-right corner
SBOX_TL="$(printf "\u250C")" # ┌ top-left corner
SBOX_BL="$(printf "\u2514")" # └ bottom-left corner
SBOX_CC="$(printf "\u253C")" # ┼ center cross
SBOX_HL="$(printf "\u2500")" # ─ horizontal line
SBOX_LT="$(printf "\u251C")" # ├ left-side "T" (right-pointing)
SBOX_RT="$(printf "\u2524")" # ┤ right-side "T" (left-pointing)
SBOX_BT="$(printf "\u2534")" # ┴ bottom "T" (up-pointing)
SBOX_TT="$(printf "\u252C")" # ┬ top "T" (down-pointing)
SBOX_VL="$(printf "\u2502")" # │ vertical line
# UTF-8 double-line box-drawing characters
DBOX_BR="$(printf "\u255D")" # ╝ bottom-right corner
DBOX_TR="$(printf "\u2557")" # ╗ top-right corner
DBOX_TL="$(printf "\u2554")" # ╔ top-left corner
DBOX_BL="$(printf "\u255A")" # ╚ bottom-left corner
DBOX_CC="$(printf "\u256C")" # ╬ center cross
DBOX_HL="$(printf "\u2550")" # ═ horizontal line
DBOX_LT="$(printf "\u2560")" # ╠ left-side "T" (right-pointing)
DBOX_RT="$(printf "\u2563")" # ╣ right-side "T" (left-pointing)
DBOX_BT="$(printf "\u2569")" # ╩ bottom "T" (up-pointing)
DBOX_TT="$(printf "\u2566")" # ╦ top "T" (down-pointing)
DBOX_VL="$(printf "\u2551")" # ║ vertical line
# UTF-8 mixed outer double/inner single-line box-drawing characters
DSBOX_LT="$(printf "\u255F")" # ╟ mixed dbl/sgl-line left-side "T" (right-pointing)
DSBOX_RT="$(printf "\u2562")" # ╢ mixed dbl/sgl-line right-side "T" (left-pointing)
DSBOX_BT="$(printf "\u2567")" # ╧ bottom "T" (up-pointing)
DSBOX_TT="$(printf "\u2564")" # ╤ top "T" (down-pointing)
# UTF-8 mixed outer single/inner double-line box-drawing characters
SDBOX_LT="$(printf "\u255E")" # ╞ mixed sgl/dbl-line left-side "T" (right-pointing)
SDBOX_RT="$(printf "\u2561")" # ╡ mixed sgl/dbl-line right-side "T" (left-pointing)
SDBOX_BT="$(printf "\u2568")" # ╨ bottom "T" (up-pointing)
SDBOX_TT="$(printf "\u2565")" # ╥ top "T" (down-pointing)
TOP_HALF_SHADOW="$(printf "\u2580")" # ▀ top half-height shadow
BOT_HALF_SHADOW="$(printf "\u2584")" # ▀ bottom half-height shadow
FULL_SHADOW="$(printf "\u2588")" # █ full-height shadow
else
# if no UTF-8, use ascii characters to draw a poor-man's box
SBOX_BR="+"
SBOX_TR="+"
SBOX_TL="+"
SBOX_BL="+"
SBOX_CC="+"
SBOX_HL="-"
SBOX_LT="+"
SBOX_RT="+"
SBOX_BT="+"
SBOX_TT="+"
SBOX_VL="|"
DBOX_BR="+"
DBOX_TR="+"
DBOX_TL="+"
DBOX_BL="+"
DBOX_CC="+"
DBOX_HL="="
DBOX_LT="+"
DBOX_RT="+"
DBOX_BT="+"
DBOX_TT="+"
DBOX_VL="|"
DSBOX_LT="+"
DSBOX_RT="+"
DSBOX_BT="+"
DSBOX_TT="+"
SDBOX_LT="+"
SDBOX_RT="+"
SDBOX_BT="+"
SDBOX_TT="+"
TOP_HALF_SHADOW="#"
BOT_HALF_SHADOW="#"
FULL_SHADOW="#"
fi
DBL_LINE="$(printf "${DBOX_HL}%.0s" {1..76})"
return
}
#-----------------------------------
# Usage: fnDRAW_BOX BOX_TOP BOX_LEFT BOX_HEIGHT BOX_WIDTH BOX_STYLE BOX_FILL BOX_SHADOW
# BOX_TOP and BOX_LEFT = the position of the upper left corner of the box
# BOX_HEIGHT = the number of rows of the box
# BOX_WIDTH = the number of columns of the box
# BOX_STYLE = single [1] or double-line [2] box-drawing characters
# BOX_FILL = preserve [0] or erase [1] anything within the box
# BOX_SHADOW = no drop-shadow [0] or add a drop-shadow [1]
fnDRAW_BOX()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
#----------
BOX_TOP="${1}"
BOX_LEFT="${2}"
BOX_HEIGHT="${3}"
BOX_WIDTH="${4}"
BOX_STYLE="${5}"
BOX_FILL="${6}"
BOX_SHADOW="${7}"
BOX_BOTTOM=$((BOX_TOP + BOX_HEIGHT))
BOX_RIGHT=$((BOX_LEFT + BOX_WIDTH))
#----------
if [[ "${BOX_STYLE}" = "1" ]]
then
# single-line characters
BOX_TL="${SBOX_TL}"
BOX_TR="${SBOX_TR}"
BOX_HL="${SBOX_HL}"
BOX_VL="${SBOX_VL}"
BOX_BL="${SBOX_BL}"
BOX_BR="${SBOX_BR}"
else
# double-line characters
BOX_TL="${DBOX_TL}"
BOX_TR="${DBOX_TR}"
BOX_HL="${DBOX_HL}"
BOX_VL="${DBOX_VL}"
BOX_BL="${DBOX_BL}"
BOX_BR="${DBOX_BR}"
fi
#----------
# Top line of the box
if [[ "${BOX_WIDTH}" -gt 2 ]]
then
tput cup "${BOX_TOP}" "${BOX_LEFT}"
for ((I=BOX_LEFT; I<=BOX_RIGHT; I++))
do
printf -- "${BOX_HL}%0.s"
done
fi
#----------
# Top-left corner
tput cup "${BOX_TOP}" "${BOX_LEFT}"
echo -n "${BOX_TL}"
# Top-right corner
tput cup "${BOX_TOP}" $((BOX_LEFT + BOX_WIDTH))
echo -n "${BOX_TR}"
BOX_CURRENT_LINE=$((BOX_TOP + 1))
#----------
# preserve or erase what's between the sides of the box
COUNT="1"
PADDING=""
if [[ "${BOX_FILL}" = "0" ]]
then
PAD="$(tput cuf1)" # Move cursor one space to the right
else
PAD=" " # one space
fi
until [[ "${COUNT}" -eq "${BOX_WIDTH}" ]]
do
PADDING="${PADDING}${PAD}"
((COUNT++))
done
#----------
# sides of the box
until [[ "${BOX_CURRENT_LINE}" -eq "${BOX_BOTTOM}" ]]
do
tput cup "${BOX_CURRENT_LINE}" "${BOX_LEFT}"
echo -n "${BOX_VL}${PADDING}${BOX_VL}"
# if shadow, print the block
if [[ "${BOX_SHADOW}" -eq 1 ]]
then
echo -n "${FULL_SHADOW}"
fi
((BOX_CURRENT_LINE++))
done
#----------
# Bottom line of the box
if [[ "${BOX_WIDTH}" -gt 2 ]]
then
tput cup "${BOX_BOTTOM}" "${BOX_LEFT}"
for ((I=BOX_LEFT; I<=BOX_RIGHT; I++))
do
printf -- "${BOX_HL}%0.s"
done
fi
#----------
# Bottom-left corner
tput cup "${BOX_BOTTOM}" "${BOX_LEFT}"
echo -n "${BOX_BL}"
# Bottom-right corner
tput cup "${BOX_BOTTOM}" "${BOX_RIGHT}"
echo -n "${BOX_BR}"
# if shadow, print the row of blocks
if [[ "${BOX_SHADOW}" -eq 1 ]]
then
tput cup "${BOX_CURRENT_LINE}" "$((BOX_RIGHT + 1))"
echo -n "${FULL_SHADOW}"
tput cup $((BOX_BOTTOM + 1)) $((BOX_LEFT + 2))
for ((I=BOX_LEFT; I<="$((BOX_RIGHT -1))"; I++))
do
printf "${TOP_HALF_SHADOW}%0.s"
done
fi
return
}
#-----------------------------------
fnTOP_DATA()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# This function deals with the optional non-scrolling zone,
# created when the -t|--top option is specified on the cmdline.
#----------
if [[ "${REPLAY}" -eq 1 ]]
then
WORKING_LOG="${REPLAY_LOG}"
fi
MY_LOG="$(basename "${WORKING_LOG}")"
#----------
# GATHER LINE 0 DATA...
# This script's version
# Pad, and truncate to fit
MY_VER="${IS_DEV}${VERSION}${IS_DEV}"
# Datestamp of the user.csv file
CSV_DATE="$(stat --print=%y /usr/local/etc/user.csv 2>/dev/null)"
CSV_DATE="${CSV_DATE%%" "*}"
# Datestamp of the cty.dat file
DAT_DATE="$(stat --print=%y /usr/local/etc/cty.dat 2>/dev/null)"
DAT_DATE="${DAT_DATE%%" "*}"
#----------
# GATHER LINE 1 DATA...
# Hotspot TX & RX frequencies
# <rant> I hate that bash can't do simple floating-point math without external help. </rant>
TX="$(printf %.4f "$(echo print "$(grep -A 9 "\[Info\]" /etc/mmdvmhost | grep "^TXFrequency=" 2>/dev/null | cut -f2 -d"=" | sed -u 's/Hz//')"/1000000 | perl)")"
RX="$(printf %.4f "$(echo print "$(grep -A 9 "\[Info\]" /etc/mmdvmhost | grep "^RXFrequency=" 2>/dev/null | cut -f2 -d"=" | sed -u 's/Hz//')"/1000000 | perl)")"
# Details about the modem
DESCRIPTION="$(grep "description:" /var/log/pi-star/MMDVM* | tail -1)"
# Firmware version
IFS=' '; CUT_ARRAY=(${DESCRIPTION}); unset IFS;
FW="${CUT_ARRAY[8]}"
unset CUT_ARRAY
# Isolate the field that contains "MHz" within the record
# If we can't find it, set it to "unknown"
TCXO="$(printf %2.4f "$(echo "${DESCRIPTION}" | awk '/MHz/ {for(i=NF;i>=1;i--) {if($i~/MHz/) {$0=i":"$i}} print}' | cut -f3 -d":" | sed -u 's/MHz//')")"
TCXO="${TCXO:-'unknown'}"
#----------
# GATHER LINE 2 DATA...
# CPU_TEMP is fetched in fnNOSCROLL_ZONE, where it can be updated frequently,
# rather than here in this function.
MODEM_PORT="$(grep -A 25 "^\[Modem\]" /etc/mmdvmhost | grep "^Port")"
IFS='/'; CUT_ARRAY=(${MODEM_PORT}); unset IFS;
MODEM_PORT="${CUT_ARRAY[2]}"
unset CUT_ARRAY
PLATFORM="$(grep Platform /etc/pistar-release|cut -f 2 -d '=')"
#----------
# GATHER LINE 3 DATA...
# elapsed time since launch, and load average, are fetched in fnNOSCROLL_ZONE,
# where it can be updated frequently, rather than in this function.
#----------
# GATHER LINE 4 DATA...
# brandmeister static and dynamic talkgroups are fetched in fnNOSCROLL_ZONE,
# where they can be updated frequently, rather than in this function.
if [[ "${DMR_ENABLED}" = "1" ]]
then
DMR_MASTER="$(grep -A 10 "^\[DMR Network\]" /etc/mmdvmhost | grep "^Address")"
DMR_MASTER="${DMR_MASTER#*=}"
DMR_MASTER_NAME="$(grep "${GTAB}${DMR_MASTER}${GTAB}" /usr/local/etc/DMR_Hosts.txt | awk '{ print $1 }' | sed -u 's/_/ /g')"
else
if [[ "${YSF2DMR_ENABLED}" = "1" ]]
then
DMR_MASTER="DMR2YSF ($(grep -A 5 "^\[DMR Network 3\]" /etc/dmrgateway | grep "^Name"))"
DMR_MASTER="${DMR_MASTER#*=}"
DMR_MASTER="${DMR_MASTER%)*}"
DMR_MASTER_NAME="${DMR_MASTER}"
else
DMR_MASTER_NAME="null"
fi
fi
if [[ "${YSF_ENABLED}" = "1" ]]
then
YSF_STARTUP="$(grep -A 5 "^\[Network\]" /etc/ysfgateway | grep "^Startup")"
YSF_STARTUP="${YSF_STARTUP#*=}"
else
if [[ "${DMR2YSF_ENABLED}" = "1" ]]
then
YSF_STARTUP="YSF2DMR ($(grep -A 5 "^\[Network\]" /etc/ysfgateway | grep "^Startup"))"
YSF_STARTUP="${YSF_STARTUP#*=}"
YSF_STARTUP="${YSF_STARTUP%)*}"
else
YSF_STARTUP="null"
fi
fi
#----------
# NXDN reflector is gathered in fnNOSCROLL_ZONE
# P25 reflector is gathered in fnNOSCROLL_ZONE
# D-Star reflector is gathered in fnNOSCROLL_ZONE
return
}
#-----------------------------------
fnDEV_INFO()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# If running on a big screen, this prints some info in the
# top-right corner of the screen, on the non-scrolling lines,
# when -t|--top is used.
if [[ "${COLUMNS}" -lt 130 ]] || [[ "${SHOW_DEV_INFO}" -eq 0 ]]
then
return
fi
# If null, set to "n/a"
BG_PID="${BG_PID:-'n/a'}"
#----------
DEV_STRING[1]=" Options: ${MY_CMDLINE}"
DEV_STRING[2]=" Debugging: 3> $(basename "${DEBUG_LOG}") Profiling: 4> $(basename "${PROFILING_LOG}")"
DEV_STRING[3]=" ${TERM} SIZE=${LINES}x${COLUMNS} TTY=${MY_TTY} PID=${MY_PID} BG_PID=${BG_PID}"
DEV_STRING[4]=" Modes/X-Modes: YSF=${YSF_ENABLED} DMR=${DMR_ENABLED} NXDN=${NXDN_ENABLED} DSTAR=${DSTAR_ENABLED} P25=${P25_ENABLED} M17=${M17_ENABLED}"
DEV_STRING[5]=" YSF2DMR=${YSF2DMR_ENABLED} YSF2NXDN=${YSF2NXDN_ENABLED} YSF2P25=${YSF2P25_ENABLED} DMR2YSR=${DMR2YSF_ENABLED} DMR2NXDN=${DMR2NXDN_ENABLED}"
DEV_STRING[6]=" Traffic: DMR=${DMR_COUNT} YSF=${YSF_COUNT} NXDN=${NXDN_COUNT} DSTAR=${DSTAR_COUNT} P25=${P25_COUNT} M17=${M17_COUNT}"
DEV_STRING[7]=" DXCC=$(grep -c . "${DXCC_CACHE_FILE}") Grid=$(grep -c . "${GRID_CACHE_FILE}") Lic=$(grep -c . "${LICENSE_CACHE_FILE}") K=${KERCHUNK_COUNT} E=${ERROR_COUNT} W=${WARNING_COUNT} R=${LOG_RESTARTS}"
#----------
# In replay mode, text is bright yellow.
# In normal mode, text is pale cyan.
if [[ "${REPLAY}" -eq 1 ]]
then
echo -n "${BOLD}${YEL}"
else
echo -n "${DIM}${CYA}"
fi
#----------
DEV_INDEX="1"
until [[ "${DEV_INDEX}" -gt 7 ]]
do
# tput references lines, beginning at 0. The array begins
# at 1. So we subtract 1 from the array index, to get the
# line number needed for tput.
X=$(( "${DEV_INDEX}" - 1 ))
# Subtract the length of the string from the width of the
# screen, to find the column at which to begin printing.
Y=$(( ("${COLUMNS}" - "${#DEV_STRING[DEV_INDEX]}") - 1 ))
# Print each line, flush right, at right edge of screen.
tput cup "${X}" "${Y}"
echo -n "${DEV_STRING[DEV_INDEX]}"
((DEV_INDEX++))
done
echo -n "${SGR0}"
return
}
#-----------------------------------
fnNOSCROLL_ZONE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
if [[ "${TOP}" = "0" ]]
then
return
fi
# Save the current cursor position, and turn the cursor off
echo -n "${CUR_SC}${CUR_OFF}"
# Set the whole screen as scrollable
tput csr 0 $((LINES - 1))
# Go to line 0
echo -n "${CUR_HOME}"
# Define the scrolling region of the screen as the line just
# below the no-scroll region, to the bottom of screen.
# (Screen lines are numbered beginning with line 0.)
tput csr "${BELOW_NOSCROLL}" $((LINES - 1))
#----------
# Fetch BM TGs for line 5
if [[ "$(grep "${GTAB}${DMR_MASTER}${GTAB}" "${DMRHOSTS}")" =~ "BM_" ]] && [[ "${DMR_ENABLED}" -eq 1 ]]
then
#----------
# Query BM about your hotspot's TGs
# If BM_POLL=0, the query is performed only once.
# If BM_POLL=1, it's performed every time.
if [[ ! -f "${BM_TG_FILE}" ]] || [[ "${BM_POLL}" -eq 1 ]]
then
curl -s --fail --max-time 2 -X 'GET' "https://api.brandmeister.network/v2/device/${HOTSPOT_ID}/profile" -H 'accept: application/json' > "${BM_TG_FILE}"
fi
#----------
BM_TG_LIST=""
BM_TG_COUNT="0"
# Parse the JSON for any static TGs
while read -r TALKGROUP
do
if [[ -n "${TALKGROUP}" ]]
then
BM_TG_LIST="${BM_TG_LIST} ${TALKGROUP}"
((BM_TG_COUNT++))
fi
done <<< "$(jq -r '.staticSubscriptions[] .talkgroup' "${BM_TG_FILE}" 2>/dev/null)"
# Parse again, for any dynamic TGs
while read -r TALKGROUP
do
if [[ -n "${TALKGROUP}" ]]
then
# We indicate a dynamic TG by marking it with an asterisk
BM_TG_LIST="${BM_TG_LIST} ${TALKGROUP}*"
((BM_TG_COUNT++))
fi
done <<< "$(jq -r '.dynamicSubscriptions[] .talkgroup' "${BM_TG_FILE}" 2>/dev/null)"
# Get the number of TGs linked (static and dynamic, combined),
# and remove quote marks from the resulting list of talkgroups
BM_TG_COUNT="$(printf %02d "${BM_TG_COUNT}")"
if [[ "${BM_TG_COUNT}" -eq 0 ]]
then
BM_TG_LIST=" No Talkgroups Linked"
else
BM_TG_LIST="${BM_TG_LIST//\"}"
fi
#----------
# See if the total characters is wider than what we can show
# within the line-length limit of the no-scroll region.
BM_TG_LENGTH="${#BM_TG_LIST}"
if [[ "${BM_TG_LENGTH}" -gt 28 ]]
then
# If so, take only the first 28 chars and add an elipsis (...)
# to show we are aware there's more, but it just won't fit.
#
# Most people (including the BM admins) would recommend against
# too many statically linked talkgroups. Dynamically linked
# talkgroups should suffice for most people's needs.
BM_TG_LIST="${BM_TG_LIST:0:26}..."
fi
BM_TG_LIST="${BM_TG_LIST}${TOP_PAD}"
BM_TG_LIST="${BM_TG_LIST:0:29}"
BM_TG_LINE="${DIM}${WHI}TALKGROUPS(${DMR_COLOR}${BM_TG_COUNT}${SGR0}${DIM}${WHI}):${SGR0}${DMR_COLOR}${BM_TG_LIST}${SGR0}"
else
BM_TG_LINE="${DIM}${WHI}TALKGROUPS: No API available${SGR0}"
fi
#----------
# Print line 0
DAT_DATE="${DAT_DATE}${TOP_PAD}"
DAT_DATE="${DAT_DATE:0:11}"
CSV_DATE="${CSV_DATE}${TOP_PAD}"
CSV_DATE="${CSV_DATE:0:11}"
MY_VER="${MY_VER}${TOP_PAD}"
MY_VER="${MY_VER:0:7}"
MY_LOG="${MY_LOG}${TOP_PAD}"
MY_LOG="${MY_LOG:0:23}"
echo -en "${DIM}${WHI}LASTQSO:${SGR0} ${VER_CHECK_COLOR}${MY_VER}${SGR0}\r"
tput cuf 16
echo -en "${DIM}${WHI}DAT:${SGR0} ${WHI}${BOLD}${DAT_DATE}${SGR0}\r"
tput cuf 32
echo -en "${DIM}${WHI}CSV:${SGR0} ${WHI}${BOLD}${CSV_DATE}${SGR0}\r"
tput cuf 48
echo "${DIM}${WHI}LOG:${SGR0} ${WHI}${BOLD}${MY_LOG}${SGR0}"
#----------
# Print line 1
TX="${TX}${TOP_PAD}"
TX="${TX:0:12}"
RX="${RX}${TOP_PAD}"
RX="${RX:0:12}"
FW="${FW}${TOP_PAD}"
FW="${FW:0:24}"
TCXO="${TCXO}${TOP_PAD}"
TCXO="${TCXO:0:10}"
echo -en "${DIM}${WHI}TX:${SGR0} ${WHI}${BOLD}${TX}${SGR0}\r"
tput cuf 16
echo -en "${DIM}${WHI}RX:${SGR0} ${WHI}${BOLD}${RX}${SGR0}\r"
tput cuf 32
echo -en "${DIM}${WHI}TCXO:${SGR0} ${WHI}${BOLD}${TCXO}${SGR0}\r"
tput cuf 48
echo "${DIM}${WHI}FW:${SGR0} ${WHI}${BOLD}${FW}${SGR0}"
#----------
# Print line 2
CPU_TEMP="$(vcgencmd measure_temp)"
CPU_TEMP="${CPU_TEMP#*=}"
CPU_TEMP="${CPU_TEMP/\'/°}"
CPU_TEMP_INT="${CPU_TEMP%.*}"
if [[ "${CPU_TEMP_INT}" -lt 50 ]]
then
CPU_TEMP_COLOR="${GRE}"
fi
if [[ "${CPU_TEMP_INT}" -ge 50 ]]
then
CPU_TEMP_COLOR="${YEL}"
fi
if [[ "${CPU_TEMP_INT}" -ge 69 ]]
then
CPU_TEMP_COLOR="${RED}"
fi
CPU_TEMP="${CPU_TEMP}${TOP_PAD}"
CPU_TEMP="${CPU_TEMP:0:10}"
MODEM_PORT="${MODEM_PORT}${TOP_PAD}"
MODEM_PORT="${MODEM_PORT:0:10}"
PLATFORM="${PLATFORM}${TOP_PAD}"
PLATFORM="${PLATFORM:0:34}"
echo -en "${DIM}${WHI}Temp:${SGR0} ${CPU_TEMP_COLOR}${BOLD}${CPU_TEMP}${SGR0}\r"
tput cuf 16
echo -en "${DIM}${WHI}Port:${SGR0} ${WHI}${BOLD}${MODEM_PORT}${SGR0}\r"
tput cuf 32
echo "${DIM}${WHI}Platform:${SGR0} ${WHI}${BOLD}${PLATFORM}${SGR0}"
#----------
# Print line 3
ELAPSED="$(fnSECONDS2TIME)"
ELAPSED="${ELAPSED}${TOP_PAD}"
ELAPSED="${ELAPSED:0:23}"
echo -en "${DIM}${WHI}Elapsed:${SGR0} ${WHI}${BOLD}${ELAPSED}${SGR0}\r"
LOAD_AVG="$(uptime | awk '{ print $(NF-2)" "$(NF-1)" "$NF }')"
LOAD_AVG="${LOAD_AVG}${TOP_PAD}"
LOAD_AVG="${LOAD_AVG:0:20}"
tput cuf 32
echo "${DIM}${WHI}Load Avg (1,5,15 mins):${SGR0} ${WHI}${BOLD}${LOAD_AVG}${SGR0}"
#----------
# Get Reflectors for line 4
NXDN_REFLECTOR="null"
if [[ "${NXDN_ENABLED}" = "1" ]]
then
SEARCH="$(grep --no-filename "reflector " /var/log/pi-star/NXDNGateway*.log | tail -1)"
IFS=' '; CUT_ARRAY=(${SEARCH}); unset IFS;
NXDNSWITCHED="${CUT_ARRAY[6]}"
NXDNSTATIC="${CUT_ARRAY[7]}"
unset CUT_ARRAY
if [[ "${SEARCH}" =~ "Switched to reflector" ]]
then
NXDN_REFLECTOR="${NXDNSWITCHED}"
else
if [[ "${SEARCH}" =~ "Statically linked to reflector" ]]
then
NXDN_REFLECTOR="${NXDNSTATIC}"
fi
fi
fi
P25_REFLECTOR="null"
if [[ "${P25_ENABLED}" = "1" ]]
then
SEARCH="$(grep --no-filename "reflector " /var/log/pi-star/P25Gateway*.log | tail -1)"
IFS=' '; CUT_ARRAY=(${SEARCH}); unset IFS;
P25LINKED="${CUT_ARRAY[6]}"
P25STATIC="${CUT_ARRAY[7]}"
unset CUT_ARRAY
if [[ "${SEARCH}" =~ "Switched to reflector" ]]
then
P25_REFLECTOR="${P25LINKED}"
else
if [[ "${SEARCH}" =~ "Statically linked to reflector" ]]
then
P25_REFLECTOR="${P25STATIC}"
fi
fi
fi
DSTAR_REFLECTOR="null"
if [[ "${DSTAR_ENABLED}" = "1" ]]
then
SEARCH="$(grep --no-filename "link to" /var/log/pi-star/ircDDBGateway*.log | tail -1)"
if [[ "${SEARCH}" =~ "established" ]]
then
DSTAR_REFLECTOR="${SEARCH:39:8}"
fi
fi
M17_STARTUP="null"
if [[ "${M17_ENABLED}" = "1" ]]
then
M17_STARTUP="$(grep -A 5 "^\[Network\]" /etc/m17gateway | grep "^Startup")"
M17_STARTUP="${M17_STARTUP#*=}"
fi
#----------
# Print line 4
YSF_STARTUP="${YSF_STARTUP}${TOP_PAD}"
YSF_STARTUP="${YSF_STARTUP:0:11}"
NXDN_REFLECTOR="${NXDN_REFLECTOR}${TOP_PAD}"
NXDN_REFLECTOR="${NXDN_REFLECTOR:0:11}"
DSTAR_REFLECTOR="${DSTAR_REFLECTOR}${TOP_PAD}"
DSTAR_REFLECTOR="${DSTAR_REFLECTOR:0:11}"
P25_REFLECTOR="${P25_REFLECTOR}${TOP_PAD}"
P25_REFLECTOR="${P25_REFLECTOR:0:9}"
M17_STARTUP="${M17_STARTUP}${TOP_PAD}"
M17_STARTUP="${M17_STARTUP:0:9}"
echo -en "${DIM}${WHI}YSF:${SGR0}${YSF_COLOR}${YSF_STARTUP}${SGR0}\r"
tput cuf 16
echo -en "${DIM}${WHI}NXDN:${SGR0}${NXDN_COLOR}${NXDN_REFLECTOR}${SGR0}\r"
tput cuf 32
echo -en "${DIM}${WHI}DSTAR:${SGR0}${DSTAR_COLOR}${DSTAR_REFLECTOR}${SGR0}\r"
tput cuf 48
echo -en "${DIM}${WHI}P25:${SGR0}${P25_COLOR}${P25_REFLECTOR}${SGR0}\r"
tput cuf 62
echo "${DIM}${WHI}M17:${SGR0}${M17_COLOR}${M17_STARTUP}${SGR0}"
#----------
# Print line 5
DMR_MASTER_NAME="${DMR_MASTER_NAME}${TOP_PAD}"
DMR_MASTER_NAME="${DMR_MASTER_NAME:0:19}"
echo -en "${DIM}${WHI}DMR MASTER:${SGR0} ${DMR_COLOR}${DMR_MASTER_NAME}${SGR0}\r"
# Print the talkgroups
tput cuf 32
echo "${BM_TG_LINE}"
#----------
# The history column headings
if [[ "${HIST_MAX}" -gt 0 ]]
then
echo "${REV}${CYA}Previous ${SBOX_VL} From ${SBOX_VL} To ${SBOX_VL} Secs ${SBOX_VL} BER% ${SBOX_VL} Src ${SBOX_VL} Loss% or S-Meter ${SGR0}${WHI}"
else
echo "${REV}${CYA} History not enabled - 0 lines specified on the commandline ${SGR0}${WHI}"
fi
#----------
# Load the current history...
. "${HISTORY_FILE}" 2>/dev/null
#----------
# DISPLAY (AND STORE) THE COMPLETE CURRENT HISTORY
# Print the QSO history lines... newest through oldest
# Save the history to a file, as well, for auto-restarts.
rm "${HISTORY_FILE}" 2>/dev/null
HIST_COUNT="1"
until [[ "${HIST_COUNT}" -gt "${HIST_MAX}" ]]
do
echo "${SGR0}${HISTORY[HIST_COUNT]}${CLR_EL}"
echo -e "HISTORY[${HIST_COUNT}]=\"${HISTORY[HIST_COUNT]}\"" >> "${HISTORY_FILE}"
((HIST_COUNT++))
done
#----------
# Print red line and clear the remainder of the line
echo "${RED}${DBL_LINE}${WHI}${SGR0}${CLR_EL}"
#----------
# If the -i|--info block is to be displayed, do it here.
if [[ "${SHOW_DEV_INFO}" = "1" ]]
then
fnDEV_INFO
fi
#----------
# Now return the cursor back to where it was, and turn it back on
echo -n "${CUR_RC}${CUR_ON}"
return
}
#-----------------------------------
fnRESET_SCROLLING()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
if [[ "${TOP}" = "0" ]]
then
return
fi
# Save the current cursor position, and turn the cursor off
echo -n "${CUR_SC}${CUR_OFF}"
# Define the entire screen as the scrolling region
tput csr 0 $((LINES - 1))
# Now return the cursor back to where it was.
echo -n "${CUR_RC}${CUR_ON}"
return
}
#-----------------------------------
fnUSAGE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
echo "USAGE: Valid options and parameters include:
Short Form:
[-12] [-c] [-d] [-D] [-e] [-f <1-6>] [-F] [-g] [-h] [-i] [-l]
[-m] [-n] [-p] [-r /path/to/file] [-t [integer]] [-v] [-w]
Long Form:
[--12hr] [--csv] [--dat] [--DXCC] [--errors] [--font <1-6>] [--FCC] [--grid]
[-help] [-info] [--logo] [--mono] [--nobig] [--poll] [--replay /path/to/file]
[--top [integer]] [--version] [--wrap]
" >&2
return
}
#-----------------------------------
fnPARSE_CMDLINE()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# Loop through all the cmdline arguments. It works like this...
#
# In the loop, current value would be the option we're evaluating.
# If the option takes a parameter, we "look ahead" at the "next"
# indexed value, and see if it makes sense to the current option.
# An example would be the -f|--font option, and it's parameter,
# for font selection. Once the option, and it's parameter are
# evaluated, we increment the index enough to skip over that
# parameter, then loop to evaluate the next option in the array.
#
# If the "current" option does not expect a parmeter, we just
# evaluate as needed, before looping to the next item in the
# array of arguments and parameters..
#
# I chose this method, because both the external GNU getopt
# command, and bash's builtin getops, have problems....
#
# GNU's getopt does not handle empty flag arguments well.
# Bash's builtin getopts does.
# (Think of the "-t|--top" option to this script, which can
# be used either with or without specifying a history depth)
#
# Bash's getopts does't parse long option names.
# GNU's getopt does.
# (Think "--help" vs "-h")
#
# The method I use can do both - it handles empty arguments (such as
# the "-t|-top" option when no optional number of history lines is
# given), and can easily accommodate long-form options. It is also,
# frankly, much easier to understand, and more portable, than either
# GNU's getopt or bash's getopts.
for (( INDEX=0; INDEX<ARG_LIST_LENGTH; INDEX++ ))
do
OPTION="${ARG_LIST[${INDEX}]}"
case "${OPTION}" in
-12|--12hr)
MIL_TIME="0"
continue
;;
-c|--csv)
GET_CSV_NOW="1"
continue
;;
-d|--dat)
GET_DAT_NOW="1"
continue
;;
-D|--DXCC)
USE_DXCC="0"
continue
;;
-e|--errors)
SHOW_LOG_ERRORS="0"
continue
;;
-f|--font)
# If this option is given, see if the next value
# is a number from 1 to 6
NEXT=$((INDEX + 1))
PARAM="${ARG_LIST[${NEXT}]}"
if [[ "${PARAM}" == ?(-)+([1-6]) ]]
then
# It is, so use it to set the font.
MY_FONT="${PARAM}"
# Skip over the number we were passed, and look
# for the next cmdline option.
((INDEX++))
else
# It is not, so show the usage text, and exit.
echo -n "${BELL}${FLASH}"
echo -e "\nERROR: The use of \"-f|--font\" requires an integer 1-6\n" >&2
fnUSAGE
stty "${STTY}"
fnCLEANUP
exit 1
fi
continue
;;
-F|--FCC)
FCC="1"
continue
;;
-g|--grid)
GRID="1"
continue
;;
-h|--help)
fnGET_HELP
stty "${STTY}"
fnCLEANUP
exit
;;
-i|--info)
SHOW_DEV_INFO="1"
continue
;;
-l|--logo)
LOGO="0"
continue
;;
-m|--mono)
# Forground colors
#BLA=""
RED=""
GRE=""
YEL=""
BLU=""
MAG=""
CYA=""
WHI=""
DEF=""
# Background colors
# Reserved for possible future use
# BG_BLA=""
# BG_RED=""
# BG_GRE=""
# BG_YEL=""
# BG_BLU=""
# BG_MAG=""
# BG_CYA=""
# BG_WHI=""
# BG_DEF=""
DMR_COLOR="${BOLD}"
YSF_COLOR="${BOLD}"
NXDN_COLOR="${BOLD}"
DSTAR_COLOR="${BOLD}"
P25_COLOR="${BOLD}"
M17_COLOR="${BOLD}"
OTHER_COLOR="${BOLD}"
continue
;;
-n|--nobig)
USE_BANNER="0"
continue
;;
-p|--poll)
BM_POLL="1"
continue
;;
-r|--replay)
# If this option is given, see if the next value
# is a file we can read
NEXT=$((INDEX + 1))
PARAM="${ARG_LIST[${NEXT}]}"
if [[ ! -f "${PARAM}" ]]
then
echo "The file specified for replay does not exist." >&2
echo "Aborting" >&2
stty "${STTY}"
fnCLEANUP
exit
fi
if [[ ! -r "${PARAM}" ]]
then
echo "The file specified for replay lacks read permission." >&2
echo "Aborting" >&2
stty "${STTY}"
fnCLEANUP
exit
fi
REPLAY="1"
REPLAY_LOG="${PARAM}"
((INDEX++))
continue
;;
-t|--top)
TOP="1"
# If this option is given, see if the next value
# is a number from 0 to 99
NEXT=$((INDEX + 1))
PARAM="${ARG_LIST[${NEXT}]}"
if [[ "${PARAM}" =~ ^-?[0-9]|[1-2][0-9]+$ ]]
then
# It is, so use it to set the history depth.
# By default, the -t|--top history is 5 lines deep.
# Can be overridden with optional numeric parameter passed to
# the "-t|--top" cmdline option. Passing a zero disables the
# history, but retains the display of all other -t|--top info.
HIST_MAX="${PARAM}"
# Skip over the number we were passed, and look
# for the next cmdline option.
((INDEX++)) || :
else
# It is not, so assume a default history of 5.
HIST_MAX="5"
fi
# When "-t|--top" is used, with or without a history, this defines
# the line number on which the scrolling region of the screen
# (the part showing the QSO details) begins.
BELOW_NOSCROLL=$((HIST_MAX + 8))
continue
;;
-v|--version)
fnVERSION
stty "${STTY}"
fnCLEANUP
exit
;;
-w|--wrap)
NO_WRAP="1"
continue
;;
*)
echo -n "${BELL}${FLASH}"
echo -e "\nERROR: \"${OPTION}\" is not a valid option.\n" >&2
fnUSAGE
echo -e "See \"pistar-lastqso --help\" for detailed descriptions of the above.\n"
stty "${STTY}"
fnCLEANUP
exit 1
;;
esac
done
return
}
#-----------------------------------
fnGET_HELP()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
cat <<'EOF' | less -M
PISTAR-LASTQSO - HELP
(Cursor Up/Down keys to scroll, Page Up/Dn to page, Q to quit help.)
With no options, the script watches for DMR, YSF, D-Star, NXDN, P25,
and M17 traffic types. Log entries are parsed and presented as each
log line is read. Ctrl-C can be used to exit the program.
-12|--12hr
By default, timestamps are shown in 24 hour military format.
For example, 5:00 PM is displayed as "17:00:00". However, by
using this option, timestamps will be displayed in 12 hour
format, with am/pm indicated, as appropriate (ex: 05:00:00 pm).
-c|--csv
Download an updated user.csv from radioid.net now, rather than
waiting for the presently installed copy to age 7 days before it
updates automatically.
-d|--dat
Download an updated cty.dat from country-files.com now, rather
than waiting for the presently installed copy to age 30 days
before it updates automatically.
-D|--DXCC
When resolving QTH location for callsigns, pistar-lastqso first
gambles that the callsign might also be either a DMR or NXDN
user. In which case, a quick check of the DMR user.csv and/or
NXDN NXDN.csv files will find their QTH data there.
If the callsign can't be found in those files, pistar-lastqso
calls upon dxcc.pl to determine the country that issued the call
sign. This can potentially impact pistar-lastqso's ability to
"keep up" with log entries, as each call to dxcc.pl can take up
to 4 or 5 seconds to resolve. When traffic lasts more than a
few seconds, this is no problem, as the script normally just
sits there saying "QSO In Progress...". But when the bulk of
traffic is "[[kerchunks]]", which pistar-lastqso considers any
traffic lasting less than 2 seconds, the kerchunks can out-pace
the ability of dxcc.pl to keep up. A few kerchunks here and
there are generally not a problem. But a large series of them
in quick succession can cause delays. Effectively, you will be
listening to one callsign, but still seeing data on the screen
for traffic logged many seconds ago.
This "-D|--DXCC" cmdline option can be used to disable calls
to the dxcc.pl script, if such delays become an issue for you.
This option has no affect on DMR or NXDN traffic. DMR and NXDN
users go through a registration process that results in their
QTH data being readily available in the user.csv and NXDN.csv
files, thus calls to the dxcc.pl script aren't needed for DMR
or NXDN traffic.
-e|--errors
By default, pistar-lastqso will display any error messages found
in the MMDVM log, as they occur. It is NOT unusual to see an
occasional, or sporadic message, such as a queue overflow. But
if these or other errors are frequent or persist, you may need
to get help from the pi-star forums. In the meantime, you can use
the "-e" or "--error" option to suppress the onscreen reporting
of these errors. The errors will however still be counted for
the current session, and the count will still be reported on the
exit screen. It is your responsibility to investigate any cause.
Use of this option DOES NOT FIX THE ERRORS coming from pi-star.
It only stops telling you that they are happening.
NOTE: The script also displays and tallies any WARNINGS that may
show up in the log. Although the -e|--error option will supress
display of ERRORS, it will NOT supress display of WARNINGS.
-f|--font <1-6>
The [-f|--font] option forces use of the selected font, regardless
of screen-width.
Valid options are 1, 2, 3, 4, 5, or 6
1 = "small"
2 = "standard"
3 = "big"
4 = "ansi_shadow"
5 = "emulator-dependent alternate charset (dbl-high/dbl-wide)"
6 = "emulator-dependent alternate charset (dbl-wide)"
If this option is omitted, the script will auto-select an
appropriately sized figlet font based on the following screen-
width thresholds:
< 80 chars wide: "small" font
80-120 chars wide: "standard" font
>120 chars wide: "big" font
The "ansi_shadow" and "emulator-dependent alternate character set"
fonts are never auto-selected.
Fonts # 5 and 6 may not work for everyone. Not all terminal
emulators or TERM-types support the vt100 ANSI control codes for
this feature.
-F|--FCC
For US callsigns only, enable amateur radio license level lookup
for callsigns, based on FCC records. The license can be one of
Novice, Technician, General, Advanced, or Extra. When enabled,
the license level is displayed on a line following the QTH data.
-g|--grid
For US callsigns only, enable Maidenhead Grid Square look-up for
callsigns, based on the US FCC mailing address of record, for a
callsign. The grid square appears at the end of the QTH data.
This works only for US callsigns.
-h|--help
Display this help screen, then exit.
-i|--info
Shows a little info in the top-right corner of the screen, when
the following THREE CONDITIONS apply...
A.) You MUST specify the "-i|--info" option.
B.) Your display MUST be at least 130 chars wide.
C.) You MUST also activate the non-scrolling information zone
at the top of the screen using the "-t|--top" option (the
number of lines of history, if any, is irrelevant).
If either B or C above is false, then A is useless. If all
three conditions are met, a small block of information, largely
helpful only to those looking to modify the script, is shown
in the top-right corner of the screen. An example looks like
this:
Options: -t 15 -f 5 -w -i -l
Debugging: 3>/dev/null Profiling: 4>/dev/null
xterm SIZE=44x168 TTY=pts0 PID=2110 BG_PID=2414
Modes/X-Modes: YSF=1 DMR=1 NXDN=1 DSTAR=1 P25=1 M17=1
YSF2DMR=0 YSF2NXDN=0 YSF2P25=0 DMR2YSR=0 DMR2NXDN=0
Traffic Counts: DMR=0 YSF=0 NXDN=0 DSTAR=0 P25=0 M17=0
DXCC=457 Grid=95105 Lic=95105 K=0 E=0 W=0 R=0
The first line shows the options and parameters passed to the
script when launched.
The second line lists any targets specified for the debug or
profiling logs, as described in the ABOUT_DEBUGGING.md and
ABOUT_PROFILING.md files.
The third line shows TERM type, and size of the screen in rows &
columns (useful to those who want to modify the script, as there
is so much cursor management stuff going on). Also shown are the
TTY of the terminal/session the script is running on, the PID of
the script, and the PID of the background task that watches for
log activity (daily log rotations and pi-star nightly updates).
The fourth line shows which of the following modes are enabled
in pi-star: YSF, DMR, NXDN, DSTAR, P25, or M17. If enabled, the
mode will show a "1". Disabled will show a "0".
The fifth line shows which of the following cross-modes are
enabled: YSF2DMR, YSF2NXDN, YSF2P25, DMR2YSR, and DMR2NXDN.
If enabled, the mode will show a "1". Disabled will show a "0".
The sixth line shows a number of counters. They show the number
of DMR, YSF, NXDN, DSTAR, P25, and M17 messages seen since the
program was launched.
The last line shows the number of records in the DXCC, Grid, and
License caches, and the number of Kerchunks, Errors, Warnings,
and log Restarts observed since program launch. When the script
is monitoring live data, the -i|--info data is shown in pale
blue. When running in -r|--replay mode, the data is shown in
bright yellow, as a quick visual reminder.
-l|--logo
Disables the animated logo at startup.
-m|--mono
For monochrome displays, this option suppresses all color codes.
-n|--nobig
Disable the large font display of callsigns and ID #s. This
conserves vertical screen-space.
-p|--poll
For DMR users configured to use Brandmeister master servers,
pistar-lastqso will ordinarily query the BM servers ONCE at
startup, to see which talkgroups (both static AND dynamic) you
are linked to. Those TGs will be shown in the non-scrolling
region, when -t|--top is used. Adding or removing talkgroups,
through PTT, TG 4000, the pi-star admin screen, Brandmeister's
"My Devices", or by any other means, will not be reflected on
screen until pistar-lastqso is stopped and run again, from the
command line.
However, if the "-p|--poll" option is used, this causes the
program to "poll" Brandmeister between every communication
displayed, resulting in a screen that will effectively track
your currently linked TGs. This is especially helpful for
users who jump around a lot between dynamic TGs, as it will
track these TGs as they are linked or unlinked.
Note that because there is a network connection made to the
BM server, and a query made, every time the data is polled,
this *can* induce a slight delay, especially on single-core
Raspberry Pi Zero-based hotspots, or on slow networks. On
multi-core Pis (including the new Raspberry Pi Zero 2), with
a decent network connection, this delay isn't a problem.
-r|--replay /path/to/file
FOR DEVELOPMENT USE: This option allows the specified file to
be "replayed" from the start, as if it were the current log.
This aids testing of coding changes, against problematic log
entries, by replaying those log entries. Troubleshooting w/
another user's log too, becomes possible. A fully qualified,
or relative path, to the filename, must be specified.
Examples:
-r /path/to/file
--replay /path/to/file
Not:
-r filename
--replay filename
This also allows adding support for modes for which the user
does not have appropriate radios, by soliciting sample logs
from contributors who do own radios of the appropriate type.
-t|--top [integer]
Adds an information zone to the top of the screen that shows
the version number of this script, date of the cty.dat and
user.csv files, name of the active log file, TX and RX freqs,
the hotspot's TCXO frequency and firmware version, CPU temp,
the Modem's device node (port), the computer platform/model,
the elapsed time since the script was launched, the system
load averages for the last 1, 5, and 15 minutes, the DMR
Master and (if Brandmeister) the first few Static & Dynamic
BrandMeister TalkGroups (dynamics are marked with an asterisk).
The last line shows the current reflectors in effect for the
other modes.
By default, the use of "-t|--top" also provides a history of
the last 5 QSOs observed. However, by specifying an integer
parameter, the number of QSOs displayed in the history can be
changed. A value of 0 (zero) disables the history (but retains
all the other information in the "-t|--top" section of the
screen). Other values determine the number of lines of history
that will be displayed.
Examples:
-t defaults to 5 lines of history
-t 0 disables the history
-t 7 displays a history 7 lines deep
-t 23 displays a history 23 lines deep
CAUTION: Specifying too many lines for your actual screen-size
will yield unpredictable (and certainly unsatisfactory) results.
I do NOT limit-check this parameter, because SSH sessions in
dynamically resizable windows could have a different number of
lines each time the screen is re-sized, rendering useless any
limit-check performed at startup.
As a point of reference, "-t 8 -n" will maximize the size of
the history, while still allowing full details for the QSO in
progress (without large fonts), on a typical 80x24 display.
Larger screens will support other combinations. Feel free to
experiment - YMMV.
-v|--version
Display this script's version number, then exit.
-w|--wrap
Ordinarily, if figlet decides it needs to wrap the text that it
displays, based on the size of the font selected, and the width
of the screen (in columns), it will do so. However, setting
this option tells figlet to ignore the screen width, and just
keep printing on the same line, even if that means the text will
disappear off the right edge of the screen. You sacrifice what-
ever information was off the edge of the screen, but without
wasting so much vertical screen space to line-wrap.
END OF HELP
(Cursor Up/Down keys to scroll, Page Up/Dn to page, Q to quit help.)
EOF
return
}
#-----------------------------------
fnVERSION()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
clear
echo "${BOLD}${WHI}$(basename "${0}") v${VERSION}${SGR0}"
cat <<'EOF'
Copyright © 2024 Ken Cormack, KE8DPF
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 <http://www.gnu.org/licenses/>.
EOF
return
}
#-----------------------------------
fnNO_LOGO()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
clear
echo "${BOLD}${WHI}PISTAR-LASTQSO v${VERSION}${SGR0}"
echo "Copyright © 2024 Ken Cormack - KE8DPF"
echo "${DIM}https://github.com/kencormack/pistar-lastqso${SGR0}"
echo
return
}
#-----------------------------------
fnLOGO()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
clear
# Initialize screen, disable line-wrap, cursor off, color to white
echo -n "${CUR_OFF}${SGR0}${WHI}"
#----------
# This "pushes the logo down" from the top of the screen...
echo -n "${CUR_HOME}"
for LINE in {6..1}
do
echo -n "${DIM}${CYA}"
{
cat <<'EOF'
██╗ █████╗ ███████╗████████╗ ██████╗ ███████╗ ██████╗
██║ ██╔══██╗██╔════╝╚══██╔══╝ ██╔═══██╗██╔════╝██╔═══██╗
██║ ███████║███████╗ ██║ ██║ ██║███████╗██║ ██║
██║ ██╔══██║╚════██║ ██║ ██║▄▄ ██║╚════██║██║ ██║
███████╗██║ ██║███████║ ██║ ╚██████╔╝███████║╚██████╔╝
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══▀▀═╝ ╚══════╝ ╚═════╝
EOF
} | head -"${LINE}" | tail -1
echo -n "${CUR_HOME}"
echo -n "${INS_LINE}"
done
for I in 1 2
do
echo -n "${CUR_HOME}"
echo -n "${INS_LINE}"
done
#----------
# This increases in "brightness", from dim, to normal, to bold.
for BRIGHTNESS in "${DIM}" "${SGR0}" "${BOLD}"
do
tput cup 3 0
echo -n "${BRIGHTNESS}${WHI}"
cat <<'EOF'
██╗ █████╗ ███████╗████████╗ ██████╗ ███████╗ ██████╗
██║ ██╔══██╗██╔════╝╚══██╔══╝ ██╔═══██╗██╔════╝██╔═══██╗
██║ ███████║███████╗ ██║ ██║ ██║███████╗██║ ██║
██║ ██╔══██║╚════██║ ██║ ██║▄▄ ██║╚════██║██║ ██║
███████╗██║ ██║███████║ ██║ ╚██████╔╝███████║╚██████╔╝
╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══▀▀═╝ ╚══════╝ ╚═════╝
EOF
done
#----------
# Draw the horizontal line under the logo
tput cup 9 9
echo -en "${SGR0}${DIM}${WHI}"
for ((I=17; I<=76; I++))
do
printf "${SBOX_HL}%0.s"
done
#----------
# This goes in the center of the horizontal line
tput cup 9 28
echo -n "${SGR0}${BOLD}${WHI} FOR PI-STAR HOTSPOTS ${SGR0}"
#----------
# Usage: fnDRAW_BOX BOX_TOP BOX_LEFT BOX_HEIGHT BOX_WIDTH BOX_STYLE BOX_FILL BOX_SHADOW
# outer yellow box
echo -n "${SGR0}${YEL}"
fnDRAW_BOX 1 4 18 69 2 0 0
# outer red box
echo -n "${SGR0}${RED}"
fnDRAW_BOX 0 2 20 73 2 0 0
#----------
# lower blue title/copyright box
echo -n "${SGR0}${BLU}"
fnDRAW_BOX 14 9 4 59 1 0 0
#----------
# title/copyright text
tput cup 15 29
echo "${SGR0}${BOLD}${YEL}PISTAR-LASTQSO v${VERSION}${WHI}${SGR0}"
tput cuf 21
echo "${SGR0}${BOLD}${BLU}Copyright © 2024 Ken Cormack/KE8DPF${WHI}${SGR0}"
tput cuf 17
echo "${SGR0}${BOLD}${BLU}https://github.com/kencormack/pistar-lastqso${WHI}${SGR0}"
echo -n "${SGR0}${WHI}${CUR_ON}"
#----------
# The 6 mode names in random sequence
local SEQUENCE
local NUM
# shellcheck disable=SC2207
SEQUENCE=($(shuf -i 1-6 -n 6))
for NUM in "${SEQUENCE[@]}"
do
case "${NUM}" in
1) tput cup 11 10 ; echo -n "${BOLD}${WHI}DMR${SGR0}" ;;
2) tput cup 11 20 ; echo -n "${BOLD}${WHI}NXDN${SGR0}" ;;
3) tput cup 11 31 ; echo -n "${BOLD}${WHI}P25${SGR0}" ;;
4) tput cup 11 41 ; echo -n "${BOLD}${WHI}DSTAR${SGR0}" ;;
5) tput cup 11 53 ; echo -n "${BOLD}${WHI}YSF${SGR0}" ;;
6) tput cup 11 63 ; echo -n "${BOLD}${WHI}M17${SGR0}" ;;
*) true ;;
esac
done
#----------
# The 6 mode boxes in random sequence
echo -n "${SGR0}${BLU}"
# shellcheck disable=SC2207
SEQUENCE=($(shuf -i 1-6 -n 6))
for NUM in "${SEQUENCE[@]}"
do
case "${NUM}" in
1) fnDRAW_BOX 10 8 2 6 1 0 1 ;;
2) fnDRAW_BOX 10 18 2 7 1 0 1 ;;
3) fnDRAW_BOX 10 29 2 6 1 0 1 ;;
4) fnDRAW_BOX 10 39 2 8 1 0 1 ;;
5) fnDRAW_BOX 10 51 2 6 1 0 1 ;;
6) fnDRAW_BOX 10 61 2 6 1 0 1 ;;
*) true ;;
esac
done
return
}
#-----------------------------------
fnCHECK_MODES_ENABLED()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
if [[ ! "${DMR_ENABLED}${YSF_ENABLED}${NXDN_ENABLED}${DSTAR_ENABLED}${P25_ENABLED}${M17_ENABLED}" =~ "1" ]]
then
#----------
echo -n "${SGR0}${RED}"
tput cup 11 18
for ((I=18; I<=59; I++))
do
printf "${DBOX_HL}%0.s"
done
fnDRAW_BOX 8 16 6 45 2 1 1
#----------
tput cup 10 24
echo "${SGR0}${SMUL}${BOLD}${YEL}NO DIGITAL MODES FOUND ENABLED${SGR0}${WHI}"
#----------
tput cuf 27
echo "AT LEAST ONE IS REQUIRED"
tput cuf 20
echo "PLEASE ENABLE, THEN RE-RUN THIS SCRIPT"
tput cup 22 0
echo "${CLR_EL}${CUR_ON}Aborting" >&2
#----------
fnCLOSE_FDS
# If debugging and/or profiling was used, show the names of the logs, at exit
if [[ "${DEBUG_LOG}" != "/dev/null" ]] || [[ "${PROFILING_LOG}" != "/dev/null" ]]
then
if [[ "${MY_FONT}" = "5" ]]
then
DEBUG_PROFILING_LINE=$((LINES - 6))
else
DEBUG_PROFILING_LINE=$((LINES - 4))
fi
tput cup "${DEBUG_PROFILING_LINE}" 0
fnLARGE_FONT "DEBUGGING: ${DEBUG_LOG}"
fnLARGE_FONT "PROFILING: ${PROFILING_LOG}"
echo
fi
#----------
tput cup "${LINES}" 0
echo -e "${TERM_RESET}"
stty "${STTY}"
reset
fnCLEANUP
exit
fi
return
}
#-----------------------------------
fnGOODBYE_BOX()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
echo -n "${SGR0}${BLU}"
fnDRAW_BOX 4 15 12 47 2 1 1
tput cup 6 19
echo "${BOLD}${WHI}THANK YOU FOR USING PISTAR-LASTQSO v${VERSION}${SGR0}${DEF}"
#----------
tput cup 7 24
ELAPSED="$(fnSECONDS2TIME)"
echo -n "${DIM}${CYA}Elapsed: ${ELAPSED}"
tput cup 8 31
echo -n "${DIM}${CYA}Log Restarts: "
printf %02d "${LOG_RESTARTS}"
# Get present value of all the counters
. "${COUNT_FILE}"
#----------
tput cup 10 23
echo -n "${DIM}${WHI}QSOs Observed This Session: ${SGR0}${YEL}"
printf %05d "${QSO_COUNT}"
#----------
tput cup 11 22
echo -n "${DIM}${WHI}(DMR ${SGR0}${CYA}"
printf %05d "${DMR_COUNT}"
tput cup 11 32
echo -n "${DIM}${WHI})${SGR0}"
tput cup 11 34
echo -n "${DIM}${WHI}(YSF ${SGR0}${CYA}"
printf %05d "${YSF_COUNT}"
tput cup 11 44
echo -n "${DIM}${WHI})${SGR0}"
tput cup 11 46
echo -n "${DIM}${WHI}(P25 ${SGR0}${CYA}"
printf %05d "${P25_COUNT}"
tput cup 11 56
echo -n "${DIM}${WHI})${SGR0}"
tput cup 12 21
echo -n "${DIM}${WHI}(DSTAR ${SGR0}${CYA}"
printf %05d "${DSTAR_COUNT}"
tput cup 12 33
echo -n "${DIM}${WHI})${SGR0}"
tput cup 12 35
echo -n "${DIM}${WHI}(NXDN ${SGR0}${CYA}"
printf %05d "${NXDN_COUNT}"
tput cup 12 45
echo -n "${DIM}${WHI})${SGR0}"
tput cup 12 47
echo -n "${DIM}${WHI}(M17 ${SGR0}${CYA}"
printf %05d "${M17_COUNT}"
tput cup 12 57
echo -n "${DIM}${WHI})${SGR0}"
#----------
if [[ "${QSO_COUNT}" -gt 0 ]]
then
if [[ "${KERCHUNK_COUNT}" -gt "${QSO_COUNT}" ]]
then
KERCHUNK_COUNT="${QSO_COUNT}"
fi
PERCENTAGE="$(awk -v KERCHUNK_COUNT="${KERCHUNK_COUNT}" -v QSO_COUNT="${QSO_COUNT}" 'BEGIN {print (KERCHUNK_COUNT / QSO_COUNT * 100)}')"
fi
tput cup 13 24
echo -n "${DIM}${WHI}Includes ${SGR0}${YEL}$(printf %04d "${KERCHUNK_COUNT}")${DIM}${WHI} Kerchunks (${SGR0}${YEL}$(printf %.2f "${PERCENTAGE}")%${DIM}${WHI})${SGR0}"
#----------
tput cup 14 17
echo -n "${CYA}Cache (DXCC $(printf %04d "$(grep -c . "${DXCC_CACHE_FILE}")"))"
tput cup 14 35
echo -n "${CYA}(GRID $(printf %05d "$(grep -c . "${GRID_CACHE_FILE}")"))"
tput cup 14 49
echo -n "${CYA}(FCC $(printf %05d "$(grep -c . "${LICENSE_CACHE_FILE}")"))"
#----------
ERROR_COUNT_COLOR=""
WARNING_COUNT_COLOR=""
if [[ "${ERROR_COUNT}" -gt 0 ]] || [[ "${WARNING_COUNT}" -gt 0 ]]
then
if [[ "${ERROR_COUNT}" -gt 0 ]]
then
ERROR_COUNT_COLOR="${SGR0}${YEL}"
fi
if [[ "${WARNING_COUNT}" -gt 0 ]]
then
WARNING_COUNT_COLOR="${SGR0}${RED}"
fi
if [[ "${ERROR_COUNT}" -gt 0 ]] || [[ "${WARNING_COUNT}" -gt 0 ]]
then
echo -n "${SGR0}${RED}"
# Usage: fnDRAW_BOX BOX_TOP BOX_LEFT BOX_HEIGHT BOX_WIDTH BOX_STYLE BOX_FILL BOX_SHADOW
fnDRAW_BOX 15 24 3 30 2 1 1
tput cup 16 27
echo -n "${SGR0}${DIM}${WHI}${ERROR_COUNT_COLOR}MMDVM Log ERR MSGS: $(printf %05d "${ERROR_COUNT}")${SGR0}"
tput cup 17 27
echo -n "${SGR0}${DIM}${WHI}${WARNING_COUNT_COLOR}MMDVM Log WARNINGS: $(printf %05d "${WARNING_COUNT}")${SGR0}"
fi
fi
return
}
#-----------------------------------
# Watches the logfile for rotation, and appends "MMDVMHOST-STOP"
# to the working log (for the "while read" loop to catch.)
fnMMDVMLOG_FUSER()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# This function (exported and spawned later in the script) runs
# as a background task, in a seperate process.
# See if the MMDVMHost process has let go of the working log.
# If so, re-start log monitoring by writing "MMDVMHOST-STOP" to
# the log we've been watching. The main "while read" loop
# will then spot this and re-start monitoring, with the new log.
# If MMDVMHost still has the log open, do nothing, sleep a few
# seconds, then check again.
while true
do
# If the parent script goes away, exit this background task
if [[ "$(pgrep -f "${BASH} /usr/local/bin/pistar-lastqso")" = "" ]]
then
exit
fi
# If MMDVMHost has let go of the log, add a marker for the main script's
# "while read" loop to detect.
if [[ ! "$(sudo fuser -v "${WORKING_LOG}" 2>&1)" =~ "MMDVMHost" ]]
then
echo -e "MMDVMHOST-STOP\n" | sudo tee -a "${WORKING_LOG}"
sync;sync;sync
exit
fi
# If pistar-lastqso is still running, and MMDVMHost still
# has the log open, sleep before checking again.
sleep 2
done
return
}
#-----------------------------------
fnSEPARATOR()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
echo -e "\r${CLR_EL}${DBL_LINE}${WHI}${SGR0}"
return
}
#-----------------------------------
fnLISTENING()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# If DMR is enabled... AND master is Brandmeister...
if [[ "$(grep "${GTAB}${DMR_MASTER}${GTAB}" "${DMRHOSTS}")" =~ "BM_" ]] && [[ "${DMR_ENABLED}" -eq 1 ]]
then
# ...AND the TG file doesn't exist... OR polling is enabled...
if [[ ! -f "${BM_TG_FILE}" ]] || [[ "${BM_POLL}" -eq 1 ]]
then
echo -en "\r${CLR_EL}"
echo -n " ${CYA}${BOLD}Polling BM Talkgroups... ${SGR0}${WHI}${CLR_EL}${CLR_ED}"
fi
fi
fnNOSCROLL_ZONE
echo -en "\r${CLR_EL}"
echo -n " ${GRE}${BOLD}Listening for Traffic... ${SGR0}${WHI}${CLR_EL}${CLR_ED}"
TA_DISPLAYED="0"
return
}
#-----------------------------------
fnQSO_IN_PROGRESS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
echo -en "\r${CLR_EL}"
echo -n " ${YEL}${BOLD}QSO In Progress... ${SGR0}${WHI}${CLR_EL}${CLR_ED}"
TA_DISPLAYED="0"
return
}
#-----------------------------------
# This takes the DMR user's callsign and looks up the
# city, state, and country fields
fnDMR_CSV_SEARCH()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# Fields in the file:
# RADIO_ID,CALLSIGN,FIRST_NAME,LAST_NAME,CITY,STATE,COUNTRY
# We want the last three.
CSV_RECORD="$(grep ",${CSV_CALLSIGN}," "${USERCSV}" | LC_ALL=C sort -u | head -1)"
# I've seen someone using the Malaysian Country Code of 5020386 as their
# DMR ID, so the log contained only that number (not a callsign), thus
# the name lookup based on the callsign failed, displaying the "From:" as
# simply "5020386 ()". This also resulted in City/State/Country lookup
# returning "n/a, Ontario, Canada" which was wierd. Anyway, this makes
# sure that the absence of a callsign doesn't return bad location data,
# by making a second pass through the user.csv file, this time looking
# for the DMR ID number, rather than the callsign.
if [[ "${CSV_RECORD}" = "" ]]
then
CSV_RECORD="$(grep "^${CSV_CALLSIGN}," "${USERCSV}" | LC_ALL=C sort -u | head -1)"
fi
if [[ "${CSV_RECORD}" != "" ]]
then
IFS=','; CUT_ARRAY=(${CSV_RECORD}); unset IFS;
CITY="${CUT_ARRAY[4]}"
STATE="${CUT_ARRAY[5]}"
COUNTRY="${CUT_ARRAY[6]}"
unset CUT_ARRAY
else
CITY=""
STATE=""
COUNTRY=""
fi
# If any fields came back empty, set a default of "n/a" (not available)
CITY="${CITY:-'n/a'}"
STATE="${STATE:-'n/a'}"
COUNTRY="${COUNTRY:-'n/a'}"
#----------
if [[ "${COUNTRY}" = "United States" ]] && [[ "${GRID}" = "1" ]]
then
fnGET_US_GRIDSQUARE "${CSV_CALLSIGN}"
else
GRIDSQUARE=""
fi
return
}
#-----------------------------------
fnCONVERT_DATE_TIME()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# Convert the log entry UTC date/timestamps to localized, human-readable form
DATE="$(date -d "${LOGREC_DATE} ${LOGREC_TIME} UTC" +"%a %d %b %Y")"
if [[ "${MIL_TIME}" -eq 1 ]]
then
TIME="$(date -d "${LOGREC_DATE} ${LOGREC_TIME} UTC" +"%H:%M:%S %Z")"
AMPM=" "
else
TIME="$(date -d "${LOGREC_DATE} ${LOGREC_TIME} UTC" +"%I:%M:%S %p %Z")"
IFS=' '; CUT_ARRAY=(${TIME}); unset IFS;
AMPM="${CUT_ARRAY[1]}"
unset CUT_ARRAY
AMPM="${AMPM:0:1}"
fi
return
}
#-----------------------------------
fnDMR_GET_FROM()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# The MMDVMHost daemon internally knows the actual DMR ID# of the user,
# and does ID number to Callsign lookups from DMRIds.dat. However, in
# the logfile, you end up with the Callsign, and lose the ID number.
# Disabling this lookup gains you the ID numbers in the logfile, but
# loses the Callsigns. If I were to disable said lookups via this script,
# pi-star web-based Dashboard users wouldn't appreciate that. So, I work
# backwords, taking the Callsign that MMDVMHost writes to the logs,
# and looking up the Callsign in the DMRIds.dat file, to find the ID.
# The problem is that in the early days of DMR, some users were registering
# multiple IDs (one for their radio, one for each hotspot, etc.) That
# practice has been halted, with the prefered method of identifying
# hotspots by adding a two-digit suffix to the user's normal 7-digit ID.
# However, those earlier cases of multiple ID numbers mapped to the same
# Callsign still exist in the file. Each DMR ID links to only one Callsign,
# but not every Callsign links to just one ID. Some Callsigns "hit" more
# than once in the file. With no guarantee that I'm picking the correct ID
# linked to a given Callsign, I just pick the first number, on the
# assumption that that's the one that represents the user, not one of their
# hotspot. I wish MMDVMHost would log both Callsign and ID#. But it's one
# or the other, not both. For the vast majority of cases, my "lesser of
# two evils" approach will serve well enough.
#
# See if the sender is a callsign...
read -r FROM_ID FROM_NAME <<< "$(grep "${GTAB}${FROM}${GTAB}" "${DMRIDS}" | awk '{ print $1 " " $NF }' | head -1)"
if [[ "${FROM_NAME}" = "" ]]
then
# If not, then see if it is a talkgroup
FROM_NAME="$(grep "^${FROM};" "${TGLIST}")"
IFS=';'; CUT_ARRAY=(${FROM_NAME}); unset IFS;
FROM_NAME="${CUT_ARRAY[2]}"
unset CUT_ARRAY
# If that came back empty, see if it's in MY_LIST.txt
if [[ "${FROM_NAME}" = "" ]] && [[ -f "${MY_LIST}" ]]
then
FROM_NAME="$(grep "^${FROM};" "${MY_LIST}")"
IFS=';'; CUT_ARRAY=(${FROM_NAME}); unset IFS;
FROM_NAME="${CUT_ARRAY[2]}"
unset CUT_ARRAY
fi
else
if [[ -f "${USERCSV}" ]]
then
# If the recipient is a callsign, not a talkgroup, lookup
# further info about that callsign in the user.csv file
CSV_CALLSIGN="${FROM}"
fnDMR_CSV_SEARCH
fi
fi
return
}
#-----------------------------------
fnDMR_GET_TO()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# See if the recipient is a callsign
read -r TO_ID TO_NAME <<< "$(grep "${GTAB}${TO}${GTAB}" "${DMRIDS}" | awk '{ print $1 " " $NF }' | head -1)"
# If not, see if it is a talkgroup
if [[ "${TO_NAME}" = "" ]]
then
TO_NAME="$(grep "^${TO};" "${TGLIST}")"
IFS=';'; CUT_ARRAY=(${TO_NAME}); unset IFS;
TO_NAME="${CUT_ARRAY[2]}"
unset CUT_ARRAY
# Some talkgroups do not have names given, in the TGList_BM.txt
# file that pi-star updates automatically. These next few lines
# allow the user to create a second lookup file in which they
# can list talkgroups they use, that are not named in pi-star's
# TGList_BM.txt file.
#
# This suplemental file is /usr/local/etc/MY_LIST.txt, and entries
# in that file must be in the same format as the TGList_BM.txt file.
# DO NOT INCLUDE SPACES. Since pi-star will not overwrite your
# MY_LIST.txt file, the info you list there will survive pi-star's
# automated updates.
#
# Exactly as with the TGList_BM.txt file, the fields in MY_LIST.txt
# include:
# Dest ID;Option;Name;Description
# Option: TG:0, REF:1, PC:2
# Examples:
# 2627;0;BADEN-WUERTTEMBERG;TG2627
# 2629;0;SACHSEN/THUERINGEN;TG2629
# 26232;0;DREILAENDERECK-MITTE-DEUTSCHLAND;TG26232
# 26274;0;BW-BOEBLINGEN;TG26274
# 26283;0;REGION-MUENCHEN;TG26283
# 26287;0;ALLGAEU-BODENSEE;TG26287
# 26298;0;THUERINGEN;TG26298
#
# You can also add certain 'private' IDs here that
# may not be listed. Example:
# 262993;0;GPS-WX;PC262993
# 310999;0;APRS;PC310999
# 9999995;0;PISTAR-HOSTFILES;PC9999995
# 9999996;0;PISTAR-SHUTDOWN;PC9999996
# 9999997;0;PISTAR-REBOOT;PC9999997
# 9999998;0;PISTAR-SVC-RESTART;PC9999998
# 9999999;0;PISTAR-SVC-KILL;PC9999999
#
if [[ "${TO_NAME}" = "" ]] && [[ -f "${MY_LIST}" ]]
then
TO_NAME="$(grep "^${TO};" "${MY_LIST}")"
IFS=';'; CUT_ARRAY=(${TO_NAME}); unset IFS;
TO_NAME="${CUT_ARRAY[2]}"
unset CUT_ARRAY
fi
else
# If the recipient is a callsign, not a talkgroup, lookup
# further info about that callsign in the user.csv file
if [[ -f "${USERCSV}" ]]
then
CSV_CALLSIGN="${TO}"
fnDMR_CSV_SEARCH
fi
fi
return
}
#-----------------------------------
fnBANNER()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# If -n|--nobig was used, get out of here.
if [[ "${USE_BANNER}" = "0" ]]
then
return
fi
#----------
if [[ "${LOGREC_MODE}" = "D-Star" ]]
then
FROM_OR_SENDER="${SENDER}"
else
FROM_OR_SENDER="${FROM}"
fi
if [[ "${LOGREC_MODE}" = "M17" ]] && [[ "${M17_HOST_SUFFIX}" != "" ]]
then
FROM_OR_SENDER="${FROM}_${M17_HOST_SUFFIX}"
else
FROM_OR_SENDER="${FROM}"
fi
#----------
# NO_WRAP is set with the "-w|--wrap" cmdline option.
if [[ "${NO_WRAP}" = "1" ]]
then
OUTPUT_WIDTH="1000"
else
OUTPUT_WIDTH="${COLUMNS}"
fi
#----------
# This deals with the alternate charset font only...
if [[ "${MY_FONT}" = "5" ]] || [[ "${MY_FONT}" = "6" ]]
then
# Fonts #5 and 6 are not figlet fonts.
# Print what we need, then get out.
fnLARGE_FONT "${BOLD}${CONTACT_COLOR}${FROM_OR_SENDER} ... ${TO}"
return
fi
#----------
# And this deals with figlet fonts...
# Disable the large font display callsign to save screen space,
# by passing the "--nobig" parameter to this script:
# pistar-lastqso -n|--nobig
case "${MY_FONT}" in
1)
FONT="small"
;;
2)
FONT="standard"
;;
3)
FONT="big"
;;
4)
FONT="ansi_shadow"
;;
*)
# A screen width of less than 80 columns will select figlet's
# "small" font. A screen width greater than 80 columns will
# use figlet's larger "standard" font. A screen width greater
# than 120 columns will use figlet's "big" font.
FONT="small"
if [[ "${COLUMNS}" -gt 80 ]]
then
FONT="standard"
fi
if [[ "${COLUMNS}" -gt 120 ]]
then
FONT="big"
fi
;;
esac
#----------
BANNER="$(figlet -w "${OUTPUT_WIDTH}" -f "${FONT}" "${FROM_OR_SENDER} ... ${TO}")"
echo "${BOLD}${CONTACT_COLOR}${BANNER}${SGR0}"
#----------
# Tighten up the leading with these two fonts.
# This moves the cursor up one line to recover
# an extra blank line that follows these fonts.
case "${FONT}" in
"big"|"ansi_shadow")
echo -n "${CUR_UP}"
;;
*)
:
;;
esac
echo -n "${CLR_ED}"
return
}
#-----------------------------------
fnYSF_END_NETWORK_TRAFFIC()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Fields to pass to the history...
BER="${BER%\%*}"
LOSS="${LOSS%\%*}"
fnCOLORIZE_BER
fnCOLORIZE_LOSS
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}%${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Loss:${SGR0} ${LOSS_COLOR}${BOLD}${LOSS}%${SGR0}${WHI}\r"
#----------
if [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
}
#-----------------------------------
fnDSTAR_END_NETWORK_TRAFFIC()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
fnCOLORIZE_BER
fnCOLORIZE_LOSS
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}%${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Loss:${SGR0} ${LOSS_COLOR}${BOLD}${LOSS}%${SGR0}${WHI}\r"
#----------
if [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
}
#-----------------------------------
fnM17_END_NETWORK_TRAFFIC()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Fields to pass to the history...
BER="n/a"
LOSS="n/a"
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
#----------
if [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
}
#-----------------------------------
fnNXDN_END_NETWORK_TRAFFIC()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Fields to pass to the history...
BER="n/a"
LOSS="n/a"
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
#----------
if [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
}
#-----------------------------------
fnP25_END_NETWORK_TRAFFIC()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Fields to pass to the history...
BER="n/a"
fnCOLORIZE_LOSS
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
tput cuf 32
echo -en "${DIM}Loss:${SGR0} ${LOSS_COLOR}${BOLD}${LOSS}%${SGR0}${WHI}\r"
#----------
if [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
}
#-----------------------------------
fnSHOW_CURRENT_CALL()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Show our human-readable date/time
echo -en "${SGR0}${DIM}${WHI}Date:${SGR0} ${BLU}${BOLD}${DATE}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Time:${SGR0} ${BLU}${BOLD}${TIME}${SGR0}${WHI}\r"
tput cuf 57
echo -n "${DIM}QSOs Watched:${SGR0} ${BLU}${BOLD}"
printf %05d "${QSO_COUNT}"
echo "${SGR0}${WHI}${CLR_ED}"
#----------
# For this, and the QTH lookups further down, we groom
# the incoming ${FROM}, to isolate just the callsign.
# For example, many YSF users will send things like
# "KE8DPF-KEN" or "KE8DPF/KEN". So we isolate just
# the "KE8DPF" callsign, to perform our lookups.
SEARCH_FROM="$(fnCHOP_CALL "${FROM}")"
# Search the CSV files for the "From" name
if ! [[ "${SEARCH_FROM}" =~ ^[0-9]+$ ]]
then
# the "From" contains letters (a callsign)
FROM_NAME="$(grep ",${SEARCH_FROM}," "${NXDNCSV}" "${USERCSV}" | tail -1)"
else
# the "From" is numeric only (not a callsign)
FROM_NAME="$(grep "^${SEARCH_FROM}," "${NXDNCSV}" "${USERCSV}" | tail -1)"
fi
IFS=','; CUT_ARRAY=(${FROM_NAME}); unset IFS;
FROM_NAME="${CUT_ARRAY[2]}"
unset CUT_ARRAY
#----------
# Show the From (and From_Name, if found above)
if [[ "${LOGREC_MODE}" = "D-Star" ]]
then
FROM_OR_SENDER="${SENDER}"
else
FROM_OR_SENDER="${FROM}"
fi
if [[ -n "${FROM_NAME}" ]]
then
P_FROM_NAME="$(printf "%-15s" "(${FROM_NAME})")"
echo -en "${DIM}From:${SGR0} ${BOLD}${FROM_OR_SENDER} ${P_FROM_NAME}${SGR0}\r"
else
echo -en "${DIM}From:${SGR0} ${BOLD}${FROM_OR_SENDER}${SGR0}\r"
fi
#----------
# Increment the counter
. "${COUNT_FILE}"
case "${LOGREC_MODE}" in
"DMR")
((DMR_COUNT++))
;;
"YSF")
((YSF_COUNT++))
;;
"NXDN")
((NXDN_COUNT++))
;;
"D-Star")
((DSTAR_COUNT++))
;;
"P25")
((P25_COUNT++))
;;
"M17")
((M17_COUNT++))
;;
*)
:
;;
esac
fnWRITE_COUNTER_FILE
#----------
# Show the To
tput cuf 31
if [[ -n "${TO}" ]]
then
echo -n "${DIM} To..:${SGR0} ${BOLD}${TO}"
fi
# If D-Star, add any reflector logged as "via"
if [[ "${LOGREC_MODE}" = "D-Star" ]] && [[ -n "${DSTAR_REFLECTOR}" ]]
then
echo -n " via ${DSTAR_REFLECTOR}"
DSTAR_REFLECTOR=""
fi
echo "${SGR0}${WHI}${CLR_ED}"
#----------
# Show the Mode
echo -en "${DIM}Mode:${SGR0} ${BOLD}${CYA}${LOGREC_MODE}${SGR0}${WHI}\r"
# Show either NET or RF
tput cuf 32
echo "${DIM}Src.:${SGR0} ${BOLD}${CYA}${SOURCE}${SGR0}${WHI}${CLR_ED}"
#----------
CSV_RECORD=""
# Lookup the QTH data for the callsign.
# Try the DMR user.csv file first...
if [[ "${LOGREC_MODE}" != "NXDN" ]]
then
QTH_SRC="user.csv"
# Search the CSV files for the "From" name
if ! [[ "${SEARCH_FROM}" =~ ^[0-9]+$ ]]
then
# the "From" contains letters (a callsign)
CSV_RECORD="$(grep ",${SEARCH_FROM}," "${USERCSV}" | LC_ALL=C sort -u | head -1)"
else
# the "From" is numeric only (not a callsign)
CSV_RECORD="$(grep "^${SEARCH_FROM}," "${USERCSV}" | LC_ALL=C sort -u | head -1)"
fi
fi
if [[ "${LOGREC_MODE}" != "DMR" ]]
then
# If nothing found, try the NXDN.csv file...
if [[ "${CSV_RECORD}" = "" ]]
then
QTH_SRC="NXDN.csv"
# Search the CSV files for the "From" name
if ! [[ "${SEARCH_FROM}" =~ ^[0-9]+$ ]]
then
# the "From" contains letters (a callsign)
CSV_RECORD="$(grep ",${SEARCH_FROM}," "${NXDNCSV}" | LC_ALL=C sort -u | head -1)"
else
# the "From" is numeric only (not a callsign)
CSV_RECORD="$(grep "^${SEARCH_FROM}," "${NXDNCSV}" | LC_ALL=C sort -u | head -1)"
fi
fi
fi
#----------
CITY=""
STATE=""
COUNTRY=""
# If we have a hit from either file, set the CITY, STATE, and COUNTRY
if [[ "${CSV_RECORD}" != "" ]]
then
# Callsign was found in one or the other csv file
IFS=','; CUT_ARRAY=(${CSV_RECORD}); unset IFS;
CITY="${CUT_ARRAY[4]}"
STATE="${CUT_ARRAY[5]}"
COUNTRY="${CUT_ARRAY[6]}"
unset CUT_ARRAY
else
# Callsign was not found in either csv file
# Last resort: Look up country based on callsign prefix
# But skip this lookup, if disabled on command line.
if [[ "${USE_DXCC}" = "1" ]]
then
# At least one numeric digit must appear in the first few characters
# of the callsign to have a chance of being valid. Yeah, I'm looking
# at users that come across as "AMERIC" (americalink). Likewise,
# we can't look up something that is digits-only. So look for BOTH
# alpha and digit chars in the first few chars of the From. Otherwise,
# don't waste resources looking it up (and knowing it will fail).
if [[ "${SEARCH_FROM:0:4}" == *[[:digit:]]* ]] && [[ "${SEARCH_FROM:0:4}" == *[[:alpha:]]* ]]
then
QTH_SRC="cty.dat"
# NOTE: The average time to resolve, calling dxcc.pl,
# is 4-5 seconds on a Raspberry Pi Zero. Calling
# the script creates a lot of overhead.
#
# Callsigns found in either user.csv or ndxn.csv
# can be resolved (and w/ greater detail) in fractions
# of a second, which is why dxcc.pl is my last resort
# to determine at least *some* QTH information.
#
# However, if I have to use dxcc.pl to resolve, I then
# cache the result so that next time that callsign
# appears, the country can be parsed from the cache
# to save time... A LOT of time!
# Search the cache.
DXCC_CACHE_RECORD="$(grep "^${SEARCH_FROM}:" "${DXCC_CACHE_FILE}")"
if [[ -n "${DXCC_CACHE_RECORD}" ]]
then
# The cache gave us the country.
COUNTRY="${DXCC_CACHE_RECORD#*:}"
COUNTRY="${COUNTRY/ of America/}"
DXCC_TIME="Cached"
else
# The cache search came up empty. Call on dxcc.pl.
# Capture both the "Country" resolved by dxcc.pl, AND the time it took to resolve.
# This technique captures both STDOUT and STDERR, saving them both in variables.
# With "TIMEFORMAT" defined earlier as "%R", the "time" command gives only the
# "real" time, in seconds.
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <( (printf '\0%s\0' "$(time dxcc.pl "${SEARCH_FROM}" | grep "^Country Name:" | awk '{ print substr($0, index($0,$3)) }')" 1>&2) 2>&1)
# ^--^--- These spaces are important for proper operation, for
# linting/beautifying, and for syntax highlighting in "vim".
COUNTRY="${CAPTURED_STDOUT}"
COUNTRY="${COUNTRY/ of America/}"
DXCC_TIME="${CAPTURED_STDERR} secs"
if [[ -n "${COUNTRY}" ]]
then
# Store the from:country in the cache.
echo "${SEARCH_FROM}:${COUNTRY}" >> "${DXCC_CACHE_FILE}"
fi
fi
fi
fi
fi
# If any fields came back empty, set a default of "n/a" (not available)
CITY="${CITY:-'n/a'}"
STATE="${STATE:-'n/a'}"
COUNTRY="${COUNTRY:-'n/a'}"
#----------
if [[ "${COUNTRY}" = "United States" ]] && [[ "${GRID}" = "1" ]]
then
fnGET_US_GRIDSQUARE "${SEARCH_FROM}"
else
GRIDSQUARE=""
fi
#----------
# Show location details.
# If all the fields came back empty...
if [[ "${CITY}" = "n/a" ]] && [[ "${STATE}" = "n/a" ]] && [[ "${COUNTRY}" = "n/a" ]]
then
# If dxcc searches are enabled (default)...
if [[ "${USE_DXCC}" = "1" ]]
then
# Let the user know we came up empty, despite all the searches
echo -en "${DIM}File:${SGR0} ${MAG}${BOLD}none${SGR0}${WHI}\r"
tput cuf 18
echo "${DIM}${WHI}QTH.: ${SGR0}${BOLD}${MAG}Unable to determine ${FROM}'s location${SGR0}${WHI}${CLR_ED}"
else
# Otherwise, remind the user that dxcc searches are disabled.
echo "${DIM}${WHI}QTH.: ${SGR0}${BOLD}${MAG}DXCC Lookups Disabled on Cmdline${SGR0}${WHI}${CLR_ED}"
fi
else
# If we got this far, we at least got some kind of results,
# either from the CSV searches, or from dxcc.
# Display those results for the user.
echo -en "${DIM}File:${SGR0} ${MAG}${BOLD}${QTH_SRC}${SGR0}${WHI}\r"
tput cuf 18
if [[ "${QTH_SRC}" = "cty.dat" ]]
then
echo "${DIM}QTH:${SGR0} ${BOLD}${MAG}${COUNTRY}${GRIDSQUARE} (DXCC: ${DXCC_TIME})${SGR0}${WHI}${CLR_ED}"
else
echo "${DIM}QTH:${SGR0} ${BOLD}${MAG}${CITY}, ${STATE}, ${COUNTRY}${GRIDSQUARE}${SGR0}${WHI}${CLR_ED}"
fi
fi
#----------
if [[ "${COUNTRY}" = "United States" ]] && [[ "${FCC}" = "1" ]]
then
fnGET_US_LICENSE "${SEARCH_FROM}"
if [[ -n "${LICENSE}" ]]
then
if [[ "${LICENSE}" = "CLUB" ]]
then
echo "${DIM}FCC :${SGR0} ${BOLD}${MAG}${LICENSE} callsign${SGR0}${WHI}${CLR_ED}"
else
echo "${DIM}FCC :${SGR0} ${BOLD}${MAG}${LICENSE} class license${SGR0}${WHI}${CLR_ED}"
fi
fi
else
LICENSE=""
fi
fnQSO_IN_PROGRESS
return
}
#-----------------------------------
fnDMR_SHOW_CURRENT_CALL()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# Show our human-readable date/time
echo -en "${SGR0}${DIM}${WHI}Date:${SGR0} ${BLU}${BOLD}${DATE}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Time:${SGR0} ${BLU}${BOLD}${TIME}${SGR0}${WHI}\r"
tput cuf 57
echo -n "${DIM}QSOs Watched:${SGR0} ${BLU}${BOLD}${CLR_ED}"
printf %05d "${QSO_COUNT}"
echo "${SGR0}${WHI}${CLR_ED}"
#----------
# Show formatted to and from names
P_FROM_NAME="$(printf "%-15s" "(${FROM_NAME})")"
P_TO_NAME="$(printf "%-15s" "(${TO_NAME})")"
echo -en "${DIM}From:${SGR0} ${BOLD}${FROM} ${P_FROM_NAME}${SGR0}\r"
. "${COUNT_FILE}"
((DMR_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 31
echo "${DIM} To..:${SGR0} ${BOLD}${TO} ${P_TO_NAME}${SGR0}${WHI}${CLR_ED}"
#----------
# Show DMR ID# (Best guess - see comment in function fnDMR_GET_FROM)
if [[ -n "${FROM_ID}" ]]
then
DMR_ID="${FROM_ID}"
else
DMR_ID="${TO_ID}"
fi
#----------
# Ok, we found the Callsign in the DMRIds.dat file. Let's see
# if the Callsign is associated with multiple DMR ID #s.
if [[ "$(grep -c "${GTAB}${FROM}${GTAB}" "${DMRIDS}")" -gt 1 ]]
then
DMR_ID="${DMR_ID}(*)"
fi
echo -en "${DIM}ID #:${SGR0} ${MAG}${BOLD}${DMR_ID}${SGR0}${WHI}\r"
tput cuf 18
#----------
# Show location details from the user.csv file
if [[ "${CITY}" = "n/a" ]] && [[ "${STATE}" = "n/a" ]] && [[ "${COUNTRY}" = "n/a" ]]
then
echo "${MAG}${BOLD}No Location Data Found${SGR0}${WHI}${CLR_ED}"
else
if echo "${CITY}${STATE}${COUNTRY}" | grep -q "[a-zA-Z]"
then
echo "${DIM}QTH:${SGR0} ${MAG}${BOLD}${CITY}, ${STATE}, ${COUNTRY}${GRIDSQUARE}${SGR0}${WHI}${CLR_ED}"
fi
fi
#----------
if [[ "${COUNTRY}" = "United States" ]] && [[ "${FCC}" = "1" ]]
then
fnGET_US_LICENSE "${FROM}"
if [[ -n "${LICENSE}" ]]
then
echo "${DIM}FCC :${SGR0} ${BOLD}${MAG}${LICENSE} class license${SGR0}${WHI}${CLR_ED}"
fi
else
LICENSE=""
fi
#----------
# show the time slot, source (RF or network), and type (voice or data)
echo -en "${DIM}Mode:${SGR0} ${CYA}${BOLD}DMR TS${TIME_SLOT}${SGR0}${WHI}\r"
tput cuf 18
echo -e "${DIM}Src:${SGR0} ${CYA}${BOLD}${SOURCE}${SGR0}${WHI}\t${DIM}Type:${SGR0} ${BOLD}${TYPE}${SGR0}${WHI}${CLR_ED}"
fnQSO_IN_PROGRESS
return
}
#-----------------------------------
fnCOLORIZE_BER()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# Default to Good/Green
BER_COLOR="${GRE}"
if [[ -n "$(echo "${BER}" | awk '$1 >=2.0{print}')" ]] && [[ -n "$(echo "${BER}" | awk '$1 <=4.9{print}')" ]]
then
# Fair
BER_COLOR="${YEL}"
else
if [[ -n "$(echo "${BER}" | awk '$1 >=5.0{print}')" ]]
then
# Poor
BER_COLOR="${RED}"
fi
fi
return
}
#-----------------------------------
fnCOLORIZE_LOSS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# Default to Good/Green
LOSS_COLOR="${GRE}"
if [[ -n "$(echo "${LOSS}" | awk '$1 >1.0{print}')" ]] && [[ -n "$(echo "${LOSS}" | awk '$1 <3.0{print}')" ]]
then
# Fair
LOSS_COLOR="${YEL}"
else
if [[ -n "$(echo "${LOSS}" | awk '$1 >=3.0{print}')" ]]
then
# Poor
LOSS_COLOR="${RED}"
fi
fi
return
}
#-----------------------------------
fnCOLORIZE_RSSI()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# What follows converts RSSI dBm to S-Meter values.
# We look for S9 through S1 signal strength...
# Very Strong
SMETER_COLOR="${GRE}"
if [[ "${DBM}" -lt 93 ]]
then
# If stronger than S9, calculate how much over
SMETER="S9+$((93-DBM))dB"
else
# Good
if [[ "${DBM}" -eq 93 ]]
then
SMETER="S9"
else
if [[ "${DBM}" -ge 99 ]] && [[ "${DBM}" -lt 93 ]]
then
SMETER="S8"
else
if [[ "${DBM}" -ge 85 ]] && [[ "${DBM}" -lt 105 ]]
then
SMETER="S7"
else
# Fair
SMETER_COLOR="${YEL}"
if [[ "${DBM}" -ge 105 ]] && [[ "${DBM}" -lt 111 ]]
then
SMETER="S6"
else
if [[ "${DBM}" -ge 111 ]] && [[ "${DBM}" -lt 117 ]]
then
SMETER="S5"
else
if [[ "${DBM}" -ge 117 ]] && [[ "${DBM}" -lt 123 ]]
then
SMETER="S4"
else
if [[ "${DBM}" -ge 123 ]] && [[ "${DBM}" -lt 129 ]]
then
SMETER="S3"
else
# Poor
SMETER_COLOR="${RED}"
if [[ "${DBM}" -ge 129 ]] && [[ "${DBM}" -lt 135 ]]
then
SMETER="S2"
else
if [[ "${DBM}" -ge 135 ]] && [[ "${DBM}" -lt 141 ]]
then
SMETER="S1"
else
# Things are REALLY bad, if you can't even reach S1 signal strength
if [[ "${DBM}" -ge 141 ]]
then
SMETER="S0"
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
fi
return
}
#-----------------------------------
fnDOWNLOAD_NXDNCSV()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# If the NXDN.csv file is missing, or is older than 7 days,
# then download a new one from the web.
if [[ ! -f "${NXDNCSV}" ]] || [[ -n "$(find "${NXDNCSV}" -mtime +7)" ]] || [[ "${GET_CSV_NOW}" -eq 1 ]]
then
echo -e "\nDownloading latest NXDN.csv file..."
sudo mount -o remount,rw / 2>/dev/null
if sudo wget -t 1 -q --show-progress --progress=bar:force:noscroll https://database.radioid.net/static/nxdn.csv -O "${NXDNCSV}.tmp"
then
# Check for a too-small (possibly corrupt) file
# Make sure it's at least ~100K in size
if [[ "$(sudo stat --format=%s "${NXDNCSV}.tmp")" -lt 100000 ]]
then
echo -e "\nThe download appears corrupt. Will continue without it.\n" >&2
sleep 2
else
echo "Applying the updated file..."
if sudo mv "${NXDNCSV}.tmp" "${NXDNCSV}" && sudo chown pi-star.pi-star "${NXDNCSV}"
then
echo "Update complete."
else
echo "There was a problem applying the update. Will continue without it." >&2
sleep 2
fi
fi
else
echo "Unable to retrieve latest NXDN.csv. Will continue without it." >&2
sleep 2
fi
sync;sync;sync
sudo mount -o remount,ro / 2>/dev/null
fi
return
}
#-----------------------------------
fnDOWNLOAD_USERCSV()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# If the user.csv file is missing, or is older than 7 days,
# then download a new one from the web.
if [[ ! -f "${USERCSV}" ]] || [[ -n "$(find "${USERCSV}" -mtime +7)" ]] || [[ "${GET_CSV_NOW}" -eq 1 ]]
then
echo -e "\nDownloading latest DMR user.csv file..."
sudo mount -o remount,rw / 2>/dev/null
if sudo wget -t 1 -q --show-progress --progress=bar:force:noscroll https://database.radioid.net/static/user.csv -O "${USERCSV}.tmp"
then
# Check for a too-small (possibly corrupt) file
# Make sure it's at least ~10M in size
if [[ "$(sudo stat --format=%s "${USERCSV}.tmp")" -lt 10000000 ]]
then
echo -e "\nThe download appears corrupt. Will continue without it.\n" >&2
sleep 2
else
echo "Applying the updated file..."
if sudo mv "${USERCSV}.tmp" "${USERCSV}" && sudo chown pi-star.pi-star "${USERCSV}"
then
echo "Update complete."
else
echo "There was a problem applying the update. Will continue without it." >&2
sleep 2
fi
fi
else
echo "Unable to retrieve latest user.csv. Will continue without it." >&2
sleep 2
fi
sync;sync;sync
sudo mount -o remount,ro / 2>/dev/null
fi
return
}
#-----------------------------------
fnDOWNLOAD_CTYDAT()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
# If the cty.dat file is missing, or is older than 30 days,
# then download a new one from the web.
if [[ ! -f "${CTYDAT}" ]] || [[ -n "$(find "${CTYDAT}" -mtime +30)" ]] || [[ "${GET_DAT_NOW}" -eq 1 ]]
then
echo -e "\nDownloading latest DXCC cty.dat file..."
sudo mount -o remount,rw / 2>/dev/null
BIGCTYZIP="$(curl -s --fail --max-time 2 "https://www.country-files.com/category/big-cty/" | grep "\[download\]" | LC_ALL=C sort -nr | head -1 | cut -f2 -d"\"")"
if [[ -n "${BIGCTYZIP}" ]]
then
mkdir /tmp/bigctydir 2>/dev/null
if sudo wget -t 1 -q --show-progress --progress=bar:force:noscroll "${BIGCTYZIP}" -O "/tmp/bigctydir/bigcty.zip"
then
# Check for a too-small (possibly corrupt) file
# Make sure it's at least ~10K in size
if [[ "$(sudo stat --format=%s "/tmp/bigctydir/bigcty.zip")" -lt 10000 ]]
then
echo -e "\nThe download appears corrupt. Will continue without it.\n" >&2
sleep 2
else
echo "Applying the updated file..."
sudo unzip /tmp/bigctydir/bigcty.zip -d /tmp/bigctydir > /dev/null 2>&1
if sudo cp /tmp/bigctydir/cty.dat "${CTYDAT}" && sudo chown pi-star.pi-star "${CTYDAT}"
then
sudo rm -r /tmp/bigctydir 2>/dev/null
echo "Update complete."
else
echo "There was a problem applying the update. Will continue without it." >&2
sleep 2
fi
fi
else
echo "Unable to retrieve latest cty.dat. Will continue without it." >&2
sleep 2
fi
sync;sync;sync
sudo mount -o remount,ro / 2>/dev/null
else
echo "Unable to determine latest available cty.dat. Will continue without it." >&2
sleep 2
fi
fi
return
}
#-----------------------------------
fnBUILD_HISTORY()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
if [[ -n "${QSO_START_TIME}" ]] && [[ -n "${FROM}" ]] && [[ -n "${TO}" ]]
then
if [[ -n "${LOSS}" ]] || [[ -n "${BER}" ]] || [[ "${DATA}" -eq 1 ]]
then
#----------
if [[ "${LOGREC_MODE}" = "D-Star" ]]
then
FROM_OR_SENDER="${SENDER}"
else
FROM_OR_SENDER="${FROM}"
fi
if [[ "${LOGREC_MODE}" = "M17" ]] && [[ "${M17_HOST_SUFFIX}" != "" ]]
then
FROM_OR_SENDER="${FROM}_${M17_HOST_SUFFIX}"
else
FROM_OR_SENDER="${FROM}"
M17_HOST_SUFFIX=""
fi
FROM_OR_SENDER="${FROM_OR_SENDER:0:11}"
#----------
# Load the current history...
. "${HISTORY_FILE}" 2>/dev/null
#----------
# PUSH CURRENT HISTORY DOWN. THIS DROPS THE OLDEST RECORD,
# AND LEAVES THE NEWEST SPOT EMPTY FOR THE RECORD WE ARE
# ABOUT TO ADD
rm "${HISTORY_FILE}" 2>/dev/null
HIST_COUNT="${HIST_MAX}"
until [[ "${HIST_COUNT}" -eq 0 ]]
do
HISTORY[HIST_COUNT]="${HISTORY[$((HIST_COUNT - 1))]}"
echo -e "HISTORY[${HIST_COUNT}]=\"${HISTORY[HIST_COUNT]}\"" >> "${HISTORY_FILE}"
((HIST_COUNT--))
done
#----------
HIST_TIME="$(printf %-8s "$(echo "${QSO_START_TIME}" | awk '{ print $1 }')")"
HIST_FROM="$(printf %-11s "${FROM_OR_SENDER}")"
HIST_TO="$(printf %-10s "${TO}")"
HIST_SECS="$(printf %5s "${SECS}")"
if [[ "${BER}" = "n/a" ]]
then
HIST_BER="-----"
else
HIST_BER="$(printf %4s "${BER}")%"
fi
if [[ "${LOSS}" = "n/a" ]]
then
HIST_LOSS="-----"
else
HIST_LOSS="$(printf %3s "${LOSS}")%"
fi
if [[ "${SOURCE}" = "NET" ]]
then
HIST_SRC="$(printf %-3s "Net")"
HIST_SMETER=""
else
HIST_SRC="$(printf %-3s "RF")"
if [[ "${RSSI}" = "n/a" ]]
then
HIST_SMETER="-----"
else
HIST_SMETER="${SMETER} (-${DBM} dBm)"
fi
fi
#----------
if [[ "${DATA}" -eq 1 ]]
then
HIST_SECS="-----"
HIST_BER="-----"
fi
#----------
# CONSTRUCT THE NEW RECORD WE'LL BE ADDING
HISTORY[1]="${BOLD}${BLU}${HIST_TIME}${DIM}${AMPM}${BOLD}${WHI}${SGR0}${SBOX_VL} ${BOLD}${CONTACT_COLOR}${HIST_FROM}${SGR0}${SBOX_VL} ${BOLD}${CONTACT_COLOR}${HIST_TO}${SGR0} ${SBOX_VL} ${BOLD}${BLU}${HIST_SECS}${WHI}${SGR0} ${SBOX_VL} ${BOLD}${BER_COLOR}${HIST_BER}${WHI}${SGR0} ${SBOX_VL} ${BOLD}${CYA}${HIST_SRC}${WHI}"
#----------
if [[ "${DATA}" -eq 1 ]]
then
if [[ -n "${BLOCKS}" ]]
then
HISTORY[1]="${HISTORY[1]} ${SBOX_VL} ${BOLD}${YEL}Data (Blocks: ${BLOCKS})${WHI}${SGR0}"
else
HISTORY[1]="${HISTORY[1]} ${SBOX_VL} ${BOLD}${YEL}Data${WHI}${SGR0}"
fi
else
if [[ -n "${RSSI}" ]]
then
HISTORY[1]="${HISTORY[1]} ${SBOX_VL} ${BOLD}${SMETER_COLOR}${HIST_SMETER}${WHI}${SGR0}"
else
HISTORY[1]="${HISTORY[1]} ${SBOX_VL} ${BOLD}${LOSS_COLOR}${HIST_LOSS}${WHI}${SGR0}"
fi
fi
fi
fi
#----------
# STORE THE NEW HISTORY, INCLUDING THE RECORD WE'VE JUST BUILT
# Save the history
HIST_COUNT="1"
until [[ "${HIST_COUNT}" -gt "${HIST_MAX}" ]]
do
echo -e "HISTORY[${HIST_COUNT}]=\"${HISTORY[HIST_COUNT]}\"" >> "${HISTORY_FILE}"
((HIST_COUNT++))
done
#----------
TIME=""
FROM=""
TO=""
SECS=""
BER=""
LOSS=""
SMETER=""
DBM=""
RSSI=""
DATA=""
return
}
#-----------------------------------
fnTALLY_ERRORS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
ERROR_MSG="$(echo "${RECORD}" | cut -f4- -d" ")"
. "${COUNT_FILE}"
((ERROR_COUNT++))
fnWRITE_COUNTER_FILE
# If we've masked the reporting of logged errors by
# using the "-e|--error" cmdline option, then
# skip displaying the error, but still tally it.
if [[ "${SHOW_LOG_ERRORS}" = "1" ]]
then
echo -n "${SGR0}${YEL}"
fnSEPARATOR
echo -en "${BOLD}${RED}ERROR: ${WHI}${ERROR_MSG}\r"
tput cuf 63
echo "${BOLD}${RED} Count: $(printf %05d "${ERROR_COUNT}")"
echo -n "${SGR0}${YEL}"
fnSEPARATOR
fi
return
}
#-----------------------------------
fnTALLY_WARNINGS()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
WARNING_MSG="$(echo "${RECORD}" | cut -f4- -d" ")"
. "${COUNT_FILE}"
((WARNING_COUNT++))
fnWRITE_COUNTER_FILE
# Unlike errors, warnings will not be masked by
# the "-e|--error" cmdline option. They will
# always be displayed (as well as tallied.)
echo -n "${SGR0}${RED}"
fnSEPARATOR
echo -en "${BOLD}${RED}WARNING: ${WHI}${WARNING_MSG}\r"
tput cuf 63
echo "${BOLD}${RED} Count: $(printf %05d "${WARNING_COUNT}")"
echo -n "${SGR0}${RED}"
fnSEPARATOR
return
}
#-----------------------------------
fnDMR_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
#----------
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_3="${CUT_ARRAY[2]}"
FIELD_5="${CUT_ARRAY[4]}"
FIELD_6="${CUT_ARRAY[5]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_9="${CUT_ARRAY[8]}"
FIELD_10="${CUT_ARRAY[9]}"
FIELD_11="${CUT_ARRAY[10]}"
FIELD_12="${CUT_ARRAY[11]}"
FIELD_13="${CUT_ARRAY[12]}"
FIELD_14="${CUT_ARRAY[13]}"
FIELD_15="${CUT_ARRAY[14]}"
FIELD_17="${CUT_ARRAY[16]}"
FIELD_18="${CUT_ARRAY[17]}"
FIELD_20="${CUT_ARRAY[19]}"
FIELD_21="${CUT_ARRAY[20]}"
unset CUT_ARRAY
#----------
# Don't waste any time with these...
if [[ "${LOGREC_REMAINING_DATA}" =~ " CSBK " ]] || [[ "${LOGREC_REMAINING_DATA}" =~ " VSBK " ]]
then
return
fi
#----------
# Compensate for shifting fields between "group" and "private" ID numbers
# "private" call log entries lack "TG" in the log line, which skews the
# data returned when parsing positional fields. These few lines inject a
# bogus "PC" (private contact) place-holder to substitute for the missing
# "TG", so as not to skew positional fields. A hack, but a useful one.
if [[ ! "${LOGREC_REMAINING_DATA}" =~ " TG " ]]
then
LOGREC_REMAINING_DATA="${LOGREC_REMAINING_DATA// to / to PC }"
fi
#----------
# NEW TA HANDLER
# At some point, the format of DMR Talker Alias log entries
# changed. The data used to come in chunks, and I had to
# ensure the data was complete before displaying it. Now,
# it looks like mmdvmhost only logs the TA when it's complete,
# and I no longer have to check to ensure we have all of it.
if [[ "${LOGREC_REMAINING_DATA}" =~ ", Talker Alias" ]]
then
if [[ "${TA_DISPLAYED}" -eq 0 ]]
then
TA="$(echo "${LOGREC_REMAINING_DATA}" | grep "Talker Alias" | cut -f2 -d'"')"
if [[ -n "${TA}" ]]
then
echo -en "\r${CLR_EL}"
echo "${DIM}TA..:${SGR0} ${MAG}${BOLD}${TA}${SGR0}${WHI}${CLR_ED}"
TA_DISPLAYED="1"
fnQSO_IN_PROGRESS
fi
fi
return
fi
# OLD TA HANDLER
# Up until recently, this was the format for DMR Talker Alias
# log entries. The data would come in chunks, and I'd need to
# ensure the data was complete in order to display it.
if [[ "${LOGREC_REMAINING_DATA}" =~ "DMR Talker Alias" ]]
then
# The log entries show that TA arrives in chunks. Make sure we have
# the whole thing before we print it. Don't bother displaying, if
# we don't get the whole thing (Kerchunkers, for example, don't
# always connect long enough to transfer the whole TA.)
# Also, sometimes, Talker Alias is present, but contains no data.
# Don't bother showing if TA is empty or contains only partial data.
TA_BYTES_RECVD="${FIELD_8%/*}"
TA_BYTES_TOTAL="${FIELD_8#*/}"
if [[ "${TA_BYTES_RECVD}" -eq "${TA_BYTES_TOTAL}" ]] && [[ "${TA_DISPLAYED}" -eq 0 ]]
then
TA="$(echo "${LOGREC_REMAINING_DATA}" | grep "DMR Talker Alias" | cut -f2- -d":" | sed -u -e "s/^ //" -e "s/'//g")"
if [[ -n "${TA}" ]]
then
echo -en "\r${CLR_EL}"
echo "${DIM}TA..:${SGR0} ${MAG}${BOLD}${TA}${SGR0}${WHI}${CLR_ED}"
TA_DISPLAYED="1"
fnQSO_IN_PROGRESS
fi
fi
return
fi
#----------
fnCONVERT_DATE_TIME
# Determine DMR Time Slot, and RF or Network traffic
TIME_SLOT="${FIELD_3%,*}"
SOURCE="${FIELD_5//network/NET}"
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "late entry from" ]]
then
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
# A QSO has started...
QSO_START_TIME="${TIME}"
FROM="${FIELD_9}"
fnDMR_GET_FROM
TO="${FIELD_12%,*}"
fnDMR_GET_TO
echo -en "\r${CLR_EL}"
fnBANNER
#----------
TYPE="${YEL}Late Entry"
fnDMR_SHOW_CURRENT_CALL
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "header from" ]]
then
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
# A QSO has started...
QSO_START_TIME="${TIME}"
FROM="${FIELD_9}"
fnDMR_GET_FROM
TO="${FIELD_12%,*}"
fnDMR_GET_TO
echo -en "\r${CLR_EL}"
fnBANNER
#----------
# Determine if voice or data
if [[ "${FIELD_6}" = "data" ]]
then
BLOCKS="${FIELD_13}"
TYPE="${YEL}Data Transfer"
else
TYPE="${CYA}Voice Call"
fi
fnDMR_SHOW_CURRENT_CALL
return
fi
#----------
# The QSO has ended.
# If it was voice traffic...
if [[ "${LOGREC_REMAINING_DATA}" =~ "end of voice" ]]
then
DATA="0"
# The number of seconds the contact lasted
SECS="${FIELD_15}"
#----------
# If it was network traffic...
if [[ "${LOGREC_REMAINING_DATA}" =~ "RSSI:" ]]
then
# For RF traffic...
# BER is in different positions, for network and RF log entries
# Remove any comma
BER="${FIELD_18%,*}"
# Remove the "%" symbol
BER="${BER%\%*}"
fnCOLORIZE_BER
#----------
# Get the RSSI
RSSI="${FIELD_20}"
DBM="${RSSI%%/*}"
DBM="${DBM#*-}"
fnCOLORIZE_RSSI
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI}${DIM} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}%${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}RSSI:${SGR0} ${SMETER_COLOR}${BOLD}${SMETER} (-${DBM} dBm)${SGR0}${WHI}\r"
else
# For network traffic...
# BER is in different positions, for network and RF log entries
BER="${FIELD_21%,*}"
# Remove the "%" symbol
BER="${BER%\%*}"
fnCOLORIZE_BER
#----------
# Get the packet loss
LOSS="${FIELD_17%\%*}"
fnCOLORIZE_LOSS
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}%${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Loss:${SGR0} ${LOSS_COLOR}${BOLD}${LOSS}%${SGR0}${WHI}\r"
fi
#----------
# If call was less than 2 secs,
# and we know who it was from,
# then call it a kerchunk.
# Responses such as "disconnected",
# received with a 4000 disconnect
# are not counted.
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
SECS="${FIELD_8}"
#----------
# Get the packet loss
LOSS="${LOSS%\%*}"
fnCOLORIZE_LOSS
#----------
# Get the BER
BER="${BER%\%*}"
fnCOLORIZE_BER
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}%${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}Loss:${SGR0} ${LOSS_COLOR}${BOLD}${LOSS}%${SGR0}${WHI}\r"
#----------
# If call was less than 2 secs,
# and we know who it was from,
# then call it a kerchunk.
# Responses such as "disconnected",
# received with a 4000 disconnect
# are not counted.
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# It was data traffic...
if [[ "${LOGREC_REMAINING_DATA}" =~ "ended network data" ]] || [[ "${LOGREC_REMAINING_DATA}" =~ "ended RF data" ]]
then
DATA="1"
if [[ "${BLOCKS}" -gt 1 ]]
then
S="s"
else
S=""
fi
echo -e "\r${CLR_EL}${YEL}${BOLD}End of Data Transmission (${BLOCKS} block${S})${SGR0}${WHI}"
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network user has timed out" ]]
then
echo "Network user has timed out"
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed DMR log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_DMR
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnYSF_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_6="${CUT_ARRAY[5]}"
FIELD_7="${CUT_ARRAY[6]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_9="${CUT_ARRAY[8]}"
FIELD_10="${CUT_ARRAY[9]}"
FIELD_11="${CUT_ARRAY[10]}"
FIELD_12="${CUT_ARRAY[11]}"
FIELD_14="${CUT_ARRAY[13]}"
FIELD_15="${CUT_ARRAY[14]}"
FIELD_17="${CUT_ARRAY[16]}"
FIELD_18="${CUT_ARRAY[17]}"
unset CUT_ARRAY
#----------
# Don't waste any time with these...
if [[ "${LOGREC_REMAINING_DATA}" =~ "invalid access attempt from" ]]
then
return
fi
#----------
# Start of YSF network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network data from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
if [[ "${FIELD_7}" = "to" ]]
then
FROM="${FIELD_6}"
DG_ID="${FIELD_8}"
DG_ID_NUM="${FIELD_9}"
MASTER="${FIELD_11}"
else
# This drops space-separated values from the transmitted callsigns.
FROM="${FIELD_6}"
DG_ID="${FIELD_9}"
DG_ID_NUM="${FIELD_10}"
MASTER="${FIELD_12}"
fi
TO="${DG_ID} ${DG_ID_NUM}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="NET"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# Start of YSF RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF header from" ]] || [[ "${LOGREC_REMAINING_DATA}" =~ "received RF late entry from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF header from" ]]
then
# Parse Recvd RF header...
if [[ "${FIELD_7}" = "to" ]]
then
FROM="${FIELD_6}"
DG_ID="${FIELD_8}"
DG_ID_NUM="${FIELD_9}"
else
# This drops space-separated values from the transmitted callsigns.
FROM="${FIELD_6}"
DG_ID="${FIELD_8}"
DG_ID_NUM="${FIELD_10}"
fi
else
# Parse RF late entries...
if [[ "${FIELD_8}" = "to" ]]
then
FROM="${FIELD_7}"
DG_ID="${FIELD_9}"
DG_ID_NUM="${FIELD_10}"
else
# This drops space-separated values from the transmitted callsigns.
FROM="${FIELD_7}"
DG_ID="${FIELD_9}"
DG_ID_NUM="${FIELD_11}"
fi
fi
TO="${DG_ID} ${DG_ID_NUM}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="RF"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# YSF RF traffic dropped
if [[ "${LOGREC_REMAINING_DATA}" =~ "transmission lost from" ]]
then
SECS="${FIELD_9}"
BER="${FIELD_12}"
RSSI="${FIELD_14}"
if [[ "${RSSI}" = "" ]]
then
RSSI="${BER}"
BER="0.0%"
fi
DG_ID_NUM="${DG_ID_NUM%,*}"
# Fields to pass to the history...
BER="${BER%,*}"
BER="${BER%\%*}"
DBM="${RSSI%%/*}"
DBM="${DBM#*-}"
fnCOLORIZE_BER
fnCOLORIZE_RSSI
#----------
echo -en "\r${CLR_EL}"
echo -e "\r${BOLD}${YEL}TRANSMISSION LOST${SGR0}${WHI}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}RSSI:${SGR0} ${SMETER_COLOR}${BOLD}${SMETER} (-${DBM} dBm)${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
echo -n "${CLR_ED}"
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# End of YSF network traffic (Wires-X)
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network end of transmission from" ]]
then
SECS="${FIELD_12}"
LOSS="${FIELD_14}"
BER="${FIELD_18}"
fnYSF_END_NETWORK_TRAFFIC
return
fi
# End of YSF network traffic (Normal traffic)
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
SECS="${FIELD_6}"
LOSS="${FIELD_8}"
BER="${FIELD_12}"
fnYSF_END_NETWORK_TRAFFIC
return
fi
#----------
# End of YSF RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF end of transmission from" ]]
then
SECS="${FIELD_12}"
BER="${FIELD_15}"
RSSI="${FIELD_17}"
if [[ "${RSSI}" = "" ]]
then
RSSI="${BER}"
BER="0.0%"
fi
DG_ID_NUM="${DG_ID_NUM%,*}"
# Fields to pass to the history...
BER="${BER%,*}"
BER="${BER%\%*}"
DBM="${RSSI%%/*}"
DBM="${DBM#*-}"
fnCOLORIZE_BER
fnCOLORIZE_RSSI
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}RSSI:${SGR0} ${SMETER_COLOR}${BOLD}${SMETER} (-${DBM} dBm)${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed YSF log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_YSF
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnM17_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
# Revise field list below, as needed
FIELD_1="${CUT_ARRAY[0]}"
FIELD_2="${CUT_ARRAY[1]}"
FIELD_3="${CUT_ARRAY[2]}"
FIELD_4="${CUT_ARRAY[3]}"
FIELD_5="${CUT_ARRAY[4]}"
FIELD_6="${CUT_ARRAY[5]}"
FIELD_7="${CUT_ARRAY[6]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_9="${CUT_ARRAY[8]}"
FIELD_10="${CUT_ARRAY[9]}"
FIELD_11="${CUT_ARRAY[10]}"
FIELD_12="${CUT_ARRAY[11]}"
FIELD_13="${CUT_ARRAY[12]}"
FIELD_14="${CUT_ARRAY[13]}"
# Revise field list above, as needed
unset CUT_ARRAY
#----------
# Start of M17 RF voice traffic
# M17, received RF voice transmission from KE8DPF to ALL
# 1 2 3 4 5 6 7 8 9
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF voice transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_7}"
TO="${FIELD_9}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="RF"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# End of M17 RF traffic
# M17, received RF end of transmission from KE8DPF to ALL, 0.0 seconds, BER: 0.0%
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF end of transmission" ]]
then
SECS="${FIELD_11}"
BER="${FIELD_14}"
BER="${BER%,*}"
BER="${BER%\%*}"
fnCOLORIZE_BER
RSSI="n/a"
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# Transmission lost
# M17, transmission lost from KE8DPF to ALL, 0.1 seconds, BER: 6.2%
# 1 2 3 4 5 6 7 8 9 10 11
if [[ "${LOGREC_REMAINING_DATA}" =~ "transmission lost from" ]]
then
echo -en "\r${CLR_EL}"
SECS="${FIELD_8}"
BER="${FIELD_11}"
RSSI="n/a"
# Fields to pass to the history...
BER="${BER%,*}"
BER="${BER%\%*}"
fnCOLORIZE_BER
#----------
echo -en "\r${CLR_EL}"
echo -e "\r${BOLD}${YEL}TRANSMISSION LOST${SGR0}${WHI}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
echo -n "${CLR_ED}"
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# Start of M17 network traffic
# M17, received network voice transmission from XXXXXX X to ALL
# M17, received network voice transmission from XXXXXX to ALL
# 1 2 3 4 5 6 7 8 9 10
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network voice transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_7}"
if [[ "${FIELD_10}" = "ALL" ]]
then
TO="${FIELD_10}"
M17_HOST_SUFFIX="${FIELD_8}"
else
TO="${FIELD_9}"
M17_HOST_SUFFIX=""
fi
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="NET"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
# #----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "M17, text Data:" ]]
then
IFS=':'; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
M17_TEXT_DATA="${CUT_ARRAY[1]}"
unset CUT_ARRAY
# Ensure display of any "text Data" entry happens only once per message
if [[ "${FROM}" != "${LAST_FROM}" ]]
then
echo -e "\r${DIM}Text:${SGR0}${BOLD}${WHI}${M17_TEXT_DATA}${SGR0}"
LAST_FROM="${FROM}"
fi
return
fi
#----------
# End of M17 network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network end of transmission from" ]]
then
FROM="${FIELD_8}"
if [[ "${FIELD_11}" = "ALL," ]]
then
TO="${FIELD_11%,*}"
M17_HOST_SUFFIX="${FIELD_9}"
SECS="${FIELD_12}"
else
TO="${FIELD_10%,*}"
M17_HOST_SUFFIX=""
SECS="${FIELD_11}"
fi
LOSS="n/a"
fnM17_END_NETWORK_TRAFFIC
# This next line resets ability to display "text Data" entries
LAST_FROM="${FROM}"
return
fi
# #----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
echo -en "\r${CLR_EL}"
SECS="${FIELD_6}"
fnM17_END_NETWORK_TRAFFIC
# This next line resets ability to display "text Data" entries
LAST_FROM="${FROM}"
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed M17 log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_M17
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnNXDN_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_6="${CUT_ARRAY[5]}"
FIELD_7="${CUT_ARRAY[6]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_9="${CUT_ARRAY[8]}"
FIELD_10="${CUT_ARRAY[9]}"
FIELD_11="${CUT_ARRAY[10]}"
FIELD_12="${CUT_ARRAY[11]}"
unset CUT_ARRAY
#----------
# Start of NXDN RF voice traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_6}"
TG_LABEL="${FIELD_8}"
TO="${FIELD_9}"
TO="${TG_LABEL} ${TO}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="RF"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# End of NXDN RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF end of transmission" ]]
then
SECS="${FIELD_7}"
BER="${FIELD_10}"
BER="${BER%,*}"
BER="${BER%\%*}"
fnCOLORIZE_BER
RSSI="n/a"
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# Start of NXDN network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_6}"
TG_LABEL="${FIELD_8}"
TO="${FIELD_9}"
TO="${TG_LABEL} ${TO}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="NET"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# End of NXDN network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network end of transmission from" ]]
then
FROM="${FIELD_8}"
TG_LABEL="${FIELD_10}"
TO="${FIELD_11%,*}"
SECS="${FIELD_12}"
LOSS="n/a"
fnNXDN_END_NETWORK_TRAFFIC
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
echo -en "\r${CLR_EL}"
SECS="${FIELD_6}"
fnNXDN_END_NETWORK_TRAFFIC
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed NXDN log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_NXDN
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnDSTAR_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
# IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
# FIELD_6="${CUT_ARRAY[5]}"
# unset CUT_ARRAY
#----------
# Ignore these
if [[ "${LOGREC_REMAINING_DATA}" =~ "invalid slow data header" ]]
then
return
fi
#----------
# Start of DSTAR network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network header from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
SOURCE="NET"
# Take any entry like "KE8DPF / INFO"
SENDER="${LOGREC_REMAINING_DATA:37:13}"
# Remove all spaces ... "KE8DPF/INFO"
# Examples such as "KE8DPF / " become "KE8DPF/"
SENDER="${SENDER// /}"
# Preserve anything to the left of the slash
FROM="${SENDER%/*}"
# If the last character is just the slash
if [[ "${SENDER:0-1}" = "/" ]]
then
# then do without the slash
SENDER="${FROM}"
fi
TO="${LOGREC_REMAINING_DATA:54:8}"
TO="${TO// /}"
DSTAR_REFLECTOR="${LOGREC_REMAINING_DATA:67:8}"
echo -en "\r${CLR_EL}"
fnBANNER
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# Start of DSTAR RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF header from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
SOURCE="RF"
#----------
# Take any entry like "KE8DPF / INFO"
SENDER="${LOGREC_REMAINING_DATA:32:13}"
# Remove all spaces ... "KE8DPF/INFO"
# Examples such as "KE8DPF / " become "KE8DPF/"
SENDER="${SENDER// /}"
# Preserve anything to the left of the slash
FROM="${SENDER%/*}"
# If the last character is just the slash
if [[ "${SENDER:0-1}" = "/" ]]
then
# then do without the slash
SENDER="${FROM}"
fi
#----------
TO="${LOGREC_REMAINING_DATA:49:8}"
TO="${TO// /}"
#----------
echo -en "\r${CLR_EL}"
fnBANNER
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# End of DSTAR RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF end of transmission" ]]
then
# Take any entry like "KE8DPF / INFO"
SENDER="${LOGREC_REMAINING_DATA:45:13}"
# Remove all spaces ... "KE8DPF/INFO"
# Examples such as "KE8DPF / " become "KE8DPF/"
SENDER="${SENDER// /}"
# Preserve anything to the left of the slash
FROM="${SENDER%/*}"
# If the last character is just the slash
if [[ "${SENDER:0-1}" = "/" ]]
then
# then do without the slash
SENDER="${FROM}"
fi
#----------
TO="${LOGREC_REMAINING_DATA:62:8}"
TO="${TO// /}"
#----------
IFS=','; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_3="${CUT_ARRAY[2]}"
FIELD_4="${CUT_ARRAY[3]}"
FIELD_5="${CUT_ARRAY[4]}"
unset CUT_ARRAY
#----------
# seconds
IFS=' '; CUT_ARRAY=(${FIELD_3}); unset IFS;
SECS="${CUT_ARRAY[0]}"
unset CUT_ARRAY
#----------
# BER
BER="${FIELD_4#*:}"
BER="${BER// /}"
BER="${BER%\%*}"
#----------
# Get the RSSI
RSSI="${FIELD_5}"
DBM="${RSSI%%/*}"
DBM="${DBM#*-}"
fnCOLORIZE_RSSI
#----------
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}RSSI:${SGR0} ${SMETER_COLOR}${BOLD}${SMETER} (-${DBM} dBm)${SGR0}${WHI}\r"
#----------
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# End of DSTAR network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network end of transmission" ]]
then
echo -en "\r${CLR_EL}"
# Take any entry like "KE8DPF / INFO"
SENDER="${LOGREC_REMAINING_DATA:50:13}"
# Remove all spaces ... "KE8DPF/INFO"
# Examples such as "KE8DPF / " become "KE8DPF/"
SENDER="${SENDER// /}"
# Preserve anything to the left of the slash
FROM="${SENDER%/*}"
# If the last character is just the slash
if [[ "${SENDER:0-1}" = "/" ]]
then
# then do without the slash
SENDER="${FROM}"
fi
TO="${LOGREC_REMAINING_DATA:67:8}"
TO="${TO// /}"
IFS=','; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_3="${CUT_ARRAY[2]}"
FIELD_4="${CUT_ARRAY[3]}"
FIELD_5="${CUT_ARRAY[4]}"
unset CUT_ARRAY
# seconds
IFS=' '; CUT_ARRAY=(${FIELD_3}); unset IFS;
SECS="${CUT_ARRAY[0]}"
unset CUT_ARRAY
# loss
LOSS="${FIELD_4%\%*}"
# BER
BER="${FIELD_5#*:}"
BER="${BER// /}"
BER="${BER%\%*}"
fnDSTAR_END_NETWORK_TRAFFIC
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
echo -en "\r${CLR_EL}"
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_6="${CUT_ARRAY[5]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_12="${CUT_ARRAY[11]}"
unset CUT_ARRAY
# seconds
SECS="${FIELD_6}"
# loss
LOSS="${FIELD_8%\%*}"
# BER
BER="${FIELD_12#*:}"
BER="${BER// /}"
BER="${BER%\%*}"
fnDSTAR_END_NETWORK_TRAFFIC
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "link status" ]]
then
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed DSTAR log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_DSTAR
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnP25_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
if [[ "${LOGREC_REMAINING_DATA}" =~ " from to " ]]
then
return
fi
# We come into this function already knowing:
# The entire log line...
# RECORD
# The first 4 fields of the log line...
# LOGREC_TYPE LOGREC_DATE LOGREC_TIME LOGREC_MODE
# And "everything following" the first 3 fields...
# LOGREC_REMAINING_DATA
#
# The fields, between "RECORD" and "LOGREC_REMAINING_DATA"
# align with each other as follows:
# RECORD...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 (awk fields)
# LOGREC_REMAINING_DATA...
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 (array indexes)
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ("FIELD_x")
#
# Grab the parts that we need
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_6="${CUT_ARRAY[5]}"
FIELD_7="${CUT_ARRAY[6]}"
FIELD_8="${CUT_ARRAY[7]}"
FIELD_9="${CUT_ARRAY[8]}"
FIELD_10="${CUT_ARRAY[9]}"
FIELD_11="${CUT_ARRAY[10]}"
FIELD_13="${CUT_ARRAY[12]}"
FIELD_16="${CUT_ARRAY[15]}"
FIELD_18="${CUT_ARRAY[17]}"
unset CUT_ARRAY
#----------
# What to do with these (if anything?)..."
# P25, received RF header
# P25, total frames: 36, bits: 44389, undecodable LC: 0, errors: 29, BER: 0.0653%
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF header" ]] || [[ "${LOGREC_REMAINING_DATA}" =~ "total frames" ]]
then
return
fi
#----------
# Start of P25 RF voice traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF voice transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_7}"
TG_LABEL="${FIELD_9}"
TO="${FIELD_10}"
TO="${TG_LABEL} ${TO}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="RF"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# Start of P25 network traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received network transmission from" ]]
then
fnCONVERT_DATE_TIME
QSO_START_TIME="${TIME}"
FROM="${FIELD_6}"
TG_LABEL="${FIELD_8}"
TO="${FIELD_9}"
TO="${TG_LABEL} ${TO}"
echo -en "\r${CLR_EL}"
fnBANNER
SOURCE="NET"
. "${COUNT_FILE}"
((QSO_COUNT++))
fnWRITE_COUNTER_FILE
fnSHOW_CURRENT_CALL
return
fi
#----------
# End of P25 RF traffic
if [[ "${LOGREC_REMAINING_DATA}" =~ "received RF end of voice transmission" ]]
then
SECS="${FIELD_13}"
BER="${FIELD_16}"
RSSI="${FIELD_18}"
# Fields to pass to the history...
BER="${BER%,*}"
BER="${BER%\%*}"
DBM="${RSSI%%/*}"
DBM="${DBM#*-}"
fnCOLORIZE_BER
fnCOLORIZE_RSSI
echo -en "\r${CLR_EL}"
echo -en "\r${DIM}Dur.:${SGR0} ${BLU}${BOLD}${SECS}${SGR0}${WHI} sec\r"
tput cuf 18
echo -en "${DIM}BER:${SGR0} ${BER_COLOR}${BOLD}${BER}${SGR0}${WHI}\r"
tput cuf 32
echo -en "${DIM}RSSI:${SGR0} ${SMETER_COLOR}${BOLD}${SMETER} (-${DBM} dBm)${SGR0}${WHI}\r"
if [[ -n "${FROM}" ]] && [[ -n "$(echo "${SECS}" | awk '$1 <2.0{print}')" ]]
then
. "${COUNT_FILE}"
((KERCHUNK_COUNT++))
fnWRITE_COUNTER_FILE
tput cuf 64
echo "${YEL}[[kerchunk]]${SGR0}${WHI}"
else
echo
fi
#----------
fnBUILD_HISTORY
echo -n "${LINE_COLOR}"
fnSEPARATOR
fnLISTENING
return
fi
#----------
# End of P25 network traffic (VARIANT A)
if [[ "${LOGREC_REMAINING_DATA}" =~ "network end of transmission from" ]]
then
SECS="${FIELD_11}"
LOSS="${FIELD_13%\%*}"
fnP25_END_NETWORK_TRAFFIC
return
fi
#----------
# End of P25 network traffic (VARIANT B)
if [[ "${LOGREC_REMAINING_DATA}" =~ "network end of transmission," ]]
then
SECS="${FIELD_6}"
LOSS="${FIELD_8%\%*}"
fnP25_END_NETWORK_TRAFFIC
return
fi
#----------
if [[ "${LOGREC_REMAINING_DATA}" =~ "network watchdog has expired" ]]
then
SECS="${FIELD_6}"
LOSS="${FIELD_8}"
fnP25_END_NETWORK_TRAFFIC
return
fi
#----------
echo -n "${LINE_COLOR}"
fnSEPARATOR
echo "Un-parsed P25 log entry..." >&2
echo "${LOGREC_REMAINING_DATA}" >&2
echo "${RECORD}" >>/tmp/unparsed_P25
echo -n "${LINE_COLOR}"
fnSEPARATOR
return
}
#-----------------------------------
fnGPS_MAIN()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
trap fnWINCH WINCH
if [[ "${RECORD}" =~ "GPS position" ]] && [[ "${GPS_HIT}" = "0" ]]
then
# This gives us data that looks like... "-20.983585,-47.647781"
IFS=' '; CUT_ARRAY=(${LOGREC_REMAINING_DATA}); unset IFS;
FIELD_3="${CUT_ARRAY[2]}"
# This separates the latitude and longitude
IFS=','; CUT_ARRAY=(${FIELD_3}); unset IFS;
LATITUDE="${CUT_ARRAY[0]}"
LONGITUDE="${CUT_ARRAY[1]}"
# Indicate North or South, and East or West, based on
# whether the numbers are positive or negative.
NS="° N"
EW="° E"
if [[ "${LATITUDE}" =~ "-" ]]
then
NS="° S"
fi
if [[ "${LONGITUDE}" =~ "-" ]]
then
EW="° W"
fi
echo -e "\r${DIM}GPS :${SGR0} ${BOLD}${WHI}Lat ${LATITUDE}${NS}, Long ${LONGITUDE}${EW}${SGR0}"
fi
fnQSO_IN_PROGRESS
return
}
#-----------------------------------
fnMAIN_LOOP()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
#----------
# For "live" logs, use tail. For replay, use cat.
if [[ "${REPLAY}" -eq 0 ]]
then
CMD="tail -f -n 1"
else
CMD="cat"
fi
#----------
# Filter the log, looking for lines we'll want to parse.
eval "${CMD}" "${WORKING_LOG}" 2>/dev/null \
| grep -E --line-buffered "^M: |^W: |^E: |^MMDVMHOST-STOP" \
| grep -v -E --line-buffered "0000:|Opening|Closing|Embedded|Downlink|is running|link status|Logged into|slow data text" \
| while read -r RECORD
do
# Square brackets cause trouble when parsing
# a record, so they are removed here. The only
# records of importance that I've seen them in
# (so far) are GPS position records.
RECORD="${RECORD//[\[\]]}"
#----------
# See if MMDVMHost has shutdown or transitioned to a new log.
if [[ "${RECORD}" =~ "MMDVMHOST-STOP" ]]
then
if [[ "${REPLAY}" -eq 0 ]]
then
# If it did, it has triggered our exit from the loop.
# We appended an additional blank line into the log, so that when
# the "tail -f -n 1" next restarts against the log, the trigger
# will have receeded far enough back to not cause a false restart
# of this script.
# Once we've caught the trigger, kill the tail that drives the loop.
TAIL_PID="$(sudo fuser -v "${WORKING_LOG}" 2>&1 | grep "tail" | awk '{ print $2 }')"
kill "${TAIL_PID}" 2>/dev/null
return
fi
fi
#----------
# The MMDVMHost daemon logs any errors or warnings it encounters.
# Errors are logged in records beginning with "E:".
# Warnings are logged in records beginning with "W:".
# FYI: Debug messages are logged in records beginning with "D:".
# FYI: Information messages are logged in records beginning with "I:".
# FYI: Modem Traffic messages are logged in records beginning with "M:".
# We filter out I: and D: records at the tail/cat, so no need to watch
# for them here. We're interested in the errors, warnings, and modem
# traffic (M:) records.
LOGREC_TYPE="${RECORD:0:2}"
case "${LOGREC_TYPE}" in
"E:")
fnTALLY_ERRORS
continue
;;
"W:")
fnTALLY_WARNINGS
continue
;;
*)
# Everything else is an M: record
:
;;
esac
#----------
# Get the date, time, and mode
IFS=' '; CUT_ARRAY=(${RECORD}); unset IFS;
LOGREC_DATE="${CUT_ARRAY[1]}"
LOGREC_TIME="${CUT_ARRAY[2]}"
LOGREC_MODE="${CUT_ARRAY[3]}"
unset CUT_ARRAY
# Strip off the comma (ie: "YSF," becomes "YSF")
LOGREC_MODE="${LOGREC_MODE%,*}"
#----------
# Grab everything that follows the date/timestamps...
LOGREC_REMAINING_DATA="${RECORD//${LOGREC_TYPE} ${LOGREC_DATE} ${LOGREC_TIME} /}"
#----------
# Now parse it accordingly.
case "${LOGREC_MODE}" in
"GPS")
fnGPS_MAIN
# This flag prevents printing the record multiple
# times, when received multiple times, during a
# single transmission.
GPS_HIT="1"
;;
"DMR")
CONTACT_COLOR="${DMR_COLOR}"
LINE_COLOR="${BLU}"
fnDMR_MAIN
GPS_HIT="0"
;;
"YSF")
CONTACT_COLOR="${YSF_COLOR}"
LINE_COLOR="${MAG}"
fnYSF_MAIN
GPS_HIT="0"
;;
"NXDN")
CONTACT_COLOR="${NXDN_COLOR}"
LINE_COLOR="${DIM}${WHI}"
fnNXDN_MAIN
GPS_HIT="0"
;;
"D-Star")
CONTACT_COLOR="${DSTAR_COLOR}"
LINE_COLOR="${CYA}"
fnDSTAR_MAIN
GPS_HIT="0"
;;
"P25")
CONTACT_COLOR="${P25_COLOR}"
LINE_COLOR="${DIM}${YEL}"
fnP25_MAIN
GPS_HIT="0"
;;
"M17")
CONTACT_COLOR="${M17_COLOR}"
LINE_COLOR="${WHI}"
fnM17_MAIN
GPS_HIT="0"
;;
*)
CONTACT_COLOR="${OTHER_COLOR}"
LINE_COLOR="${DIM}${WHI}"
GPS_HIT="0"
;;
esac
done
return
}
#-----------------------------------
fnABORT()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
fnRESET_SCROLLING
#----------
# Clear the bottom line of the screen
tput cup "${LINES}" 0
echo -en "\r${CLR_EL}"
#----------
# Re-Enable line-wrap, turn the cursor on, reset the terminal,
# and switch back to the primary screen buffer
echo -n "${WRAP_ON}${CUR_ON}${SGR0}${RMCUP}"
#----------
if [[ "${HAS_BUFFER}" -eq 0 ]]
then
fnLOGO
fi
#----------
fnGOODBYE_BOX
tput cup "${LINES}" 0
#----------
fnCLEANUP
fnCLOSE_FDS
#----------
# If debugging and/or profiling was used, show the names of the logs, at exit
if [[ "${DEBUG_LOG}" != "/dev/null" ]] || [[ "${PROFILING_LOG}" != "/dev/null" ]]
then
if [[ "${MY_FONT}" = "5" ]]
then
DEBUG_PROFILING_LINE=$((LINES - 6))
else
DEBUG_PROFILING_LINE=$((LINES - 4))
fi
tput cup "${DEBUG_PROFILING_LINE}" 0
fnLARGE_FONT "DEBUGGING: ${DEBUG_LOG}"
fnLARGE_FONT "PROFILING: ${PROFILING_LOG}"
echo
fi
#----------
tput cup "${LINES}" 0
echo -en "${TERM_RESET}"
stty "${STTY}"
reset
exit
}
#-----------------------------------
fnWINCH()
{
echo -e "\n----------\nenter: ${FUNCNAME[0]} <- called by ${FUNCNAME[1]}" >&3
#----------
# Get the screensize. We'll be needing the number of lines.
read -r LINES COLUMNS <<< "$(python3 -c "${PY3_SCRIPT}")" 2>/dev/null
tput csr 0 $((LINES - 1))
clear
#----------
if [[ "${TOP}" -eq 1 ]]
then
tput cup "${BELOW_NOSCROLL}" 0
fnNOSCROLL_ZONE
# Clear to end of display
echo -n "${CLR_ED}"
fi
return
}
###########################################################
# A few last items in the pre-flight checklist...
fnWRITE_COUNTER_FILE
# If user presses Ctrl-C, stop the program
trap fnABORT SIGINT SIGTERM
trap fnCLEANUP EXIT
#----------
# Check for any options passed to the script.
if [[ -n "${ARG_LIST[0]}" ]]
then
fnPARSE_CMDLINE
fi
#----------
# Make sure we have updated user.csv and NXDN.csv files
fnDOWNLOAD_USERCSV
fnDOWNLOAD_NXDNCSV
# Make sure we have an updated cty.dat file
fnDOWNLOAD_CTYDAT
#----------
fnDEFINE_BOXCHARS
#----------
if [[ "${LOGO}" -eq 1 ]]
then
fnLOGO
tput cup 22 2
else
fnNO_LOGO
fi
#----------
# If running normally (not in replay mode)... ensure that
# MMDVMHost is running, and that it is writing to the log.
if [[ "${REPLAY}" -eq 0 ]]
then
echo -en "\r${CLR_EL}${SGR0}${WHI}Checking MMDVMHost... "
if [[ "$(pgrep -f "/usr/local/bin/MMDVMHost /etc/mmdvmhost")" = "" ]]
then
echo -e "\n ${RED}MMDVMHost is NOT RUNNING.${SGR0}" >&2
echo -e " Check status in the pi-star GUI and try again.\n" >&2
stty "${STTY}"
fnCLEANUP
exit
else
if [[ ! "$(sudo fuser -v "${WORKING_LOG}" 2>&1)" =~ "MMDVMHost" ]]
then
echo -e "\n ${YEL}MMDVMHost is running, but is NOT writing to $(basename "${WORKING_LOG}").${SGR0}" >&2
echo -e " Check status in the pi-star GUI and try again.\n" >&2
stty "${STTY}"
fnCLEANUP
exit
fi
fi
sleep .5
fi
#----------
# Fetch the version number from what's posted on github.
# Compare that version number with what is currently running.
# If github has a later version, tell the user.
echo -en "\r${CLR_EL}${SGR0}${WHI}Performing Version Check... "
VER_CHECK_COLOR="${WHI}${BOLD}"
VER_CHECK="$(curl -s --fail --max-time 2 "https://raw.githubusercontent.com/kencormack/pistar-lastqso/main/pistar-lastqso" 2>/dev/null | grep -m 1 "^VERSION=")"
VER_CHECK="${VER_CHECK#*=}"
#----------
# Indicator to me, when I'm working with test/development version.
IS_DEV=""
if fnCOMPARE_FLOAT_GT "${VERSION}" "${VER_CHECK}" | grep -q ">"
then
IS_DEV="*"
fi
#----------
# See if the user is running a version older than the latest available
if fnCOMPARE_FLOAT_LT "${VERSION}" "${VER_CHECK}" | grep -q "<"
then
echo -e "\r${GRE}${BOLD}AN UPDATE TO THIS SCRIPT IS AVAILABLE AT:"
echo "${GRE}${DIM}https://github.com/kencormack/pistar-lastqso"
echo "See the CHANGE_LOG for details"
echo "${WHI}${DIM} Your Version: ${VERSION}${SGR0}"
echo -e "${WHI}${BOLD}Available Update: ${VER_CHECK}${SGR0}${WHI}\n"
VER_CHECK_COLOR="${RED}"
sleep 3
fi
#----------
echo -en "\r${CLR_EL}${SGR0}${WHI}Checking Active Modes... "
fnGET_MODES
fnCHECK_MODES_ENABLED
sleep .5
#----------
# Prime the cache files.
# Also performed in fnCLEANUP, to preserve
# copies of the caches in /home/pi-star.
echo -en "\r${CLR_EL}${SGR0}${WHI}Priming Caches... "
fnCOPY_CACHE
#----------
echo -en "\r${CLR_EL}${SGR0}${WHI}Initializing... "
sleep .5
#----------
# CREATE A FRESH EMPTY HISTORY
# These will store the history to be displayed, if the
# no-scroll region is called upon.
if [[ "${TOP}" = 1 ]]
then
HIST_COUNT="1"
until [[ "${HIST_COUNT}" -gt "${HIST_MAX}" ]]
do
HISTORY[HIST_COUNT]=" ${SBOX_VL} ${SBOX_VL} ${SBOX_VL} ${SBOX_VL} ${SBOX_VL} ${SBOX_VL}${SGR0}${WHI}"
echo -e "HISTORY[${HIST_COUNT}]=\"${HISTORY[HIST_COUNT]}\"" >> "${HISTORY_FILE}"
((HIST_COUNT++))
done
fi
#----------
# collect the data for the no-scroll region
fnTOP_DATA
#----------
echo -en "\r${CLR_EL}${SMCUP}"
echo -n "${CUR_HOME}"
if [[ "${TOP}" -eq 1 ]]
then
fnNOSCROLL_ZONE
tput cup "${BELOW_NOSCROLL}" 0
echo -n "${CLR_ED}"
fi
#----------
# Make the named function available for execution as a
# seperate job that can be run in the background.
export -f fnMMDVMLOG_FUSER
###########################################################
# Now for the meat and potatoes...
while true
do
#----------
# Read in the counter values
. "${COUNT_FILE}"
#----------
if [[ "${REPLAY}" -eq 0 ]]
then
# This forks fnMMDVMLOG_FUSER into the background as a child process.
# It runs "fuser" against the log. If "MMDVMHost" no longer shows
# in the output, then MMDVMHost is no longer writing to the log. The
# function then appends "MMDVMHOST-STOP" to the log file. The "read" in
# fnMAIN_LOOP will spot this, and act on it to re-start the main loop.
#
# If the background job is already running, don't spawn another copy.
if [[ "$(pgrep -f "${BASH} -c fnMMDVMLOG_FUSER")" = "" ]]
then
/bin/bash -c fnMMDVMLOG_FUSER > /dev/null 2>&1 &
BG_PID="$(pgrep -o -f "${BASH} -c fnMMDVMLOG_FUSER")"
fi
fi
#----------
fnLISTENING
#----------
# And here we go...
# set -x
fnMAIN_LOOP
# set +x
#----------
fnCOPY_CACHE
#----------
# See if we were replaying a log. If so, we've reached the end of the log.
if [[ "${REPLAY}" -eq 1 ]]
then
if [[ ! "$(sudo fuser -v "${WORKING_LOG}" 2>&1)" =~ "cat" ]]
then
echo -n "${BOLD}${WHI}"
fnSEPARATOR
fnSEPARATOR
echo -n "${SGR0}${GRE}"
figlet -w 80 -f ansi_shadow " COMPLETE"
tput cuu1
# echo "COMPLETE"
echo -n "${BOLD}${WHI}"
fnSEPARATOR
fnSEPARATOR
echo -n "${SGR0}${DEF}"
stty "${STTY}"
fnCLEANUP
exit
fi
fi
#----------
# If we've exited fnMAIN_LOOP, it's because MMDVMHost is no longer writing
# to the log that we've been watching. Either the service was stopped
# during pi-star's nightly update, it has rolled to a new day's log, or the
# user has reconfiged or performed some other task that required pi-star to
# cycle the services.
#
# Watch for new log activity, then resume watching QSOs.
echo -e "\n\n${RED}${DBL_LINE}${WHI}${SGR0}${CLR_EL}"
echo "${RED}$(basename "${WORKING_LOG}") LOG ACTIVITY HAS STOPPED.${WHI}${SGR0}"
#----------
# If we weren't replaying, then watch for logging to restart.
echo -n "${YEL}WATCHING FOR NEW LOG ACTIVITY...${WHI}${SGR0} "
OLD_LOG="${WORKING_LOG}"
while [[ ! "$(sudo fuser -v "${WORKING_LOG}" 2>&1)" =~ "MMDVMHost" ]]
do
sleep 1
# Make sure we're working with latest log.
CANDIDATE_FILES=("${LOGDIR}/${LOGROOT}"*.log) NEWEST=${CANDIDATE_FILES[0]}
for F in "${CANDIDATE_FILES[@]}"
do
if [[ "${F}" -nt "${NEWEST}" ]]
then
NEWEST="${F}"
fi
done
WORKING_LOG="${NEWEST}"
done
#----------
# Set the appropriate message for the history display, indicating why logging cycled.
if [[ "${OLD_LOG}" != "${WORKING_LOG}" ]]
then
echo -e "\n${GRE}LOG ACTIVITY HAS RESUMED - NOW USING $(basename "${WORKING_LOG}")...${WHI}${SGR0}"
echo "${GRE}${DIM}Daily MMDVM log rotation detected.${WHI}${SGR0}"
BOUNCE_MSG="--LOG ROTATION--"
else
echo -e "\n${GRE}LOG ACTIVITY HAS RESUMED - STILL USING $(basename "${WORKING_LOG}")...${WHI}${SGR0}"
echo "${GRE}${DIM}Service bounce detected (pi-star update, user reconfig, or other restart.)${WHI}${SGR0}"
BOUNCE_MSG="-SVCS RESTARTED-"
fi
#----------
# Load the current history...
. "${HISTORY_FILE}"
# Push all the entries one position later, in the list.
# This opens the first position for the restart indicator.
HIST_COUNT="${HIST_MAX}"
until [[ "${HIST_COUNT}" -eq 0 ]]
do
HISTORY[HIST_COUNT]="${HISTORY[$((HIST_COUNT - 1))]}"
((HIST_COUNT--))
done
# Insert the restart marker as the most recent history entry.
HISTORY[1]="${CYA}$(date +%H:%M:%S) ${SGR0}${WHI}${SBOX_VL} ---------- ${SBOX_VL} ---------- ${SBOX_VL} ----- ${SBOX_VL} ----- ${SBOX_VL} --- ${SBOX_VL}${CYA} ${BOUNCE_MSG} ${SGR0}${WHI}"
# Save the updated history to a file, newest through oldest.
rm "${HISTORY_FILE}" 2>/dev/null
HIST_COUNT="1"
until [[ "${HIST_COUNT}" -gt "${HIST_MAX}" ]]
do
echo -e "HISTORY[${HIST_COUNT}]=\"${HISTORY[HIST_COUNT]}\"" >> "${HISTORY_FILE}"
((HIST_COUNT++))
done
#----------
# Reexamine the activated modes, in case services were bounced due to
# configuration changes made in the GUI's "Configuration" page.
fnGET_MODES
#----------
# Reexamine other details (such as log name, Master Servers, etc.),
# in case any values shown in the no-scroll zone have changed.
fnTOP_DATA
echo -e "${GRE}${DBL_LINE}${WHI}${SGR0}${CLR_EL}\n"
((LOG_RESTARTS++))
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment