#!/usr/bin/python
################################################################################
# Copyright 2025 ModalAI Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# 4. The Software is used solely in conjunction with devices provided by
#    ModalAI Inc.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
################################################################################

#==============================================================================
# Description:
#   Contains basic commands to configure the WWAN modems.
#
#   Supported Modems:
#       1. feather WNC
#       2. avnet/WNC usb module
#
#==============================================================================

import os
import sys
import time
import subprocess
import signal

import getopt
import json

#==============================================================================
# Constants
#==============================================================================

FNULL = open(os.devnull, 'w')  # route unwanted console ouput

# Note on terminology: "Feather" is a term used to describe a Voxl
# add-on board such as the modem card. Most feathers have an ID
# that can be read to determine the card type

FEATHER_DETECT_MODEM_NONE = 'None'

WNC_FEATHER_NONE_ID     = '0101'  # default when no feather attached
WNC_FEATHER_ID          = '0100'
WNC_FEATHER_WORLD_ID    = '0110'

## If the WNC modem is disabled, any low (0) to high(1) signal will
#  enable the chip causing lsusb to detect it thus triggering the
#  usb drivers to install.
#
WNC_ENABLE_MIN_DELAY_IN_SECONDS = 1

## If the WNC modem is in the enabled state, then a 3 second active low
#  will trigger the disable state
#
WNC_DISABLE_MIN_DELAY_IN_SECONDS = 2

# Whenever the WNC module changes state from enabled/disabled it takes a
# few seconds for the transition to complete.  Note: device driver unloading
# will take several more seconds
#
WNC_MODULE_MIN_DELAY_TO_DETECT_STATE_TRANSITION_IN_SECS = 3

## To hard reset the module, pull RESET for MIN delay
#
WNC_RESET_MIN_DELAY_IN_SECONDS = 3
WNC_RESET_MAX_DELAY_IN_SECONDS = 8
WNC_RESET_HOLD_ZERO_DELAY_IN_SECONDS = 5

# Since there is no easy way to determine the state of the WNC
# module, we have to monitor the state of the assocaited
# USB device that will be available when the module is ENABLED
MONITOR_DEVICE_DEFAULT = "/dev/ttyUSB0"

#------------------------------------------------
# MODEM CARD GPIO DEFINITIONS
#-------------------------------------------------
# GPIO106 WWAN_POWER_ON_N (APQ->MODEM)
# GPIO107 WWAN_RESET_N (APQ->MODEM)
# GPIO108 GPIO SPARE NOT CURRENTLY USED
# GPIO70  WWAN_WAKEUP_IN (APQ->MODEM)
# GPIO71  WWAN_WAKEUP_OUT (APQ<-MODEM)
# GPIO72  WWAN_STATE (APQ<-MODEM)
# GPIO59 (BLSP11_2_RX) WWAN_UART_TX (APQ<-MODEM) Should config as UART
# GPIO58 (BLSP11_3_TX) WWAN_UART_RX (APQ->MODEM) Should config as UART
# GPIO114 BOARD_ID4 (MODEM BOARD --> APQ)  // MSB
# GPIO104 BOARD_ID3 (MODEM BOARD --> APQ)
# GPIO103 BOARD_ID2 (MODEM BOARD --> APQ)
# GPIO102 BOARD_ID1 (MODEM BOARD --> APQ)  // LSB

#------------------------------------------------
# OLD CONFIG WNC_FEATHER_QC
#------------------------------------------------
FEATHER_WNC_QC_GPIO_NUMBERS = [72, 102, 103, 104, 106, 107, 108, 114]
FEATHER_WNC_QC_GPIO_DIRECTIONS = ['in', 'in', 'in', 'in', 'out', 'out', 'out', 'in']
FEATHER_WNC_QC_MODEM_POWER_PINS = [106, 107, 108]
FEAHTER_WNC_QC_FEATHER_ID_PINS = [114, 104, 102, 103]

#------------------------------------------------
# CONFIG as of WNC_FEATHER_WORLD_ID
#------------------------------------------------
GPIO_PIN_POWER_ON_N         = 106
GPIO_PIN_WWAN_STATE         = 72
GPIO_PIN_WWAN_WAKEUP_IN     = 70
GPIO_PIN_WWAN_POWER_ON_N    = 106
GPIO_PIN_WWAN_RESET_N       = 107

GPIO_NUMBERS = [70, 71, 72, 102, 103, 104, 106, 107, 108, 114]
GPIO_DIRECTIONS = ['out', 'in', 'in', 'in', 'in', 'in', 'out', 'out', 'out', 'in']
MODEM_POWER_PINS = [70, 106, 107]
FEATHER_ID_PINS = [114, 104, 103, 102]

#------------------------------------------------
# MICROHARD CONFIG
#------------------------------------------------
MH_GPIO_NUMBERS = [70, 71, 72, 92, 102, 103, 104, 106, 107, 108, 114]
MH_GPIO_DIRECTIONS = ['out', 'in', 'in', 'out', 'in', 'in', 'in', 'out', 'out', 'out', 'in']
MH_MODEM_POWER_PINS = [70, 106, 107]
MH_FEATHER_ID_PINS = [114, 104, 103, 102]

# Set default global confguration
#-----------------------------------------------
class ConfigData:
    serial_port         = MONITOR_DEVICE_DEFAULT
    qmi_dev             = '/dev/qcqmi0'
    network_interface   = 'rmnet_usb0'
    at_cmd_dev          = '/dev/ttyACM0'
    uqmi_bin            = 'uqmi'
    disable_band_5      = False
    band_3_only         = False
    is_modem_on_usb     = False


