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
Wanted to come and say Thanks...have been using this and meant to extend my gratitude.
Hey Mason-- Does that script need to live in a particular directory when I execute it..?
The only thing I remember from using it on HamVOIP was my default root partition was insufficient so I had to gpart it bigger; doesn't look like it'll be a problem on ASL3...
LATER: Nevermind, all OK, though OMG, you're correct; that takes a long time to process all the callsigns (+/â 1hr on a RPi-4 2MB). Working great!
I found this script on a Web search. It seems the -n option is ignored. For example, I am passing -n 519321 to the script but it proceeds to process the entire file! Is this a bug?
Great script. I just got it working. The link for enabling the needed services was very helpful. Thanks and 73, N9KIW.
I was telling a friend about this script. Heâs running a ClearNode with Hamvoip. Will this script work with Hamvoip or can it be modified to do so? Thanks.
HamVOIP (I think) comes with itâs own version, /usr/local/sbin/write_node_callsigns sh. If not bundled, than certainly thereâs a reference to add it on the HamVOIP website. Sorry, itâs been years since I did it last. If memory serves, I had to expand the / (root) filesystem because it wasnât big enough as created. YMMV.
HamVoIP has a built-in script to handle this.
/usr/local/sbin/write_node_callsigns.sh
The switches are similar to, but not identical to this script.
For example, there is no -f to force the script running, I.E. for automation purposes, because it doesnât prompt for anything, so there is no need for it.
The bad thing about HamVoIPâs version, though, is it doesnât do any comparing. So if a nodeâs call sign field has changed, it will skip that node if a file has already been generated for it, unless you specifically update just that node number, or all of them. So it doesnât take long before some things become stale, even if you keep it updating regularly.
It is, however, much faster than the referenced script for processing all nodes, because it just sticks files together using cat, not SoX, and doesnât do any kind of encoding or processing to the raw input or output files.
Speaking of that:
Since I make it a point to change the sound files on my ASL and HamVoIP nodes (I really hate Allison, the stock Asterisk voice), I also modified my local copy of this script to write out ulaw files instead of GSM, because the artifacts of GSM compression really bugs me. Like, seriously, I have an actual, visceral reaction to GSM artifacts. I wonder if Iâm the only one.
The files I use are much faster than stock, so Iâm not too much concerned about space, even with 43000 plus nodes.