#!/bin/bash

###AUTHOR###
#Harry Dove-Robinson 5/8/2017
#harry@doverobinson.me
#https://gist.github.com/hdoverobinson
#https://github.com/hdoverobinson

###USAGE###
#This is a script used to configure u-blox GPS/GNSS modules from a text file generated by u-center.
#This allows for the possibility of simple in-situ u-blox receiver configuration on a Linux host. (u-center is Windows-only software.)
#It has been tested on the NEO-M8T and EVK-M8T modules with u-center 8.24 and should work with any GPS receiver on the u-blox 8/M8 firmware.
#The script takes its first argument as the path to a GPS device file and the second argument as the path to a .txt file used to configure the GPS.
#Example: ./ubxconfig.sh /dev/ttyACM0 NEO-M8T_10HZ_GNSS_SBAS.txt
#The receiver is first reset to factory defaults before it is configured from the text file. The configuration is then saved to all available flash.
#The configuration text file used should be one that is outputted from u-center (Tools -> GNSS Configuration...).
#The packages "dos2unix" and "bc" are required for this script to run.

###NOTES###
#The script will not run unless there is an exact match between the MON-VER string of the text file and the MON-VER output of the GPS receiver.
#The MON-VER response is the longest parsed by the script, and it may take a few tries before the full message is able to be parsed.
#All other messages are sent up to 10 times before being permanently declared "NOT ACKNOWLEDGED". Note that this does not mean that it was explicitly rejected.
#If "ERROR - MESSAGE NOT ACKNOWLEDGED" is shown after 10 retries, then there is likely a problem with the contents of the configuration file or communication with the GPS receiver.
#It is expected behavior for some UBX messages to be rejected by the receiver. Try running the configuration file within u-center to see which ones.
#The STTY variable holds the stty configuration message that sets up the serial line for this script. It assumes a USB device able to run at 921600 baud.
#Other stty configuration messages can be loaded in with the output of "stty -F /dev/GPS0 -g" where "GPS0" is the character device file of the GPS.
#Currently this script cannot handle changes in the baudrate when used over an analog serial line. The GPS receiver should be connected to the host via USB.

export GPS=$1
export CONFIG=$2
export STTY="406:0:18b7:8a30:3:1c:7f:8:4:2:64:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0"
export RETRY_COUNT="10"
export TIMEOUT=".25s"
export UBX_ACK_ACK="b5620501"
export UBX_ACK_NAK="b5620500"
export UBX_PREAMBLE="B5 62"
export UBX_MON_VER_POLL="0A 04 00 00"
export UBX_CFG_CFG_REVERTDEFAULT="06 09 0D 00 FF FF 00 00 00 00 00 00 FF FF 00 00 17"
export UBX_CFG_CFG_SAVE="06 09 0D 00 00 00 00 00 FF FF 00 00 00 00 00 00 17"

#counts number of sent messages up to the set number of retries
#anything sent into the counter should fail with exit code 1 and pass with exit code 0
send_counter ()
{
SENT_COUNTER=0
until [ $SENT_COUNTER == $RETRY_COUNT ] && echo "$(tput setaf 1)ERROR - MESSAGE NOT ACKNOWLEDGED$(tput sgr0)" && let ERROR_COUNTER=ERROR_COUNTER+1 || "$@"
do
let SENT_COUNTER=SENT_COUNTER+1
echo "NOT ACKNOWLEDGED"
if [ $SENT_COUNTER -ge 1 ] && [ $SENT_COUNTER -le $(expr $RETRY_COUNT - 1) ]
then
echo "RETRYING"
fi
done
}

#these needed to be functions so that ver_check can loop multiple times if it fails
file_mon_ver ()
{
#pulls MON-VER string from configuration file, finds only the hex and reformats for comparison
grep "MON-VER" $1 | sed 's/^.*-\s//' | xxd -r -p | xxd -p | tr -d '\n'
}
dev_mon_ver ()
{
#sends the MON-VER polling message, has larger timeout and set byte range because we expect a long response
echo "`ubx_chksum "$UBX_MON_VER_POLL"`" | xxd -r -p > $GPS && timeout 5s cat $GPS | xxd -p -l50000 | grep -m1 -a -A 10 "b5620a04" | tr -d '\n'
}

ver_check ()
{
if [[ `dev_mon_ver` =~ .*`file_mon_ver $1`.* ]]
then
echo "Version match!"
else
return 1
fi
}