_config = ConfigData()

#==============================================================================
# Functions
#==============================================================================

#------------------------------------------------------------------------------
# GPIO API
#------------------------------------------------------------------------------

## All GPIO pins need to be exported before they can be used
#
def gpio_export(num):
    subprocess.call("echo " + num + " > /sys/class/gpio/export",
                    stderr=FNULL, shell=True)

## Each GPIO has to have its direction set prior to use
# @param num  pin number
# @param direction 'in' or 'out'
#
def gpio_direction_map(num, direction):
    subprocess.call("echo " + direction + " > /sys/class/gpio/gpio" \
                    + num + "/direction",
                    stderr=FNULL, shell=True)

## Set a value to an 'out' gpio
#
def set_gpio(num, value):
    subprocess.call("echo " + value + " > /sys/class/gpio/gpio" + num + "/value",
                    stderr=FNULL, shell=True)

##  Reads a GPIO pin
#   @apram pin
#       pin number
#   @return
#       integer value [0,1]
def gpio_read_int(pin_int):
    gpio_file = '/sys/class/gpio/gpio' + str(pin_int) + '/value'
    value = subprocess.check_output(['cat', gpio_file]).strip()
    return int(value)

#------------------------------------------------------------------------------
# FEATHER API
#------------------------------------------------------------------------------

def feather_reset():
    print 'Feather RESET start'
    IF_GPIO_IS_CONFIGURED_THIS_PATH_EXISTS = '/sys/class/gpio/gpio' + str(FEATHER_ID_PINS[0]) +'/value'

    if not os.path.exists(IF_GPIO_IS_CONFIGURED_THIS_PATH_EXISTS):
        feather_configure_gpio_pins()

    set_gpio(str(GPIO_PIN_WWAN_RESET_N ),   '1')  # should already be at 1, but just in case
    time.sleep( 1 )
    set_gpio(str(GPIO_PIN_WWAN_RESET_N), '0')
    time.sleep( WNC_RESET_HOLD_ZERO_DELAY_IN_SECONDS  )
    set_gpio(str(GPIO_PIN_WWAN_RESET_N), '1')
    print 'Feather RESET end'

## Configures the GPIO and sets the WWAN pins to a known state
#
def feather_configure_gpio_pins():
    print 'Configuring GPIO pins'
    for gpio_number in GPIO_NUMBERS:
        gpio_export(str(gpio_number))

    for gpio_number, gpio_direction in zip(GPIO_NUMBERS, GPIO_DIRECTIONS):
        gpio_direction_map(str(gpio_number), gpio_direction)

    print '    Setting defaults:'
    print '        GPIO_PIN_WWAN_WAKEUP_IN  = 1'
    print '        GPIO_PIN_WWAN_POWER_ON_N = 1'
    print '        GPIO_PIN_WWAN_RESET_N    = 1'

    set_gpio(str(GPIO_PIN_WWAN_WAKEUP_IN),  '1')
    set_gpio(str(GPIO_PIN_WWAN_POWER_ON_N), '1')
    set_gpio(str(GPIO_PIN_WWAN_RESET_N ),   '1')

## Reads the device id via AT command
#
#  Experimental, not for general use
def read_modem_device_id(device):

    print 'Reading modem device id'

    # try:
    #     modem_id = subprocess.check_output(["/usr/bin/docker", "run", "-it", \
    #                                         "--rm", "--privileged", "modem_id", \
    #                                         "python", "/hw_id.py"], stderr=FNULL)
    #
    # except subprocess.CalledProcessError:
    #     print '    Warning: got exception when checking modem device id'
    #     sys.exit(-1)
    # else:
    #     modem_id_lines = modem_id.splitlines()
    #     for line in modem_id_lines:
    #         if ('Manufacturer:' in line) or ('Model:' in line):
    #             print '\t' + line

    return ('WNC', 'M18Q2FG')

## Returns the ID of the feather board by reading GPIOs.
#
#   @param is_configure_gpio_if_needed
#       If this is True then feather_configure will be invoked to configure
#       GPIO pins if the GPIO pins have not been setup.
#
#   @return
#       String continaing ID
#
def feather_read_id(is_configure_gpio_if_needed = True ):
    feather_id = ''

    IF_GPIO_IS_CONFIGURED_THIS_PATH_EXISTS = '/sys/class/gpio/gpio' + str(FEATHER_ID_PINS[0]) +'/value'
    #
    if not os.path.exists(IF_GPIO_IS_CONFIGURED_THIS_PATH_EXISTS):
        feather_configure_gpio_pins()

    try:
        for pin in FEATHER_ID_PINS:
            id_fd = '/sys/class/gpio/gpio' + str(pin) + '/value'
            feather_id += subprocess.check_output(['cat', id_fd], stderr=FNULL).strip()
    except:
        print 'ERROR: Can not ready GPIOs'

    return feather_id


## Check the WWAN state signal from the modem
#   @param retry_count
#       The number of times attempt reading WWAN state pin for UP
#
#   @returns
#       - 1 when connected
#       - 0 when not connected
#
def feather_read_wwan_state_int(retry_count = 3):
    status = '0'
    while status == '0':
        cmd = 'cat /sys/class/gpio/gpio%d/value' % GPIO_PIN_WWAN_STATE
        #status = subprocess.check_output(['cat', '/sys/class/gpio/gpio72/value'])
        status = subprocess.check_output( cmd.split() )
        status = status.strip()
        # status == 1 when wwan is up
        if status == '0':
            retry_count -= 1
            if retry_count == 0:
                return 0
            time.sleep(1)
    return 1


