#!/bin/bash
#
# Modem control script
# October 2020
#
# Supported models:
#   - PLCM09/PLCM10/PLCM11/PLCM12 Plugin with Quectel 3G/4G Modem
#   - PLCM10B BSP-3009
#
# Prerequisites:
#   - udev must be up and running (for setup of /dev/plugin* links)
#
# Callers:
#   - managed by /etc/init.d/networking ('start','stop')
#   - EPAD ServiceManager poll ('status')
#   - Cron event mobile signal pool ('rssi')
#
# Return values (upon start):
#   0       Success
#   1       Usage error
#   2       System Parameter Error (notified to EPAD)
#
# Return values (upon status):
#   0       Success
#   1       Usage error
#   2       System Parameter Error (notified to EPAD)
#
# References
#   - 3GPP TS 27.007

MODEM_BAUD=$(sys_params -l services/mobile/baud 2>/dev/null)
MODEM_TECH=$(sys_params -l services/mobile/tech 2>/dev/null)
GPSAUTO=$(sys_params -l services/mobile/gps/autostart 2>/dev/null)
GPSENABLED=$(sys_params -l -V services/mobile/gps/enabled 2>/dev/null)
GPSREAD_COMMAND="/usr/bin/gpsread -b start"
USBVERSION_KEY="services/mobile/usbversion"
MODEM_PID="/var/run/modem.pid"  # this script is alive only while starting
MODEM_STATE="/var/run/modem.state"
MODEM_IFACE="ppp0"
MODEM_STARTING=0
USBHUB=""
TMPFILE="/tmp/plcmxx.tmp"

MODEMOUT="/tmp/modem.out"
MAX_RETRIES=10
MODEM_KILLING=0

ERR_SUCCESS=0
ERR_GENERICERROR=1
ERR_SYSTEMERROR=10
ERR_MODEMNOTFOUND=100
ERR_MODEMBUSY=101
ERR_MODEMCOMM=110
ERR_MODEMTIMEOUT=120
ERR_MODEMERROR=130
ERR_INVALIDVERSION=140
ERR_MODEMOFF=150
ERR_SIMMISSING=200
ERR_PINREQUIRED=300
ERR_NEWPINREQUIRED=301
ERR_PINERROR=310
ERR_PUKREQUIRED=320
ERR_PUKERROR=330
ERR_ROAMINGBLOCKED=400

VERBOSE=0
FORCECOM=0
USEQMI=0

# global var PLCM_DEV = /dev/plugin*
# global var PLCM_REL = PLCMxx version. 9 = PLCM09, 10 = PLCM10, 11 = PLCM11, 12 = PLCM12, 8 = PLCM10B

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

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

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

errdie()
{
    err "$1"
    exit $2
}

cmd_help()
{
    echo "Usage: $(basename $0) [OPTS] CMD, where CMD can be:"
    echo
    echo "              start           start modem (if detected)"
    echo "              stop            stop modem (if detected)"
    echo "              restart         restart modem"
    echo "              status          get modem status"
    echo "              rssi            get 3g/4g signal quality"
    echo 
    echo "              dev             get device name (returns 1 if not detected)"
    echo "              comidx          get index of serial device (returns 1 if not detected)"
    echo "              com             get serial device name (returns 1 if not detected)"
    echo "              on              power up only"
    echo "              off             power off only"
    echo "              hwver           hardware version"
    echo "              plcrel          plcm release"
    echo "              gps             get gps output port"
    echo "              gpson           enable gps"
    echo "              gpsff           disable gps"
    echo "              wifion          enable wifi module"
    echo "              wifioff         disable wifi module"
    echo
    echo "Other OPTS:"
    echo "              -v              verbose mode"
    echo "              -c              force COM port"
    echo "              -q              use qmi interface"
}

plcname()
{
   local code=$1
   case ${code} in
     8)
       echo "PLCM10B"
       ;;
     9)
       echo "PLCM09"
       ;;
     *)
       echo "PLCM${code}"
       ;;
   esac
   return 0
}

funcarea_bit()
{
    # Note: currently handles up to 24b only
    local funcarea="$(dd if=${PLCM_DEV}/eeprom skip=36 count=24 bs=1 2>/dev/null | hexdump -e '24/1 "%02x "')"

    local rbyte=$(( ${1}/8 +1))
    local roffset=$(( 1 << ( ${1} & 7) ))

    if [ "$(( 0x$( echo ${funcarea} | cut -d' ' -f ${rbyte}) & ${roffset} ))" -ne 0 ]; then
       echo 1
    else
       echo 0
    fi
}

cleanup()
{
    log "cleanup"

    if [ ! -z $modem_pid ] && [ -e /proc/$modem_pid ]; then
        pkill -P $modem_pid 2>/dev/null
    fi

    MODEM_KILLING=1
    close_reader

    [ "${MODEM_STARTING}" = "1" ] && rm -f "${MODEM_PID}"
}

cmd_dev()
{
    for plugin in /dev/plugin*; do
    if [ "`cat ${plugin}/installed 2>/dev/null`" = "1" ]; then
## Keep for compability See BSP-2298
        if [ "`cat ${plugin}/hwcode 2> /dev/null`" = "14" ] || [ "`cat ${plugin}/hwcode 2> /dev/null`" = "13" ]; then 
            echo ${plugin}
            return 0
        fi
##
        if [ -e "${plugin}/plcmxx_version" ] && [ "`cat ${plugin}/plcmxx_version 2> /dev/null`" != "0" ]; then
            echo ${plugin}
            return 0
        fi
    fi
    done
    echo ""
    return ${ERR_MODEMNOTFOUND}
#    die ${ERR_MODEMNOTFOUND}
}

