#!/bin/bash
#
#       Copyright (C) MOXA Inc. All rights reserved.
#       Copyright (C) 2014-2015  Lock Lin <Lock.Lin@moxa.com>
#       Copyright (C) 2015       Harry YJ Jhou <HarryYJ.Jhou@moxa.com>
#
#       This software is distributed under the terms of the
#       MOXA License.  See the file COPYING-MOXA for details.
#
#       cell_mgmt
#               cellular management. It supports sierra MC9090 MC7304 MC7354.
#


#--------- Initialize Global variables ---------
PKG="moxa-cellular-utils"
VERSION="2.0.3"

PROG=$0
PID=$$
STATUS_DIR=/var/run/${PKG}

# profile
CONF_DIR=/etc/${PKG}
#CONF_DIR=/home/moxa/moxa-cellular-utils${CONF_DIR}
PROFILE_FILE=${CONF_DIR}/${PKG}.conf
PRODUCT_DIR=${CONF_DIR}/product.d
MODULE_DIR=${CONF_DIR}/module.d
MODULE_ON_DIR=${CONF_DIR}/module-on.d
MODULE_OFF_DIR=${CONF_DIR}/module-off.d

KVERSION=$(which kversion)
[ -z "${KVERSION}" ] && >&2 echo "Error: \`kversion\` not found." && exit 1

PRODUCT="$(${KVERSION} 2> /dev/null | awk '{print $1}')"

# QMI
# new libqmi includes a tool called "qmi-proxy"
# use qmicli -p means interaction with the QMI interface of modem by "qmi-proxy"
QMICLI=$(which qmicli)
QMICLI_END="-p"
[ -z "${QMICLI}" ] && >&2 echo "Error: \`qmicli\` not found." && exit 1

# variables
FORCE=0
NEED_LOCK=0
NEED_INITIALIZE=0


_exit() {
	local ret=${1:-0}

	if [ $ret -eq 60 ]; then
		_module_crash_exit
	fi
	exit $ret
}

_error() {
	local code=${1:-0}
	if [ ${code} -eq 0 ]; then
		>&2 echo "$2"
	else
		case ${code} in
			1)
				>&2 echo "Error: Unknown error"
				;;
			2)
				>&2 echo "Error: \"${PROFILE_FILE}\" does not exist."
				;;
			3)
				>&2 echo "Error: Operation not supported."
				;;
			4)
				>&2 echo "Error: Invalid input."
				;;
			5)
				>&2 echo "Error: Module not supported."
				;;
			6)
				>&2 echo "Error: Module not found."
				;;
			60)
				>&2 echo "Error: ${PROG} is busy, please " \
					"wait a moment and try again."
				;;
			*)
				;;
		esac
	fi
}

_func_exist() {
	if type ${1} 2> /dev/null | grep -q 'function' 2>/dev/null; then
		return 0
	fi
	return 1
}

# nested is not recommended, recursive calling may occurred
__get_op_func() {
	local op="$1"

	if _func_exist _module_${MODULE_ID}_${op}; then
		echo _module_${MODULE_ID}_${op}
	elif [ x"${QMI_PORT}" != x"NotSupport" ] && _func_exist qmi_${op}; then
		echo qmi_${op}
	elif [ x"${AT_PORT}" != x"NotSupport" ] && _func_exist at_${op}; then
		echo at_${op}
	else
		echo ""
	fi
}

_get_op_func() {
	local op="$1"

	# cell_${op} or _xxx_cell_${op}: top most functions, nested is allowed
	if _func_exist _module_${MODULE_ID}_cell_${op}; then
		echo _module_${MODULE_ID}_cell_${op}
	elif _func_exist cell_${op}; then
		echo cell_${op}
	else
		__get_op_func "${op}"
	fi
}

_exec_op_rtn() {
	local func="$(__get_op_func $1)"

	if [ x"${func}" != x"" ]; then
		#echo "eval ${func} ${@:2}"
		eval ${func} ${@:2}
	else
		_error 3
		return 3
	fi
}

_exec_op() {
	local func="$(__get_op_func $1)"

	if [ x"${func}" == x"" ]; then
		_error 3
		_exit 3
	fi

	#echo "eval ${func} ${@:2}"
	eval ${func} ${@:2}
}

_save_state() {
	local KEY=$1
	local VAL=$2

	echo "Saving state... ($KEY: $VAL)"

	if [ -f $STATE_FILE ]; then
		PREVIOUS=`cat $STATE_FILE 2>/dev/null`
		PREVIOUS=`echo "$PREVIOUS" | grep -v $KEY`
		if [ "x$PREVIOUS" != "x" ]; then
			echo $PREVIOUS > $STATE_FILE
		else
			rm $STATE_FILE
		fi
	fi

	if [ "x$VAL" != "x" ]; then
		echo "$KEY=\"$VAL\"" >> $STATE_FILE
	fi
}

_load_state() {
	if [ -f $STATE_FILE ]; then
		# Loading previous state
		. $STATE_FILE
	fi
}

_clear_state() {
	echo "Clearing state..."
	rm -f $STATE_FILE
	rm -f $STATUS_FILE
}


_load_profile() {
	. $PROFILE_FILE
	for conf in $(ls ${PRODUCT_DIR}); do
		if [ "${conf: -5}" == ".conf" ]; then
			. ${PRODUCT_DIR}/$conf
		fi
	done
	for conf in $(ls ${MODULE_DIR}); do
		if [ "${conf: -5}" == ".conf" ]; then
			. ${MODULE_DIR}/$conf
		fi
	done

	eval "_product_${PRODUCT}_profile" 2>/dev/null
	if [ x"$?" != x"0" ]; then
		_error 0 "Error: \`kversion\` or profile not found."
		_exit 1
	fi
}

_search_wwan_node() {
	local module_name
	local vendor_id
	local product_id
	local slot_num=0
	local id=-1
	local unsupport_num=0

	for i in ${MODULE_PATH[@]}; do
		let slot_num++
		if [ ! -e $i ]; then
			continue
		fi

		vendor_id=$(cat $i/idVendor 2> /dev/null)
		product_id=$(cat $i/idProduct 2> /dev/null)
		module_name=$(cat $i/product 2> /dev/null)

		if ! _func_exist _module_${vendor_id}_${product_id}; then
			_error 0 "Error: Module \"${module_name}\" not supported."
			let unsupport_num++
			continue
		fi

		let id++
		ALL_MODULE_ID[${id}]=${vendor_id}_${product_id}
		ALL_MODULE_NAME[${id}]=${module_name}
		ALL_SLOT_NUM[${id}]=$slot_num
		ALL_INTERFACE[${id}]=""
		ALL_QMI_PROTOCOL[${id}]="802-3"
		ALL_QMI_PORT[${id}]="NotSupport"
		ALL_AT_PORT[${id}]="NotSupport"
		ALL_GPS_PORT[${id}]="NotSupport"
		ALL_MODEM_PORT[${id}]="NotSupport"
		ALL_AT_PORT_RESV[${id}]="NotSupport"
		ALL_AT_TIMEOUT[${id}]="0.3"

		eval "_module_${vendor_id}_${product_id} \
			$i ${id} \"${module_name}\""
		if [ $? -ne 0 ]; then
			_error 0 "Error: Initialize module"\
				"\"${ALL_MODULE_NAME[$id]}\" failed."
			continue
		fi
	done

	if [ $unsupport_num -gt 0 ] && [ $id -lt 0 ]; then
		_error 5
		_exit 5
	fi
}

_set_state_file() {
	local slot=$1
	mkdir -p "${STATUS_DIR}"
	LOCK_FD=$((slot+119))
	LOCK_FD_GLOBAL=$((slot+129))
	LOCK_FILE="${STATUS_DIR}/${slot}.lock"
	LOCK_FILE_GLOBAL="${STATUS_DIR}/${slot}.global.lock"
	INIT_FILE="${STATUS_DIR}/${slot}.init"
	STATE_FILE="${STATUS_DIR}/${slot}.state"
	STATUS_FILE="${STATUS_DIR}/${slot}.status"
	AT_LOCKED_FILE="${STATUS_DIR}/${slot}.at.locked"
}

