#!/bin/sh
# helper functions sourced from other scripts

: "${GPIO:=/usr/local/bin/telem/gpio.sh}"
: "${rev:=$(cat /tmp/telem/board/rev)}"

MODEM_TTY='/dev/modem_tty'
MODEM_TTY_CMD='/dev/modem_tty_cmd'
sim1conf='/etc/ppp/peers/sim1.conf'
sim2conf='/etc/ppp/peers/sim2.conf'

modemNotInstalled() {
    grep -qF -- "no modem" /var/local/telem/ordercode/modem_type
}

modemInstalled() {
    ! modemNotInstalled
}

modemConfigured() {
    [ -e "${sim1conf}" ] || [ -e "${sim2conf}" ]
}

modem_is_alive(){
    local USB_MODEM="nothing"
    [ -f /var/local/telem/usb-modem-vidpid ] && . /var/local/telem/usb-modem-vidpid
    lsusb | grep -q -- "$USB_MODEM"
}

modem_tty_cmd_in_use() {
    fuser -s "${MODEM_TTY_CMD}" 2>/dev/null
}

modem_tty_cmd_wait() {
    local count=0
    while [ $((count++)) -lt 7 ] && modem_tty_cmd_in_use; do sleep 1; done
}

modem_tty_cmd_ok() {
    # If symlink does not exist or symlink is broken then it will fail
    [ -e "${MODEM_TTY_CMD}" ]
}

call_socat() {
    # $1: AT command
    modem_tty_cmd_wait
    echo "$1" | socat "${MODEM_TTY_CMD}",creat=0,raw,echo=0,crlf -
}

call_chat() {
    # $1: chatfile
    modem_tty_cmd_wait
    socat "${MODEM_TTY_CMD}",creat=0,raw,echo=0,crlf exec:"chat -V -f ${1}",pty,echo=0,cr 2>&1
}

modem_test_at() {
    # test if AT command works
    modem_tty_cmd_wait
    2> /dev/null chat -t1 '' 'AT' 'OK' < "${MODEM_TTY_CMD}" > "${MODEM_TTY_CMD}"
}

telit_modem_rev4_turnOFF(){
    # Do not call outside this file.
    if $GPIO --is-on MODEM_PWR_MON; then
        # MODEM_PWR is inverted
        $GPIO -s MODEM_PWR; sleep 4; $GPIO -c MODEM_PWR; sleep 1;
        # wait for modem to shut down
        i="0"
        while $GPIO --is-on MODEM_PWR_MON && [ "$i" -lt "15" ]; do
            i=$((i+1))
            sleep 1
        done
        # HW SHUTDOWN Unconditional: if modem is still on
        $GPIO --is-on MODEM_PWR_MON && $GPIO -t RST_MODEM
    fi
}

telit_modem_rev4_reset(){
    # Do not call outside this file.
    # From HE910 Hardware User Guide
    # MODEM_PWR = ON_OFF pin
    # RST_MODEM = HW_SHUTDOWN pin ?
    modprobe cdc-acm
    # turn off modem if it is on
    telit_modem_rev4_turnOFF
    # turn back on
    if $GPIO --is-off MODEM_PWR_MON; then
        i="0"
        while $GPIO --is-off MODEM_PWR_MON && [ "$i" -lt "2" ]; do
            i=$((i+1))
            $GPIO -s MODEM_PWR; sleep 6; $GPIO -c MODEM_PWR
            sleep 1
            # HW SHUTDOWN Unconditional: if modem is still off
            $GPIO --is-off MODEM_PWR_MON && $GPIO -t RST_MODEM
        done
        # Start AT CMD part not needed here
    fi
    $GPIO -s MODEM_USB_PWR
}

huawei_modem_reset(){
    modprobe option

    if modem_is_alive; then
        # Modem is alive, lets try reset pin.

        # HUAWEI Reset pulse timing:
        # * 50-100ms
        # RESIN_N pin must not be pulled down for more than 1s.
        $GPIO -s RST_MODEM
        usleep 55000 # sleep 55ms
        $GPIO -c RST_MODEM
    else
        # modem is not alive, lets try to reset power

        # HUAWEI Power on timing sequence:
        # MU609: 3-5s
        # MU709: 7s
        # ME909: 14s
        # Do not toggle RESIN_N pin during the power on sequence.
        # Pulling RESIN_N pin low will extend time for module startup.
        if $GPIO --is-on MODEM_PWR; then
            # Modem is not really turning off?
            # Wait for capacitor to discharge?
            # TESTED: It is a hardware issue
            #   gpio.sh -c MODEM_PWR; sleep 1; gpio.sh -s MODEM_PWR
            #   command works without long sleep if power pin has a resistor to pull down power faster.
            $GPIO -c MODEM_PWR
            logg "Will sleep for 45 seconds now"
            sleep 25 # additional 20 seconds outside this if
        fi
        # keep it off for 20s, because we dont know if it has been off long enough.
        sleep 20
        $GPIO -s MODEM_PWR
        sleep 14
    fi
}