cmd_hwver()
{
    local dev=${PLCM_DEV}
    [ -z ${dev} ] && return ${ERR_MODEMNOTFOUND}

    cat ${dev}/hwcode
    return 0
}

cmd_plcrel()
{
    local dev=${PLCM_DEV}
    [ -z ${dev} ] && return ${ERR_MODEMNOTFOUND}

    case $(cmd_hwver) in
      13) echo 9
          ;;
      *)
          if [ -e "${plugin}/plcmxx_version" ]; then
              echo "${plugin}/plcmxx_version"
              return 0
          fi
## Keep for compability see BSP-2298
          local bit14=$(funcarea_bit 14)
          local bit15=$(funcarea_bit 15)
          local bit16=$(funcarea_bit 16)
          if [ ${bit16} -eq 1 ]; then
              #PLCM10B 4G ONLY
              echo 8
          elif [ ${bit14} -eq 0 ] && [ ${bit15} -eq 0 ]; then
              #Invalid version
              return ${ERR_MODEMNOTFOUND}
          elif [ ${bit14} -eq 0 ]; then
              #PLCM12 WIFI ONLY
              echo 12
          elif [ ${bit15} -eq 0 ]; then
              #PLCM11 4G ONLY
              echo 11
          else
              echo 10
          fi
##
          ;;
    esac
    return 0
}

cmd_comidx()
{
    local dev=${PLCM_DEV}
    local i
    # 2 plugins for each serial device starting from 1
    i=$(echo ${dev} | tr -d /dev/plugin/)
    echo $((i/2+1))
}

cmd_usbidx()
{
    local myport=9999
    [ "$(ls /dev/usbmodem* 2>/dev/null)" != "" ] && \
        for usbdev in $(realpath /dev/usbmodem* 2>/dev/null); do
           local port=${usbdev#*ttyUSB}
           if [ ! -z "${port}" ] && [ ${port} -lt ${myport} ]; then
                myport=${port}
           fi
        done
    if [ ${myport} -eq 9999 ]; then
        return 1
    fi
    echo ${myport}
    return 0
}

cmd_com()
{
    local idx=0
    local rel=${PLCM_REL}
    case ${rel} in
        9)
           idx=$(cmd_comidx)
           [ $? -ne 0 ] && return 1
           echo /dev/com$((idx+1))
           ;;
        10 | 11 | 12 | 8)
           local com="null"
           if [ ${FORCECOM} -ne 1 ]; then
               idx=$(cmd_usbidx)
               [ $? -eq 0 ] && com="/dev/ttyUSB$((idx+3))"
           fi
           if [ -e "${com}" ]; then
             echo ${com}
           else
             idx=$(cmd_comidx)
             com="/dev/com$((idx+1))"
             if [ -e "$com" ]; then
                 echo ${com}
             else
                 return ${ERR_MODEMCOMM}
             fi
           fi
           ;;
    esac
    return 0
}

cmd_com_atusb()
{
    local idx=0
    local rel=${PLCM_REL}
    case ${rel} in
        9)
           echo ""
           return 1 #no usb
           ;;
        10 | 11 | 12 | 8)
           idx=$(cmd_usbidx)
           if [ $? -ne 0 ]; then
               echo ""
               return 1
           fi
           echo "/dev/ttyUSB$((idx+2))"
           ;;
        *)
           echo ""
           return ${ERR_INVALIDVERSION}
           ;;
    esac
    return 0
}

cmd_com_atcom()
{
    local idx=0
    local rel=${PLCM_REL}
    case ${rel} in
        9 | 10 | 11 | 12 | 8)
           idx=$(cmd_comidx)
           [ $? -ne 0 ] && return 1
           echo /dev/com$((idx+1))
           ;;
        *)
           return ${ERR_INVALIDVERSION}
           ;;
    esac
    return 0
}

cmd_com_at()
{
    local idx=0
    local rel=${PLCM_REL}
    case ${rel} in
        9)
           idx=$(cmd_comidx)
           [ $? -ne 0 ] && return 1
           echo /dev/com$((idx+1))
           ;;
        10 | 11 | 12 | 8)
           local com="null"
           if [ ${FORCECOM} -ne 1 ]; then
               idx=$(cmd_usbidx)
               [ $? -eq 0 ] && com="/dev/ttyUSB$((idx+2))"
           fi
           if [ -e "${com}" ]; then
             echo ${com}
           else
             idx=$(cmd_comidx)
             com="/dev/com$((idx+1))"
             if [ -e "$com" ]; then
                echo ${com}
             else
               return ${ERR_MODEMCOMM}
             fi
           fi
           ;;
        *)
           return ${ERR_INVALIDVERSION}
           ;;
    esac
    return 0
}

cmd_gps()
{
    local vers=${PLCM_REL}
    local idx=0
    if [ ${FORCECOM} -eq 1 ]; then
       return ${ERR_INVALIDVERSION}
    fi
    case ${vers} in
       10 | 11 | 12)
           idx=$(cmd_usbidx)
           [ $? -ne 0 ] && return 1
           echo /dev/ttyUSB$((idx+1))
           ;;
       *)
          return ${ERR_INVALIDVERSION}
          ;;
    esac
    return 0
}

# dev need to be initialized to at port
gps_program()
{
    log "Programming GPS on ${dev}"
    out=$(wrap_send send_expect "${dev}" "AT+QGPS?" "^\+QGPS:")
    operator=$(echo "${out}" | cut -b 8)
    if [ "${operator}" = "1" ]; then
        log "Gps already enabled"
        return 0
    fi

    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"outport\",\"usbnmea\"" "^(OK)" "1" >/dev/null
    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"gnssconfig\",1" "^(OK)" "1" >/dev/null
    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"agnssprotocol\",3,0" "^(OK)" "1" >/dev/null
    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"fixfreq\",1" "^(OK)" "1" >/dev/null
    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"gpsnmeatype\",3" "^(OK)" "1" >/dev/null
    wrap_send send_expect "${dev}" "AT+QGPSCFG=\"glonassnmeatype\",0" "^(OK)" "1" >/dev/null

    wrap_send send_expect "${dev}" "AT+QGPS=1" "^(OK)" "1" >/dev/null
}

