#!/bin/bash
#
# This script is used to update the firmware of the Moitessier HAT microcontroller.
# The latest firmware will be loaded automatically form an online repository.
# 
# Ensure that you have the I2C module and the flashing tool compiled (see 
# https://github.com/mr-rooney/moitessier for further information). You might
# need to change the paths below.
#
# Usage: ./fw_update_moitessier -h
#
# Most common commands:
#   sudo moitessier/scripts/production_test -k -s --ignore 2>&1 | tee ~/Desktop/production_test.logg
#   sudo moitessier/scripts/production_test -k -x --ignore 2>&1 | tee ~/Desktop/production_test.logg

flasher=../flasher/fw_update_moitessier

eepromFlasher=../device_tree/eeprom_update_moitessier           # the path to the EEPROM update script (relative path)
sensorScript=../app/sensors/check_functionality                 # the path to the script used to check the sensor functionality (relative path)
sensorFile=../app/sensors/sensValues.txt                        # The file where the sensor readouts should be stored
overlayPath=/boot/overlays                                      # the path to the device tree overlay directory on the Raspberry Pi (absolute path)
configFile=/boot/config.txt                                     # the boot configuration file used by the Raspberry Pi (absolute path)
moitessierDeviceTree=../device_tree/moitessier.dtbo             # the compiled device tree, this will be used as overlay (relative path)
i2cDeviceNr=3                                                   # The bit banged I2C interface. This parameter must equal with the settings in the 
                                                                # device tree overlay. SDA and SCL for this interface are specified in the overlay.
                                                                # FIXME: currently the kernel 4.19 only supports I2C bus number 3.
device=/dev/moitessier.spi                                      # the device file to read the Moitessier HAT data                                                           
powerPinNumber=21                                               # The GPIO pin used to power cycle the board. This pin is only used for the production test adapter.
sensorIterations=1
database=/home/pi/Desktop/moitessier_db.txt
moitessierCtrl=../app/moitessier_ctrl/moitessier_ctrl
deviceCtrl=/dev/moitessier.ctrl

# Default values for sensor sanity check.
temperature=35                                                  # temperature in C       
temperatureRange=15                                             # actual range is temperature +/- temperatureRange
pressure=1000                                                   # pressure in mbar
pressureRange=200                                               # actual range is pressure +/- pressureRange


dtparamI2CValid=0
dtoverlayMoitessierValid=0
dtoverlayI2CgpioValid=0

bus=/dev/i2c-${i2cDeviceNr}    

pwd="`dirname \"$0\"`"

# https://misc.flogisoft.com/bash/tip_colors_and_formatting
FORMAT_RED="\e[1;31m"
FORMAT_YELLOW="\e[1;93m"
FORMAT_GREEN="\e[1;32m"
FORMAT_DEFAULT="\e[0m"

unload()
{
    echo "INFO: Unloading \"$1\"."
	rmmod $1
}

help()
{
    echo
    echo "******************************************************************************************"
    echo "This script is used to test the Moitessier HAT."
    echo       
    echo "Available options:"
    echo "    -k        : If set the device tree overlay will be kept."
    echo "    -s        : If set the board will shutdown afterwards."
    echo "    -r        : If set the board will reboot afterwards."
    echo "    -x        : If set the board will not shutdown/reboot afterwards."
    echo "    --ignore  : If set errors will be ignored and tests will be continued. This option should"
    echo "                be set if you want to test all functions of the board and you do not want to"
    echo "                abort at the first found error."
    echo "******************************************************************************************"
    echo
}

error()
{
    echo
    echo "******************************************************************************************"
    echo -e "${FORMAT_RED}ERROR occured!!${FORMAT_DEFAULT}"
    echo
    echo -e "${FORMAT_RED}${1}${FORMAT_DEFAULT}"
    echo "******************************************************************************************"
    echo
}

info()
{
    echo
    echo "******************************************************************************************"
    echo $1
    echo "******************************************************************************************"
    echo
}