## Returns true if the module is disabled
#  Unfortunately, we can only tell if the module is disabled if the
#  file device assigned to the driver is not loaded (e.g. /dev/ttyUSB0)
#   @param device
#       device used to determine if feather has been disabled
#   @return
#       - true feather is disabled
#       - false otherwise
#
def feather_is_disabled( device ):

    cmd = "ls %s" % device

    try:
        response = subprocess.check_output( cmd.split(), stderr=FNULL)
        response = response.rstrip()
        #print '[%s] == [%s]' % ( response, device )
        if response == device :
            print '    feather is ENABLED, device detected: %s' % device
            return False
        else:
            print '    command failed, device state unknown'
            return False

    except:
        # called when ls fails
        print '    feather is DISABLED'
        return True

    print '    feather is DISABLED'
    return True


## Whenever the WNC module changes state from enabled/disabled it takes a
#  few seconds for the transition to complete.  This function will start with
#  an initial delay followed by a polling period where the module will be
#  checked for a state change.
#
#  @param device (e.g. /dev/ttyUSB0)
#       Usb device monitored to determine if transition has completed since there is
#       no other way to discover the state of the WNC modem.
#
def feather_wait_for_transition_to_complete( device, is_to_disabled_transition=True ):

    # Now delay a bit for the module to transition to take affect
    #
    time.sleep(WNC_MODULE_MIN_DELAY_TO_DETECT_STATE_TRANSITION_IN_SECS)

    #
    # now we poll to make sure the module turns off
    # FYI: it will take a bit long for the USB device driver to unload
    #
    max_polling_iterations = 20
    sleep_time_per_iteration_secs = 2
    detected_transition = False
    #
    for i in range(max_polling_iterations) :
        #print 'polling_sleep_iterations:%d' % i
        #print '%r == %r' % (feather_is_disabled( device ), is_to_disabled_transition)
        if feather_is_disabled( device ) == is_to_disabled_transition:
            detected_transition = True
            break  # transition detected
        else:
            time.sleep(sleep_time_per_iteration_secs)

    if detected_transition == False:
        print 'WARNING: feather state transtion failed.'
        return False

    return True


## Disables the feather WNC module
#  Unloading of USB driver will still be several seconds afterwards
#
#  @param device (e.g. /dev/ttyUSB0)
#       Usb device monitored to determine if transition has completed since there is
#       no other way to discover the state of the WNC modem.
#
def feather_disable( device ):

    if feather_is_disabled( device ):
        # modem is already in disabled state, if we change state of POWER_ON_N it will enable
        return

    print 'DISABLE WNC feather module'
    set_gpio(str(106), '0')
    time.sleep( WNC_DISABLE_MIN_DELAY_IN_SECONDS )
    set_gpio(str(106), '1')

    # Now delay a bit for the module to transition
    #
    if feather_wait_for_transition_to_complete( device, is_to_disabled_transition = True ):
        return True

    return False


## Enables the feather WNC module
#  Loading of USB driver will still be several seconds afterwards
#  @param device (e.g. /dev/ttyUSB0)
#       Usb device monitored to determine if transition has completed since there is
#       no other way to discover the state of the WNC modem.
#  @param retry_count
#       During testing it was determined that the device doesnt come up at times.
#
def feather_enable( device, retry_count = 3 ):

    if feather_is_disabled( device ) == False:
        return  # already up


    for i in range(retry_count):
        print 'ENABLE WNC feather module'
        set_gpio(str(106), '0')
        time.sleep( WNC_ENABLE_MIN_DELAY_IN_SECONDS )
        set_gpio(str(106), '1')

        # Now delay a bit for the module to transition
        #
        if feather_wait_for_transition_to_complete( device, is_to_disabled_transition = False ):
            return True

        print '   enable failed, try again %d retries remaining' % i

    return False


## Toggles GPIO pins to reset feather modem
#  Power control is driven active low and based on how long the it
#  stays low, determines power UP or POWER DOWN
def feather_cycle_power( device=MONITOR_DEVICE_DEFAULT ):

    time.sleep(1)
    print 'Toggling the GPIOs to CYCLE POWER on the modem'

    # set GPIO to know state (1), module trigger on active low transitions
    # and hold times
    #

    feather_id = feather_read_id()
    if feather_id == WNC_FEATHER_WORLD_ID:
        # the blue modem
        #
        for gpio_number in MODEM_POWER_PINS:
            set_gpio(str(gpio_number), '1')

    elif feather_id == WNC_FEATHER_ID:
        # the original qc modems
        #
        for gpio_number in FEATHER_WNC_QC_MODEM_POWER_PINS:
            set_gpio(str(gpio_number), '1')

    else:
        print 'ERROR: operation not supported for this modem id:' + feather_id
        sys.exit(1)

    time.sleep(1)
    feather_disable( device )
    feather_enable( device )
    print '    Modem Should be Running'


## Configures modem GPIO to bring it up.
#
#  @param device (e.g. /dev/ttyUSB0)
#       Usb device monitored to determine if transition has completed since there is
#       no other way to discover the state of the WNC modem.
#
def feather_setup_gpio_and_activate_modem( device ):
    feather_configure_gpio_pins()
    feather_enable( device )