cmd_gpson()
{
    log "turn on gps module"
    local vers=${PLCM_REL}
    case ${vers} in
      10 | 11)
        local gpsport=$(cmd_gps)
        if [ $? -ne 0 ] || [ -z ${gpsport} ]; then
            err "Invalid gps port"
            return ${ERR_GENERICERROR}
        fi
        if [ ! -e ${gpsport} ]; then
            err "No gps port available"
            return ${ERR_MODEMERROR}
        fi
        local usbport=$(cmd_com_atusb)
        if [ ! -e ${usbport} ]; then
            err "No at command port available"
            return ${ERR_MODEMERROR}
        fi

        log "Initializeting gps on $1"
        local dev=${usbport}

        if [ ! -z $MODEM_PID ] && [ -e $MODEM_PID ]; then
            log "Modem busy"
            return ${ERR_MODEMBUSY}
        fi

        if [ "$(lsof $dev 2>/dev/null)" != "" -o "$(fuser $dev 2>/dev/null)" != "" ]; then
            log "Device busy"
            return ${ERR_MODEMBUSY}
        fi

        local out
        local operator
        spawn_reader

        wrap_send send_expect "${dev}" "ATE0" "^(atE0|OK)" "1" >/dev/null

        gps_program

        log "GPSREAD start"
        ${GPSREAD_COMMAND}
        ;;
      *)
        err "Invalid device version"
        return ${ERR_INVALIDVERSION}
        ;;
    esac
    return 0
}

cmd_gpsoff()
{
    log "turn off gps module"
    local vers=${PLCM_REL}
    case ${vers} in
      10 | 11)
        local gpsport=$(cmd_gps)
        if [ $? -ne 0 ] || [ -z ${gpsport} ]; then
            err "Invalid gps port"
            return ${ERR_GENERICERROR}
        fi
        if [ ! -e ${gpsport} ]; then
            err "No gps port available"
            return ${ERR_MODEMERROR}
        fi
        local usbport=$(cmd_com_atusb)
        if [ ! -e ${usbport} ]; then
            err "No at command port available"
            return ${ERR_MODEMERROR}
        fi

        log "Deinitializiting gps module on $1"
        local dev=${usbport}

        if [ ! -z $MODEM_PID ] && [ -e $MODEM_PID ]; then
            log "Modem busy"
            return ${ERR_MODEMBUSY}
        fi

        if [ "$(lsof $dev 2>/dev/null)" != "" -o "$(fuser $dev 2>/dev/null)" != "" ]; then
            log "Device busy"
            return ${ERR_MODEMBUSY}
        fi

        local out
        local operator

        spawn_reader
        wrap_send send_expect "${dev}" "ATE0" "^(atE0|OK)" "1" >/dev/null

        out=$(wrap_send send_expect "${dev}" "AT+QGPS?" "^(\+QGPS:|OK)")
        operator=$(echo "${out}" | cut -b 8)
        if [ "${operator}" = "0" ]; then
        log "Gps already disabled"
            return 0
        fi
        wrap_send send_expect "${dev}" "AT+QGPSEND" "^(OK)" "1" >/dev/null

        /usr/bin/gpsread stop
        ;;
      *)
        err "Invalid device version"
        return ${ERR_INVALIDVERSION}
        ;;
    esac
    return 0
}

cmd_wifion()
{
    log "Turn on wifi module"
    local vers=${PLCM_REL}
    if [ ${FORCECOM} -eq 1 ]; then
        return ${ERR_INVALIDVERSION}
    fi
    case ${vers} in
      10 | 12)
        local dev="${PLCM_DEV}/plcmxx_wlan"
        if [ ! -e ${dev} ]; then
            err "Unable to find file ${dev}"
            return ${ERR_GENERICERROR}
        fi
        echo 1 > ${dev}
        if [ $? -ne 0 ]; then
            err "Unable to write wlan device"
            return ${ERR_SYSTEMERROR}
        fi
        log "Command sent to device"
        #log "Applying RS9113 patch"
        #${WIFIPATCH_COMMAND}
        ;;
      *)
        err "Invalid device version"
        return ${ERR_INVALIDVERSION}
        ;;
   esac
   return 0
}

cmd_wifioff()
{
    log "Turn off wifi module"
    local vers=${PLCM_REL}
    if [ ${FORCECOM} -eq 1 ]; then
        return ${ERR_INVALIDVERSION}
    fi
    case ${vers} in
      10 | 12)
        local dev="${PLCM_DEV}/plcmxx_wlan"
        if [ ! -e ${dev} ]; then
            err "Unable to find file ${dev}"
            return ${ERR_GENERICERROR}
        fi
        echo 0 > ${dev}
        if [ $? -ne 0 ]; then
            err "Unable to write wlan device"
            return ${ERR_SYSTEMERROR}
        fi
        log "Command sent to device"
        ;;
      *)
        err "Invalid device version"
        return ${ERR_INVALIDVERSION}
        ;;
    esac
    return 0
}

cmd_send()
{
    log "=> $2"
    printf "$2\r" > $1 2>/dev/null
}

cmd_usbversion()
{
    local res=$(sys_params -l "${USBVERSION_KEY}" 2>/dev/null)
    echo ${res}
    if [ -z ${res} ]; then
        return 1
    else
        return 0
    fi
}