_set_variables() {
	export SLOT="${ALL_SLOT_NUM[$INTERFACE]}"
	[ -z "${SLOT}" ] && SLOT=1
	MODULE_ID="${ALL_MODULE_ID[$INTERFACE]}"
	MODULE_NAME="${ALL_MODULE_NAME[$INTERFACE]}"
	QMI_PROTOCOL=${ALL_QMI_PROTOCOL[${INTERFACE}]}

	QMI_NODE="${ALL_INTERFACE[$INTERFACE]}"
	export WWAN_NODE="${QMI_NODE}"
	if [ x"${ALL_QMI_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		QMI_PORT="NotSupport"
	else
		export QMI_PORT="/dev/${ALL_QMI_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_AT_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		AT_PORT="NotSupport"
	else
		export AT_PORT="/dev/${ALL_AT_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_AT_PORT_RESV[$INTERFACE]}" == x"NotSupport" ]; then
		AT_PORT_RESV="NotSupport"
	else
		export AT_PORT_RESV=$(echo ${ALL_AT_PORT_RESV[$INTERFACE]} | \
			awk '{for(i=1;i<=NF;++i) {sub(/^/,"/dev/",$i)};print}')
	fi
	if [ x"${ALL_GPS_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		GPS_PORT="NotSupport"
	else
		export GPS_PORT="/dev/${ALL_GPS_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_MODEM_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		MODEM_PORT="NotSupport"
	else
		export MODEM_PORT="/dev/${ALL_MODEM_PORT[$INTERFACE]}"
	fi
	AT_TIMEOUT="${ALL_AT_TIMEOUT[$INTERFACE]}"

	# state file for different interface
	if [ x"$INTERFACE" != x"" ]; then
		_set_state_file ${SLOT}
	fi

	# Initialize if never initialized before
	if [ ! -f "${INIT_FILE}" ]; then
		NEED_INITIALIZE=1
	fi

	# Load previous state, if any
	CID=""
	PDH=""
	_load_state

	# Update WWAN interface
	if [ -z "${QMI_NODE}" ]; then
		QMI_NODE="$(_exec_op status | grep "^PPPIFName: " | \
			awk '{print $2}')"
	fi
}

_init() {
	unset ALL_MODULE_ID
	unset ALL_MODULE_NAME
	unset ALL_SLOT_NUM
	unset ALL_INTERFACE
	unset ALL_QMI_PROTOCOL
	unset ALL_QMI_PORT
	unset ALL_AT_PORT
	unset ALL_GPS_PORT
	unset ALL_MODEM_PORT
	unset ALL_AT_TIMEOUT

	_search_wwan_node
	if [ ${#ALL_INTERFACE[@]} -eq 0 ]; then
		INTERFACE=
		return
	fi

	if [ x"${INTERFACE}" == x"" ] || \
		[ ${INTERFACE} -ge ${#ALL_INTERFACE[@]} ]; then
		INTERFACE=0
	fi
	_set_variables
}

init() {
	if [ -f $PROFILE_FILE ]; then
		_load_profile
		_init
	else
		_error 2
		_exit 2
	fi
}

_signal_csq2dbm() {
	local signal="$1"

	signal=${signal##*AT}
	signal=${signal%%,*}

	if [ ! -z "${signal}" ]; then
		if [ "${signal}" -le "31" 2>/dev/null ] && [ "${signal}" -ge "2" 2>/dev/null ] ; then
			let signal=signal-2
			let signal=signal*2-109
			echo "${signal} dBm"
		else
			echo "-999"
		fi
	else
		echo "-999"
	fi
}

_signal_dbm2csq() {
	local signal="$1"

	# csq = (dbm + 113) / 2
	if [ $signal -le -113 ]; then
		echo "99"
		return 0
	fi
	signal=$(echo ${signal} | awk '{printf "%d\n",int((($1+113)/2+0.5))}')
	echo ${signal}
}

_connect_error_note() {
	>&2 echo "Please check the following items before start LTE connection:"
	>&2 echo "  1. Antenna setting"
	>&2 echo "  2. SIM card inserted and PIN code unlocked, \`sim_status\`"
	>&2 echo "  3. Signal strength \`signal\`"
	>&2 echo "  4. Remember to set_apn before start LTE connection"
}

_get_profile() {
	local key="$1"
	local _id="$INTERFACE"
	[ x"${_id}" == x"0" ] && _id=""

	key=${key}${_id}
	echo ${!key}
}

_update_profile() {
	local key="$1"
	local value="$2"

	if grep -q "${key}=" ${PROFILE_FILE}; then
		eval "sed -i s/${key}=.*/${key}=${value}/ $PROFILE_FILE"
	else
		echo "${key}=${value}" >> ${PROFILE_FILE}
	fi
}

_contains() {
	[[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && return 0 || return 1
}

_start_network_init() {
	ignore_dns_gw=0
	local card_status=""

	local VAL_OPTS="APN Auth Username Password PIN Phone \
		OpCode OpRadioType OpPreferredRadioType"
	local BOOL_OPTS="ignore-dns-gw"
	local _id="$INTERFACE"
	[ x"${_id}" == x"0" ] && _id=""


	# get value from configuration
	for opt in ${VAL_OPTS}; do
		eval "${opt}=\$${opt}${_id}"
	done

	# modify APN Username Password PIN
	for i in $(seq 1 $#); do
		eval "arg=\$$i"
		param=$(echo "$arg" | cut -d'=' -f1)
		if _contains "${VAL_OPTS}" ${param}; then
			eval "${param}=\"$(echo "$arg" | cut -d'=' -f2-)\""
			_update_profile ${param}${_id} ${!param}
		elif _contains "${BOOL_OPTS}" ${param}; then
			param=$(echo "${param}" | tr "-" "_")
			eval "${param}=1"
		fi
	done

	# check APN
	if [ x"$APN" == x"" ]; then
		_error 0 "Error: Please assign a valid APN."
		_cell_usage_cmd _cell_usage_start
		_exit 4
	fi

	card_status="$(_exec_op sim_status | grep "^+CPIN")"
	if [ "$(echo "${card_status}" | grep "SIM PIN")" ] && [ x"$PIN" == x"" ]; then
		_error 0 "Error: Please assign the PIN code to unlock SIM card."
		_cell_usage_cmd _cell_usage_start
		_exit 4
	fi

	# Check PIN status
	_cell_unlock_pin "${PIN}" "_cell_usage_start"
}

#--------- qmi base functions -----------
_qmicli() {
	local i
	local qmi_out=""
	local qmi_ret=0

	for i in {1..5}; do
		qmi_out="$(${QMICLI} -d ${QMI_PORT} $@ ${QMICLI_END} 2>&1)"
		qmi_ret=$?
		# error 3 (internal) and 31 (invalid service type)
		if ! (echo ${qmi_out} | grep "QMI protocol error (3)") &&
			! (echo ${qmi_out} | grep "QMI protocol error (31)"); then
			echo "${qmi_out}"
			return 0
		fi
	done

	# reset qmi service
	_error 0 "Error: Internal error, please reset module and try again."
	_exit 1
}

_qmicli_value() {
	local output="$1"
	local key="$2"
	local num="$3"
	local num_opt=""
	local cmd=""

	if [ -z "${3}" ]; then
		num=1
	fi
	num_opt="| sed -n ${num}p"

	cmd="echo \"${output}\" | awk '/${key}:/{ print}' ${num_opt} | \
		sed \"s/^[^:]*: [']*//;s/[' \t]*$//\""
	eval "${cmd}"
}

_qmi_start_network() {
	local is_raw_ip="N"
	local raw_ip_file="/sys/class/net/${QMI_NODE}/qmi/raw_ip"
	local auth_arg=""

	if [ x"$CID" != x"" ]; then
		USE_PREVIOUS_CID="--client-cid=$CID"
	fi

	if [ x"$PDH" != x"" ]; then
		_error 0 "Error: Cannot re-start network, PDH already exists."
		_exit 1
	fi

	# initialize the interface with protocol
	if [ x"${QMI_PROTOCOL}" == x"802-3" ]; then
		is_raw_ip=N
	elif [ x"${QMI_PROTOCOL}" == x"raw-ip" ]; then
		is_raw_ip=Y
	else
		QMI_PROTOCOL="802-3"
		is_raw_ip=N
	fi

	# initialize the authorization arg.
	if [ x"$Auth" != x"" ]; then
		auth_arg=",auth=$Auth"
	fi
	if [ x"$Username" != x"" ]; then
		auth_arg="$auth_arg,username=$Username"
	fi
	if [ x"$Password" != x"" ]; then
		auth_arg="$auth_arg,password=$Password"
	fi

	if [ -f "${raw_ip_file}" ]; then
		echo ${is_raw_ip} > ${raw_ip_file} 2> /dev/null
		_qmicli --set-expected-data-format=${QMI_PROTOCOL} || \
			{ _error 0 "Error: Cannot update data format."; \
			 _exit 1; }
	elif [ x"${QMI_PROTOCOL}" == x"raw-ip" ]; then
		_error 0 "Error: \"raw-ip\" not supported."
		_exit 1
	fi

	#START_NETWORK_CMD="$QMICLI -d $QMI_PORT --wds-start-network=$APN --client-no-release-cid --device-open-net=net-802-3|net-no-qos-header $QMICLI_END"
	START_NETWORK_CMD="_qmicli --wds-start-network=apn=$APN,ip-type=4$auth_arg --client-no-release-cid --device-open-net=net-${QMI_PROTOCOL}|net-no-qos-header"
	echo "Starting network with '$START_NETWORK_CMD'..."

	if [ x"$QMIDEBUG" != x"" ]; then
		if [ x"$INTERFACE" == x"" -o x"$INTERFACE" == x"1" ]; then
			START_NETWORK_OUT="\
[/dev/cdc-wdm0] Network started
        Packet data handle: '3634026241'
[/dev/cdc-wdm0] Client ID not released:
        Service: 'wds'
        CID: '80'"
		else
			START_NETWORK_OUT="\
[/dev/cdc-wdm1] Network started
        Packet data handle: '363402624$3'
[/dev/cdc-wdm1] Client ID not released:
        Service: 'wds'
        CID: '80'"
		fi
	else
		START_NETWORK_OUT=$($START_NETWORK_CMD)
	fi

	# Save the new CID if we didn't use any before
	if [ x"$CID" == x"" ]; then
		CID=`echo "$START_NETWORK_OUT" | sed -n "s/.*CID.*'\(.*\)'.*/\1/p"`
		if [ x"$CID" == x"" ]; then
			_error 0 "Error: Network start failed, client not allocated."
			_connect_error_note
			_exit 1
		else
			_save_state "CID" $CID
		fi
	fi

	PDH=$(echo "$START_NETWORK_OUT" | sed -n "s/.*handle.*'\(.*\)'.*/\1/p")
	if [ x"$PDH" == x"" ]; then
		_error 0 "Error: Network start failed, no packet data handle."
		_connect_error_note
		# Cleanup the client
		#$QMICLI -d "$QMI_PORT" --wds-noop --client-cid="$CID" $QMICLI_END
		_qmicli --wds-noop --client-cid="$CID"
		_clear_state
		_exit 1
	else
		_save_state "PDH" $PDH
	fi

	echo "Network started successfully"
}

_qmi_stop_network() {
	if [ x"$CID" == x"" ]; then
		echo "Network already stopped"
	elif [ x"$PDH" == x"" ]; then
		echo "Network already stopped; need to cleanup CID $CID"
		# Cleanup the client
		#$QMICLI -d "$QMI_PORT" --wds-noop --client-cid="$CID" $QMICLI_END
		_qmicli --wds-noop --client-cid="$CID"
		PDH=""
	else
		#STOP_NETWORK_CMD="$QMICLI -d $QMI_PORT --wds-stop-network=$PDH --client-cid=$CID $QMICLI_END"
		STOP_NETWORK_CMD="_qmicli --wds-stop-network=$PDH --client-cid=$CID"
		echo "Stopping network with '$STOP_NETWORK_CMD'..."

		if [ x"$QMIDEBUG" != x"" ]; then
			STOP_NETWORK_OUT="\
[/dev/cdc-wdm$INTERFACE] Network stopped
"
		else
			STOP_NETWORK_OUT=`$STOP_NETWORK_CMD`
		fi
		PDH=""
		CID=""

		echo "Network stopped successfully"
	fi

	_clear_state
}

_qmi_packet_service_status() {
	if [ x"$CID" != x"" ]; then
		USE_PREVIOUS_CID="--client-cid=$CID --client-no-release-cid"
	fi

	#STATUS_CMD="$QMICLI -d $QMI_PORT --wds-get-packet-service-status $USE_PREVIOUS_CID $QMICLI_END"
	STATUS_CMD="_qmicli --wds-get-packet-service-status $USE_PREVIOUS_CID"
	echo "Getting status with '$STATUS_CMD'..."

	if [ x"$QMIDEBUG" != x"" ]; then
		CONN="disconnected"
	else
		local info
		info=$(${STATUS_CMD}) || return $?
		CONN="$(_qmicli_value "${info}" "Connection status")"
	fi

	if [ x"$CONN" == x"" ]; then
		_error 0 "Error: Couldn't get packet service status."
		_exit 1
	else
		echo "Status: $CONN"
		if [ x"$CONN" != x"connected" ]; then
			_exit 1
		fi
	fi
}

_qmi_kill_proxy() {
	local proxy_pid=$(ps wwax | grep -v "grep" | grep "qmi-proxy" | awk '{print $1}')
	if [ ! -z "${proxy_pid}" ]; then
		kill ${proxy_pid}
	fi
}

_qmi_card_app_state() {
	local app_state="$(_qmicli_value "${1}" "Application state")"

	case "${app_state}" in
		"pin1-or-upin-pin-required")
			echo "+CPIN: SIM PIN"
			;;
		"puk1-or-upin-puk-required")
			echo "+CPIN: SIM PUK"
			;;
		"ready")
			echo "+CPIN: READY"
			;;
		*)
			echo "+CME ERROR: SIM ${app_state}"
			return 1
			;;
	esac
}

_qmi_card_status() {
	local card_info=
	local card_state=

	card_info="$(_qmicli --uim-get-card-status)" || \
		{ _error 1; _exit 1; }
	card_state="$(_qmicli_value "${card_info}" "Card state")"
	case "${card_state}" in
		"absent")
			echo "+CME ERROR: SIM not inserted"
			;;
		"present")
			_qmi_card_app_state "${card_info}"
			;;
		*)
			echo "+CME ERROR: SIM ${card_state}"
			return 1
			;;
	esac
}

#--------- qmi functions -----------
qmi_fw_update() {
		# arg1 is fw path.
		local cmd="-w $QMI_PORT --update $1"
		qmi-firmware-update $cmd
}

qmi_attach_status() {
	local info=

	info="$(_qmicli --nas-get-serving-system)" || \
		{ _error 1; _exit 1; }

	echo "CS: $(_qmicli_value "${info}" "CS")"
	echo "PS: $(_qmicli_value "${info}" "PS")"
}

#check cellular status
qmi_status() {
	local con_status=$(_qmi_packet_service_status | grep "Status")


	if [ x"$(echo ${con_status}| awk '{print $2}')" == x"connected" ]; then
		if [ -f "${STATUS_FILE}" ]; then
			cat "${STATUS_FILE}"
		else
			echo ${con_status}
		fi
		_exit 0
	else
		echo "Status: disconnected"
		_exit 1
	fi
}

#cellular signal strength in level
qmi_signal() {
	local sig_info=""
	sig_info=$(qmi_signal_adv | grep "^RAT:\|^Signal Level:" | sed 's/RAT://g;s/Signal Level:/Level/g;' | sed 's/gsm/2G/g;s/umts/3G/g;s/lte/4G/g')
	echo $sig_info
}

# cell_signal_adv
qmi_signal_adv() {
	local rat=""
	local csq=""
	local current=""
	local rssi=""
	local ecio=""
	local rsrp=""
	local rsrq=""
	local signal=""

	signal=$(_qmicli --nas-get-signal-strength | \
		sed "s/'\|'://g;s/^[ \t]*Network //g") || \
		{ _error 1;  exit 1; }
	current=$(echo "${signal}" | awk '/Current:/{getline; print}')
	rssi=$(echo "${signal}" | awk '/RSSI:/{getline; print}' | cut -d ' ' -f 2)
	ecio=$(echo "${signal}" | awk '/ECIO:/{getline; print}' | cut -d ' ' -f 2)
	rsrp=$(echo "${signal}" | awk '/RSRP:/{getline; print}' | cut -d ' ' -f 2)
	rsrq=$(echo "${signal}" | awk '/RSRQ:/{getline; print}' | cut -d ' ' -f 2)
	rat=$(echo ${current} | cut -d ' ' -f 1)
	signal=$(echo "${rssi}" | awk '{print $(NF-1)}')
	csq=$(_signal_dbm2csq ${signal})
	[ -z "${csq}" ] && csq=99
	if [ "${rat}" = "umts" ] || [ "${rat}" = "cmda" ] \
		|| [ "${rat}" = "wcdma" ]; then
		sig_indicator="RSSI"
		sig_strength="${rssi}"
	elif [ "${rat}" = "lte" ]; then
		sig_indicator="RSRP"
		sig_strength="${rsrp}"
	else
		rat="none"
		sig_indicator="none"
		sig_strength="-999"
	fi
	_format_signal_adv "${rat} ${csq} ${rssi:-"-"} - ${rscp:-"-"} ${ecio:-"-"} ${rsrp:-"-"} ${rsrq:-"-"} ${sig_strength} ${sig_indicator}"
	return 0
}

# cell_start
qmi_start_network() {
	local card_status=""
	local ignore_dns_gw=0
	local pin_status


	_start_network_init $@

	_qmi_start_network
	# query IP from DHCP Server
	ip link set ${QMI_NODE} up
	if [ x"${QMI_PROTOCOL}" == x"raw-ip" ]; then
		cell_udhcpc ${QMI_NODE} $ignore_dns_gw
	else
		cell_dhclient ${QMI_NODE} $ignore_dns_gw
	fi

	_exit 0
}

# cell_stop
qmi_stop_network() {
	# release IP by dhclient -r
	if [ x"${QMI_NODE}" != x"" ]; then
		if [ x"${QMI_PROTOCOL}" == x"raw-ip" ]; then
			cell_udhcpc_kill "${QMI_NODE}"
		else
			cell_dhclient_kill "${QMI_NODE}"
		fi
	fi
	# cellular stop by qmi-network
	_qmi_stop_network
	if [ x"${QMI_NODE}" != x"" ]; then
		ip link set ${QMI_NODE} down
	fi
}

qmi_sim_status() {
	_qmi_card_status
}

# unlock pin code
qmi_unlock_pin() {
	local ret=

	ret=$(_qmicli --uim-verify-pin=PIN1,$1) || \
		{ _error 1;  exit 1; }
	if ! echo "${ret}" | grep -q 'PIN verified successfully'; then
		echo $ret
		_exit 1
	fi
}

qmi_pin_retries() {
	# [/dev/cdc-wdm0] PIN status retrieved successfully
	# [/dev/cdc-wdm0] PIN1:
	#	Status: enabled-verified
	#	Verify: 3
	#	Unblock: 10
	# [/dev/cdc-wdm0] PIN2:
	#	Status: blocked
	#	Verify: 0
	#	Unblock: 10

	local pin_info
	local pin_status
	local pin_retries

	pin_info="$(_qmicli --dms-uim-get-pin-status)" || \
		{ _error 1;  exit 1; }
	pin_status="$(_qmicli_value "${pin_info}" "Status" 1)"
	pin_retries="$(_qmicli_value "${pin_info}" "Verify" 1)"

	case "${pin_status}" in
		"enabled-verified"|"enabled-not-verified")
			echo ${pin_retries}
			;;
		*)
			echo 0
			;;
	esac
}