results()
{
    echo
    echo "******************************************************************************************"
    echo -e "**************************************\e[1;45m TEST RESULTS ${FORMAT_DEFAULT}**************************************"
    echo "******************************************************************************************"
    echo
    format=${FORMAT_RED}        
    if [ "${systemFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${systemFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "SYSTEM            ${format}${systemFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${sensorsFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${sensorsFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "SENSORS           ${format}${sensorsFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${firmwareFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${firmwareFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "FIRMWARE          ${format}${firmwareFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${eepromFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${eepromFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "EEPROM            ${format}${eepromFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${gpioFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${gpioFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "GPIO              ${format}${gpioFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${microcontrollerFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${microcontrollerFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "MICROCONTROLLER   ${format}${microcontrollerFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${receiverFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${receiverFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "RECEIVER          ${format}${receiverFailed}${FORMAT_DEFAULT}"
    
    format=${FORMAT_RED}
    if [ "${databaseFailed}" == "${TEST_OK}" ]
    then        
        format=${FORMAT_GREEN}
    elif [ "${databaseFailed}" == "${NOT_TESTED}" ]
    then
        format=${FORMAT_YELLOW}
    fi
    echo -e "DATABASE          ${format}${databaseFailed}${FORMAT_DEFAULT}"
    
    echo "******************************************************************************************"
    echo
}

help

nOption="-n"
kOption=
sOption=0
rOption=0
xOption=0
ignoreOption=0

NOT_TESTED="not tested"
TEST_OK="ok"
TEST_NOK="failed"

sensorsFailed=${NOT_TESTED}
firmwareFailed=${NOT_TESTED}
eepromFailed=${NOT_TESTED}
receiverFailed=${NOT_TESTED}
microcontrollerFailed=${NOT_TESTED}
databaseFailed=${NOT_TESTED}
gpioFailed=${NOT_TESTED}
systemFailed=${NOT_TESTED}

while getopts "kshxr-:" option
do
    case $option in
        k) kOption="-k"
            ;;
        s) sOption=1
            ;;
        r) rOption=1
            ;;
        h) exit 0
            ;;
        x) xOption=1
            ;;
        -) case "${OPTARG}" in
                ignore)
                    ignoreOption=1
                    ;;
                *) echo
                    error "Invalid option."
                    exit 1
                    ;;                    
            esac
            ;;            
        \?) echo
            error "Invalid option."
            exit 1
            ;;
    esac
done

if [ "$EUID" -ne 0 ]
then 
    error "You must run this script with root privileges."
    exit
fi

bootOption=$(expr $rOption + $sOption + $xOption)

if [ $bootOption -gt 1 ]
then
    error "Option -r, -s and/or -n must not be applied at the same time."
    exit 1
fi

# check if the relevant parameters are set in the configuration file to load the device tree
while IFS='' read -r line || [[ -n "$line" ]]; do
    if [ "$line" = "dtparam=i2c_vc=on" ]
    then
        dtparamI2CValid=1
    fi
    if [ "$line" = "dtoverlay=moitessier" ]
    then
        dtoverlayMoitessierValid=1
    fi
    if [ "$line" = "dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,bus=${i2cDeviceNr}" ]
    then
        dtoverlayI2CgpioValid=1
    fi
done < "$configFile"

if [ $dtparamI2CValid == "0" ]
then    
    sh -c "echo 'dtparam=i2c_vc=on' >> $configFile"
fi
    
if [ $dtoverlayMoitessierValid == "0" ]
then    
    sh -c "echo 'dtoverlay=moitessier' >> $configFile"
fi

if [ $dtoverlayI2CgpioValid == "0" ]
then    
    sh -c "echo 'dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,bus=${i2cDeviceNr}' >> $configFile"
fi

cp "${pwd}/${moitessierDeviceTree}" ${overlayPath}
if [ $? -ne 0 ]
then
    error "Copying device tree \"${pwd}/${moitessierDeviceTree}\" to \"${overlayPath}\" failed."
    systemFailed=${TEST_NOK}
    results
    exit 1
else
    systemFailed=${TEST_OK}    
fi

# we need to reboot the Raspberry Pi to apply the changes in the configuration
if [ $dtparamI2CValid == "0" -o $dtoverlayMoitessierValid == "0" -o $dtoverlayI2CgpioValid == "0" ] 
then
    info "${configFile} has been changed. System will reboot. You need to call this script afterwards again."
    sleep 4
    reboot
fi

# check the sensors
"${pwd}/${sensorScript}" -i ${bus} -c ${sensorIterations} --all -s -t ${temperature} -r ${temperatureRange} -p ${pressure} -n ${pressureRange}
if [ $? -ne 0 ]
then
    error "Checking sensors failed. This might be okay, if one of the sensors is not assembled on the board."
    sensorsFailed=${TEST_NOK}
    if [ $ignoreOption == "1" ]
    then
        sleep 3  
    else
        results
        exit 1      
    fi
else
    sensorsFailed=${TEST_OK}
fi

# check if the file exists where the sensor data has been written and parse the values
if [ -e "${pwd}/${sensorFile}" ]
then
    sensValues=$(cat "${pwd}/${sensorFile}")
    while IFS=';' read -ra ADDR; do
        for i in "${ADDR[@]}"; do
            param=$(echo $i | sed 's/:.*//')
            val=$(echo $i | sed 's/.*://' | sed 's/;.*//')
            
            case "$param" in
                "cpuTemp") cpuTemp=$val;;
                "pressureTemp") pressureTemp=$val;;
                "pressure") pressure=$val;;
                "mpuTemp") mpuTemp=$val;;
                "*") error "Found value in ${pwd}/${sensorFile} that cannot be processed"
            esac
        done
    done <<< "$sensValues"
    rm "${pwd}/${sensorFile}"
    if [ $? -ne 0 ]
    then
        error "Could not delete file ${pwd}/${sensorFile}"
        sensorsFailed=${TEST_NOK}
        if [ $ignoreOption == "1" ]
        then
            sleep 3  
        else
            results
            exit 1      
        fi
    fi