modem_reader()
{
    local dev=$1

    log "Starting reader on ${dev}"

    cat "${dev}" | while read line; do
        [ "${line}" = "" ] && continue
        log "<= ${line}"
        echo "$line" | tr -d '\r' >> "${MODEMOUT}"
        if [ "${tmpfile}" != "" ] && [ ! -e $tmpfile ]; then
          break
        fi 
    done
}

send_expect()
{
    local dev=$1
    local send=$2
    local expect=$3
    local retry=$4
    local j

    rm -f "${MODEMOUT}"

    log "Expect ${expect}"

    log "=> ${send}"
    printf "${send}\r" > "${dev}"

    for ((i=0; i < ${MAX_RETRIES}; i++)); do
      for ((j=0; j < 5; j++)); do
        [ ${MODEM_KILLING} -eq 1 ] && return 0
        [ -e "${MODEMOUT}" ] && grep -E "${expect}" "${MODEMOUT}" && return 0
        [ -e "${MODEMOUT}" ] && grep -q -E "^\+CME ERROR: 16" "${MODEMOUT}" && return 100
        [ -e "${MODEMOUT}" ] && grep -q -E "^.*ERROR" "${MODEMOUT}" && return 10
        usleep 200000
      done
      #sleep 1
      if [ "${retry}" = "1" ]; then
             log "x=> ${send}"
             printf "${send}\r" > "${dev}"
      fi
      if [ "${tmpfile}" != "" ] && [ ! -f $tmpfile ]; then
          return 0
      fi
    done
    return 1
}

wrap_send()
{
    $@
    local rc=$?
    [ ${rc} -eq 0 ] && return 0

    #cmd_off

    case ${rc} in
        1)
            notify_error ${ERR_MODEMTIMEOUT}
            ;;
        10)
            notify_error ${ERR_MODEMERROR}
            ;;
        100)
            notify_error ${ERR_PINERROR}
            ;;
        *)
            notify_error ${ERR_SYSTEMERROR}
            ;;
    esac            
}

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

# Reset error state
epad_reset()
{
    # set error state
    dbus-send --print-reply --system --dest=com.exor.EPAD '/' com.exor.EPAD.setVolatileParameter string:"services/mobile/state" string:"0" >/dev/null
    [ $? -ne 0 ] && err "Failed sending EPAD error state"

    # set actual error code
    dbus-send --print-reply --system --dest=com.exor.EPAD '/' com.exor.EPAD.setVolatileParameter string:"services/mobile/error" string:"0" >/dev/null
    [ $? -ne 0 ] && err "Failed sending EPAD error code"
}

# $1: error code
epad_error()
{
    [ -z "$1" ] && epad_error ${ERR_SYSTEMERROR}

    # set error state
    dbus-send --print-reply --system --dest=com.exor.EPAD '/' com.exor.EPAD.setVolatileParameter string:"services/mobile/state" string:"-1" >/dev/null
    [ $? -ne 0 ] && err "Failed sending EPAD error state"

    # set actual error code
    dbus-send --print-reply --system --dest=com.exor.EPAD '/' com.exor.EPAD.setVolatileParameter string:"services/mobile/error" string:"$1" >/dev/null
    [ $? -ne 0 ] && err "Failed sending EPAD error code"
}

# Set error as EPAD Volatile System Parameter
# $1: error code
notify_error()
{
    local err=$1
    [ -z ${err} ] && epad_error ${ERR_SYSTEMERROR}

    err "code: ${err}"

    epad_error ${err}
    cleanup
    exit ${err}
}

spawn_reader()
{
    tmpfile=$(mktemp)
    log "Temporary file created ${tmpfile}"

    modem_reader "${dev}" &
    modem_pid=$!

    log "Reader on PID ${modem_pid}"
}

close_reader()
{
    if [ "${tmpfile}" != "" ] && [ -f $tmpfile ]; then
       rm -f ${tmpfile}
       log "Temporary file removed ${tmpfile}"
       if [ ! -z $modem_pid ] && [ -e /proc/$modem_pid ]; then
           wrap_send send_expect "$(cmd_com_at)" "AT" "^(at|OK)" "1" >/dev/null
       fi
    fi
}

setusbversion()
{
    local oldvalue=$(cmd_usbversion)
    if [ "$1" != "${oldvalue}" ]; then
        sys_params -w "${USBVERSION_KEY}" "$1" 2>/dev/null
        local rc=$?
        if [ $rc -gt 0 ]; then
            log "Unable to set plcm usbversion. Err ${rc}"
        fi
        return ${rc}
    fi
    return 0
}

# Quectel specs
#         0 -113dBm or less
#         1 -111dBm
#         2...30 -109... -53dBm
#         31 -51dBm or greater
#         99 Not known or not detectable
#
# http://www.gprsmodems.co.uk/images/csq1.pdf

rssiToSignal()
{
    [ -z "$1" ] && return 0

    local lev="$((-113 + $1*2))"

    if [ ${lev} -le -100 ]; then
        echo "0"
    elif [ ${lev} -ge -50 ]; then
        echo "100"
    else
        echo $((2 * (${lev}+100)))
    fi
}