# sim card pin code protection
qmi_pin_protection(){
	local pin_enable="$1"
	local pin_current="$2"
	local par_fail="0"


	# user does not give current pin
	[ $# -ne 2 ] && par_fail="1"
	[ x"$pin_enable" != x"enable" ] && [ x"$pin_enable" != x"disable" ] && par_fail="1"

	if [ x"$par_fail" == x"1" ]; then
		_cell_usage_cmd _cell_usage_pin_protection
		_exit 4
	else
		_qmicli --uim-set-pin-protection=PIN1,$pin_enable,$pin_current
		_exit 0
	fi

}

qmi_set_flight_mode() {
	local flight=${1:-1}
	local mode=""

	# flight mode (persistent-low-power)
	# online mode (online)
	if [ ${flight} -eq 1 ]; then
		mode="low-power"
	else
		mode="online"
	fi
	if _qmicli --dms-set-operating-mode="${mode}" &> /dev/null; then
		return 0
	fi
	return 1
}

qmi_get_profiles() {
	_qmicli --wds-get-profile-list=3gpp | \
		awk -f ${CONF_DIR}/qmicli-profile-scan.awk
}

qmi_module_ids() {
	local info=

	info="$(_qmicli --dms-get-ids)" || \
		{ _error 1;  exit 1; }
	echo IMEI: $(_qmicli_value "${info}" "IMEI")
	echo ESN: $(_qmicli_value "${info}" "ESN")
}

qmi_iccid() {
	local info=

	info="$(_qmicli --dms-uim-get-iccid)" || \
		{ _error 1;  exit 1; }
	echo ICC-ID: $(_qmicli_value "${info}" "ICCID")
}

qmi_imsi() {
	local info=

	info=$(_qmicli --dms-uim-get-imsi) || \
		{ _error 1;  exit 1; }
	echo IMSI: $(_qmicli_value "${info}" "IMSI")
}

qmi_location_info() {
	local info=""
	local service=""
	local lac=""
	local tac=""
	local cellid=""
	local bid=""
	local nid=""
	local lcid=""


	info="$(_qmicli --nas-get-cell-location-info)" || \
		{ _error 1;  exit 1; }
	service="$(echo "${info}" | sed -n 2p | sed 's/[\t ]Info//g')"
	if [[ ${service} == CDMA* ]]; then
		bid=$(_qmicli_value "${info}" "Base Station ID")
		nid=$(_qmicli_value "${info}" "Network ID")
	elif [ x"${service}" == x"Intrafrequency LTE" ]; then
		cellid=$(_qmicli_value "${info}" "Global Cell ID")
		tac=$(_qmicli_value "${info}" "Tracking Area Code")
	else
		cellid=$(_qmicli_value "${info}" "Cell ID")
		lac=$(_qmicli_value "${info}" "Location Area Code")
		lcid=$(_qmicli_value "${info}" "${service} Cell ID")
	fi

	# for UMTS
	if [ x"$lac" != x"" ]; then
		echo LAC: $lac
	fi
	# for LTE
	if [ x"$tac" != x"" ]; then
		echo TAC: $tac
	fi
	# for CDMA
	if [ x"$nid" != x"" ]; then
		echo NID: $nid
	fi

	# for UMTS & LTE
	if [ x"$cellid" != x"" ]; then
		echo CellID: $cellid
	fi
	# for CDMA
	if [ x"$bid" != x"" ]; then
		echo BID: $bid
	fi

	if [ x"$lcid" != x"" ]; then
		echo LCID: $lcid
	fi
}

#--------- at functions -----------

# AT command
_at_cmd() {
	local at_port=$1
	local cmd="${2}"
	local timeout=${3:-0.3}
	local force_timeout=$((${timeout%.*} + 1))


	ret=$(timeout ${force_timeout} \
		mxat -d "${at_port}" -c "${cmd}" -t ${timeout})
	echo "${ret}"
	if echo "${ret}" | grep -q "OK"; then
		return 0
	fi
	return 1
}
cell_at_cmd() {
	local at_port=""
	local cmd="$1"
	local timeout="${2:-${AT_TIMEOUT}}"
	local ret=0


	if [ x"$cmd" == x"" ]; then
		_error 0 "Error: Please assign the AT command."
		_cell_usage_cmd _cell_usage_at
		_exit 4
	fi

	if [ ! -z "$3" ]; then
		at_port="$3"
	elif [ x"${AT_PORT}" == x"NotSupport" ]; then
		if [ x"${MODEM_PORT}" == x"NotSupport" ]; then
			_error 0 "Error: AT port and modem port not available."
			return 3
		fi
		at_port=${MODEM_PORT}
	else
		at_port=${AT_PORT}
	fi

	ls -l ${at_port} &> /dev/null
	if [ $? -ne 0 ]; then
		_error 0 "Error: AT/Modem port (${at_port}) not ready."
		return 1
	fi

	# exit if module crashed
	_double_check_module_crash_exit

	at_cmd="_at_cmd \"${at_port}\" '"${cmd}"' ${timeout}"

	_lock ${LOCK_FD} ${LOCK_FILE} ${FLOCK_TIMEOUT}
	exec_func "${at_cmd}"
	ret=$?
	_unlock ${LOCK_FD}

	return ${ret}
}

at_signal_csq() {
	local signal="99"

	signal=$(cell_at_cmd 'AT+CSQ')
	if [ $? -ne 0 ]; then
		echo "99"
		_exit 1
	fi
	signal=$(echo "${signal}" | grep "^+CSQ:" | awk '{print $2}' | cut -d',' -f1)
	echo ${signal}
}

at_signal() {
	local service="umts"
	local sig_strength=""
	local sig_level=""
	local rat="none"
	service=$(_exec_op_rtn service | grep "Service:" | \
		cut -d ' ' -f 2- | awk '{print tolower($0)}')
	${service:-umts}
	sig_info=$(_signal_csq2dbm $(at_signal_csq))
	if [ "${sig_info}" = "-999" ]; then
		sig_strength="-999"
	else
		sig_strength=$(echo "${sig_info}" | cut -d ' ' -f 1)
	fi
	sig_level=$(_signal_strength_to_level ${sig_strength} "RSSI")
	[ "${service}" = "umts" ] && rat="3G"
	[ "${service}" = "lte" ] && rat="4G"

	echo "${rat} Level ${sig_level}"
}

at_sim_status() {
	cell_at_cmd 'AT+CPIN?' | grep '^+CPIN:'
}

# unlock pin code
at_unlock_pin() {
	local ret=""

	ret=$(cell_at_cmd "AT+CPIN=$1" 1)
	if [ $? -ne 0 ]; then
		echo $ret
		_exit 1
	fi
}

at_pin_protection(){
	local pin_enable="0"
	local pin_code="$2"
	local par_fail="0"
	local cmd="AT+CLCK=\"SC\",2"
	local ret=""


	ret=$(cell_at_cmd "${cmd}" 1) || return $?
	ret=$(echo "${ret}" | grep "^+CLCK:" | awk '{print $2}')

	if [ x"$1" == x"enable" ]; then
		if [ x"${ret}" == x"1" ]; then
			echo "PIN code of the SIM card already enabled."
			return 0
		fi
		pin_enable=1
	elif [ x"$1" == x"disable" ]; then
		if [ x"${ret}" == x"0" ]; then
			echo "PIN code of the SIM card already disabled."
			return 0
		fi
		pin_enable=0
	else
		_cell_usage_cmd _cell_usage_pin_protection
		return 4
	fi

	cmd="AT+CLCK=\"SC\",${pin_enable},\"${pin_code}\""
	ret=$(cell_at_cmd "${cmd}" 1) || return $?
}

at_set_flight_mode() {
	local flight=${1:-1}
	local reset=${2}
	local mode=

	# flight mode (persistent-low-power)
	# online mode (online)
	if [ x"${flight}" == x"1" ]; then
		[ x"${reset}" == x"1" ] && mode="4,1" || mode=4
	else
		[ x"${reset}" == x"1" ] && mode="1,1" || mode=1
	fi
	[ "${reset}" == "1" ] && run-parts --new-session ${MODULE_OFF_DIR}
	if cell_at_cmd "AT+CFUN=${mode}" 5 | grep -q "OK"; then
		[ "${reset}" == "1" ] && sleep 5 && _cell_wait_module_back
		return 0
	fi
	return 1
}

# get profile list
at_get_profiles() {
	local output=""
	local ret=0


	output=$(cell_at_cmd 'AT+CGDCONT?') || return $?
	echo "${output}" | grep "^+CGDCONT:" | cut -d' ' -s -f 2 | \
		awk -F"," '{print $1","$3","$2}' | sed -e 's/\"//g'
}

at_set_profile() {
	local id="$1"
	local apn="$2"
	local type="${3}"


	if [ -z "${apn}" ]; then
		data="${id}"
	else
		data="${id},\"${type}\",\"${apn}\""
	fi
	cell_at_cmd "AT+CGDCONT=${data}" 1>/dev/null
}

at_set_apn() {
	_exec_op_rtn set_profile 1 "${APN}" "IPV4V6"
}

# +CNUM: "ABC","12345678901",129
# OK
at_number() {
	local number=""
	local ret=0


	number=$(cell_at_cmd 'AT+CNUM') || return $?

	number=$(echo "${number}" | grep "^+CNUM:" | head -1 | sed 's/"//g')
	if [ ! -z "${number}" ]; then
		echo $(echo "${number}" | cut -d ',' -f 2)
	else
		echo "No number."
	fi
}

# telecommunication operator
at_operator() {
	local result=""
	local ret=0


	result=$(cell_at_cmd 'AT+COPS?') || return $?

	result=$(echo "${result}" | grep "^+COPS:" | \
		cut -d ' ' -f 2- | cut -s -d ',' -f 3 | sed 's/\"//g')
	if [ ! -z "${result}" ]; then
		echo ${result}
	else
		echo "Unknown"
	fi
}

at_imsi() {
	local result=""
	local ret=0


	result=$(cell_at_cmd 'AT+CIMI') || return $?

	result=$(echo "${result}" | grep '^[0-9]*.$')
	echo IMSI: $result
}

at_location_info() {
	local ret=0
	local output=""
	local lac=""
	local cellid=""


	cell_at_cmd 'AT+CREG=2' 1> /dev/null || return $?
	output=$(cell_at_cmd 'AT+CREG?') || return $?
	output=$(echo "${output}" | grep '^+CREG:')
	[ "${#output}" -lt "11" ] && output=""
	if [ x"$ret" != x"" ]; then
		lac=${output#*\"}
		lac=${lac%%\"*}
		cellid=${output%\"*}
		cellid=${cellid##*\"}
	fi

	if [ x"$lac" != x"" ]; then
		echo LAC: $lac
	fi
	if [ x"$cellid" != x"" ]; then
		echo CellID: $cellid
	fi
}

at_module_ids() {
	local info=""
	local ret=0


	info="$(cell_at_cmd 'ATI')" || return $?

	local imei=$(echo "${info}" | grep "^IMEI:" | awk '{print $2}')
	local esn=$(echo "${info}" | grep "^ESN:" | awk '{print $2}')

	echo IMEI: $imei
	echo ESN: $esn
}

at_wvdial_template_file() {
	echo ${CONF_DIR}/wvdial/wvdial.conf.template
}

at_ppp_template_file() {
	echo ${CONF_DIR}/ppp/peers/wvdial.template
}

# Using wvdial & ppp as default
at_start_network() {
	local ignore_dns_gw=0

	local isp_name=wvdial-${SLOT}
	local wvdial_temp=$(_exec_op_rtn wvdial_template_file)
	local wvdial_conf=$(cat ${wvdial_temp})
	local wvdial_path=${CONF_DIR}/wvdial/wvdial-${SLOT}.conf
	local ppp_temp=$(_exec_op_rtn ppp_template_file)
	local ppp_conf=$(cat ${ppp_temp})
	local ppp_path=/etc/ppp/peers/${isp_name}
	local modem_port=


	_start_network_init $@

	wvdial_conf=$(echo "${wvdial_conf}" | sed -e "s|@apn@|${APN}|g")
	if [ x"${MODEM_PORT}" == x"NotSupport" ]; then
		if [ x"${AT_PORT}" == x"NotSupport" ]; then
			_error 0 "Error: AT port and modem port not available."
			return 3
		fi
		modem_port=${AT_PORT}
	else
		modem_port=${MODEM_PORT}
	fi
	wvdial_conf=$(echo "${wvdial_conf}" | sed -e "s|@modem_port@|${modem_port}|g")
	if [ x"${Phone}" == x"" ]; then
		Phone="*99#"
	fi
	wvdial_conf=$(echo "${wvdial_conf}" | sed -e "s|@phone@|${Phone}|g")
	wvdial_conf=$(echo "${wvdial_conf}" | sed -e "s|@username@|${Username}|g")
	wvdial_conf=$(echo "${wvdial_conf}" | sed -e "s|@password@|${Password}|g")
	_auth_command=$(_exec_op at_auth_command 2>/dev/null)
	_radio_access_command=$(_exec_op at_radio_access_command 2>/dev/null)
	_operator_command=$(_exec_op at_operator_command 2>/dev/null)
	wvdial_conf=$(echo "${wvdial_conf}" | \
		sed -e "s|@auth_command@|${_auth_command}|g")
	wvdial_conf=$(echo "${wvdial_conf}" | \
		sed -e "s|@operator_command@|${_operator_command}|g")
	wvdial_conf=$(echo "${wvdial_conf}" | \
		sed -e "s|@radio_access_command@|${_radio_access_command}|g")
	echo "${wvdial_conf}" > ${wvdial_path}

	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@wvdial_conf@|${wvdial_path}|g")
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@slot@|${SLOT}|g")

	# ignore-dns-gw
	local route=defaultroute
	local dns=

	if [ x"$ignore_dns_gw" == x"1" ];   then
		route=nodefaultroute
		dns=nodns
	fi
	# defaultroute | nodefaultroute
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@defaultroute@|${route}|g")
	# nodns or ""
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@nodns@|${dns}|g")
	# name
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@slot@|${SLOT}|g")
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@pkg@|${PKG}|g")
	ppp_conf=$(echo "${ppp_conf}" | sed -e "s|@modem_port@|${modem_port}|g")
	echo "${ppp_conf}" > ${ppp_path}
	if [ ! -z "${Auth}" ] && [ x"${Auth}" != x"NONE" ]; then
		echo "user ${Username}" >> ${ppp_path}
		echo "password ${Password}" >> ${ppp_path}
        fi

	echo "Status: connecting" > "${STATUS_FILE}"
	pon ${isp_name} linkname ${SLOT}
	_save_state "PPP_ISP_NAME" ${isp_name}
}

at_stop_network() {
	[ ! -z "${PPP_ISP_NAME}" ] && poff ${PPP_ISP_NAME}
	_clear_state
}

at_attach_status() {
	local info=""
	local status="0"
	local service="0"
	local cs="detached"
	local ps="detached"


	info=$(cell_at_cmd "AT^SYSINFO") || return $?
	info=$(echo "${info}" | grep "^\^SYSINFO:" | awk '{print $2}')
	status=$(echo "${info}" | cut -d ',' -f 1)
	service=$(echo "${info}" | cut -d ',' -f 2)
	if [ "${status}" == "2" ]; then
		if [ "${service}" == "1" ]; then
			cs="attached"
		elif [ "${service}" == "2" ]; then
			ps="attached"
		elif [ "${service}" == "3" ]; then
			cs="attached"
			ps="attached"
		fi
	fi
	echo "CS: ${cs}"
	echo "PS: ${ps}"
}

at_status() {
	[ ! -z "${PPP_ISP_NAME}" ] && \
		[ ! -f "/var/run/ppp-${SLOT}.pid" ] && \
		_clear_state &> /dev/null

	if [ -f "${STATUS_FILE}" ]; then
		cat "${STATUS_FILE}"
		_exit 0
	else
		echo "Status: disconnected"
		_exit 1
	fi
}

#--------- generic functions -----------

_cell_initialize() {
	local module_id="${2:-${ALL_MODULE_ID[$1]}}"
	local ret=0

	if _func_exist _module_${module_id}_initialize; then
		_module_${module_id}_initialize $@ || return $?
	else
	        for i in {1..10}; do
			cell_at_cmd "ATE0" 10 > /dev/null 2>&1 && break
			[ $i -eq 10 ] && return 1 || sleep 1
		done
		stty -F ${AT_PORT} -echo raw || return $?
	fi
	if _func_exist _module_${module_id}_init_profile; then
		_module_${module_id}_init_profile || return $?
	fi
	echo "_module_${module_id}_initialize" > ${INIT_FILE}
}

cell_initialize() {
	if [ "$1" == "all" ]; then
		for (( i=0 ; i<${#ALL_INTERFACE[@]} ; i++ )); do
			_cell_initialize $i
		done
	else
		_cell_initialize ${INTERFACE}
	fi
}

_cell_interface_by_slot() {
	local slot=$1


	for (( i=0 ; i<${#ALL_INTERFACE[@]} ; i++ )); do
		if [ x"${ALL_SLOT_NUM[$i]}" == x"${slot}" ]; then
			echo $i
			return 0
		fi
	done
	return 4
}
_cell_wait_module_back() {
	local slot=${SLOT}
	local vendor_id=""
	local product_id=""
	local module_id=""
	local timeout=${1:-120}
	local _times=0


	while ((1))
	do
		product_id=$(cat ${MODULE_PATH[$((SLOT-1))]}/idProduct \
			 2> /dev/null)
		if [ x"${product_id}" != x"" ]; then
			_init
			INTERFACE=$(_cell_interface_by_slot ${slot})
			[ -z "${INTERFACE}" ] && \
				_error 6 && _exit 6
			_set_variables
			_cell_initialize ${INTERFACE}
			run-parts --new-session ${MODULE_ON_DIR}
			break
		fi
		_times=$((_times+1))
		[ ${_times} -ge ${timeout} ] && \
			_error 6 && _exit 6

		sleep 1
	done
}

# cellular power on by GPIO
cell_power_on() {
	local power_on_funcs="$(echo "$POWER_ON_FUN" | sed -e "s/\$SLOT/$SLOT/g")"

	IFS=$'\n'
	for i in $power_on_funcs ;
	do
		eval "$i"
	done

	_cell_wait_module_back
}

# cellular power off by GPIO
cell_power_off() {
	run-parts --new-session ${MODULE_OFF_DIR}
	_exec_op_rtn stop_network 2> /dev/null
	_qmi_kill_proxy


	power_off_funcs="$(echo "$POWER_OFF_FUN" | sed -e "s/\$SLOT/$SLOT/g")"

	IFS=$'\n'
	for i in $power_off_funcs; do
		eval "$i"
	done

	while ((1)); do
		product_id=$(cat ${MODULE_PATH[$((SLOT-1))]}/idProduct \
			 2> /dev/null)
		if [ x"${product_id}" == x"" ]; then
			break;
		fi

		sleep 1
	done
}

#cellular power off and on by GPIO
cell_power_cycle() {
	cell_power_off
	sleep 1
	cell_power_on
}

cell_switch_sim() {
	local sim_slot=$1


	if [ -z "$SWITCH_SIM_FUNC" ]; then
		_error 3
		_exit 3
	fi

	if [ -z "$sim_slot" ] || \
			[ "$sim_slot" != "1" ] && [ "$sim_slot" != "2" ]; then
		_error 0 "Error: Please specify a valid SIM slot id."
		_cell_usage_cmd _cell_usage_switch_sim
		_exit 4
	fi

	_exec_op_rtn stop_network &> /dev/null
	_qmi_kill_proxy &> /dev/null

	switch_sim_funcs="$(echo "$SWITCH_SIM_FUNC" | \
		sed -e "s/\$SLOT/$SLOT/g" | sed -e "s/\$SIM_SLOT/$sim_slot/g")"

	IFS=$'\n'
	for i in $switch_sim_funcs; do
		eval "$i"
	done
	cell_power_cycle
}

cell_set_profile() {
	local id="$1"
	local apn="$2"
	local type="${3:-IPV4V6}"
	local ret=0

	if [ $# -lt 1 ]; then
		_cell_usage_cmd _cell_usage_set_profile
		_exit 4
	fi

	[ -z "${apn}" ] && apn="\"\""
	_exec_op_rtn set_flight_mode 1
	ret=$?
	if [ ${ret} -eq 0 ]; then
		_exec_op_rtn set_profile "${id}" "${apn}" "${type}"
		ret=$?
	fi
	_exec_op_rtn set_flight_mode 0
	[ $? -eq 0 ] && _exit ${ret}
	_exit 1
}

# set apn into PDP context id 1
cell_set_apn() {
	local ret
	local apn="$1"

	if [ -z "${apn}" ]; then
		_cell_usage_cmd _cell_usage_set_apn
		_exit 4
	fi

	local _id="$INTERFACE"
	[ "${_id}" == "0" ] && _id=""

	if [ -e $PROFILE_FILE ]; then
		old_APN=APN${_id}
		echo "old APN=${!old_APN}, new APN=$apn"
		_update_profile APN${_id} $apn
	else
		echo -e "APN=$apn\nusername=\npassword=\nPIN="
	fi
	APN=${apn}

	_exec_op_rtn set_apn "${APN}" || _exit 1
	_exit 0
}

# dhclient
cell_dhclient() {
	local node="$1"
	local ignore_dns_gw="$2"


	dhclient -nw -pf /var/run/dhclient-${node}.pid \
		-e PKG=${PKG} \
		-e SLOT=${SLOT} \
		-e IGNORE_DNS_GW=${ignore_dns_gw} \
		$node
}

cell_dhclient_kill() {
	local node="$1"

	dhclient -r -pf /var/run/dhclient-${node}.pid \
		-e PKG=${PKG} \
		-e SLOT=${SLOT} \
		${node}
}

# udhcpc
cell_udhcpc() {
	local node="$1"
	local ignore_dns_gw="$2"
	local udhcpc_opt=""
	local udhcpc_conf=""
	local udhcpc_script_path="${CONF_DIR}/udhcpc/${SLOT}-udhcpc.script"

	# setup udhcpc script
	if [ x"$ignore_dns_gw" == x"1" ];   then
		udhcpc_conf="${CONF_DIR}/udhcpc/ignore-gw-dns.script.tmpl"
	else
		udhcpc_conf="${CONF_DIR}/udhcpc/default.script.tmpl"
	fi
	udhcpc_conf=$(cat "${udhcpc_conf}" | sed -e "s|@slot@|${SLOT}|g")
	udhcpc_conf=$(echo "${udhcpc_conf}" | sed -e "s|@conf_dir@|${CONF_DIR}|g")
	echo "${udhcpc_conf}" > ${udhcpc_script_path}
	chmod a+x ${udhcpc_script_path}

	udhcpc_opt="-s ${udhcpc_script_path}"

	#udhcpc -q -f ${udhcpc_opt} -i $node
	udhcpc -b -R ${udhcpc_opt} -i $node -p /var/run/udhcpc-${node}.pid
}

cell_udhcpc_kill() {
	local node="$1"

	cat /var/run/udhcpc-${node}.pid 2> /dev/null| \
		xargs --no-run-if-empty kill
}

# module info
cell_module_info() {
	echo SLOT: $SLOT
	echo Module: $MODULE_NAME
	echo WWAN_node: $QMI_NODE
	echo AT_port: $AT_PORT
	echo GPS_port: $GPS_PORT
	echo QMI_port: $QMI_PORT
	echo Modem_port: $MODEM_PORT
	echo "AT_port (resvered): ${AT_PORT_RESV}"
}

# module info (fully)
cell_m_info() {
	local info=""
	local lac=""
	local cellid=""
	local lcid=""
	local ret=""
	local iccid=""
	local imei=""


	iccid="$(_exec_op_rtn iccid | cut -d' ' -s -f 2-)"

	info="$(_exec_op_rtn location_info)"
	cellid="$(echo "${info}" | grep "CellID:" | cut -d' ' -s -f 2-)"
	lac="$(echo "${info}" | grep "LAC:" | cut -d' ' -s -f 2-)"
	lcid="$(echo "${info}" | grep "LCID:" | cut -d' ' -s -f 2-)"

	info="$(_exec_op_rtn module_ids)"
	imei="$(echo "${info}" | grep "IMEI:" | cut -d' ' -s -f 2-)"

	echo Module=$MODULE_NAME
	echo WWAN_node=$QMI_NODE
	echo AT_port=$AT_PORT
	echo GPS_port=$GPS_PORT
	echo LAC=$lac
	echo CellID=$cellid
	echo ICC-ID=$iccid
	echo IMEI=$imei
	echo QMI_port=$QMI_PORT
	echo LCID=$lcid
	#echo slot=$SLOT
	#echo SYS_USB=$SYS_USB
}

# verizon auto dialup
cell_vzwauto() {
	local profile=$(_exec_op_rtn get_profiles | grep "^3")


	[ -z "${profile}" ] && \
		_error 0 "Error: PDP context 3 doesn't exist." && _exit 4
	APN=$(echo "${profile}" | cut -d ',' -f 2)
	_exec_op start_network
	#echo -e "\n\n APN=${APN} is in used\n\n"
	_exit 0
}

cell_pin_code_status() {
	local card_status="$(_exec_op sim_status | grep "^+CPIN")"


	if [ "$(echo "${card_status}" | grep "READY")" ]; then
		echo "PIN code: Disabled or verified"
	elif [ "$(echo "${card_status}" | grep "PIN")" ]; then
		echo "PIN code: Not Verified"
	elif [ "$(echo "${card_status}" | grep "PUK")" ]; then
		echo "PIN code: Blocked"
		_exit 1
	else
		_error 0 "Error: SIM card not available, please check."
		_exit 1
	fi
}

_cell_unlock_pin() {
	local pin_code="$1"
	local usage_func="$2"
	local card_status=""


	# unlock PIN
	card_status="$(_exec_op sim_status | grep "^+CPIN")"
	if [ "$(echo "${card_status}" | grep "READY")" ]; then
		echo "PIN code: Disabled or verified"
	elif [ "$(echo "${card_status}" | grep "PIN")" ]; then
		echo "PIN code: Not Verified"
		if [ -z "$pin_code" ]; then
			_error 0 "Error: Please assign the PIN code."
			if [ -z "${usage_func}" ]; then
				_cell_usage_cmd "_cell_usage_unlock_pin"
			else
				_cell_usage_cmd "${usage_func}"
			fi
			_exit 4
		fi
		_exec_op unlock_pin ${pin_code}
		return $?
	elif [ "$(echo "${card_status}" | grep "PUK")" ]; then
		echo "PIN code: Blocked"
		echo "Please unblock PIN first."
		_exit 1
	else
		echo "SIM card not available, please check."
		_exit 1
	fi
}

cell_unlock_pin() {
	local ret=""
	local pin_code="$1"
	local card_status=""

	local _id="$INTERFACE"
	[ "${_id}" == "0" ] && _id=""

	# save to profile
	if [ -e $PROFILE_FILE ]; then
		old_PIN=PIN${_id}
		echo "old PIN=${!old_PIN}, new PIN=$pin_code"
		_update_profile PIN${_id} ${pin_code}
	else
		echo -e "APN=\nusername=\npassword=\nPIN=$pin_code"
	fi
	PIN=${pin_code}

	_cell_unlock_pin ${PIN}
}

# cellular restart
cell_restart() {
	# restart cellular (reconnect)
	_exec_op stop_network
	sleep 5
	_exec_op start_network
}

cell_sim_status() {
	local ret=$(_exec_op sim_status)


	if [ x"$ret" != x"" ]; then
		echo "$ret"
	else
		_error 0 "Error: Please insert the sim card and reboot."
		_exit 1
	fi
}

# cell_mgmt setinterface
cell_interface() {
	case $1 in
		"0" | "1" | "2" | "3")
			[ $1 -ge ${#ALL_INTERFACE[@]} ] && \
				_error 0 "Error: Interface out of range." && \
				_exit 4
			sed -i "s/INTERFACE=.*/INTERFACE=$1/" $PROFILE_FILE
			echo "set interface=$1"
			_exit 0
			;;
		"")
			for (( i=0 ; i<${#ALL_INTERFACE[@]} ; i++ ))
			do
				echo "[$i] ${ALL_INTERFACE[$i]}" \
					"$([ x$i == x$INTERFACE ] &&
						echo '   <Current>')"
			done
			;;
		*)
			_error 0 "Error: Invalid interface."
			_cell_usage_cmd _cell_usage_interface
			_exit 4
			;;
	esac
}

cell_interfaces() {
	echo ${#ALL_INTERFACE[@]}
}

cell_slot() {
	echo ${SLOT}
}

# cell_mgmt version
cell_version() {
	echo "$PKG $VERSION"
}

# show cell_mgmt usage list
_cell_usage_cmd() {
	echo "Usage:"
	eval "$1"
}

_cell_usage_switch_sim() {
	echo -e "	switch_sim <1|2>"
	echo -e "		Switch SIM slot."
}

_cell_usage_at() {
	echo -e "	at <'AT_COMMAND'>"
	echo -e "		Input AT Command."
	echo -e "		Must use SINGLE QUOTATION to enclose AT Command."
}

_cell_usage_interface() {
	echo -e "	interface [interface id]"
	echo -e "		Switching and checking module interface(s)"
}

_cell_usage_unlock_pin() {
	echo -e "	unlock_pin <PIN>"
	echo -e "		Unlock PIN code and save to configuration file."
}

_cell_usage_pin_protection() {
	echo -e "	pin_protection <enable|disable> <current PIN>"
	echo -e "		Set PIN protection in the UIM."
}

_cell_usage_set_apn() {
	#set_apn
	echo -e "	set_apn <APN>"
	echo -e "		Set APN to configuration file and PDP profile."
	echo -e "		And use this APN as the default one."
}

_cell_usage_start() {
	echo -e "	start [OPTIONS]"
	echo -e "		Start network."
	echo -e ""
	echo -e "		OPTIONS:"
	echo -e "		Phone - Phone number (especially for AT based modules)"
	echo -e "		Auth - Authentication type(CHAP|PAP|BOTH), default=NONE."
	echo -e "		Username"
	echo -e "		Password"
	echo -e ""
	echo -e "		example:"
	echo -e "			cell_mgmt start"
	echo -e "			cell_mgmt start Phone=*99#"
}

_cell_usage_switch_carrier() {
	echo -e "	switch_carrier <$CARRIER>"
	echo -e "		Switching between carrier profiles."
}

cell_usage() {
	echo -e "Usage: "
	echo -e "	$0 [-i <module id>] [-s <slot id>] <OPTIONS>\n"
	echo -e "OPTIONS"
	echo -e "	-i <module id>"
	echo -e "		Module identifier, start from 0 and default to 0."
	echo -e "	-s <slot id>"
	echo -e "		Slot identifier, start from 1 and default value depends "
	echo -e "		on module interface."
	echo -e "		example: module 0 may in slot 2"
	#modules
	echo -e "	modules"
	echo -e "		Shows module numbers supported."
	#slot
	echo -e "	slot"
	echo -e "		Shows module slot id"
	#interface
	_cell_usage_interface
	#start
	_cell_usage_start
	#stop
	echo -e "	stop"
	echo -e "		Stop network."
	#power on
	echo -e "	power_on"
	echo -e "		Power ON."
	#power off
	echo -e "	power_off"
	echo -e "		Power OFF."
	#power cycle
	echo -e "	power_cycle"
	echo -e "		Power cycle the module slot."
	#switch sim slot
	_cell_usage_switch_sim
	#gps on
	echo -e "	gps_on"
	echo -e "		GPS ON."
	#gps off
	echo -e "	gps_off"
	echo -e "		GPS OFF."
	#attach_status
	echo -e "	attach_status"
	echo -e "		Query network registration status."
	#status
	echo -e "	status"
	echo -e "		Query network connection status."
	#signal
	echo -e "	signal"
	echo -e "		Get signal strength."
	#at
	_cell_usage_at
	#sim_status
	echo -e "	sim_status"
	echo -e "		Query sim card status."
	#unlock_pin
	_cell_usage_unlock_pin
	#pin_retries
	echo -e "	pin_retries"
	echo -e "		Get PIN code retry remain times."
	#pin_protection
	_cell_usage_pin_protection
	#set_flight_mode
	echo -e "	set_flight_mode [0|1]"
	echo -e "		Set module into flight mode (1) or online mode (0), Default=1."
	#set_apn
	_cell_usage_set_apn
	#check_carrier
	echo -e "	check_carrier"
	echo -e "		Check current carrier."
	_cell_usage_switch_carrier
	#m_info
	echo -e "	m_info (deprecated)"
	echo -e "		Module/SIM information."
	#module_info
	echo -e "	module_info"
	echo -e "		Module information."
	#module_ids
	echo -e "	module_ids"
	echo -e "		Get device IDs (ex: IMEI and/or ESN)."
	#iccid
	echo -e "	iccid"
	echo -e "		Get SIM card ID"
	#imsi
	echo -e "	imsi"
	echo -e "		Get IMSI (International Mobile Subscriber Identity)."
	#location_info
	echo -e "	location_info"
	echo -e "		Get cell location information."
	#operator
	echo -e "	operator"
	echo -e "		Telecommunication operator."
	#verizon auto dial up
	echo -e "	vzwauto"
	echo -e "		Verizon Private Network auto dialup."
	#version
	echo -e "	version"
	echo -e "		Cellular management version."
}

get_op_func() {
	local arg1=$1
	local args="$2"
	local all=0
	local op_func=""


	if [ -z "${INTERFACE}" ]; then
		if [ "${arg1}" != "version" ] && \
			[ "${arg1}" != "power_on" ] && \
			[ "${arg1}" != "power_off" ] && \
			[ "${arg1}" != "power_cycle" ]; then
			for arg in ${2}; do
				[ "${arg}" == "'all'" ] && all=1
			done
			[ "${arg1}" != "initialize" ] && \
				_error 6 && exit 6
			[ "${arg1}" == "initialize" ] && \
				[ "${all}" != "1" ] && \
				_error 6 && exit 6
		fi
	fi

	case "$arg1" in
		# private options
		post_start)
			op_func="$(_get_op_func $arg1)"
			;;
		# public options
		# module info.
		modules)
			op_func=cell_interfaces
			;;
		slot)
			op_func=cell_slot
			;;
		interface)
			op_func=cell_interface
			;;
		module_info)
			op_func=cell_module_info
			;;
		# module related
		fw_update)
			op_func="$(_get_op_func ${arg1})"
			NEED_LOCK=1
			;;
		power_on)
			op_func=cell_power_on
			NEED_LOCK=1
			NEED_INITIALIZE=0
			;;
		power_off)
			op_func=cell_power_off
			NEED_LOCK=1
			NEED_INITIALIZE=0
			;;
		power_cycle)
			op_func=cell_power_cycle
			NEED_LOCK=1
			NEED_INITIALIZE=0
			;;
		switch_sim)
			op_func=cell_switch_sim
			NEED_LOCK=1
			NEED_INITIALIZE=0
			;;
		initialize)
			op_func=cell_initialize
			NEED_LOCK=1
			NEED_INITIALIZE=0
			;;
		check_carrier)
			op_func="$(_get_op_func $arg1)"
			NEED_LOCK=1
			;;
		switch_carrier)
			op_func="$(_get_op_func $arg1)"
			NEED_LOCK=1
			;;
		find_operators)
			op_func="$(_get_op_func $arg1)"
			;;
		set_operator)
			op_func="$(_get_op_func $arg1)"
			;;
		# connection
		start)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		stop)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		## internal use
		ppp_start)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		## internal use
		ppp_stop)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		restart)
			op_func=cell_restart
			;;
		sim_status)
			op_func=cell_sim_status
			;;
		attach_status)
			op_func="$(_get_op_func $arg1)"
			;;
		status)
			op_func="$(_get_op_func $arg1)"
			;;
		service)
			op_func="$(_get_op_func $arg1)"
			;;
		signal)
			op_func="$(_get_op_func $arg1)"
			;;
		signal_adv)
			op_func="$(_get_op_func $arg1)"
			;;
		pin_code_status)
			op_func=cell_pin_code_status
			;;
		unlock_pin)
			op_func="cell_unlock_pin"
			#_exec_op unlock_pin $arg2
			;;
		pin_retries)
			op_func="$(_get_op_func $arg1)"
			;;
		pin_protection)
			op_func="$(_get_op_func $arg1)"
			;;
		set_flight_mode)
			op_func="$(_get_op_func $arg1)"
			;;
		get_profiles)
			op_func="$(_get_op_func $arg1)"
			;;
		set_profile)
			op_func="$(_get_op_func $arg1)"
			;;
		set_apn)
			op_func="cell_set_apn"
			;;
		# others
		at)
			op_func="cell_at_cmd"
			;;
		gps_on)
			op_func="$(_get_op_func $arg1)"
			;;
		gps_off)
			op_func="$(_get_op_func $arg1)"
			;;
		operator)
			op_func="$(_get_op_func $arg1)"
			;;
		m_info)
			op_func=cell_m_info
			;;
		module_ids)
			op_func="$(_get_op_func $arg1)"
			;;
		iccid)
			op_func="$(_get_op_func $arg1)"
			;;
		imsi)
			op_func="$(_get_op_func $arg1)"
			;;
		location_info)
			op_func="$(_get_op_func $arg1)"
			;;
		vzwauto)
			op_func=cell_vzwauto
			;;
		version)
			cell_version
			exit 0
			;;
		help)
			cell_usage
			exit 0
			;;
		*)
			cell_usage
			exit 3
			;;
	esac

	if [ x"${op_func}" == x"" ]; then
		_error 3
		exit 3
	fi

	OP_FUNC="${op_func} ${args}"
}