##
#   @returns a string containing the modems description
#
def feather_detect_modem( exitOnFailure = False ):
    feather = feather_read_id()
    if feather == WNC_FEATHER_ID:
        return 'WNC Legacy Modem (green)'
    elif feather == WNC_FEATHER_WORLD_ID:
        return 'WNC World Modem PSS/CSS (blue)'
    else:
        if exitOnFailure:
            print 'ERROR: The required modem was not detected.'
            sys.exit(1)
        return FEATHER_DETECT_MODEM_NONE
    
def dtc_configure():
    uname = os.uname()

    time.sleep(5)

    dmesg_output = subprocess.check_output(["dmesg"], shell=True)

    print("[INFO] Opening config file...")
    config_file = open("/etc/modalai/voxl-modem.conf")

    print("[INFO] Converting to json...")
    config_dict = json.load(config_file)

    print("[INFO] Reading DTC IP...")
    dtc_ip = config_dict["dtc_ip"]
  
    if "eth0" in dmesg_output:
        print '[INFO] Using eth0 for DTC'
        if "qrb5165" in uname or "qrb5165-rb5" in uname or "m0104" in uname or "m0054" in uname or "m0052" in uname:
            subprocess.call(["ip", "link", "set", "dev", "eth0", "up"])                                          
            subprocess.call(["ip", "addr", "flush", "dev", "eth0"])
            time.sleep(1)
            print("[INFO] Setting IP to:", dtc_ip) 
            subprocess.call(["ip", "addr", "add", dtc_ip + "/255.255.255.0", "dev", "eth0"])
            time.sleep(1)
            
        else:
            subprocess.call(["ifdown", "eth0"])                                                            
            subprocess.call(["ifup", "eth0"])

    else:
        print '[INFO] Using usb0 for DTC'
        while "usb0" not in dmesg_output:
            time.sleep(1)
            try:       
                dmesg_output = subprocess.check_output(["dmesg"], shell=True)
            except:
                print("waiting for usb0...")

        print '[INFO] usb0 detected, pulling up'
        if "qrb5165" in uname or "qrb5165-rb5" in uname or "m0104" in uname or "m0054" in uname or "m0052" in uname:

            print("[INFO] Waiting for usb0 interface to be available")
            while "usb0" not in subprocess.check_output(["ifconfig"], shell=True):
                time.sleep(1)

            print("[INFO] Setting IP to:", dtc_ip) 
            subprocess.call(["ifconfig", "usb0", dtc_ip])
        else:                                                            
            subprocess.call(["ifup", "usb0"])


## Configures Microhard Modem GPIO to bring it up.
#
def microhard_configure():

    uname = os.uname()

    if "apq8096" in uname:
        print 'Setting up GPIO pins for the Microhard Modem'
        for gpio_number in MH_GPIO_NUMBERS:
            gpio_export(str(gpio_number))

        for gpio_number, gpio_direction in zip(MH_GPIO_NUMBERS, MH_GPIO_DIRECTIONS):
            gpio_direction_map(str(gpio_number), gpio_direction)

        # SET DEFAULT GPIOS
        set_gpio(str(70), '0')
        set_gpio(str(106), '1')
        set_gpio(str(107), '1')
        set_gpio(str(92), '1')
    elif "qrb5165" in uname or "qrb5165-rb5" in uname or "m0104" in uname or "m0054" in uname or "m0052" in uname:
        print("qrb5165 detected")
    else:
        print("[ERROR] Unknown platform:", uname, "exiting...")
        sys.exit(404)

    time.sleep(5)

    dmesg_output = subprocess.check_output(["dmesg"], shell=True)
  
    if "eth0" in dmesg_output:
        print("Using eth0 workflow")
        if "qrb5165" in uname or "qrb5165-rb5" in uname or "m0104" in uname or "m0054" in uname or "m0052" in uname:
            print("[INFO] Opening config file...")
            config_file = open("/etc/modalai/voxl-modem.conf")

            print("[INFO] Converting to json...")
            config_dict = json.load(config_file)

            print("[INFO] Reading Microhard IP...")
            microhard_ip = config_dict["microhard_ip"]
            while(True):
                print("Waiting for 'eth0' to have the correct IP address...")
                try:
                    # Execute the command to get network interface information
                    result = subprocess.check_output(["ifconfig eth0"], shell=True)
                    print(result)
                    # Check if the command executed successfully
                    if "inet 192.168.168." in result:
                        break

                except Exception as e:
                    print("IP not yet set")  
                time.sleep(1)

            print("[INFO] Setting IP to:", microhard_ip) 
            subprocess.call(["ifconfig", "eth0", microhard_ip])
        else:
            print '[INFO] Using eth0 for Microhard'
            subprocess.call(["ifdown", "eth0"])                                                            
            subprocess.call(["ifup", "eth0"])
    else:
        print '[INFO] Using usb0 for Microhard'
        while "usb0" not in dmesg_output:
            time.sleep(1)
            try:       
                dmesg_output = subprocess.check_output(["dmesg"], shell=True)
            except:
                print("waiting for usb0...")

        print '[INFO] usb0 detected, pulling up'
        if "qrb5165" in uname or "qrb5165-rb5" in uname or "m0104" in uname or "m0054" in uname or "m0052" in uname:
            print("[INFO] Opening config file...")
            config_file = open("/etc/modalai/voxl-modem.conf")

            print("[INFO] Converting to json...")
            config_dict = json.load(config_file)

            print("[INFO] Reading Microhard IP...")
            microhard_ip = config_dict["microhard_ip"]
            while(True):
                print("Waiting for 'usb0' to have the correct IP address...")
                try:
                    # Execute the command to get network interface information
                    result = subprocess.check_output(["ifconfig usb0"], shell=True)
                    print(result)
                    # Check if the command executed successfully
                    if "inet 192.168.168." in result:
                        break

                except Exception as e:
                    print("IP not yet set")  
                time.sleep(1)

            print("[INFO] Setting IP to:", microhard_ip) 
            subprocess.call(["ifconfig", "usb0", microhard_ip])
        else:                                                            
            subprocess.call(["ifup", "usb0"])

