#!/bin/bash
#
# Gps sentences reader for PLCMxx
# October 2020
#
# Supported models:
#   - PLCM10/PLCM11/PLCM12 Plugin with Quectel 3G/4G Modem
#
# Calls:
#   - modem gps for gps port
#
# Info:
#   - service running in background
#
# Params:
#   - -s decode show sentences to stdout
#   - -v verbose mode
#   - -b process in background
#

TIMESEEKPORT=10 # Seconds to check gps port availability
TIMESETGPS=5 # Seconds to wait between two sets of gps sentence
DISPLAY=0 # 1 to show sentences to stdout
SKIPSENTENCE=1 # skip n sentence from gps
GPSROOT="services/mobile/gps"
GPSVAR="${GPSROOT}/info"
GPSENABLEDVAR="${GPSROOT}/enabled"
ENABLEDVALUE=-1

GPSREAD_PID="/var/run/gpsread.pid"
TPID=0

ERR_ALREADYRUNNING=100
ERR_NOGPSCOM=101
ERR_NOTRUNNING=102
ERR_SYSTEM=103

gps_lat=""
gps_lon=""
gps_fix="0"
gps_hgt=""
gps_nst="0"
gps_dat=""
gps_hor=""
gps_spd=""
gps_sts=""

info()
{
    >&2 echo "# $@" 
    echo "$@" | logger -t modem
}

log()
{
    [ $VERBOSE -eq 0 ] && return 0
    info "$@"
}

err()
{
    info "[ERROR] $@"
}

# $1: key
# $1: value
epad_set_volatile()
{
    local value="${2}"
    if [ -z "${value// }" ]; then
        log "Deleting EPAD Volatile Parameter: $1"
        value=""
    else
        log "Setting EPAD Volatile Parameter: $1=$value"
    fi
    dbus-send --print-reply --system --dest=com.exor.EPAD '/' com.exor.EPAD.setVolatileParameter string:"$1" string:"$value" >/dev/null
    [ $? -ne 0 ] && err "Failed sending EPAD parameter ${1} \"$value\""
}

epocdate()
{
   local dat=${1}
   local tim=${2}

   if [ -z "${dat}" ] || [ -z "${tim}" ]; then
       echo ""
       return 1
   fi
   local param="20${dat:4:2}-${dat:2:2}-${dat:0:2} ${tim:0:2}:${tim:2:2}:${tim:4:2}"
   local res=$(date -d "${param}" +"%s" 2> /dev/null)
   if [ "$?" != "0" ]; then
       echo ""
       return 1
   fi
   echo ${res}
   return 0
}

storegps()
{
    local st="${gps_fix},${gps_nst},${gps_dat},${gps_hor},${gps_lat},${gps_lon},${gps_hgt},${gps_spd},${gps_sts}"
    epad_set_volatile ${GPSVAR} ${st}
## gps_fix = 0 no fix
## gps_nst = n sat available
## gps_dat = date ddmmyy
## gps_hor = time hhmmss.ss Z
## gps_lat = latitude dd (mm.mmmmmm*60),(N|S)
## gps_lon = longitude ddd (mm.mmmmmm*60),(E|W)
## gps_hgt = altitude in meters
## gps_spd = speed in knots -> 1 kn = 0.51 m/s
## gps_sts = status (A = valid)
    epad_set_volatile ${GPSROOT}/nmea "${gps_fix}"
    epad_set_volatile ${GPSROOT}/nsat "${gps_nst}"
    epad_set_volatile ${GPSROOT}/datetime "$(epocdate ${gps_dat} ${gps_hor})"
    epad_set_volatile ${GPSROOT}/longitude "${gps_lon}"
    epad_set_volatile ${GPSROOT}/latitude "${gps_lat}"
    epad_set_volatile ${GPSROOT}/altitude "${gps_hgt}"
    epad_set_volatile ${GPSROOT}/speed "${gps_spd}"
    if [ "${gps_sts}" = "A" ]; then
        epad_set_volatile ${GPSROOT}/state 1
    else
        epad_set_volatile ${GPSROOT}/state 0
    fi
}


