I would like to know if anyone has successfully been able to run write_node_callsigns.sh with ASL3. I am legally blind and have been using this feature on one of my HamVoip nodes. Many thanks, Steve WB4IZC
#!/bin/bash
# N5LSN
# Make app_rpt telemetry use ASL callsigns instead of node numbers
# Intended for use on ASL3
# Directories and files
SRCDIR="/var/log/asterisk"
DESTDIR="/usr/share/asterisk/sounds/en/rpt/nodenames"
RPTSOUNDS="/usr/share/asterisk/sounds/en/rpt"
LETTERS="/usr/share/asterisk/sounds/en/letters"
NUMBERS="/usr/share/asterisk/sounds/en/digits"
PREV_DB="/tmp/previous_astdb.txt"
# Start time tracking
start_time=$(date +%s%3N)
# Usage instructions
usage() {
cat << EOF
Usage: write_node_callsigns.sh options
OPTIONS:
-h Show this message
-a Process all nodes
-i Include node number with call
-n node Process a single node
-d path Specify destination directory (default: /usr/share/asterisk/sounds/en/rpt/nodenames)
-v Verbose output
-f Force run without user confirmation
Examples:
./write_node_callsigns.sh -a # Process all nodes
./write_node_callsigns.sh -n 40000 # Process single node 40000
./write_node_callsigns.sh -f # Force execution without confirmation
EOF
}
STRING=""
VERBOSE=""
INCNODE=""
FORCE_RUN=0
MAX_PREVIEW=10
# Create directories if missing
ensure_directory_exists() {
if [ ! -d "$1" ]; then
echo "Creating directory: $1"
mkdir -p "$1" || { echo "Failed to create directory $1"; exit 1; }
fi
}
# Find the correct audio file (.gsm or .ulaw)
find_audio_file() {
basepath=$1
if [ -f "${basepath}.gsm" ]; then
echo "${basepath}.gsm"
elif [ -f "${basepath}.ulaw" ]; then
echo "${basepath}.ulaw"
else
echo ""
fi
}
# Process each character in the callsign and form the audio filenames
make_call() {
local foo=${1,,}
STRING=""
for (( i=0; i<${#foo}; i++ )); do
local char=${foo:$i:1}
case $char in
[0-9]) FILENAME=$(find_audio_file "$NUMBERS/$char") ;;
"/") FILENAME=$(find_audio_file "$LETTERS/slash") ;;
"-") FILENAME=$(find_audio_file "$LETTERS/dash") ;;
[a-z]) FILENAME=$(find_audio_file "$LETTERS/$char") ;;
esac
if [ -n "$FILENAME" ]; then
STRING="$STRING $FILENAME"
else
echo "Error: Audio file for '$char' not found."
fi
done
}
# Handle .ulaw files with sox
process_file() {
local file=$1
if [[ "$file" == *.ulaw ]]; then
echo "-t raw -e u-law -r 8000 -c 1 $file"
else
echo "$file"
fi
}
# Concatenate the audio files into the final output and measure time
write_call() {
local output_file="$DESTDIR/$f1.gsm"
local start=$(date +%s%3N) # Get start time in milliseconds
# Generate processed file paths for sox
local processed_files=""
for file in $STRING; do
processed_files="$processed_files $(process_file $file)"
done
# Execute sox to concatenate files (always overwrite)
sox $processed_files $output_file
local end=$(date +%s%3N) # Get end time in milliseconds
local duration=$((end - start)) # Calculate processing time
# Output in the required format
echo "[$(printf "%04d" $duration)ms] - $f1 - $f2"
}
# Load previous database into associative array for fast lookup
declare -A previous_callsigns
load_previous_database() {
if [ -f "$PREV_DB" ]; then
while IFS='|' read -r node callsign _; do
# Ensure the node ID is valid (non-empty)
if [ -n "$node" ]; then
previous_callsigns["$node"]="$callsign"
fi
done < "$PREV_DB"
fi
}
# Compare current astdb.txt with previous_db.txt to find new or modified nodes
compare_databases() {
echo "Comparing databases..."
new_nodes=() # Array to store nodes that are new or changed
changes=() # Array to store changes for preview
declare -A latest_node_callsigns # To handle duplicate node numbers
while IFS='|' read -r f1 f2 _; do
# Skip lines starting with a semicolon or empty lines
[[ "$f1" =~ ^\; ]] || [ -z "$f1" ] && continue
# Handle duplicates by keeping the last occurrence of each node
latest_node_callsigns["$f1"]="$f2"
done < "$SRCDIR/astdb.txt"
# Now compare the latest callsigns with the previous database
for node in "${!latest_node_callsigns[@]}"; do
current_callsign="${latest_node_callsigns[$node]}"
old_callsign="${previous_callsigns[$node]}"
if [ -z "$old_callsign" ]; then
# New node
new_nodes+=("$node|$current_callsign")
changes+=("$node: NEW -> $current_callsign")
elif [ "$old_callsign" != "$current_callsign" ]; then
# Callsign has changed
new_nodes+=("$node|$current_callsign")
changes+=("$node: $old_callsign -> $current_callsign")
fi
done
}
# Prompt the user to confirm before proceeding, show the first 10 nodes with changes
confirm_processing() {
local node_count=$1
if [ $FORCE_RUN -eq 1 ]; then
return # Skip confirmation if forced to run
fi
echo "$node_count nodes need to be processed."
echo "Preview of changes:"
local i=0
for change in "${changes[@]}"; do
echo " $change"
((i++))
if [ $i -ge $MAX_PREVIEW ]; then
echo " ...and more."
break
fi
done
read -p "Continue? [y/n]: " response
case "$response" in
[yY][eE][sS]|[yY])
echo "Starting processing..."
;;
*)
echo "Aborting."
exit 0
;;
esac
}
# Update the previous_db.txt file with the current astdb.txt data
update_previous_db() {
cp "$SRCDIR/astdb.txt" "$PREV_DB"
}
# Format the total execution time into the appropriate unit (seconds, minutes, hours)
format_total_time() {
local total_time_ms=$1
if (( total_time_ms < 1000 )); then
# Less than 1 second: display in milliseconds
echo "${total_time_ms}ms"
elif (( total_time_ms < 60000 )); then
# Less than 1 minute: display in seconds
local total_time_sec=$(echo "scale=1; $total_time_ms / 1000" | bc)
echo "${total_time_sec}s"
elif (( total_time_ms < 3600000 )); then
# Less than 1 hour: display in minutes
local total_time_min=$(echo "scale=1; $total_time_ms / 60000" | bc)
echo "${total_time_min}min"
else
# More than 1 hour: display in hours
local total_time_hr=$(echo "scale=1; $total_time_ms / 3600000" | bc)
echo "${total_time_hr}h"
fi
}
# Main processing logic for new or modified nodes
process_nodes() {
# Load the previous database into memory for fast lookups
load_previous_database
# Compare current and previous databases
compare_databases
local node_count=${#new_nodes[@]}
if [ $node_count -eq 0 ]; then
echo "No new or changed nodes to process."
return
fi
# Confirm processing with user
confirm_processing "$node_count"
# Process new or modified nodes
for node_data in "${new_nodes[@]}"; do
IFS='|' read -r f1 f2 <<< "$node_data"
make_call "$f2"
if [ "$INCNODE" ]; then
STRING="$STRING $(find_audio_file "$RPTSOUNDS/node")"
make_call "$f1"
fi
write_call
done
# Update the previous database with the current data
update_previous_db
}
# Parse command-line options
while getopts "hail:vn:d:fv" OPTION; do
case $OPTION in
h) usage; exit 0 ;;
a) ;;
i) INCNODE=1 ;;
n) node=$OPTARG ;;
d) DESTDIR=$OPTARG ;;
f) FORCE_RUN=1 ;; # Force execution without confirmation
v) VERBOSE=1 ;;
?) usage; exit 1 ;;
esac
done
# Ensure necessary directories exist
ensure_directory_exists "$DESTDIR"
# Verify that the source file exists
if [ ! -f "$SRCDIR/astdb.txt" ]; then
echo "$SRCDIR/astdb.txt not found. Please verify the location."
exit 1
fi
# Start processing nodes
process_nodes
# Calculate total script execution time
end_time=$(date +%s%3N)
total_duration=$((end_time - start_time))
formatted_duration=$(format_total_time $total_duration)
echo "Total script execution time: $formatted_duration"
Thank You Mason, I will give this a try. Many thanks and 73, Steve WB4IZC
I run it every midnight via cron. The first time you run it, it will take forever. After that, it only updates the callsigns for nodes with new/updated info. It uses astdb.txt, so you’ll need to read this: Other Software Products - AllStarLink Manual