#------------------------------------------------------------------------------
# GENERAL functions
#------------------------------------------------------------------------------

## Determines the existence of a specific serial port
#   @param port
#       serial port name (i.e. /dev/ttyUSB0)
#   @returns
#       - true if it exists
#       - false otherwise
#
def check_for_serial_port(port):
    counter = 0
    print '    checking for serial port: %s' % port
    while not os.path.exists(port):
        time.sleep(2)
        if counter == 20:
            return False
        counter += 1
    return True


##
#  @apram modem_on_usb
#       - True modem is connected via USB
#       - False modem is connected using a feather board
#
def wait_for_diag_serial_port_to_activate( diag_serial_port, modem_on_usb = False ):
    print 'Waiting for the diag serial port(%s) to activate' % diag_serial_port

    max_retry = 5

    while not check_for_serial_port(diag_serial_port):
        max_retry -= 1
        print '    ' + diag_serial_port + ' not found.'

        if modem_on_usb == False:
            print '    Power cycling feather'
            feather_cycle_power( diag_serial_port )
        else:
            print '    Waiting for 2 seconds for modem to appear'
            time.sleep(2)

        if max_retry <= 0:
            print ('    Aborting, failed to detect: ' + diag_serial_port )
            sys.exit(1)


    print '    ' + diag_serial_port + ' is available'
    return True

##
#
def verify_at_cmd_dev( at_cmd_dev, exitOnFailure = True ):
    print 'Verifying existence of AT command port(%s)' % at_cmd_dev

    if not check_for_serial_port(at_cmd_dev):
        print '    ' + at_cmd_dev + ' does not exist'
        if exitOnFailure:
            sys.exit(-1)

    print '    ' + at_cmd_dev + ' is available'

##
#
def verify_qmi_dev( qmi_dev, exitOnFailure = True ):
    print 'Verifying existence of QMI port(%s)' % qmi_dev

    if not check_for_serial_port(qmi_dev):
        print '    ' + qmi_dev + ' does not exist'
        if exitOnFailure:
            sys.exit(-1)

    print '    ' + qmi_dev + ' is available'


##  Configure the LTE bands to use
#   @param  port
#       Serial port to use for Qualcomm Diag commands.
#   @param value
#       The bitmask to use for band configuration.
#
def configure_lte_bands( port, value ):

    print 'Configuring LTE bands'

    try:
        status = subprocess.check_output(["configband", "write", port, \
                                          value], \
                                         stderr=FNULL)
    except subprocess.CalledProcessError:
        print '    Warning: got exception when setting band configuration'
        sys.exit(-1)

    print '    LTE bands configured'

#------------------------------------------------------------------------------
# UQMI functions
#------------------------------------------------------------------------------
#   WARNING:  Due to a WNC.patch hack, two different uqmi binaries are required
#   to support dual modems.  This is why the uqmi binary is passed to all
#   functions.
#------------------------------------------------------------------------------

##  Verifies that the qmi interface is functional by issuing a uqmi command.  The command
#   blocks on failure (no response) thus a seperate process is used during validation.
#   @param  uqmi
#       uqmi app to use
#   @param exitOnFailure
#   @param retry_count
#   @param timeout
#       seconds
#
def uqmi_verify_sw_interface( uqmi, qmi_dev, exitOnFailure=False, retry_count=5 ):

    print 'Verifying QMI software interface'

    #retry_count = 5
    got_response = False
    while not got_response:
        print '    Trying QMI command: '+uqmi+' -P -d ' + qmi_dev + ' --get-msisdn'
        timeout = 15
        task = subprocess.Popen([uqmi, "-P", "-d", qmi_dev, "--get-msisdn"],
                                stdout=subprocess.PIPE, stderr=FNULL)
        while task.poll() is None and timeout > 0:
            time.sleep(1)
            timeout -= 1
        if timeout == 0:
            print '        QMI response timed out, retrying'
            task.send_signal(signal.SIGINT)
            time.sleep(1)
            task.terminate()
            task.kill()
            if retry_count == 0:
                print '        Giving up on QMI command'
                if exitOnFailure:
                    sys.exit(-1)
                break
            else:
                retry_count -= 1
        else:
            print '        Got a QMI response'
            got_response = True

##  Change to LTE only. Disables other modes like WCDMA.
#   @param  uqmi
#       uqmi app to use
#   @param qmi_dev
#       i.e. /dev/qcqmi0
#
def uqmi_set_network_lte_only( uqmi, qmi_dev ):

    print 'Setting network mode to LTE only'

    try:
        status = subprocess.check_output([uqmi, "-d", qmi_dev, \
                                          "--set-network-modes", "lte"], \
                                         stderr=FNULL)
    except subprocess.CalledProcessError:
        print '    Warning: got exception when setting network mode to LTE only'
        sys.exit(-1)

    print '    Network mode set to LTE only'