# find pids of D stat processes related to AT port
_find_d_stat_proc() {
	local key=${1}
	local pids=""
	local d_pids=""

	pids=$(ps wwax | grep "${key}" | grep -v grep | \
			awk '{print $1}')
	for pid in ${pids}; do
		for stat in $(ps -h -o stat -p ${pid} --ppid ${pid}); do
			if [ x"${stat}" == x"D" ]; then
				d_pids+="${pid} "
			fi
		done
	done
	echo ${d_pids}
}

# double check if process realy locked
_is_proc_locked() {
	local key=${1}
	local pids1=""
	local pids2=""

	pids1=$(_find_d_stat_proc "${key}")
	if [ ! -z "${pids1}" ]; then
		pids2=$(_find_d_stat_proc "${key}")

		for pid2 in ${pids2}; do
			for pid1 in ${pids1}; do
				if [ x"${pid1}" == x"${pid2}" ]; then
					echo 1
					return 1
				fi
			done
		done
	fi
	echo 0
	return 0
}

_module_crash_exit() {
	if ! _is_proc_locked "${AT_PORT}" 1>/dev/null 2>&1 || \
		! _is_proc_locked "${PROG}" 1>/dev/null 2>&1 ; then
		echo "1" > ${AT_LOCKED_FILE}
		_error 0 "Error: AT/Modem port (${AT_PORT}) crashed, " \
			"please reboot to recovery."
		exit 99
	fi
}