cmd_rssi()
{
    if [ ! -e "${MODEM_STATE}" ] || [  "$(cat ${MODEM_STATE} 2>/dev/null)" != "running" ]; then
       log "Modem not running"
       #echo 99
       return 0
    fi
    if [ ! -z $MODEM_PID ] && [ -e $MODEM_PID ]; then
        log "Modem busy"
        #echo 99
        return ${ERR_MODEMBUSY}
    fi
    if [ ${FORCECOM} -eq 1 ]; then
        log "RSSI command can be used only with USB modem"
        return ${ERR_INVALIDVERSION}
    fi

    local test=${PLCM_DEV}
    if [ ! -e $test ]; then
      log "No device found"
      echo 99
      return ${ERR_MODEMNOTFOUND}
    fi

    local vers=${PLCM_REL}
    case ${vers} in
        9)
          log "Invalid version"
          return ${ERR_INVALIDVERSION}
          ;;
        10 | 11 | 8)
          ;;
        *)
          log "Invalid version"
          return ${ERR_INVALIDVERSION}
          ;;
    esac        

    local dev;
    
    if [ "${FORCECOM}" = "1" ]; then
      dev=$(cmd_com_atcom)
      log "Setting ${dev} speed to 115200"
      stty -F ${dev} 115200
    else
      dev=$(cmd_com_atusb)
    fi
    if [ ! -e "$dev" ]; then
      log "No at-command port found"
      #epad_set_volatile "services/mobile/info/signal" "99"
      #echo "99"
      return ${ERR_MODEMOFF}
    fi

    if [ "$(lsof $dev 2>/dev/null)" != "" -o "$(fuser $dev 2>/dev/null)" != "" ]; then
      log "Modem busy"
      return ${ERR_MODEMBUSY}
    fi

    spawn_reader

    wrap_send send_expect "${dev}" "ATE0" "^(atE0|OK)" "1" >/dev/null

    local out=$(wrap_send send_expect "${dev}" "AT+CSQ" "^\+CSQ:")
    local rssi="$(echo ${out} | cut -s -d ' ' -f 2 | cut -s -d ',' -f 1)"
    # echo "Readed ${out} ${rssi}"
    if [ "${rssi}" != "99" ]; then  # 99 = error
        local signal="$(rssiToSignal ${rssi})"
        log "Setting RSSI value ${signal}"
        epad_set_volatile "services/mobile/info/signal" "${signal}"
        echo "${signal}"
    else
        log "No rssi signal"
        epad_set_volatile "services/mobile/info/signal" "99"
        echo "99"
    fi

    #close_reader
    return 0
}