##  Detect the simcard by checking for msisdn
#   @param  uqmi
#       uqmi app to use
#
def uqmi_validate_sim_card( uqmi, qmi_dev ):

    print 'Validating SIM card by reading msisdn'

    try:
        msisdn = subprocess.check_output([uqmi, "-P", "-d", qmi_dev, "--get-msisdn"], stderr=FNULL)

    except subprocess.CalledProcessError:
        print '    Warning: got exception when checking msisdn'
        # Don't exit here. This will happen with the Uber SIM cards
        # print 'Got exception when checking msisdn'
        # sys.exit(-1)
    else:
        msisdn = msisdn.strip().strip('"')
        if msisdn == 'UIM uninitialized':
            print '    No valid SIM found'
            sys.exit(-1)
        else:
            print '    Device number: ' + msisdn


## Registers with cellular system (WWAN).
#   @param  uqmi
#       uqmi app to use
#
def uqmi_register_with_system( uqmi, qmi_dev, retry_count = 12 ):

    attempts = 0
    print 'Checking for system registration'
    print 'TODO: This call may block, needs timeout'
    registered = False
    while not registered:
        attempts += 1
        print '   registration attempt: ' + str(attempts)
        response = ''
        try:
            response = subprocess.check_output([ uqmi, "-P", "-d", qmi_dev,
                                                "--get-serving-system"],
                                            stderr=FNULL)
            print response
        except subprocess.CalledProcessError:
            print '    Got exception when checking serving system'
            sys.exit(-1)

        if '"registration":' in response:
                print 'response: [' + response + ']'
                tokens = response.split('"')
                if len(tokens) > 4:
                    if tokens[1] == 'registration':
                        if tokens[3] == 'registered':
                            print '    Successfully registered with the network'
                            registered = True
                        else:
                            print tokens[1] + ": " + tokens[3]
        retry_count -= 1
        if retry_count == 0:
            print '    Giving up on registration'
            sys.exit(-1)
        if not registered:
            time.sleep(5)


## Set data format.
#
def uqmi_set_data_format( uqmi, qmi_dev ):

    print 'Setting data format'
    try:
        subprocess.call([uqmi, "-P", "-d", qmi_dev,
                        "--set-data-format", "raw-ip"],
                        stderr=FNULL)
    except subprocess.CalledProcessError:
        print '    Got exception when setting data format'
        sys.exit(-1)


## Starts network connectivity
#
def uqmi_start_network( uqmi, qmi_dev ):
    print 'Starting network'

    try:
        subprocess.call([uqmi, "-d", qmi_dev,
                        "-k", "wds", "--start-network"],
                        stderr=FNULL)
    except subprocess.CalledProcessError:
        print '    Got exception when starting network'
        sys.exit(-1)

    print '    Checking connection status'
    try:
        response = subprocess.check_output([uqmi, "-d", qmi_dev,
                                            "-k", "wds", "--get-data-status"],
                                        stderr=FNULL)
    except subprocess.CalledProcessError:
        print '        Could not check connection status'
        sys.exit(-1)
    if response.rstrip() != '"connected"':
        print response
        print '        Connection failed'
        sys.exit(-1)
    else:

        print '        Connection is up'


## Confgures the radio and registers with the cellular system
#   @param uqmi
#       uqmi app, uqmi2 is required to support dual WNC modems
#       e.g. uqmi
#   @apram serial_port
#       modem serial port
#   @param qmi_dev
#       i.e. /dev/qcqmi0
#   @param at_cmd_dev
#       i.e. /dev/ttyACM0
#   @param network_interface
#       i.e. rment_usb0
#   @param  modem_on_usb
#       - true will initialize GPIO
#       - false will not initialize GPIOs
#
def uqmi_connect_to_network( config ):

    # setup modem interface
    #
    if config.is_modem_on_usb == False:
        print 'Activating modem'
        feather_setup_gpio_and_activate_modem( config.serial_port )
        print 'Detecting feather modem:'
        print '    ' + feather_detect_modem( True )  # exits if fails to detect

    else:
        print 'Skipping feather modem initialization for ' + config.network_interface

    wait_for_diag_serial_port_to_activate( config.serial_port, config.is_modem_on_usb )
    verify_qmi_dev( config.qmi_dev, True )  # True - exits on verification failure
    verify_at_cmd_dev( config.at_cmd_dev, True )
    uqmi_verify_sw_interface( config.uqmi_bin, config.qmi_dev, False )
    uqmi_validate_sim_card( config.uqmi_bin, config.qmi_dev )

    # Handle special band configuration requests
    if config.disable_band_5:
        print 'Disabling band 5'
        uqmi_set_network_lte_only( config.uqmi_bin, config.qmi_dev )
        configure_lte_bands(config.serial_port, "0x80a")
    elif config.band_3_only:
        print 'Disabling all bands except for band 3'
        uqmi_set_network_lte_only( config.uqmi_bin, config.qmi_dev )
        configure_lte_bands(config.serial_port, "0x4")

    # connect to network
    #
    uqmi_register_with_system( config.uqmi_bin, config.qmi_dev )
    uqmi_set_data_format( config.uqmi_bin, config.qmi_dev )
    uqmi_start_network( config.uqmi_bin, config.qmi_dev )
    #
    verify_network_interface( config.network_interface, True )  # exit on failure
    dhcp_request( config.network_interface )


#------------------------------------------------------------------------------
# CALL/NETWOR SETUP FUNCTIONS
#------------------------------------------------------------------------------

