voacap - Initial ported of VOACAP script from unreleased version of EmComm Tools R2.

This commit is contained in:
thetechprepper
2025-08-16 15:42:14 -07:00
parent dac68c038c
commit e54df6de10
4 changed files with 306 additions and 0 deletions
@@ -0,0 +1,140 @@
#!/bin/bash
#
# Author : Gaston Gonzalez
# Date : 23 May 2023
# Updated : 16 August 2025
# Purpose : Offline HF prediction using voacapl
if [[ $# -ne 4 ]]; then
echo "Usage $(basename $0) <callsign1> <callsign2> <power> <mode>"
echo " <callsign1> Callsign of transmitting station"
echo " <callsign2> Callsign of receiving station"
echo " <power> Output power [5|100|500|1500]"
echo " <mode> Mode [AM|CW|FT8|SSB]"
exit 1
fi
ET_SSN="${HOME}/.local/share/emcomm-tools/voacap/ssn.txt"
if [[ ! -e ${ET_SSN} ]]; then
"Sunspot numbers not available: ${ET_SSN}. Exiting"
exit 1
fi
ET_VOA_WORKING_DIR=$HOME/itshfbc/run
ET_VOA_REPORT=${ET_VOA_WORKING_DIR}/voacapl.txt
INP=${ET_VOA_WORKING_DIR}/voacapx.dat
OUT=${ET_VOA_WORKING_DIR}/voacapx.out
# Year = 2023
YEAR=$(echo $(date '+%Y'))
# Month (May) = 5.00
MONTH=$(date +'%m')
MONTH_FMT=$(date +'%-m.00')
#######################################################################
# TX Antenna
#######################################################################
TX_STATION=$1
TX_JSON=tx-station.json
curl -f -s http://localhost:1981/api/license?callsign=$TX_STATION > $TX_JSON
TL=$(cat $TX_JSON | jq .lat)
TK=$(cat $TX_JSON | jq .lon)
TL1=$( awk -v n1=$TL -v n2=90 -v n3=-90 'BEGIN {if (n1<n3 || n1>n2) printf ("%s", "a"); else printf ("%.2f", n1);}' )
# add North or South (N/S)
TLA=$( awk -v n1=$TL1 -v n2=0 'BEGIN {if (n1<n2) { n1=substr(n1,2); printf ("%6sS", n1); } else printf ("%6sN", n1);}' )
TK1=$( awk -v n1=$TK -v n2=180 -v n3=-180 'BEGIN {if (n1<n3 || n1>n2) printf ("%s", "a"); else printf ("%.2f", n1);}' )
# add East or West (E/W)
TLO=$( awk -v n1=$TK1 -v n2=0 'BEGIN {if (n1<n2) { n1=substr(n1,2); printf ("%7sW", n1); } else printf ("%7sE", n1);}' )
#######################################################################
# RX Antenna
#######################################################################
RX_STATION=$2
RX_JSON=rx-station.json
curl -f -s http://localhost:1981/api/license?callsign=$RX_STATION > $RX_JSON
RL=$(cat $RX_JSON | jq .lat)
RK=$(cat $RX_JSON | jq .lon)
echo "RX ($RX_STATION): $RL,$RK"
RL1=$( awk -v n1=$RL -v n2=90 -v n3=-90 'BEGIN {if (n1<n3 || n1>n2) printf ("%s", "a"); else printf ("%.2f", n1);}' )
# add North or South
RLA=$( awk -v n1=$RL1 -v n2=0 'BEGIN {if (n1<n2) { n1=substr(n1,2); printf ("%6sS", n1); } else printf ("%6sN", n1);}' )
RK1=$( awk -v n1=$RK -v n2=180 -v n3=-180 'BEGIN {if (n1<n3 || n1>n2) printf ("%s", "a"); else printf ("%.2f", n1);}' )
# add East or West
RLO=$( awk -v n1=$RK1 -v n2=0 'BEGIN {if (n1<n2) { n1=substr(n1,2); printf ("%7sW", n1); } else printf ("%7sE", n1);}' )
# Power settings
PWR=$3
PW="0.0800"
echo "TX Power: ${PWR} watts"
if [ "$PWR" = "5" ]; then
PW="0.0040"
elif [ "$PWR" = "100" ]; then
PW="0.0800"
elif [ "$PWR" = "500" ]; then
PW="0.4000"
elif [ "$PWR" = "1500" ]; then
PW="1.2000"
fi
# Mode
MODE=$4
MD="24.0"
echo "Mode: ${MODE}"
if [ "$MODE" = "FT8" ]; then
MD="13.0"
elif [ "$MODE" = "CW" ]; then
MD="24.0"
elif [ "$MODE" = "SSB" ]; then
MD="38.0"
elif [ "$opt" = "AM" ]; then
MD="49.0"
fi
# Format for 117
# NOTE: The card requires <SSN>. (117.)
ssn=`grep "$YEAR $MONTH" ${ET_SSN} | awk '{print $5}' | cut -d"." -f1`
# read more about fine-tuning your input file:
# http://www.voacap.com/voacapw.html
# http://www.voacap.com/frequency.html
echo -e "\n"
cat << END | tee $INP
LINEMAX 999 number of lines-per-page
COEFFS CCIR
TIME 1 24 1 1
MONTH $YEAR $MONTH_FMT
SUNSPOT $ssn.
LABEL $TX$RX
CIRCUIT $TLA $TLO $RLA $RLO S 0
SYSTEM 1. 155. 3.00 90. $MD 3.00 0.10
FPROB 1.00 1.00 1.00 0.00
ANTENNA 1 1 2 30 0.000[samples/sample.00 ] 0.0 $PW
ANTENNA 2 2 2 30 0.000[samples/sample.00 ] 0.0 0.0000
FREQUENCY 3.60 5.30 7.1010.1014.1018.1021.1024.9028.20 0.00 0.00
METHOD 30 0
BOTLINES 8 12 21
TOPLINES 1 2 3 4 6
EXECUTE
QUIT
END
voacapl -s ~/itshfbc
echo "voacapl exited with $? using in=$INP and out=$OUT"
(tail -n +27 $OUT | head -n 5 && grep -e'- REL' $OUT && grep -e'- S DBW' $OUT) | ./rel.pl > ${ET_VOA_REPORT}
cat ${ET_VOA_REPORT}
@@ -0,0 +1,15 @@
#!/bin/bash
# Author : Gaston Gonzalez
# Date : 16 August 2025
# Purpose : Fetch SSNs for VOACAP prediction
source /opt/emcomm-tools/bin/et-common
FILE="ssn.txt"
URL="https://www.sidc.be/silso/FORECASTS/prediML.txt"
curl -s -f -L -o ${FILE} ${URL}
if [[ $? -ne 0 ]]; then
echo -e "${RED}Failed to update ${FILE} from: ${URL}."
exit 1
fi
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/perl
# Author: Jari Perkiömäki OH6BG
# 12 June 2016: initial release
# 18 June 2016: added Long-Path predictions; use unpack for extracting table data
# 18 June 2016: added Power, Mode; small improvements
#
use strict;
use warnings;
my @relhour = ();
my @sdbwhour = ();
my @co = ();
my @mo = ();
my @pwr = ();
my @db = ();
my $km = 0;
my $mi = 0;
my $brn = 0;
my $fh;
my $voa;
my $mode;
my $path = "Short-Path";
# main loop
open $fh, '>>', \$voa or die $!;
process ();
close $fh;
print $voa;
exit;
### end ###
sub process {
my $format = 'A4 A5 A5 A5 A5 A5 A5 A5 A5 A5';
while (<>) {
s/^\s+|\s+$//g;
if ( substr ($_, -1) eq "s" ) { @mo = split /\s+/; }
elsif ( substr ($_, -2) eq "kW" ) { @pwr = split /\s+/; }
elsif ( substr ($_, -2) eq "dB" ) {
@db = split /\s+/;
if ( $db[13] eq "24.0" ) { $mode = "CW"; }
elsif ( $db[13] eq "38.0" ) { $mode = "SSB"; }
else { $mode = "AM"; }
}
elsif ( /Long/ ) { $path = "Long-Path"; }
elsif ( substr ($_, -1) =~ /\d/ ) {
@co = split /\s+/;
$brn = sprintf "%d", $co[9] + 0.5;
$km = sprintf "%d", $co[12] + 0.5;
$mi = sprintf "%d", ($co[12] * 0.62137) + 0.5;
}
elsif ( substr ($_, -3) eq "REL" ) { push (@relhour, [ unpack( $format, $_ ) ] ); }
elsif ( substr ($_, -3) eq "DBW" ) { push (@sdbwhour,[ unpack( $format, $_ ) ] ); }
} # while
for ($fh) {
print $_ "VOACAP Prediction via $path. $mo[0] $mo[1]: SSN $mo[4] Power = $pwr[$#pwr], $mode\n";
print $_ "TX ($co[0]$co[1], $co[2]$co[3]) to RX ($co[5]$co[6], $co[7]$co[8]): $km km, $mi mi, $brn deg\n\n";
print $_ " | 01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|\n";
}
foreach my $freq (reverse 1..9) {
print $fh band($freq) . "|";
foreach my $h (0..23) { print $fh makehourfreq( $h, $freq ); }
print $fh "|" . band($freq) . "\n";
}
for ($fh) {
print $_ " | 01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|\n\n";
print $_ "A = 90 - 100% d = 25 - 49% * = REL 0%, but Signal Power over Noise\n";
print $_ "B = 75 - 89% e = 10 - 24%\n";
print $_ "C = 50 - 74% f = 1 - 9%\n\n";
}
@relhour = ();
@sdbwhour = ();
} # process
sub makehourfreq {
my $h = $_[0];
my $f = $_[1];
my $r = $relhour[$h][$f];
my $s = $sdbwhour[$h][$f];
if ( ($r == 0.00 && $s >= -167) ) { return " * "; }
else { return col($relhour[$h][$f]); }
}
sub band {
my $b = $_[0];
if ($b == 1) { return "80"; }
if ($b == 2) { return "60"; }
if ($b == 3) { return "40"; }
if ($b == 4) { return "30"; }
if ($b == 5) { return "20"; }
if ($b == 6) { return "17"; }
if ($b == 7) { return "15"; }
if ($b == 8) { return "12"; }
if ($b == 9) { return "10"; }
}
sub col {
my $rel = $_[0];
if ($rel == 0.00 ) { return " "; }
elsif ($rel >= 0.01 && $rel < 0.10 ) { return " f "; }
elsif ($rel >= 0.10 && $rel < 0.25 ) { return " e "; }
elsif ($rel >= 0.25 && $rel < 0.50 ) { return " d "; }
elsif ($rel >= 0.50 && $rel < 0.75 ) { return " C "; }
elsif ($rel >= 0.75 && $rel < 0.90 ) { return " B "; }
elsif ($rel >= 0.90 && $rel < 1.01 ) { return " A "; }
else { return " - "; }
}
@@ -0,0 +1,18 @@
2025 02 2025.122 : 145.4 5.0
2025 03 2025.204 : 145.5 10.2
2025 04 2025.286 : 145.0 15.5
2025 05 2025.371 : 143.1 20.7
2025 06 2025.453 : 140.9 24.6
2025 07 2025.538 : 137.3 26.5
2025 08 2025.623 : 133.1 27.0
2025 09 2025.705 : 129.4 27.3
2025 10 2025.790 : 126.1 29.0
2025 11 2025.873 : 123.2 30.0
2025 12 2025.958 : 120.8 29.3
2026 01 2026.042 : 118.0 28.2
2026 02 2026.122 : 114.6 27.4
2026 03 2026.204 : 110.4 25.8
2026 04 2026.286 : 106.1 24.6
2026 05 2026.371 : 102.1 23.8
2026 06 2026.453 : 97.8 23.9
2026 07 2026.538 : 94.0 25.1