_double_check_module_crash_exit() {
	if [ -f "${AT_LOCKED_FILE}" ]; then
		# exit if module locked by driver
		_module_crash_exit

		rm "${AT_LOCKED_FILE}"
	fi
}

_lock() {
	local fd=${1:-$LOCK_FD}
	local lock_file=${2:-$LOCK_FILE}
	local wait_timeout=${3}


	# create lock file
	eval "exec $fd>$lock_file"

	# acquier the lock
	if [ -z "${wait_timeout}" ]; then
		flock -n $fd
	else
		flock -w ${wait_timeout} $fd
	fi
	[ $? -eq 0 ] || \
		 { _error 60; _exit 60; }
	return 0
}

_unlock() {
	eval "flock -u ${1:-$LOCK_FD}"
}

_wait_lock() {
	local fd=${1:-${LOCK_FD}}
	local file=${2:-${LOCK_FILE}}
	local timeout=${3:-${FLOCK_TIMEOUT}}
	local start_time=$(date +%s)


	_lock ${fd} ${file} ${timeout}
	_unlock ${fd}
	echo $(expr `date +%s` - ${start_time})
}


exec_func() {
	local cmd="${1}"
	local timeout="${2}"


	# check global lock
	_wait_lock ${LOCK_FD_GLOBAL} ${LOCK_FILE_GLOBAL} ${timeout} \
		1 > /dev/null 2>&1
	[ $? -eq 99 ] && return 99

	eval "${cmd}"
	return $?
}