##  Runs ifconfig to determine is the network interface is up.
#
#   @param network_interface
#       Network interface (i.e. rmnet_usb0) to monitor.
#
def verify_network_interface( network_interface, exitOnFailure = True ):

    print 'Verifying that the network interface(%s) is UP' % network_interface

    network_interface_up = False
    try:
        response = subprocess.check_output(["ifconfig", network_interface], stderr=FNULL)

    except subprocess.CalledProcessError:
        print '    Network interface ' + network_interface + ' is not available'
        if exitOnFailure : sys.exit(-1)

    if 'rmnet_usb0' not in response:
        print '    Network interface ' + network_interface + ' is not available'
        if exitOnFailure : sys.exit(-1)

    else:
        print '    Network interface (%s) is up' % network_interface


##  Uses udhcpc to issue a DHCP client request.
#
#   @param network_interface
#       Network interface (i.e. rmnet_usb0) to use.
#
def dhcp_request( network_interface ):

    print 'Using the DHCP client to get an IP address'
    dhcp_command = ['udhcpc', '-v', '-i', network_interface, '-s',
                    '/usr/local/qr-linux/udhcpc.sh']

    # TODO: Add a timeout for this. It can hang
    print subprocess.check_output(dhcp_command)



#------------------------------------------------------------------------------
# Load and Save Functions
#------------------------------------------------------------------------------

# Loads configuration options from a file.
#
def load_config_file(filename, config ):
    print 'LOADing configuration from file: ' + filename
    try:
        f = open( filename, "r")
        config_line = f.read()
        config_params = config_line.split()

        for config_param in config_params:

            # most are assignments x=y
            assignment =  config_param.split("=")
            opt = assignment[0]
            arg = None
            if len(assignment) >= 2:
                arg = assignment[1]

            if opt in ("--network_interface"):
                config.network_interface=arg
            if opt in ("--serial_port"):
                config.serial_port=arg
            if opt in ("--qmi_dev"):
                config.qmi_dev=arg
            if opt in ("--uqmi_bin"):
                config.uqmi_bin=arg
            if opt in ("--modem_on_usb"):
                config.is_modem_on_usb = True

            if opt in ("--save_config"):
                config.save_config_filename = arg

    except:
        print 'ERROR: Failed to load config file: ' + filename
        sys.exit(1)


def save_config_file( filename, config ):
    print("SAVEd settings to config file:" + save_config_filename )
    try:
        f = open( save_config_filename, "w")
        f.write("--serial_port=" + serial_port
            + " --qmi_dev=" + qmi_dev
            + " --network_interface=" + network_interface
            + " --uqmi_bin=" + uqmi_bin )
        if is_modem_on_usb:
            f.write( " --modem_on_usb")
        f.write("\n")
        f.close()

    except:
        print("    WARNING: Failed to create config file:" + save_config_filename )


##
#
def verify_connect_to_network_configuration(config):
        print 'Verifying configuration for connecting to network'

        if config.network_interface == None:
            error_exit( "    ERROR: Missing option: --network_interface" )
        elif config.serial_port == None:
            error_exit( "    ERROR: Missing option: --serial_port")
        elif config.qmi_dev == None:
            error_exit( "    ERROR: Missing option: --qmi_dev")
        elif config.uqmi_bin == None:
            error_exit( "    ERROR: Missing option: --qmi_bin")


#--------------------------------------------------------------------
# Command line utility functions
#--------------------------------------------------------------------

# Prints usage info.

def print_help():
        print "Usage: voxl-modem [options]"
        print ""
        print "    --feather_configure          Configures GPIOs to bring up feather based modem."
        print "    --feather_detect_modem       Read modem ID and returns a description of the results"
        print "    --feather_read_id            Detects the feather board ID."
        print "    --feather_cycle_power        Power cycles the feather board."
        print "    --feather_reset              Reset feather"
        print ""
        print "    --feather_enable=device      Toggles pins to enable feather and monitors device to validate action"
        print "                                    --feather_enable=/dev/ttyUSB0"
        print "    --feather_disable=device     Toggles pins to disable feather and monitors device to validate action"
        print ""
        print "    --microhard_configure        Configures GPIOs and network to bring up Microhard modem."
        print "                                     Usage: voxl-modem --microhard_configure"
        print ""
        print "    --dtc_configure          Configures network to bring up DTC modem."
        print "                                     Usage: voxl-modem --dtc_configure"
        print "    --verify_serial_port=port    Verifies that the serial port exists."
        print "    --verify_qmi_dev=dev         Verifies that QMI is functional."
        print ""
        print "    --connect_to_network         Connects to network. GPIOs must be setup and serial port and "
        print "                                 QMI must be verified."
        print "                                     Usage: voxl-modem --connect_to_network"
        print "                                     Usage: voxl-modem --modem_on_usb --connect_to_network"
        print "                                     Usage: voxl-modem --connect_to_network --serial_port=/dev/ttyUSB0 --qmi_dev=/dev/qcqmi0 --network_interface=rmnet_usb0"
        print ""
        print "    --network_interface=iface    Set the network intrface to use."
        print "                                      Example: --network_interface=rmnet_usb0"
        print "    --serial_port=port           Set the serial port."
        print "                                     Example: --serial_port=/dev/ttyUSB0"
        print "    --qmi_dev=device             Sets the qmi device to use."
        print "                                     Example: --qmi_dev=/dev/qcqmi0"
        print "    --uqmi_bin=uqmi              Overrrides the default uqmi executable(uqmi)."
        print "                                      e.g. --uqmi_bin=uqmi2"
        print ""
        print "    --save_config=filename       Saves connect_to_netwo9rk config to a file."
        print "    --load_config=filename       Loads connect_to_netwo9rk config from a file."
        print ""
        print "    --modem_on_usb               Disables feather use (e.g. using USB modem)"
        print ""
        print "    --disable_band_5             Disables band 5 for aircraft use"
        print "    --band_3_only                Only enable band 3 (U.S. DoD use)"
        print ""