# $1: serial com device
cmd_config()
{
    local out
    local dev=$1
    local vers=$2
    local gpsauto=0

    [ "${dev}" = "" ] && dev=$(cmd_com_at)
    [ "${vers}" = "" ] && vers=${PLCM_REL}

    local name=""
    case ${vers} in
        9)
          name="PLCM09"
          ;;
       10 | 11 | 12)
          name="PLCM${vers}"
          gpsauto=${GPSAUTO}
          ;;
        8)
          name="PLCM10B"
          ;;
        *)
          err "Invalid hardware code ${vers}"
          ;;
    esac
    log "Configuring ${name} on port ${dev}"

    spawn_reader

    wrap_send send_expect "${dev}" "ATE0" "^(atE0|OK)" "1" >/dev/null

    out=$(wrap_send send_expect "${dev}" "AT+CGSN" "^[0-9]+")
    [ "${out}" = "" ] || epad_set_volatile "services/mobile/info/imei" "${out}"

    out=$(wrap_send send_expect "${dev}" "AT+QSIMSTAT?" "^\+QSIMSTAT:" | cut -s -d ',' -f 2)
    [ "${out}" = "1" ] || notify_error ${ERR_SIMMISSING}

    # Since AT+CPINR is unimplemented..
    # https://developer.gemalto.com/tutorial/how-query-pin-counter-ehsx
    out="$(send_expect ${dev} "AT+CRSM=242" "^\+CRSM:" | tr -d \")"
    local crsm=$(echo "${out}" | cut -s -d',' -f 3)
    local sim_type=${crsm:0:2}
    local pin_left
    local puk_left
    if [ "${sim_type}" = "62" ]; then
        log "Detected USIM"

        out="$(send_expect ${dev} "AT+CSIM=10,\"0020000100\"" "^\+CSIM: 4" | tr -d \")"
        local left_hex=$(echo "${out}" | cut -s -d',' -f 2 | tr -d \" | tail -c 2)
        pin_left="$((16#${left_hex}))"

        out="$(send_expect ${dev} "AT+CSIM=10,\"002C000100\"" "^\+CSIM: 4" | tr -d \")"
        local left_hex=$(echo "${out}" | cut -s -d',' -f 2 | tr -d \" | tail -c 2)
        puk_left="$((16#${left_hex}))"
    else
        log "Detected SIM"
        # Note: assume SIM is initialized
        pin_left="$((16#${crsm:37:1}))"
        puk_left="$((16#${crsm:39:1}))"
        epad_set_volatile "services/mobile/puk_left" "$((16#${crsm:39:1}))"
    fi
    # set initial counters
    epad_set_volatile "services/mobile/pin_left" "${pin_left}"
    epad_set_volatile "services/mobile/puk_left" "${puk_left}"

    out=$(send_expect "${dev}" "AT+CPIN?" "^\+CPIN:")
    if [ "${out}" = "+CPIN: SIM PIN" ]; then
        local pin="`/usr/bin/sys_params services/mobile/pin 2>/dev/null`"
        if [ "${pin}" = "" ]; then 
            notify_error ${ERR_PINREQUIRED}
        else
            send_expect "${dev}" "AT+CPIN=${pin}" "^(\+QIND: PB DONE|OK)" >/dev/null
            # remember PIN only if it was correct
            if [ $? -ne 0 ]; then
                /usr/bin/sys_params -w services/mobile/pin ""
                # decrease counter avoiding extra read
                epad_set_volatile "services/mobile/pin_left" "$((pin_left-1))"
                if [ "$((pin_left-1))" = "0" ]; then
                    notify_error ${ERR_PUKREQUIRED}
                else
                    notify_error ${ERR_PINERROR}
                fi
            fi 
            # reset counters avoiding extra read
            epad_set_volatile "services/mobile/pin_left" "3"
        fi
    elif [ "${out}" = "+CPIN: SIM PUK" ]; then
        local puk="`/usr/bin/sys_params services/mobile/puk 2>/dev/null`"
        local pin="`/usr/bin/sys_params services/mobile/pin 2>/dev/null`"
        if [ "${puk}" = "" ]; then 
            notify_error ${ERR_PUKREQUIRED}
        elif [ "${pin}" = "" ]; then
            notify_error ${ERR_NEWPINREQUIRED}
        else
            # Note: quotes are necessary in this case
            send_expect "${dev}" "AT+CPIN=\"${puk}\",\"${pin}\"" "^(\+QIND: PB DONE|OK)"
            # never remember PUK to avoid auto lock out
            if [ $? -ne 0 ]; then
                /usr/bin/sys_params -w services/mobile/puk ""
                /usr/bin/sys_params -w services/mobile/pin ""
                # decrease counter avoiding extra read
                epad_set_volatile "services/mobile/puk_left" "$((puk_left-1))"
                notify_error ${ERR_PUKERROR}
            fi
            # reset counters avoiding extra read
            epad_set_volatile "services/mobile/pin_left" "3"
            epad_set_volatile "services/mobile/puk_left" "10"
            /usr/bin/sys_params -w services/mobile/puk ""
        fi
    fi

    # give some time to obtain network info
    sleep 5

    # CIND - currently unused
    # wrap_send send_expect "${dev}" "AT+CIND=?" "^\+CIND:"
    # +CIND: ("call",(0,1)),("roam",(0,1)),("signal",(0-5)),("service",(0,1)),("GPRS coverage",(0,1))
    #out=$(wrap_send send_expect "${dev}" "AT+CIND?" "^\+CIND:")

    out=$(wrap_send send_expect "${dev}" "AT+CSQ" "^\+CSQ:")
    local rssi="$(echo ${out} | cut -s -d ' ' -f 2 | cut -s -d ',' -f 1)"
    if [ "${rssi}" != "99" ]; then  # 99 = error
        local signal="$(rssiToSignal ${rssi})"
        epad_set_volatile "services/mobile/info/signal" "${signal}"
    fi

    out=$(wrap_send send_expect "${dev}" "AT+COPS?" "^\+COPS:")
    local operator="$(echo ${out} | cut -s -d, -f 3 | tr -d \")"
    epad_set_volatile "services/mobile/info/operator" "${operator}"
    # 0   GSM                               2G
    # 2   UTRAN                             2G
    # 3   GSM W/EGPRS                       2G
    # 4   UTRAN W/HSDPA                     3G
    # 5   UTRAN W/HSUPA                     3G
    # 6   UTRAN W/HSDPA and HSUPA           3G
    # 7   E-UTRAN                           LTE
    local tech="$(echo ${out} | cut -s -d, -f 4)"
    epad_set_volatile "services/mobile/info/tech" "${tech}"

    # Enable services registration with location information
    wrap_send send_expect "${dev}" "AT+CREG=2" "^OK" > /dev/null

    # Technology selection, or 0 (AUTO)
    wrap_send send_expect "${dev}" "AT+QCFG=\"nwscanmode\",${MODEM_TECH:-0}" "^OK" > /dev/null

    # <n>,<stat>[,<lac>,<ci>[,<Act>]]
    out=$(wrap_send send_expect "${dev}" "AT+CREG?" "^\+CREG:")
    # 0 Not registered, ME is not currently searching a new operator to register to
    # 1 Registered, home network
    # 2 Not registered, but ME is currently searching a new operator to register to 
    # 3 Registration denied
    # 4 Unknown
    # 5 Registered, roaming
    local regStat="$(echo ${out} | cut -s -d, -f 2)"
    epad_set_volatile "services/mobile/info/reg/stat" "${regStat}"

    if [ "${regStat}" = "5" ]; then
        log "Roaming enabled"
        local roamingEnabled="$(/usr/bin/sys_params -r services/mobile/roamingEnabled)"
        [ "${roamingEnabled}" = "false" ] && notify_error ${ERR_ROAMINGBLOCKED}
    fi

    if [ "${gpsauto}" != "0" ] && [ "${GPSAUTO}" = "true" ]; then
        local gpsport=$(cmd_gps)
        if [ ! -e "${gpsport}" ]; then
            log "Invalid GPS port ${gpsport}"
        else
            gps_program
            log "Launching gps read app"
            ${GPSREAD_COMMAND}
        fi
    fi

    if [ ${FORCECOM} -eq 1 ] || [ ${vers} -eq 9 ]; then
        info "Setting speed to ${MODEM_BAUD}"
        cmd_send $1 "AT+IPR=${MODEM_BAUD}"

        # once baud rate is raised, we can't use serial to detect status
        # modem resets and takes a while to become operational
        sleep 2
    fi

    close_reader

    info "Modem configured"
}

# $1: plugin device
cmd_on()
{
    local dev=${PLCM_DEV}
    local vers=${PLCM_REL}
    local name=""
    case ${vers} in
        9) name="PLCM09"
           ;;
        10 | 11)
           name="PLCM${vers}"
           ;;
        8)
           name="PLCM10B"
           ;;
        *)
           info "Modem not available on this version"
           return 0
           ;;
    esac

    case ${vers} in
        9)
            info "Resetting ${name} module"
            cmd_off 0

            setusbversion 0
            info "Powering on PLCM09 at ${dev}"
            echo 1 > ${dev}/plcm09_power
            [ $? -ne 0 ] && return 1
            sleep 1
            ;;
        10 | 11 | 8) 

            # BSP-3268 PLCM module may show inconsistent status
            if [ ${FORCECOM} -eq 0 ]; then
                state="$(cat ${dev}/plcmxx_status 2>/dev/null)"
                devs="$(ls /dev/usbmodem* 2>/dev/null)"
                if [ "${state}" = "0" -a "${devs}" != "" ]; then
                    info "Inconsistent status 1 detected!"
                    info "Resetting ${name} at ${dev}"
                    echo 1 > ${dev}/plcmxx_reset
                elif [ "${state}" = "1" -a "${devs}" = "" ]; then
                    info "Inconsistent status 2 detected!"
                    info "Powering on ${name} at ${dev} with inverted status.."
                    echo 0 > ${dev}/plcmxx_power
                else
                    info "Powering on ${name} at USB ${dev}"
                    cmd_off
                    echo 1 > ${dev}/plcmxx_power
                fi
            else
                info "Powering on ${name} at COM ${dev}"
                cmd_off
                echo 1 > ${dev}/plcmxx_power
            fi
            [ $? -ne 0 ] && return 1

            sleep 2
            local attempts=0
            log "Waiting power on"
            SECONDS=0  # internally incremented by Bash

            while [ "`cat ${dev}/plcmxx_status 2>/dev/null`" = "0" ]; do
                   if [ $attempts -gt 4 ]; then
                        if [ ${FORCECOM} -eq 0 ]; then
                                # BSP-3268 USB interface may still be working with inconsistent state..
                                log "Too many attempts - giving up and continuing"
                                break
                        fi
                        err "Too many attempts - modem not enabled"
                        return 1
                   fi
                   sleep 3
                   attempts=$((attempts+1));
                   log "[${SECONDS}s] Attempt power on ${attempts}"
            done
            log "[${SECONDS}s] Turned on"
            SECONDS=0  # internally incremented by Bash
            attempts=0
            local usb=""
            log "Attempt to find comunication port usbmodem"
            if [ ${FORCECOM} -ne 1 ]; then
                while [ ! -e "`find /dev/usbmodem* 2> /dev/null| head -n 1`" ] && [ $attempts -lt 24 ]; do
                     sleep 1;
                     log "[${SECONDS}s] Searching device usbmodem attempt ${attempts}"
                     attempts=$((attempts+1));
                done
                usb=$(cmd_com_atusb)
            fi
            if [ "${usb}" != "" ] && [ -e $usb ]; then
                log "[${SECONDS}s] ${usb} found!"
                log "Set stty"
                setusbversion 1
                stty -echo -F ${usb}
                return 0
            fi
            setusbversion 0
            if [ "${FORCECOM}" = "1" ]; then
                usb=$(cmd_com_atcom)
                log "Attempt to find comunication port ${usb}"
                sleep 1
                if [ -e ${usb} ]; then
                    log "[${SECONDS}s] ${usb} found!!"
                    log "Setting speed ${usb} to 115200"
                    stty -F ${usb} 115200
                    return 0
                else
                    log "No COM port found"
                    return 1
                fi
            fi
            info "No USB port found"
            return 1
            ;; 
        *)
            info "Invalid PLCM version"
            return 1
            ;;
    esac
    return 0
}