send_ubx ()
{
echo "Sending UBX message: "$@""
#timeout is allowed in case no response is received
ubx_response=`timeout $TIMEOUT cat $GPS | xxd -p | grep -m1 -a -e "$UBX_ACK_ACK" -e "$UBX_ACK_NAK" & echo $2 | xxd -r -p > $GPS`
if [[ $ubx_response == *$UBX_ACK_ACK* ]]
then
echo "$(tput setaf 2)ACKNOWLEDGED$(tput sgr0)" && let ACK_COUNTER=ACK_COUNTER+1
elif [[ $ubx_response == *$UBX_ACK_NAK* ]]
then
echo "$(tput setaf 3)REJECTED$(tput sgr0)" && let NAK_COUNTER=NAK_COUNTER+1
else
return 1
fi
}

ubx_chksum ()
#############################################################################################################################
###Fletcher checksum calculator adapted from: http://www.aeronetworks.ca/2014/07/fletcher-checksum-calculator-in-bash.html###
#############################################################################################################################
{
SUM=0
FLETCHER=0
j=0

printf "$UBX_PREAMBLE "

for i in $1
do
 j=$(echo "ibase=16;$i" | bc)
 printf "%02X " "$j"
 SUM=$(echo "$SUM + $j" | bc)
 SUM=$(echo "$SUM%256" | bc)

 FLETCHER=$(echo "$FLETCHER + $SUM" | bc)
 FLETCHER=$(echo "$FLETCHER%256" | bc)
done

printf "%02X " "$SUM"
printf "%02X\n" "$FLETCHER"
}

main ()
{
#validate the GPS device and .txt file
if [[ -c $GPS ]] && [[ -f $CONFIG && -s $CONFIG && $CONFIG == *.txt ]]
then
#prepare serial port
stty -F $GPS $STTY
#remove windows carriage returns from config file
dos2unix $CONFIG

#version compatibility loop
echo "Checking compatibility..."
until ver_check $1
do
echo "Version mismatch! Checking again..."
done

ACK_COUNTER=0
NAK_COUNTER=0
ERROR_COUNTER=0

#configuration start
echo "Configuring u-blox GPS at $GPS from $1..."
echo "Resetting to factory defaults..."
send_counter send_ubx "UBX-CFG-CFG-REVERTDEFAULT" "$(ubx_chksum "$UBX_CFG_CFG_REVERTDEFAULT")"

echo "Restoring configuration from file..."
#loop through config file after MON-VER line and pass through ubx_chksum, send_ubx, and send_counter
while read -r line
do
send_counter send_ubx "`echo $line | sed 's/\s.*$//'`" "`ubx_chksum "$(echo $line | sed 's/^.*-\s//')"`"
done < <(tail -n+2 $1)

echo "Saving configuration to flash..."
send_counter send_ubx "UBX-CFG-CFG-SAVE" "$(ubx_chksum "$UBX_CFG_CFG_SAVE")"

echo -en "\n"
echo "Done!"

#print message stats
TOTAL=$(($ACK_COUNTER+$NAK_COUNTER+$ERROR_COUNTER))
echo -en "\n"
echo "----------------------------------------"
echo -en "\n"
echo "UBX MESSAGE STATISTICS:"
echo "$(tput setaf 2)ACKNOWLEDGED:$(tput sgr0) $ACK_COUNTER ($(echo "scale=2; $ACK_COUNTER / $TOTAL * 100" | bc | sed s/\\.[0-9]\\+//)%)"
echo "$(tput setaf 3)REJECTED:$(tput sgr0) $NAK_COUNTER ($(echo "scale=2; $NAK_COUNTER / $TOTAL * 100" | bc | sed s/\\.[0-9]\\+//)%)"
echo "$(tput setaf 1)ERRORS:$(tput sgr0) $ERROR_COUNTER ($(echo "scale=2; $ERROR_COUNTER / $TOTAL * 100" | bc | sed s/\\.[0-9]\\+//)%)"
echo "TOTAL SENT: $TOTAL"
echo -en "\n"

else
echo "Provide the GPS device as first argument and .txt file as second!"
return 1
fi
}

#check for dos2unix
if ! command -v dos2unix > /dev/null 2>&1
then
echo "Please install dos2unix!"
exit 2
fi

#check for bc
if ! command -v bc > /dev/null 2>&1
then
echo "Please install bc!"
exit 2
fi

#run
main $CONFIG

exit $ERROR_COUNTER