plc_hwver()
{
    cat ${dev}/hwcode
    return 0
}


dev_nodie()
{
    for plugin in /dev/plugin*; do
        if [ "`cat ${plugin}/installed 2>/dev/null`" = "1" ]; then
            if [ "`cat ${plugin}/hwcode 2> /dev/null`" = "14" ] || [ "`cat ${plugin}/hwcode 2> /dev/null`" = "13" ]; then 
                echo ${plugin}
                return 0
            fi
        fi
    done
    echo ""
    return 1
}

parse_coord3() ##longitude dddxx.xxxxxx -> ddd.(xxxxxxxx/60)°
{
   local coord1=${1:0:3}
   local coord2=${1:3:2}${1:6:6}
   local div=60
   coord2=$((coord2/div))
   local dir=$2

   echo "${coord1}.${coord2}° ${dir}"
   return 0
}

parse_coord2() ##latitude ddxx.xxxxxx -> dd.(xxxxxxxx/60)°
{
   local coord1=${1:0:2}
   local coord2=${1:2:2}${1:5:6}
   local div=60
   coord2=$((coord2/div))
   local dir=$2

   echo "${coord1}.${coord2}° ${dir}"
   return 0
}

parse_height()
{
   local h=$1
   local u=$2
   echo "${h} ${u}"
   return 0
}

parse_speed()
{
   local speed=$1
   speed=$(bc <<< "$speed * 0.51")
   echo ${speed}
   return 0
}

parse_status()
{
   local status=$1
   echo "${status}"
   return 0 
}