# Args:                         stat_arr Idx
#-----------                    ---------
# 1  => RAT                     0
# 2  => CSQ                     1
# 3  => RSSI                    2
# 4  => RxQual                  3
# 5  => RSCP                    4
# 6  => ECIO                    5
# 7  => RSRP                    6
# 8  => RSRQ                    7
# 9  => Signal Strength         8
# 10 => Signal Indicator        9
#-----------                    ----------
_format_signal_adv() {
	local stat_arr=(${1})
	local level_info=""
	level_info=$(_signal_strength_to_level ${stat_arr[8]} ${stat_arr[9]})
	echo "RAT: ${stat_arr[0]}"
	echo "CSQ: ${stat_arr[1]}"
	[ "${stat_arr[2]}" != "-" ] && echo "RSSI: ${stat_arr[2]} dBm"
	[ "${stat_arr[3]}" != "-" ] && echo "RXQUAL: ${stat_arr[3]} dB"
	[ "${stat_arr[4]}" != "-" ] && echo "RSCP: ${stat_arr[4]} dBm"
	[ "${stat_arr[5]}" != "-" ] && echo "ECIO: ${stat_arr[5]} dB"
	[ "${stat_arr[6]}" != "-" ] && echo "RSRP: ${stat_arr[6]} dBm"
	[ "${stat_arr[7]}" != "-" ] && echo "RSRQ: ${stat_arr[7]} dB"
	echo "Signal Indicator: ${stat_arr[9]}"
	echo "Signal Level: ${level_info}"
}