quectel_modem_reset(){
    modprobe option

    if modem_is_alive; then
        # Modem is alive, lets try reset pin.

        # QUECTEL Reset pulse timing:
        # * 150-460ms
        $GPIO -s RST_MODEM
        usleep 230000 # sleep 230ms
        $GPIO -c RST_MODEM
        sleep 1
    else
        # modem is not alive, lets try to reset power

        # There is no clear information for module power on timings!
        # Each manual gives different information!
        # Fig 12 of "Quectel_EC21_Hardware_Design_V1.4.pdf"
        # QUECTEL Power on timing sequence:
        # EC21: 13s+ for usb
        # Before turning on RESET_N (PERST#) must be high?
        if $GPIO --is-on MODEM_PWR; then
            # Fig 13 of "Quectel_EC21_Hardware_Design_V1.4.pdf"
            # Power down procedure lasts 30s+
            # There is no access to PWRKEY on PCIe module?
            # TODO use AT comand
            $GPIO -c MODEM_PWR
            logg "Will sleep for 45 seconds now"
            sleep 25 # additional 20 seconds outside this if
        fi
        # keep it off for 20s, because we dont know if it has been off long enough.
        sleep 20
        $GPIO -c RST_MODEM
        $GPIO -s MODEM_PWR
        sleep 15
    fi
}

mikrotik_modem_reset() {
    # May be incorrect, no access to manual
    modprobe cdc-acm
    modprobe cdc-ether
    
    if modem_is_alive; then
        $GPIO -s RST_MODEM
        sleep 1
        $GPIO -c RST_MODEM
        sleep 1
    else
        if $GPIO --is-on MODEM_PWR; then
            $GPIO -c MODEM_PWR
            logg "Will sleep for 25 seconds now"
            sleep 25
        fi
        $GPIO -c RST_MODEM
        $GPIO -s MODEM_PWR
        sleep 15
    fi
}

usingPCI_module(){
    # Telem-GWM rev8 (two prototypes) is first board with PCI module
    # Telem-GWM rev8 is excluded for simplicity
    test "$rev" -ge "9" && return
    # All SLC, AGW, SFP boards
    grep -q -- 'SLC\|AGW\|SFP' '/proc/device-tree/info/base-board' 2>/dev/null && return

    # Old board with onboard Telit modem
    false
}

canSwitchSIM(){
    # MODEM_SIM_SELECT available from GWM rev4
    $GPIO -a MODEM_SIM_SELECT &>/dev/null
}

selectSIM1(){
    $GPIO -c MODEM_SIM_SELECT &>/dev/null
}

selectSIM2(){
    $GPIO -s MODEM_SIM_SELECT &>/dev/null
}

selectedSIM1(){
    $GPIO --is-off MODEM_SIM_SELECT &>/dev/null
}

selectedSIM2(){
    $GPIO --is-on MODEM_SIM_SELECT &>/dev/null
}

insertedSIM1(){
    # MODEM_SIM1 & MODEM_SIM2 available from GWM rev 8
    $GPIO -a MODEM_SIM1 &>/dev/null || return 0
    $GPIO --is-on MODEM_SIM1
}

insertedSIM2(){
    # MODEM_SIM1 & MODEM_SIM2 available from GWM rev 8
    $GPIO -a MODEM_SIM2 &>/dev/null || return 0
    $GPIO --is-on MODEM_SIM2
}

modem_lock() {
    resetLockFile="/tmp/modem_reset_2min_lock"
    touch "${resetLockFile}"
}

modem_reset_hw(){
    # Do not call outside this file.
    if usingPCI_module; then
        # TODO FIX?: enabling MODEM_PWR with 'gpio.sh -e' will set this gpio to '0' (OFF)
        $GPIO -e MODEM_PWR
        $GPIO -e RST_MODEM
        if $GPIO --is-on MODEM_PWR; then
            # do modem specific reset
            local USB_MODEM="nothing"
            [ -f /var/local/telem/usb-modem-vidpid ] && . /var/local/telem/usb-modem-vidpid
            case "$USB_MODEM" in
                12d1*)
                    huawei_modem_reset
                    ;;
                2c7c*)
                    quectel_modem_reset
                    ;;
                1e0e*)
                    quectel_modem_reset
                    ;;
                2cd2*)
                    mikrotik_modem_reset
                    ;;
                *)
                    huawei_modem_reset
                    ;;
            esac
        else
            # just turn on
            $GPIO -c RST_MODEM
            $GPIO -s MODEM_PWR
            sleep 7 # extra sleep
        fi
        sleep 7
    else
        $GPIO -e MODEM_USB_PWR
        $GPIO -e MODEM_PWR_MON
        $GPIO -e MODEM_PWR
        $GPIO -e RST_MODEM

        $GPIO -c MODEM_USB_PWR
        telit_modem_rev4_reset
        $GPIO -s MODEM_USB_PWR
    fi
}

