diff --git a/overlay/opt/emcomm-tools/bin/et-data-winlink b/overlay/opt/emcomm-tools/bin/et-data-winlink new file mode 100755 index 0000000..987f5b3 --- /dev/null +++ b/overlay/opt/emcomm-tools/bin/et-data-winlink @@ -0,0 +1,51 @@ +#!/bin/bash +# Author : Gaston Gonzalez +# Date : 29 November 2025 +# Updated : 30 November 2025 +# Purpose : Generates a denormalized Winlink RMS channel data set from +# Pat Winlink's JSON definition. Each line represents a single +# Winlink RMS channel. This generated file is the source for +# building the Winlink search index. +source /opt/emcomm-tools/bin/et-common + +RMS_SRC_JSON_FILE=~/.local/share/pat/rmslist.json +RMS_DATA_OUT_FILE=/opt/emcomm-tools-api/data/rmslist.csv + +# Return a denormalized list of RMS stations filtered by the supported +# Winlink modes in EmComm Tools Community. +# +# 0 Packet 1200 +# 41 ARDOP 500 +# 43 ARDOP 2000 +# 50 VARA HF +# 51 VARA FM +# 52 VARA FM Wide +# 54 VARA HF 2750 + +cat ${RMS_SRC_JSON_FILE} | jq -r ' + .Gateways[] + | . as $gw + | $gw.GatewayChannels[] + | select(.Mode | IN(0, 41, 43, 50, 51, 52, 54)) + | [ + $gw.BaseCallsign, + $gw.Callsign, + $gw.Latitude, + $gw.Longitude, + .SupportedModes, + .Mode, + .Frequency + ] + | join("|") +' > ${RMS_DATA_OUT_FILE} + +LINES=$(wc -l $RMS_DATA_OUT_FILE | awk '{print $1}') +echo "Created RMS dataset: ${RMS_DATA_OUT_FILE} with ${LINES} records." +echo +echo -e "${YELLOW}To update the Winlink index, perform the following as your normal user:${NC}" +echo +echo -e "${RED}DO NOT RUN THESE AS ROOT!${NC}" +echo +echo -e "${YELLOW}1. Stop the ETC API: ${WHITE}systemctl --user stop et-service-et-api${NC}" +echo -e "${YELLOW}2. Remove the old index: ${WHITE}rm -rf /opt/emcomm-tools-api/index/rmslist${NC}" +echo -e "${YELLOW}3. Start the ETC API: ${WHITE}systemctl --user start et-service-et-api${NC}" diff --git a/overlay/opt/emcomm-tools/bin/et-find-winlink b/overlay/opt/emcomm-tools/bin/et-find-winlink new file mode 100755 index 0000000..fd5bd7a --- /dev/null +++ b/overlay/opt/emcomm-tools/bin/et-find-winlink @@ -0,0 +1,187 @@ +#!/bin/bash +# +# Author : Gaston +# Date : 30 November 2025 +# Purpose : Finds Winlink RMS stations by channel near a given location +set -e +set -o pipefail +trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG +trap 'echo -e "${RED}\"${last_command}\" command failed with exit code $?.${NC}"' ERR + +source /opt/emcomm-tools/bin/et-common + +# Temporary file to hold location for all supported location types +LOCATION_FILE=/tmp/location.json + +[[ -e ${TMP_FILE} ]] && rm ${TMP_FILE} + +usage() { + echo "Usage: $(basename $0) [OPTIONS]" + echo + echo "Location options (choose one):" + echo " --call CALLSIGN Search near callsign" + echo " --grid GRID Search near Maidenhead grid" + echo " --latlon LAT,LON Search near coordinates (use decimal degrees)" + echo + echo "If no location option is specified, your position will be pulled from a supported" + echo "ETC GPS unit or fallback to the grid square configured by 'et-user'." + echo + echo "Other options (optional):" + echo " -b BAND Band [70cm|2m|6m|10m|12m|15m|17m|20m|30m|40m|80m|160m]" + echo " -m MODE Mode [ardop|vara|packet]" + echo + + exit 1 +} + +loc_type="" +loc_value="" +band="" +mode="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --call) loc_type="call"; loc_value="$2"; shift 2 ;; + --grid) loc_type="grid"; loc_value="$2"; shift 2 ;; + --latlon) loc_type="latlon"; loc_value="$2"; shift 2 ;; + -b) band="$2"; shift 2 ;; + -m) mode="$2"; shift 2 ;; + -*|--*) echo "Unknown option: $1"; usage ;; + -h|--help) usage ;; + *) echo "Unexpected argument: $1"; usage ;; + esac +done + +# Use local ETC user position if location is not defined +if [[ -z $loc_type ]]; then + loc_type="gps" +fi + +#echo "$loc_type = $loc_value" + +case "$loc_type" in + gps) + curl -f -s "http://localhost:1981/api/geo/position" | jq .position > ${LOCATION_FILE} + if [[ $? -ne 0 ]]; then + echo -e "${RED}Can't determine your position for use as the transmitting station.${NC}" >&2 + echo "Try specifying your callsign, grid or lat,lon." >&2 + echo " --call CALLSIGN Search near callsign" >&2 + echo " --grid GRID Search near Maidenhead grid" >&2 + echo " --latlon LAT,LON Search near coordinates (use decimal degrees)" >&2 + exit 1 + fi + ;; + call) + curl -f -s "http://localhost:1981/api/license?callsign=${loc_value}" > ${LOCATION_FILE} + if [[ $? -ne 0 ]]; then + echo -e "${RED}Location not found for callsign: ${loc_value}.${NC}" >&2 + exit 1 + fi + ;; + grid) + curl -f -s "http://localhost:1981/api/geo/grid?gridSquare=${loc_value}" | jq '{lat: .position.lat, lon: .position.lon}' > ${LOCATION_FILE} + if [[ $? -ne 0 ]]; then + echo -e "${RED}Error converting grid to lat/lon using value: ${loc_value}.${NC}" >&2 + exit 1 + fi + ;; + latlon) + # Write directly to JSON for consistency + IFS=',' read -r tmpLat tmpLon <<< "$loc_value" + if [[ -z $tmpLat || -z $tmpLon ]]; then + echo -e "${RED}Invalid lat,lon format: $value. Expected 'lat,lon'.${NC}" >&2 + exit 1 + fi + jq -n --arg lat "$tmpLat" --arg lon "$tmpLon" '{lat: ($lat|tonumber), lon: ($lon|tonumber)}' > ${LOCATION_FILE} + ;; + *) + echo -e "${RED}Invalid location type: $loc_type${NC}" >&2 + exit 1 + ;; +esac + +validate_latitude() { + local lat="$1" + + if [[ -z "$lat" ]]; then + echo -e "${RED}Latitude can't be an empty value${NC}" >&2 + return 1 + fi + + if ! [[ "$lat" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then + echo -e "${RED}Latitude must be a number${NC}" >&2 + return 1 + fi + + local latf + latf=$(printf "%.10f" "$lat" 2>/dev/null) || { + echo -e "${RED}Error converting latitude${NC}" >&2 + return 1 + } + + # Check range: -90 <= lat <= 90 + if (( $(echo "$latf < -90" | bc -l) )) || (( $(echo "$latf > 90" | bc -l) )); then + echo -e "${RED}Latitude must be in range between -90 and 90 degrees${NC}" >&2 + return 1 + fi +} + +validate_longitude() { + local lon="$1" + + if [[ -z "$lon" ]]; then + echo -e "${RED}Longitude can't be an empty value${NC}" >&2 + return 1 + fi + + if ! [[ "$lon" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then + echo -e "${RED}Longitude must be a number${NC}" >&2 + return 1 + fi + + local lonf + lonf=$(printf "%.10f" "$lon" 2>/dev/null) || { + echo -e "${RED}Error converting longitude ${NC}" >&2 + return 1 + } + + # Check range: -180 <= lat <= 1800 + if (( $(echo "$lon < -180" | bc -l) )) || (( $(echo "$lon > 180" | bc -l) )); then + echo -e "${RED}Longitude must be in range between -180 and 180 degrees${NC}" >&2 + return 1 + fi +} + +LAT=$(jq .lat $LOCATION_FILE) +LON=$(jq .lon $LOCATION_FILE) + +validate_latitude "$LAT" || exit 1 +validate_longitude "$LON" || exit 1 + +QUERY="http://localhost:1981/api/winlink/near?lat=${LAT}&lon=${LON}" +[[ -n "${band}" ]] && QUERY="${QUERY}&band=${band}" +[[ -n "${mode}" ]] && QUERY="${QUERY}&mode=${mode}" + +OUT=$(curl -s "${QUERY}") + +if [[ -z "${OUT}" ]] || [[ "${OUT}" = "[]" ]]; then + echo "No Winlink RMS channels found." + exit 1 +fi + +echo +echo "Nearby Winlink RMS Channels:" +echo "----------------------------" + +echo "${OUT}" | jq -r ' + (["Station","Callsign","Lat","Lon","Mode","Freq","Band"] | @tsv), + (.[] | [ + .callsign, + .base_callsign, + (.lat | tostring), + (.lon | tostring), + .mode, + (.freq | tostring), + .band + ] | @tsv) +' | column -t -s $'\t' diff --git a/scripts/install-et-api.sh b/scripts/install-et-api.sh index 8b1ac41..287cea9 100755 --- a/scripts/install-et-api.sh +++ b/scripts/install-et-api.sh @@ -1,7 +1,7 @@ #!/bin/bash # Author : Gaston Gonzalez # Date : 13 August 2025 -# Updated : 12 November 2025 +# Updated : 30 November 2025 # Purpose : Install EmComm Tools API set -e trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG @@ -11,7 +11,7 @@ trap 'et-log "\"${last_command}\" command failed with exit code $?."' ERR . ../overlay/opt/emcomm-tools/bin/et-common APP=et-api -VERSION=1.1.1 +VERSION=1.2.0 ET_API_JAR="emcomm-tools-api-${VERSION}.jar" BASE_URL="https://github.com/thetechprepper/et-api-java/releases/download/${VERSION}" @@ -34,6 +34,7 @@ chmod 755 "${BIN_DIR}/${APP}" FILES=( "faa.csv" "license.csv" + "rmslist.csv" "zip2geo.csv" "zip2geo-elevation.csv" )