else    
    error "File ${pwd}/${sensorFile} not found"
    sensorsFailed=${TEST_NOK}
    if [ $ignoreOption == "1" ]
    then
        sleep 3  
    else
        results
        exit 1      
    fi
fi

# write the firmware of the microcontroller
dir="`dirname \"${pwd}/${flasher}\"`"
"${pwd}/${flasher}" -s "${dir}/application.binary"
if [ $? -ne 0 ]
then
    error "Writing firmware failed"
    firmwareFailed=${TEST_NOK}
    results
    exit 1
else
    firmwareFailed=${TEST_OK}
fi

info "power cycling Moitessier HAT"
if [ -d "/sys/class/gpio/gpio${powerPinNumber}" ]
then
    echo ${powerPinNumber} > /sys/class/gpio/unexport
fi    
echo ${powerPinNumber} > /sys/class/gpio/export
if [ $? -ne 0 ]
then
    error "Exporting pin gpio${powerPinNumber} failed"
    gpioFailed=${TEST_NOK}
    results
    exit 1
else
    gpioFailed=${TEST_OK}
fi
echo "out" > /sys/class/gpio/gpio${powerPinNumber}/direction
echo 0 > /sys/class/gpio/gpio${powerPinNumber}/value
sleep 0.2
echo 1 > /sys/class/gpio/gpio${powerPinNumber}/value
sleep 1
echo 0 > /sys/class/gpio/gpio${powerPinNumber}/value
sleep 2

# write the EEPROM
"${pwd}/${eepromFlasher}" ${nOption} ${kOption}
if [ $? -ne 0 ]
then
    error "Writing EEPROM failed"
    eepromFailed=${TEST_NOK}
    results
    exit 1
else
    eepromFailed=${TEST_OK}
fi

# check the microcontroller and read relevant information
serial=$("${pwd}/${moitessierCtrl}" ${deviceCtrl} 1 | grep serial)
if [ $? -ne 0 ]
then
    error "Could not get serial number from device"
    microcontrollerFailed=${TEST_NOK}
    results
    exit 1
else
    microcontrollerFailed=${TEST_OK}
