#!/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="1.29.2"
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

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
}

_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
		>&2 echo "Error: Operation not support."
		return 3
	fi
}

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

	if [ x"${func}" == x"" ]; then
		>&2 echo "Error: Operation not support."
		_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
		>&2 echo "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
			>&2 echo "Error: Module \"${module_name}\" not support."
			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}\"" 2>/dev/null
		if [ $? -ne 0 ]; then
			>&2 echo "Error: Initialize module \"${ALL_MODULE_NAME[$id]}\" failed."
			continue
		fi
	done

	if [ $unsupport_num -gt 0 ] && [ $id -lt 0 ]; then
		>&2 echo "Error: only unsupport module on slot."
		_exit 5
	fi
}

_set_variables() {
	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]}"
	if [ x"${ALL_QMI_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		QMI_PORT="NotSupport"
	else
		QMI_PORT="/dev/${ALL_QMI_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_AT_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		AT_PORT="NotSupport"
	else
		AT_PORT="/dev/${ALL_AT_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_AT_PORT_RESV[$INTERFACE]}" == x"NotSupport" ]; then
		AT_PORT_RESV="NotSupport"
	else
		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
		GPS_PORT="/dev/${ALL_GPS_PORT[$INTERFACE]}"
	fi
	if [ x"${ALL_MODEM_PORT[$INTERFACE]}" == x"NotSupport" ]; then
		MODEM_PORT="NotSupport"
	else
		MODEM_PORT="/dev/${ALL_MODEM_PORT[$INTERFACE]}"
	fi
	AT_TIMEOUT="${ALL_AT_TIMEOUT[$INTERFACE]}"

	# state file for different interface
	if [ x"$INTERFACE" != x"" ]; then
		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"
	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
		local ifname="$(_exec_op status | grep "^PPPIFName: " | \
			awk '{print $2}')"
		QMI_NODE=${ifname}
	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 [ x"${INTERFACE}" == x"" ] || \
		[ ${INTERFACE} -ge ${#ALL_INTERFACE[@]} ]; then
		INTERFACE=0
	fi
	_set_variables
}

init() {
	if [ -f $PROFILE_FILE ]; then
		_load_profile
		_init
	else
		>&2 echo "Error: "${PROFILE_FILE}" does not exist." && \
			_exit 2
	fi
}

_signal_csq2dbm() {
	local signal="$1"
	local service="$2"

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

	if [ x"$signal" != x"" ]; then
		if [ "$signal" -le "30" 2>/dev/null ] && [ "$signal" -ge "2" 2>/dev/null ] ; then
			let signal=signal-2
			let signal=signal*2-109
			echo "${service} $signal dbm"
		else
			echo "signal out of range"
		fi
	else
		echo "signal out of range"
	fi
}

_signal_dbm2csq() {
	local signal="$1"


	# csq = (dbm + 113) / 2
	if [ "${signal}" == "signal out of range" ] || [ $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"
}

_start_network_init() {
	ignore_dns_gw=0
	local card_status=""
	local interface_num="$INTERFACE"
	[ x"$interface_num" == x"0" ] && interface_num=""


	APN="$(eval echo \$APN$interface_num)"
	PIN="$(eval echo \$PIN$interface_num)"
	Phone="$(eval echo \$Phone$interface_num)"
	Username="$(eval echo \$Username$interface_num)"
	Password="$(eval echo \$Password$interface_num)"
	Auth="$(eval echo \$Auth$interface_num)"

	# modify APN Username Password PIN
	for args in $@; do
		for par in "APN" "Auth" "Username" "Password" "PIN" "Phone"; do
			if [ $(echo $args | grep "$par=") ]; then
				eval "$par=${args##*$par=}"
				eval "sed -i s/$par$interface_num=.*/$par$interface_num=\$$par/ $PROFILE_FILE"
			fi
		done
		[ x"$args" == x"ignore-dns-gw" ] && ignore_dns_gw=1
	done

	# check APN
	if [ x"$APN" == x"" ]; then
		>&2 echo "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
		>&2 echo "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_cmd=""
	local qmi_std=""
	local qmi_err=""
	local qmi_ret=0
	local cmd=""

	qmi_cmd="$QMICLI -d $QMI_PORT $@ $QMICLI_END"
	#echo ${qmi_cmd}

	for i in {1..5}; do
		eval "$( (${qmi_cmd}) 2> \
			>(qmi_err=$(cat); typeset -p qmi_err) > \
			>(qmi_std=$(cat); typeset -p qmi_std); \
			qmi_ret=$?; typeset -p qmi_ret )"
		# error 3 (internal) and 31 (invalid service type)
		if ! (echo ${qmi_err} | grep "QMI protocol error (3)") &&
			! (echo ${qmi_err} | grep "QMI protocol error (31)"); then
			echo "${qmi_std}"
			return 0
		fi
	done

	# reset qmi service
	>&2 echo "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
		>&2 echo "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} || \
			(>&2 echo "Error: Cannot update data format." && \
			 _exit 1)
	elif [ x"${QMI_PROTOCOL}" == x"raw-ip" ]; then
		>&2 echo "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
			>&2 echo "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
		>&2 echo "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
		CONN="$(_qmicli_value "$(${STATUS_CMD})" "Connection status")"
	fi

	if [ x"$CONN" == x"" ]; then
		>&2 echo "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="$(_qmicli --uim-get-card-status)"
	local 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="$(_qmicli --nas-get-serving-system)"
	local cs=$(_qmicli_value "${info}" "CS")
	local ps=$(_qmicli_value "${info}" "PS")

	echo "CS: ${cs}"
	echo "PS: ${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 dbm
qmi_signal() {
	local signal


	#signal=`$QMICLI -d ${QMI_PORT} --nas-get-signal-strength $QMICLI_END | tr "'" " " | grep "Network" | head -1 | awk '{print $2 " " $4}'`
	signal=`_qmicli --nas-get-signal-strength | tr "'" " " | grep "Network" | head -1 | awk '{print $2 " " $4}'`
	signal_dbm="$(echo $signal | awk '{print $2}')"
	if [ "$signal_dbm" -le "-1" 2>/dev/null ]; then
		echo "${signal} dbm"
		return 0
	else
		echo "signal out of range"
		return 1
	fi
}

# cell_signal_adv
qmi_signal_adv() {
	local csq
	local rssi
	local ecio
	local signal

	signal=$(_qmicli --nas-get-signal-strength | \
		 sed "s/'\|'://g;s/^[ \t]*Network //g")
	rssi=$(echo "${signal}" | awk '/Current:/{getline; print}')
	ecio=$(echo "${signal}" | awk '/ECIO:/{getline; print}')

	#csq=$(at_signal_csq)
	signal=$(echo "${rssi}" | awk '{print $(NF-1)}')
	csq=$(_signal_dbm2csq ${signal})
	[ -z "${csq}" ] && csq=99

	echo "CSQ: ${csq}"
	echo "RSSI: ${rssi}"
	echo "EcIo: ${ecio}"
	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
			cat /var/run/udhcpc-${QMI_NODE}.pid 2> /dev/null| \
				xargs --no-run-if-empty kill
		else
			cell_dhclient_kill "${QMI_NODE}"
		fi
	fi
	# cellular stop by qmi-network
	_qmi_stop_network
}

qmi_sim_status() {
	_qmi_card_status
}

# unlock pin code
qmi_unlock_pin() {
	ret=$(_qmicli --uim-verify-pin=PIN1,$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="$(_qmicli --dms-uim-get-pin-status)"
	local pin_status="$(_qmicli_value "${pin_info}" "Status" 1)"
	local 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="$(_qmicli --dms-get-ids)"
	local imei=$(_qmicli_value "${info}" "IMEI")
	local esn=$(_qmicli_value "${info}" "ESN")

	echo IMEI: $imei
	echo ESN: $esn
}

qmi_iccid() {
	local iccid=$(_qmicli_value "$(_qmicli --dms-uim-get-iccid)" "ICCID")
	echo ICC-ID: $iccid
}

qmi_imsi() {
	local imsi=$(_qmicli_value "$(_qmicli --dms-uim-get-imsi)" "IMSI")
	echo IMSI: $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)"
	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
		>&2 echo "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
			>&2 echo "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
		>&2 echo "Error: AT/Modem port (${at_port}) not ready."
		return 3
	fi

	# exit if module crashed
	_double_check_module_crash_exit

	cmd=$(echo "${cmd}" | sed 's/"/\\"/g')
	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"

	service=$(_exec_op_rtn service | grep "Service:" | \
		cut -d ' ' -f 2- | awk '{print tolower($0)}')
	_signal_csq2dbm $(at_signal_csq) ${service:-umts}
}

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
	if cell_at_cmd "AT+CFUN=${mode}" 2 | 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
			>&2 echo "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")
	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}

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

at_stop_network() {
	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() {
	if [ -f "${STATUS_FILE}" ]; then
		cat "${STATUS_FILE}"
		_exit 0
	else
		echo "Status: disconnected"
		_exit 1
	fi
}

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

_cell_initialize() {
	local module_id="${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
	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=""


	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}" ] && \
				>&2 echo "Error: Module not found." && _exit 6
			_set_variables
			_cell_initialize ${INTERFACE}
			break
		fi

		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() {
	_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
		>&2 echo "Error: Operation not support."
		_exit 3
	fi

	if [ -z "$sim_slot" ] || \
			[ "$sim_slot" != "1" ] && [ "$sim_slot" != "2" ]; then
		>&2 echo "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}"


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

	[ -z "${apn}" ] && apn="\"\""
	_exec_op_rtn set_flight_mode 1 && \
		_exec_op_rtn set_profile "${id}" "${apn}" "${type}" && \
		_exec_op_rtn set_flight_mode 0 &&
		_exit 0
	_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

	if [ -e $PROFILE_FILE ]; then
		echo "old APN=$APN, new APN=$apn"
		sed -i "s/APN=.*/APN=$apn/" $PROFILE_FILE
	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=""

	if [ x"$ignore_dns_gw" == x"1" ];   then
		udhcpc_opt="-s /etc/udhcpc/ignore-gw-dns.script"
	fi
	#udhcpc -q -f ${udhcpc_opt} -i $node
	udhcpc -b -R ${udhcpc_opt} -i $node -p /var/run/udhcpc-${node}.pid
}

# 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}" ] && \
		>&2 echo "Error: PDP context 3 doesn't exist." && _exit 1
	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
		>&2 echo "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
			>&2 echo "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=""



	# save to profile
	if [ -e $PROFILE_FILE ]; then
		echo "old PIN=$PIN, new PIN=$pin_code"
		sed -i "s/PIN=.*/PIN=$pin_code/" $PROFILE_FILE
	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
		>&2 echo "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[@]} ] && \
				>&2 echo "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
			;;
		*)
			>&2 echo "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_profile() {
	#set_profile
	echo -e "	set_profile <id> [APN [PDP Type]]"
	echo -e "		Update PDP profile. Profile will be deleted if APN and"
	echo -e "		PDP Type doesn't given. PDP Type default to \"IPV4V6\"."
}