_signal_strength_to_level() {
	# $1: signal strength in dBm
	# $2: indicator
	local level="0"
	local threshold=()
	local rssi_threshold="-113 -107 -97 -87 -77 999"
	local rsrp_threshold="-128 -118 -108 -98 -85 999"
	local description=("NoSignal" "Very Poor" "Poor" "Fair" "Good" "Excellent")
	if [ "${2}" = "RSSI" ]; then
		threshold=(${rssi_threshold})
	elif [ "${2}" = "RSRP" ]; then
		threshold=(${rsrp_threshold})
	else
		echo "0 (${description[0]})"
		return
	fi
	for level in 0 1 2 3 4 5; do
		if [ $(echo "${1} > ${threshold[${level}]}" | bc) = "1" ]; then
			continue
		else
			echo "$((level)) (${description[${level}]})"
			return
		fi
	done
}

#--------- Initialize Global variables start ---------

init

#--------- Initialize Global variables end ---------

while getopts  "i:s:" flag; do
	if [ "${flag}" == "i" ]; then
		if [ "${INTERFACE}" != "$OPTARG" ]; then
			INTERFACE=$OPTARG
			_set_variables
		fi
	elif [ "${flag}" == "s" ]; then
		SLOT=$OPTARG
		INTERFACE=$(_cell_interface_by_slot ${SLOT})
		[ ! -z "${INTERFACE}" ] && _set_variables || \
			_set_state_file ${SLOT}
	fi