parse_time()
{
   local value=$1
   if [ -z $value ]; then
       return 1
   fi

   local length=${#value}
   if [ ${length} -ne 9 -o "${value:6:1}" != "." ]; then
       log "Invalid time format"
       return 1
   fi

   local hour=${value:0:2}
   local min=${value:2:2}
   local sec=${value:4:2}
   local msec=${value:7:2}

   local int='^[0-9][0-9]$'

   if [[ ! ${hour} =~ $int ]]; then
       log "Invalid hour"
       return 1
   fi
   if [ ${hour} -gt 23 ]; then
       log "Invalid time format"
       return 1
   fi

   if [[ ! ${min} =~ $int ]]; then
       log "Invalid hour"
       return 1
   fi
   if [ ${min} -gt 60 ]; then
       log "Invalid time format"
       return 1
   fi

   if [[ ! ${sec} =~ $int ]]; then
       log "Invalid hour"
       return 1
   fi
   if [ ${sec} -gt 60 ]; then
       log "Invalid time format"
       return 1
   fi

   if [[ ! ${msec} =~ $int ]]; then
       log "Invalid hour"
       return 1
   fi
   
   echo "${hour}${min}${sec}.${msec}"  
   return 0
}


parse_date()
{
   local value=$1
   if [ -z $value ]; then
       log "No date"
       return 1
   fi

   local length=${#value}
   if [ ${length} -ne 6 ]; then
       log "Invalid date format"
       return 1
   fi

   local day=${value:0:2}
   local month=${value:2:2}
   local year=${value:4:2}

   local int='^[0-9][0-9]$'

   if [[ ! ${day} =~ $int ]]; then
       log "Invalid hour"
       return 1
   fi
   if [ ${day} -gt 31 -o ${day} -lt 1 ]; then
       log "Invalid day"
       return 1
   fi

   if [[ ! ${month} =~ $int ]]; then
       log "Invalid month"
       return 1
   fi
   if [ ${month} -gt 12 -o ${month} -lt 1 ]; then
       log "Invalid month format"
       return 1
   fi

   if [[ ! ${year} =~ $int ]]; then
       log "Invalid year"
       return 1
   fi
   
   echo "${day}${month}20${year}"  
   return 0
}

use_gga()
{  
   local line=$1
   log "GGA ${line}"
   
   line=${line:6}
   #Test line=,092725.00,4717.11399,N,00833.91590,E,1,0,0,0,0,0
   IFS=',' read -r -a array <<< "${line}"
   
   gps_lat=","
   gps_lon=","
   gps_fix="0"
   gps_hgt=""
   gps_nst="1"
   
   if [ ${#array[@]} -lt 11 ]; then
     err "GPS GGA Invalid array size"
     return 1
   fi
   
   gps_lat="${array[2]},${array[3]}"
   gps_lon="${array[4]},${array[5]}"
   gps_fix="${array[6]}"
   gps_hgt="${array[9]}"
   gps_nst="${array[7]}"
   
   if [ "${DISPLAY}" = "1" ]; then
     local fix=${array[6]}
     echo "Fix ${fix}"
     [ -z "$fix" ] && return 1

     if [ "$fix" != "0" ]; then
        # Fix OK
        echo "Valid fix"
     else
        # Fix Not valid
        return 1
     fi

     local gpstime=$(parse_time ${array[1]})
     echo "gps time ${gpstime}"
     [ -z "$gpstime" ] && return 1

     local lat=$(parse_coord2 ${array[2]} ${array[3]})
     echo "Latitude ${lat}"
     [ -z "$lat" ] && return 1
  
     local lon=$(parse_coord3 ${array[4]} ${array[5]})
     echo "Longitude ${lon}"
     [ -z "$lon" ] && return 1
   
     local nsat=${array[7]}
     echo "NSat: ${nsat}"
     [ -z "$nsat" ] && return 1
     local height=$(parse_height ${array[9]} ${array[10]})
     echo "Height: ${height}"
     [ -z "$height" ] && return 1
   fi
   return 0
}

use_rmc()
{
   local line=$1
   log "RMC ${line}"

   line=${line:6}
   #Test line=,092725.00,A,4717.11399,N,00833.91590,E,0.004,77.52,090120
   IFS=',' read -r -a array <<< "${line}"

   gps_dat=""
   gps_hor=""
   gps_spd=""
   gps_sts=""
   
   if [ ${#array[@]} -lt 10 ]; then
     err "GPS RMC Invalid array size"
     return 1
   fi
   
   gps_dat="${array[9]}"
   gps_hor="${array[1]}"
   gps_spd="${array[7]}"
   gps_sts="${array[2]}"

   if [ "${DISPLAY}" = "1" ]; then
     local gpstime=$(parse_time ${array[1]})
     echo "gps time ${gpstime}"
     [ -z "$gpstime" ] && return 1

     local status=$(parse_status ${array[2]})
     echo "Status ${status}"
     [ -z "$status" ] && return 1

     if [ "${status}" == "A" ]; then
      # Fix OK
      echo "Valid data"
     else
      # Fix Not valid
      echo "Invalid data"
      # epad_set_volatile "services/mobile/info/gps" ""
      return 1
     fi

     local lat=$(parse_coord2 ${array[3]} ${array[4]})
     echo "Latitude ${lat}"
     [ -z "$lat" ] && return 1
  
     local lon=$(parse_coord3 ${array[5]} ${array[6]})
     echo "Longitude ${lon}"
     [ -z "$lon" ] && return 1
   
     local speed=$(parse_speed ${array[7]})
     echo "Speed ${speed}"
     [ -z "$speed" ] && return 1

     local dat=$(parse_date ${array[9]})
     echo "Date ${dat}"
     [ -z "$dat" ] && return 1
   fi
   # epad_set_volatile "services/mobile/info/gps" "${dat},${gpstime},${lat},${lon}"
     
   return 0
}

set_enabled()
{
    local newvalue=$1
    if [ ! ${newvalue} -eq ${ENABLEDVALUE} ]; then
        ENABLEDVALUE=${newvalue}
        epad_set_volatile ${GPSENABLEDVAR} ${newvalue}
        if [ ${newvalue} -eq 0 ]; then
            epad_set_volatile ${GPSVAR}
            epad_set_volatile ${GPSROOT}/state
        fi
    fi
}

modem_reader()
{
    log "Starting reader on ${com}"

    local enabled=0
    local counter1=${SKIPSENTENCE}
    local counter2=${SKIPSENTENCE}
    SECONDS=0
    stty -F ${com} -crtscts -echo
    cat "${com}" | while read line; do
        [ "${line}" = "" ] && continue
        if [ "${enabled}" != "1" ]; then
            set_enabled 2
            enabled=1
        fi
        if [ ${line:1:5} == "GPGGA" ]; then
            if [ ${counter1} -le 0 ]; then
                use_gga ${line}
                counter1=${SKIPSENTENCE}
            else
                counter1=$((counter1-1))
            fi
        fi
        if [ ${line:1:5} == "GPRMC" ]; then
            if [ ${counter2} = 0 ]; then
                use_rmc ${line}
                counter2=${SKIPSENTENCE}
            else
                counter2=$((counter2-1))
            fi
        fi
        if [ ${SECONDS} -gt ${TIMESETGPS} ]; then
            log "Store gps sentence"
            storegps
            SECONDS=0
        fi
    done
    set_enabled 1
}

prg_run()
{
    set_enabled 0
    com=$(modem gps)
    log "Gps on device ${com}"
    while [ -e "${GPSREAD_PID}" ]; do
           log "search com ${com}"
           if [ -e ${com} ]; then
               set_enabled 1
               log "${com} found"
               modem_reader
           else
               set_enabled 0
               sleep ${TIMESEEKPORT}
           fi
    done < <(log "GPSREAD $$")
    set_enabled 0
}

exit_reader()
{
    log "GPSREAD exiting"
}

#Main

VERBOSE=0
BACKGROUND=0
FORCERM=0

trap exit_reader INT TERM EXIT

while getopts ":vsbf" opt; do
    case ${opt} in
        v) VERBOSE=1
           ;;
        s) DISPLAY=1
           ;;
        b) BACKGROUND=1
           ;;
        f) FORCERM=1
           ;;
        *) err "Invalid option: ${opt}!"
           exit 1
           ;;
    esac
done

noarg=1
com=""

for arg in $@; do
    case "$arg" in 
       "start")
           noarg=0
           if [ -f "${GPSREAD_PID}" ]; then
               err "$0 is already running"
               exit ${ERR_ALREADYRUNNING}
           fi
           if [ "${BACKGROUND}" = "1" ]; then
               prg_run &
               TPID=$!
               echo ${TPID} > "${GPSREAD_PID}"
               if [ ! $? -eq 0 ]; then
                   err "Unable to start process"
                   #kill ${pid}
                   exit ${ERR_SYSTEM}
               fi
           else
               echo $$ > "${GPSREAD_PID}"
               prg_run
           fi
           ;;
       "stop")
           noarg=0
           if [ -e "${GPSREAD_PID}" ]; then
               pid=$(cat ${GPSREAD_PID})
               log "GPSREAD close pid ${pid}"
               rm -f "${GPSREAD_PID}"
               pkill -P ${pid} 2>/dev/null
           else
               if [ ${FORCERM} -eq 1 ]; then
                   log "GPSREAD kill process"
                   killall -9 gpsread
               fi
           fi
           ;;
       "restart")
           noarg=0
           $0 stop
           $0 start
           ;;
       "status")
           noarg=0
           if [ -f "${GPSREAD_PID}" ]; then
               echo gpsread is running, pid=`cat ${GPSREAD_PID}`
           else
               echo gpsread is not running
               exit ${ERR_NOTRUNNING}
           fi
           ;;
    esac
done

if [ "${noarg}" = "1" ]; then
    echo "Usage: $0 [-v] [-s] [-b] {start|stop|status|restart}"
fi

log "Exiting"

exit 0