fi
rng=$("${pwd}/${moitessierCtrl}" ${deviceCtrl} 1 | grep rng)
if [ $? -ne 0 ]
then
    error "Could not get rng from device"
    microcontrollerFailed=${TEST_NOK}
    results
    exit 1
else
    microcontrollerFailed=${TEST_OK}
fi

appVersion=$("${pwd}/${moitessierCtrl}" ${deviceCtrl} 1 | grep "app version")
if [ $? -ne 0 ]
then
    error "Could not get app version"
    microcontrollerFailed=${TEST_NOK}
    results
    exit 1
else
    microcontrollerFailed=${TEST_OK}
fi

hwVersion=$("${pwd}/${moitessierCtrl}" ${deviceCtrl} 1 | grep "hardware version")
if [ $? -ne 0 ]
then
    error "Could not get hardware version"
    microcontrollerFailed=${TEST_NOK}
    results
    exit 1
else
    microcontrollerFailed=${TEST_OK}
fi

# check AIS and GNSS
IFS=$'\r\n' GLOBIGNORE='*' command eval  'dat=($(head -n 60 ${device}))'
printf '%s\n' "${dat[@]}"

ais1Valid=0
ais1Cnt=0
ais2Valid=0
ais2Cnt=0
gnssValid=0
gnssCnt=0
for i in "${dat[@]}"
do
	echo $i
	if [[ "$i" == *"!AIVDM,1,1,,A,"* ]]
	then
	    ais1Valid=1
	    ((ais1Cnt++))
	fi
	
	if [[ "$i" == *"!AIVDM,1,1,,B,"* ]]
	then
	    ais2Valid=1
	    ((ais2Cnt++))
	fi
	
	if [[ "$i" == *"GPRMC"* ]]
	then
	    gnssValid=1
	    ((gnssCnt++))
	fi
	
	if [[ "$i" == *"GNRMC"* ]]
	then
	    gnssValid=1
	    ((gnssCnt++))
	fi
done

if [ $gnssValid -eq 1 ] && [ $ais1Valid -eq 1 ] && [ $ais2Valid -eq 1 ]
then
    info "AIS/GNSS successfull - AIS1 = ${ais1Cnt}, AIS2 = ${ais2Cnt}, GNSS = ${gnssCnt}"
    receiverFailed=${TEST_OK}
else
    error "AIS/GNSS failed"
    receiverFailed=${TEST_NOK}
    results
    exit 1  
fi

timestamp=$(wget -qSO- --max-redirect=0 google.com 2>&1 | grep Date: | cut -d' ' -f5-8)
if [ $? -ne 0 ]
then
    error "Could not get current internet time"
    results
    exit 1
fi

if [ "$sensorsFailed" != "${TEST_OK}" -o "$firmwareFailed" != "${TEST_OK}" -o "$eepromFailed" != "${TEST_OK}" -o "$receiverFailed" != "${TEST_OK}" -o "$microcontrollerFailed" != "${TEST_OK}" -o "$gpioFailed" != "${TEST_OK}" -o "$systemFailed" != "${TEST_OK}" ]
then
    results
    exit 1
fi

if [ ! -e "${database}" ]
then
    info "creating database"
    touch "${database}"
    if [ $? -ne 0 ]
    then
        error "Could not create database file ${database}"
        databaseFailed=${TEST_NOK}
        results
        exit 1
    else
        databaseFailed=${TEST_OK}
    fi
fi

dbEntry=${timestamp}";"${serial}";"${rng}";"${appVersion}";"${hwVersion}";"${ais1Cnt}";"${ais2Cnt}";"${gnssCnt}";"${cpuTemp}";"${pressureTemp}";"${pressure}";"${mpuTemp}
echo ${dbEntry} >> ${database}
if [ $? -ne 0 ]
then
    error "Could not write database file ${database}"
    databaseFailed=${TEST_NOK}
    results
    exit 1
else
    databaseFailed=${TEST_OK}
fi

results

sleep 2
if [ ${sOption} -eq 1 ]
then
    info "shutting down system"
    sleep 1
    shutdown now
fi    

if [ ${rOption} -eq 1 ]
then
    info "rebooting system"
    sleep 1
    reboot
fi   
exit 0