done

args=""
for i in $(seq $OPTIND 1 $#); do
	eval "var=\$$i"
	if [ "${var}" == "force" ]; then
		FORCE=1
		continue
	fi
	j=$(($j + 1))
	eval "arg$j=\$$i"
	args+=" '${var}'"
done

args="$(echo ${args} | cut -s -d' ' -f2-)"
#echo "args: ${args}"

[ -z "${SLOT}" ] && SLOT=1 && _set_state_file ${SLOT}

# set timeout for specific commands
FLOCK_TIMEOUT=$(eval echo \${${arg1}_timeout})
FLOCK_TIMEOUT=${FLOCK_TIMEOUT:-10}
#echo "timeout ${FLOCK_TIMEOUT}"

# get operation function and initial variables
get_op_func $arg1 "${args}"
#echo "func: ${OP_FUNC}"
#echo "need initialize: ${NEED_INITIALIZE}"
#echo "need global lock: ${NEED_LOCK}"

RET=0
if [ ${NEED_INITIALIZE} -eq 1 ]; then
	init_timeout=$(eval echo \${initialize_timeout})
	init_timeout=${init_timeout:-10}
	exec_func "_cell_initialize ${INTERFACE}" "${init_timeout}"
fi

if [ ${NEED_LOCK} -eq 1 ]; then
	$(_double_check_module_crash_exit 1>/dev/null 2>&1)
	[ $? -ne 99 ] && wait_time=$(_wait_lock) || wait_time=0

	_lock ${LOCK_FD_GLOBAL} ${LOCK_FILE_GLOBAL} \
		$(expr ${FLOCK_TIMEOUT} - ${wait_time})
	eval "${OP_FUNC}"
	RET=$?
	_unlock ${LOCK_FD_GLOBAL}
else
	exec_func "${OP_FUNC}" "${FLOCK_TIMEOUT}"
	RET=$?
fi
_exit ${RET}