def error_exit(text, error_code=1):
    print text
    sys.exit(error_code)


# Processes command line arguments and sets global vars.
#
def process_command_line_arguments(argv):

    try:
        opts, args = getopt.getopt( argv, "h", ["feather_configure", "feather_read_id", "feather_cycle_power",
            "feather_detect_modem",  "feather_reset", "disable_band_5", "band_3_only",
            "verify_serial_port=", "verify_qmi_dev=", "serial_port=", "qmi_dev=", "network_interface=",
            "uqmi_bin=", "connect_to_network", "modem_on_usb", "save_config=", "load_config=",
            "feather_enable=", "feather_disable=", "feather_read_wwan_state", "microhard_configure", "dtc_configure"] )

    except getopt.GetoptError as err:
        print "Error: ", err
        print_help()
        sys.exit(2)

    if len(opts) == 0:
        print_help()
        sys.exit(0)


    global _config

    # parameters from command line options
    #
    is_modem_on_usb = False
    network_interface = None
    serial_port = None
    qmi_dev = None
    uqmi_bin = None
    #
    is_modem_on_usb             = False
    is_connecting_to_network    = False
    #
    save_config_filename        = None
    load_config_filename        = None
    #
    is_verify_serial_port           = False
    is_configure_gpio_and_activate  = False

    # process command line arguments
    #
    for opt, arg in opts:

        opt_assignments = {}
        opt_params = arg.split(',')
        for config_param in opt_params:
            assignment =  config_param.split("=")
            if len(assignment) >= 2:
                opt_assignments[ assignment[0] ] = assignment[1]

        if opt == '-h':
            print_help()
            sys.exit(0)
        elif opt in ("--feather_reset"):
            feather_reset()
            sys.exit(0)
        elif opt in ("--feather_configure"):
            is_configure_gpio_and_activate = True
        elif opt in  ("--feather_read_id" ):
            print feather_read_id()
        elif opt in ("--feather_detect_modem"):
            print feather_detect_modem()
        elif opt in ("--feather_cycle_power"):
            feather_cycle_power()

        elif opt in ("--feather_read_wwan_state"):
            print str(feather_read_wwan_state_int())

        elif opt in ("--feather_enable"):
            feather_enable(arg)
        elif opt in ("--feather_disable"):
            feather_disable(arg)

        elif opt in ("--verify_serial_port"):
            serial_port = arg
            is_verify_serial_port = True

        elif opt in ("--verify_qmi_dev"):
            verify_qmi_dev( arg )
            sys.exit(0)

        elif opt in ("--microhard_configure"):
            microhard_configure()

        elif opt in ("--dtc_configure"):
            dtc_configure()

        if opt in ("--modem_on_usb"):
            is_modem_on_usb = True

        if opt in ("--network_interface"):
            network_interface=arg
        if opt in ("--serial_port"):
            serial_port=arg
        if opt in ("--qmi_dev"):
            qmi_dev=arg
        if opt in ("--uqmi_bin"):
            uqmi_bin=arg

        elif opt in ("--connect_to_network"):
            is_connecting_to_network = True

        if opt in ("--disable_band_5"):
            _config.disable_band_5 = True
        elif opt in ("--band_3_only"):
            _config.band_3_only = True

        if opt in ("--load_config="):
            load_config_filename = arg
        if opt in ("--save_config"):
            save_config_filename = arg


    # Run single action commands that require multiple parameters
    if is_verify_serial_port:
            response=wait_for_diag_serial_port_to_activate( serial_port, is_modem_on_usb )
            print response
            sys.exit(0)

    if is_configure_gpio_and_activate:
        feather_setup_gpio_and_activate_modem( _config.serial_port)

    if load_config_filename != None:
        load_config_file( load_config_filename, _config )

    # Config file parameters CAN BE over written via command line, so apply changes here
    #
    _config.is_modem_on_usb = is_modem_on_usb
    if network_interface != None:
        _config.network_interface = network_interface
    if serial_port != None:
        _config.serial_port = serial_port
    if qmi_dev != None:
        _config.qmi_dev = qmi_dev
    if uqmi_bin != None:
        _config.uqmi_bin = uqmi_bin

    # when connecting to network, verify required arguments
    #
    if is_connecting_to_network or save_config_filename != None:
        verify_connect_to_network_configuration( _config )

    # Save
    #
    if save_config_filename != None:
        save_config_file( save_config_filename, _config )

    if is_connecting_to_network:
        # Connect
        print 'Configuration:'
        print '    --serial_port:       ' + _config.serial_port
        print '    --qmi_dev:           ' + _config.qmi_dev
        print '    --network_interface: ' + _config.network_interface
        print '    --uqmi_bin:          ' + _config.uqmi_bin
        print '    --modem_on_usb:      ' + str( _config.is_modem_on_usb)
        #
        uqmi_connect_to_network( _config )

#--------------------------------------------------------------------
# Main
#--------------------------------------------------------------------

if __name__ == '__main__':
    # Running as a script NOT as an imported module
    #
    process_command_line_arguments( sys.argv[1:] )