_cell_usage_set_apn() {
	#set_apn
	echo -e "	set_apn <APN>"
	echo -e "		Set APN to configuration file and PDP profile index 1."
}

_cell_usage_start() {
	echo -e "	start [OPTIONS]"
	echo -e "		Start network."
	echo -e ""
	echo -e "		OPTIONS:"
	echo -e "		APN - Access point name"
	echo -e "		PIN - PIN code"
	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 APN=internet"
	echo -e "			cell_mgmt start APN=internet PIN=0000"
	echo -e "			cell_mgmt start APN=internet PIN=0000 Phone=*99#"
}

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."
	#restart
	echo -e "	restart"
	echo -e "		Restart 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)."
	#get_profiles
	echo -e "	get_profiles"
	echo -e "		Get profile list."
	echo -e "		format:"
	echo -e "			<id>,<APN>,<PDP Type>"
	echo -e "		example:"
	echo -e "			1,internet,IPV4V6"
	#set_profile
	_cell_usage_set_profile
	#set_apn
	_cell_usage_set_apn
	#check_carrier
	echo -e "	check_carrier"
	echo -e "		Check current carrier."
	#switch_carrier
	echo -e "	switch_carrier <$CARRIER>"
	echo -e "		Switching between US carrier frequency bands."
	#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 op_func=""


	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
			;;
		# connection
		start)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		stop)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		ppp_start)
			op_func="$(_get_op_func ${arg1}_network)"
			;;
		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
		>&2 echo "Error: Operation not support."
		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}
		>&2 echo "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 ] || \
		 { >&2 echo "Error: ${PROG} is busy, please " \
			"wait a moment and try again."; \
			_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 $?
}


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

init

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

PROG=$0

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}" ] && \
			>&2 echo "Error: Module not found." && exit 1
		_set_variables
	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}"

# 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}