modem_reset_at() {
    modem_tty_cmd_wait
    2> /dev/null chat -t2 '' 'at+cfun=1,1' 'OK' < "${MODEM_TTY_CMD}" > "${MODEM_TTY_CMD}"
}

modem_reset_locked_hw() {
    # find -mmin -0.2 will always be true if file epoch is < 12 seconds (current workaround: S01hwclock should set clock at lest to year 2000)
    # -mmin +0 avoids files whose date is in the future or close to epoch 0
    resetLockFile="/tmp/modem_reset_2min_lock"
    [ "$(find "${resetLockFile}" -mmin -1.5 -mmin +0 2>/dev/null)" == "${resetLockFile}" ]
}

modem_reset_locked_at() {
    # find -mmin -0.2 will always be true if file epoch is < 12 seconds (current workaround: S01hwclock should set clock at lest to year 2000)
    # -mmin +0 avoids files whose date is in the future or close to epoch 0
    resetLockFile="/tmp/modem_reset_2min_lock"
    [ "$(find "${resetLockFile}" -mmin -0.3 -mmin +0 2>/dev/null)" == "${resetLockFile}" ]
}

modem_reset_locked() {
    resetType="${1:-hw}"
    modem_reset_locked_"${resetType}"
}

# You have to wait for modem at least +10 seconds after modem device appears or chat script will fail
wait_for_modem() {
    waited=false
    modem_reset_locked && sleep 1 || return 0
    # wait until modem reset is enabled or device appears
    while modem_reset_locked && [ ! -e "${MODEM_TTY_CMD}" ]; do sleep 2; waited=true; done
    [ ! -e "${MODEM_TTY_CMD}" ] && return
    # Wait some more, if device was just created
    $waited && modem_reset_locked && sleep 1
    # Device may not still be ready
    # Test until AT command works
    while modem_reset_locked && ! modem_test_at; do sleep 2; waited=true; done
    ! $waited
}

modem_reset() {
    resetType="${1:-hw}"
    modem_lock
    modem_reset_"${resetType}"
}

# there are at least three scripts where modem reseting is initiated
# so it would be good idea to use modem_reset_lock
modem_reset_lock()
{
    resetType="${1:-hw}"
    if modem_reset_locked_"${resetType}"; then
        # just wait
        logg "Can't reset modem, it is already being reset (${resetType})"
        sleep 1
    else
        modem_lock
        logg "Resetting modem (${resetType})"
        modem_reset_"${resetType}"        
    fi
    wait_for_modem
}

modem_turnOFF()
{
    if usingPCI_module; then
        $GPIO -e MODEM_PWR; $GPIO -c MODEM_PWR
        # TODO: turn off with AT command
    else
        $GPIO -e MODEM_USB_PWR
        $GPIO -e MODEM_PWR_MON
        $GPIO -e MODEM_PWR
        $GPIO -e RST_MODEM

        $GPIO -c MODEM_USB_PWR
        telit_modem_rev4_turnOFF
    fi
}

# Service monitoring functions

modemServiceOK() {
    # Should be Called every time a service script is starting new loop (modemst.sh & StartPPP)
    local serviceStamp='/tmp/.modem_service_running'
    touch "${serviceStamp}"
}

isModemServiceRunning() {
    local serviceStamp='/tmp/.modem_service_running'
    # Bad after 5 minutes
    [ "$(find "${serviceStamp}" -mmin -5 -mmin +0 2>/dev/null)" == "${serviceStamp}" ]
}

defaultRebootTimeout() {
    BASE_SLC=false
    grep -Fq -- SLC '/sys/firmware/devicetree/base/info/base-board' 2>/dev/null && BASE_SLC=true

    ${BASE_SLC} && echo "60" || echo "2"
}

# Reboot timeout for modem issues
isAllowedToReboot() {
    # $1: Desired timeout in minutes
    local upSeconds="$(grep -o -- '^[0-9]\+' /proc/uptime)"
    local upMins=$((${upSeconds} / 60))
    [ "${upMins}" -gt "${1:-$(defaultRebootTimeout)}" ]
}