# $1: off value (should be 0 or 1 if in BSP-3268 inconsistent state..)
cmd_off()
{
    local dev=${PLCM_DEV}
    local vers=${PLCM_REL}
    local val=$1
    [ -z $val ] && val=0
    case ${vers} in
        9) 
         info "Powering off PLCM09 at ${dev}"
         echo ${val} > ${dev}/plcm09_power
         [ $? -ne 0 ] && return 1
         sleep 2
         ;;
       10 | 11 | 12 | 8) 
         info "Powering off $(plcname ${vers}) at ${dev}"
         echo ${val} > ${dev}/plcmxx_power
         [ $? -ne 0 ] && return 1
         sleep 2
         for ((i=0; i < 10; i++)); do
             [ $(cat ${dev}/plcmxx_status) -eq ${val} ] && return 0
             sleep 2
         done
         info "Power off failed"
         return 1
         ;;
        *)  
         info "Invalid PLCM version"
         return 1
         ;;
    esac
    return 0
}

cmd_start()
{
    local state
    local devs

    [ -e "${MODEM_STATE}" ] && info "Already running" && exit 0

    echo "starting" > "${MODEM_STATE}"
    MODEM_STARTING=1
    echo $$ > "${MODEM_PID}"

    # This check has been pulled out so service can be controlled even without
    # service being enabled (no autostart)
    #if [ "$(/usr/bin/sys_params services/mobile/autostart)" != "true" ]; then
    #    info "Mobile autostart not enabled in System Parameters"
    #    return 0
    #fi

    epad_reset
    epad_set_volatile "services/mobile/info/signal" ""

    local dev=${PLCM_DEV}
    if [ -z $dev ]; then
        info "No PLCM09/PLCMxx present"
        return 0
    fi

    local vers=${PLCM_REL}

    log "Found PCLM on device: ${dev}"

    cmd_on ${dev}
    [ $? -eq 0 ] || notify_error ${ERR_MODEMCOMM}

    local com=$(cmd_com_at)
    [ -e ${com} ] || notify_error ${ERR_MODEMCOMM}

    if [ "$(lsof $dev 2>/dev/null)" != "" -o "$(fuser $dev 2>/dev/null)" != "" ]; then
        notify_error ${ERR_MODEMBUSY}
    fi

    cmd_config ${com} ${vers}

    if [ "${USEQMI}" = "1" ]; then
       info "Starting QMI"
       /usr/bin/quectel-CM &
    else
       info "Starting PPP"
       /etc/init.d/ppp start
    fi
#Used by cmd_rssi
    echo "running" > "${MODEM_STATE}"
}

cmd_stop()
{
    local state
    local devs

    [ ! -e "${MODEM_STATE}" ] && info "Already stopped" && exit 0

    echo "stopping" > "${MODEM_STATE}"

    local dev=${PLCM_DEV}

    # Reset all mobile volatile info and state
    epad_set_volatile "services/mobile/info/signal" ""
    epad_set_volatile "services/mobile/info/operator" ""
    epad_set_volatile "services/mobile/info/tech" ""
    epad_set_volatile "services/mobile/info/reg/stat" ""
    epad_set_volatile "services/mobile/state" "0"  # idle
    epad_set_volatile "services/mobile/error" ""

    if [ "${USEQMI}" = "1" ]; then
      info "Stopping QMI"
      pkill quectel-CM 2>/dev/null
    else
      info "Stopping PPP"
      /etc/init.d/ppp stop
    fi
    sleep 1

    # Reset gps volatile (after cmd_off to prevent gpsread.sh to write new data)
    local gpsenabled=${GPSENABLED}
    if [ "${gpsenabled}" == "1" ] || [ "${gpsenabled}" == "2" ]; then
        log "Stopping GPS"
        /usr/bin/gpsread stop
        sleep 1
        epad_set_volatile "services/mobile/info/gps" ""
    fi

    # BSP-3268 PLCM module may show inconsistent status
    if [ ${FORCECOM} -eq 0 ]; then
        state="$(cat ${dev}/plcmxx_status 2>/dev/null)"
        devs="$(ls /dev/usbmodem* 2>/dev/null)"
        if [ "${state}" = "0" -a "${devs}" != "" ]; then
            info "Inconsistent status 1 detected - powering off=1!"
            cmd_off 1
        elif [ "${state}" = "1" -a "${devs}" = "" ]; then
            info "Inconsistent status 2 detected - do nothing"
        else
            cmd_off 0
        fi
    else
        cmd_off 0
    fi

    rm -f "${MODEM_STATE}"
}

cmd_restart()
{
    cmd_stop
    cmd_start
}

# Used by EPAD to update state
# Returns connection state and prints:
# <state>;<rx/tx>;<rxe/txe>
cmd_status()
{
    # BSP-3306 Only mark connected once IP is configured because auth failure may result in up without IP
    if ip a s ppp0 2>/dev/null | grep -q inet; then
        # update stats
        local rx="$(cat /sys/class/net/${MODEM_IFACE}/statistics/rx_bytes)"
        local tx="$(cat /sys/class/net/${MODEM_IFACE}/statistics/tx_bytes)"
        if [ -e /proc/tty/driver/OMAP-SERIAL ]; then
            local sinfo="$(cat /proc/tty/driver/OMAP-SERIAL)"
        elif [ -e /proc/tty/driver/IMX-uart ]; then
            local sinfo="$(cat /proc/tty/driver/IMX-uart)"
        fi
        local oe="$(echo "${sinfo}" | grep ^$(cmd_comidx): | grep -o 'oe:[^ ]*' | cut -d ':' -f 2)"
        echo "connected;${rx}/${tx};${oe}"
        return 3
    fi
    [ ! -e "${MODEM_STATE}" ] && echo "idle" && return 0
    [ -e "${MODEM_PID}" ] && echo "starting" && return 1
    pidof pppd >/dev/null && echo "running" && return 2
    pidof quectel-CM >/dev/null && echo "running" && return 2
    [ "$(cat ${MODEM_STATE} 2>/dev/null)" = "stopping" ] && return 10
    echo "error" && return 99
}

## Main
trap cleanup INT TERM EXIT
#log ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Modem $@"
while getopts ":hvcq" opt; do
      case ${opt} in
        h)
            cmd_help
            exit 0
            ;;
        v)
            VERBOSE=1
            ;;
        c)
            FORCECOM=1
            log "COM port forced"
            ;;
        q)  
            USEQMI=1
            log "Use QMI protocol"
            ;;
        *)
            err "Invalid option: ${opt}!"
            exit 1
            ;;
      esac
done
shift $((OPTIND-1))

if [ -z $@ ]; then
    err "Missing command!"
    cmd_help
    exit 1
fi

for arg in $@; do
    type cmd_$arg &>/dev/null || errdie "Invalid command ${arg}" ${ERR_GENERICERROR}
done

if [ ! -f "${TMPFILE}" ]; then
    PLCM_DEV=$(cmd_dev)
    errloc=$?
    if [ $errloc -ne 0 ]; then
        log "PLCM Hardware not found"
        exit $errloc
    fi
    log "Hardware found ${PLCM_DEV}"

    PLCM_REL=$(cmd_plcrel)
    errloc=$?
    [ $errloc -ne 0 ] && errdie "PLCM Invalid version" $errloc
    USBHUB=$(lsusb 2> /dev/null | grep -c "0424:2513")
    echo "$USBHUB,$PLCM_DEV,$PLCM_REL" > "${TMPFILE}"
  else
    TMPVAR=$(cat "${TMPFILE}")
    TMPVARAR=(${TMPVAR//,/ })
    USBHUB=${TMPVARAR[0]}
    PLCM_DEV=${TMPVARAR[1]}
    PLCM_REL=${TMPVARAR[2]}
    log "Hardware found ${PLCM_DEV}"
fi
log "$(plcname ${PLCM_REL}) found"

if [ ${USBHUB} -eq 0 ]; then
  FORCECOM=1
fi

if [ ${FORCECOM} -eq 1 ]; then
  log "$(plcname ${PLCM_REL}) Using com port"
fi

for arg in $@; do
    cmd_$arg
    exit $?
done

exit 0

# vi: et sw=4 ts=4
