Skip to content

Instantly share code, notes, and snippets.

@Geofferey
Last active June 15, 2025 04:15
Show Gist options
  • Save Geofferey/f329280729a469cb44cfa711ba9651ff to your computer and use it in GitHub Desktop.
Save Geofferey/f329280729a469cb44cfa711ba9651ff to your computer and use it in GitHub Desktop.
Grabs location data from gps_cli utility available on Novatel Hotspots and Similar and converts to CoT for use with ATAK input
#!/bin/bash
PATH=/data/bin:/bin:/sbin:/usr/bin:/usr/sbin:/opt/nvtl/bin:/opt/nvtl/data/branding/bin
LD_LIBRARY_PATH=/opt/nvtl/lib:/opt/nvtl/data/branding/lib
SCRIPT=$(basename "$0")
CURRENT_PID=$$
DNS_REC_HOST=""
gps_cli set_privacy 1 >> /dev/null 2>&1
gps_cli gps_start >> /dev/null 2>&1
if ! [ -d /data/bin ]; then
mkdir /data/bin
fi
if ! [ -e /data/bin/git-bins ]; then
wget --directory-prefix=/data/bin/ https://gist.githubusercontent.com/Geofferey/39db654c2916c95d2fe30e6abbc464b0/raw/git-bins
chmod +x /data/bin/git-bins
fi
if ! [ -e /data/bin/s6-dnstxt ]; then
git-bins s6-dnstxt
fi
if ! [ -e /data/bin/netcat ]; then
git-bins netcat
fi
if ! [ -e /data/bin/bc ]; then
git-bins bc
fi
DNS_TXT_HOST=$(s6-dnstxt host."${DNS_REC_HOST}" | txtdec)
#DNS_TXT_HOST=$(s6-dnstxt host."${DNS_REC_HOST}" | cut -d '"' -f2)
DNS_TXT_PORT=$(s6-dnstxt port."${DNS_REC_HOST}" | txtdec)
#DNS_TXT_PORT=$(s6-dnstxt port."${DNS_REC_HOST}" | cut -d '"' -f2)
if [ -z "${1}" ] || [ -z "${2}" ]; then
HOST=${DNS_TXT_HOST}
PORT=${DNS_TXT_PORT}
else
HOST="${1}"
PORT="${2}"
fi
if [ -n "${INTERVAL}" ]; then
BASE_INTERVAL="${INTERVAL}"
elif [ -z "${INTERVAL}" ]; then
BASE_INTERVAL=$(s6-dnstxt interval."${DNS_REC_HOST}" | txtdec)
C2_INTERVAL="1"
else
INTERVAL=60
fi
# Kill previous copies
for PID in $(pgrep -f "${SCRIPT}"); do
if [ "${PID}" != "${CURRENT_PID}" ]; then
kill "${PID}" >> /dev/null 2>&1
sleep 10
fi
done
DEFAULT_TYPE="\"a-n-G-E-S\""
START_TIME="\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\""
ICCID="\"$(modem2_cli sim_get_iccid | grep -w "iccid :" | cut -d ' ' -f3)\""
IMSI="\"$(modem2_cli get_imsi | grep -w "imsi :" | cut -d ':' -f2 | cut -d ' ' -f2)\""
TYPE=${TYPE:-$DEFAULT_TYPE}
echo "${HOST} ${PORT}"
if [ -z "${UDP}" ]; then
coproc NETCAT { netcat ${HOST} ${PORT}; }
else
coproc NETCAT { nc -u "${HOST}" "${PORT}"; }
fi
if [ -z "${NETCAT_PID}" ]; then
echo "Failed to create TCP connection to $HOST:$PORT"
exit 1
fi
trap 'echo "Caught termination signal, cleaning up..."; kill ${NETCAT_PID} 2>/dev/null; exit 0' SIGINT SIGTERM
# Haversine formula (km)
haversine() {
local lat1=$1 lon1=$2 lat2=$3 lon2=$4 R=6371
lat1=$(echo "$lat1 * 0.0174533" | bc -l)
lon1=$(echo "$lon1 * 0.0174533" | bc -l)
lat2=$(echo "$lat2 * 0.0174533" | bc -l)
lon2=$(echo "$lon2 * 0.0174533" | bc -l)
local dlat=$(echo "$lat2 - $lat1" | bc -l)
local dlon=$(echo "$lon2 - $lon1" | bc -l)
local a=$(echo "s($dlat/2)^2 + c($lat1)*c($lat2)*s($dlon/2)^2" | bc -l)
local c=$(echo "2*a(1)*a(sqrt($a)/sqrt(1-$a))" | bc -l)
echo "$(echo "$R * $c" | bc -l)"
}
# Loop init
INTERVAL=${BASE_INTERVAL}
FIRSTSHOT=""
PREV_LAT=""
PREV_LON=""
PREV_TIME=""
IDLE_COUNT=0
IDLE_THRESH=10
while true; do
if ! [ -z "C2_INTERVAL" ]; then
BASE_INTERVAL=$(s6-dnstxt interval."${DNS_REC_HOST}" | txtdec)
fi
now_ts=$(date +%s)
CURRENT_TIME="\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\""
STALE_TIME="\"$(date -u -d "@$((now_ts + 600))" +"%Y-%m-%dT%H:%M:%SZ")\""
GPS_OUTPUT=$(gps_cli get_last_fix)
LATITUDE="\"$(echo "$GPS_OUTPUT" | grep latitude: | cut -d '[' -f2 | cut -d ']' -f1 | tr -d ' ')\""
LONGITUDE="\"$(echo "$GPS_OUTPUT" | grep longitude: | cut -d '[' -f2 | cut -d ']' -f1 | tr -d ' ')\""
HAE="\"$(echo "$GPS_OUTPUT" | grep -w "alt_ellipsoid:" | cut -d '[' -f2 | cut -d ']' -f1)\""
CE="\"$(echo "$GPS_OUTPUT" | grep -w "horiz_unc_cir:" | cut -d '[' -f2 | cut -d ']' -f1)\""
LE="\"$(echo "$GPS_OUTPUT" | grep -w "vertical_unc:" | cut -d '[' -f2 | cut -d ']' -f1)\""
#COURSE="\"$(echo "$GPS_OUTPUT" | grep -w "heading:" | cut -d '[' -f2 | cut -d ']' -f1)\""
LAT_RAW=$(echo "${LATITUDE}" | tr -d '"')
LON_RAW=$(echo "${LONGITUDE}" | tr -d '"')
if [ -n "${PREV_LAT}" ] && [ -n "${PREV_LON}" ] && [ -n "${PREV_TIME}" ]; then
TIME_DIFF=$(echo "$(date +%s) - ${PREV_TIME}" | bc)
if [ "${TIME_DIFF}" -gt 0 ]; then
DISTANCE=$(haversine "${PREV_LAT}" "${PREV_LON}" "${LAT_RAW}" "${LON_RAW}")
SPEED_KMH=$(echo "${DISTANCE} / ${TIME_DIFF} * 3600" | bc -l)
# reset idle count on movement
if [ "$(echo "${SPEED_KMH} > 6" | bc -l)" -eq 1 ]; then
IDLE_COUNT=0
else
IDLE_COUNT=$((IDLE_COUNT + 1))
fi
# dynamic interval thresholds
if [ "$(echo "${SPEED_KMH} > 60" | bc -l)" -eq 1 ]; then
INTERVAL=0.1
elif [ "$(echo "${SPEED_KMH} > 40" | bc -l)" -eq 1 ]; then
INTERVAL=3
elif [ "$(echo "${SPEED_KMH} > 6" | bc -l)" -eq 1 ]; then
INTERVAL=5
else
# back-off: only use base interval after threshold
if [ ${IDLE_COUNT} -ge ${IDLE_THRESH} ]; then
INTERVAL=${BASE_INTERVAL}
fi
fi
fi
fi
PREV_LAT=${LAT_RAW}
PREV_LON=${LON_RAW}
PREV_TIME=$(date +%s)
XML="<event version=\"2.0\" uid=${ICCID} type=${TYPE} time=${CURRENT_TIME} start=${START_TIME} stale=${STALE_TIME} how=\"m-g\"><point lat=${LATITUDE} lon=${LONGITUDE} hae=${HAE} ce=${CE} le=${LE}/><detail><contact callsign=${IMSI}/></detail></event>"
if [ -z "${FIRSTSHOT}" ]; then
printf "%s" "$XML" >&"${NETCAT[1]}"
sleep 1
fi
FIRSTSHOT=1
if [ -n "${DEBUG}" ]; then
echo -e "
${XML}
"
fi
if ! printf "%s" "${XML}" >&"${NETCAT[1]}"; then
if ! [ -z ${1} ]; then
${SCRIPT} ${1} ${2} &
exit 1
else
${SCRIPT} &
exit 1
fi
fi
if [ -n "${ONESHOT}" ]; then
break
fi
if [ "${INTERVAL}" -eq "${BASE_INTERVAL}" ] && [ ! -z "${POWERSAVE}" ]; then
gps_cli enable_powersave_mode 1 >> /dev/null 2>&1
gps_cli set_privacy 0 >> /dev/null 2>&1
fi
gps_cli enable_powersave_mode 1 >> /dev/null 2>&1
sleep "${INTERVAL}"
gps_cli set_privacy 1 >> /dev/null 2>&1 &
sleep 0.1
gps_cli gps_start >> /dev/null 2>&1 &
sleep 0.1
gps_cli enable_powersave_mode 0 >> /dev/null 2>&1 &
done
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment