#!/bin/sh

start="$(date +%s)"
tag="UPD_ME909[$$]"
firmware=""
modem_nr=0
verbose=0
force=0
reboot=0
sx=lsx

######################################################################
#
# Convenience function to print and log messages
#
# arguments: prio msg
#
msg()
{
	local prio="$1"
	shift

	logger -t "${tag}" -p "${prio}" -- $@

	test ${verbose} -eq 0 && return

	echo "$@"
}

######################################################################
#
# Function does not return and reports an error
#
# arguments: msg
#
abort()
{
	msg error "${@}, aborting"

	# Cleanup is called by the trap if necessary
	exit 1
}

######################################################################
#
# Show script usage
#
# arguments:
#
usage()
{
	echo "$0 [-b] [-f] [-m <modem_nr>] [-r] [-v] <firmware>"
	echo -e "  -b\tRun in background as daemon"
	echo -e "  -f\tForce update"
	echo -e "  -r\tReboot system when finished"
	echo -e "  -v\tAlso print log to stdout"
}

######################################################################
#
# Cleanup function to be called when the script exits
#
# arguments:
#
cleanup()
{
	trap - INT TERM EXIT

	msg info "Starting cleanup"

	sync

	msg info  "Restoring watchdog"
	pic -w 60 &>/dev/null

	msg info "Starting gpio daemon"
	/etc/init.d/S36gpio.sh start &>/dev/null

	msg info "Script finished in $(($(date +%s) - ${start})) s"

	if [ ${reboot} -ne 0 ]; then
		local tmo=5

		msg info "Rebooting system in ${tmo} s"

		sleep ${tmo}

		reboot
	fi
}

######################################################################
#
# Wait for a process specified in a PID file to terminate
#
# arguments: pid [timeout]
#
wait_pidfile()
{
	local pid="$1"
	local tmo=${2:-10}

	# Return successfully if pid file does not exist
	test ! -e "${pid}" && return

	for i in $(seq ${tmo}); do
		# Return successfully if process terminated
		! kill -0 $(cat "${pid}") &>/dev/null && return

		# Return unsuccessfully on timeout
		test ${i} -lt ${tmo} || return

		sleep 1
	done
}


######################################################################


while getopts "bfhm:rv" opt; do
	case "$opt" in
	b)
		echo "Starting script as daemon"

		# Using same arguments but remove "-b"
		start-stop-daemon -S -b -m -p "/var/run/upd_me909.pid" -x "${0}" -- ${@/-b/}

		exit
		;;
	f)
		force=1
		;;
	m)
		case "${OPTARG}" in
		0|1)
			modem_nr="${OPTARG}"
			;;
		esac
		;;
	r)
		reboot=1
		;;
	v)
		verbose=1
		;;
	h)
		usage

		exit
		;;
	*)
		usage

		exit 1
		;;
	esac
done

# Shift away the options that have been parsed
shift $((OPTIND - 1))

if [ $# -ne 1 ]; then
	usage

	exit 1
fi
firmware="$1"

# Determine device file
dev="/dev/clctrl${modem_nr}"
test -e "${dev}" || abort "Modem is missing or turned off"

# Print configuration
msg info "Using firmware file '${firmware}' on device '${dev}'"

# Determine xmodem binary
which ${sx} &>/dev/null || sx="sx"

#----------------------------------------------------------

# Check current version
v="$(at -m ${modem_nr} 'AT+CGMR' | awk '/^[0-9.]+/' | tr -d "\r" )"
test -n "${v}" || abort "Failed to determine current version. Is the modem running?"


#
# The name of the firmware typically is:
#   ME909_UPDATE_11.617.24.00.00.FWL
#
# AT+CGMR returns the following version string:
#   11.617.24.00.00
#
# Match if the firmware version string is found in the name of the firmware file
#
if [ "$(basename ${firmware})" = *${v}* ]; then
	msg info "Firmware '${v}' already installed"

	test ${force} -eq 0 && exit 0

	msg info "Force set, continuing anyway"
else
	msg info "Firmware '${v}' found"
fi

# Make sure to cleanup properly
trap 'cleanup; exit' INT TERM EXIT


# Disable gpio daemon to avoid modem restarts during update
msg info "Stopping gpio daemon"
pic -w 0 &>/dev/null
/etc/init.d/S36gpio.sh stop &>/dev/null
sleep 1
# In some versions, not all gpio daemon clients terminate after stop
killall gpio_daemon &>/dev/null
wait_pidfile "/var/run/gpiod.pid" || abort "Timeout while waiting for gpio_daemon to terminate"
# Guarantee that pppd is down
for i in $(seq 0 1); do
	ppppid=""/var/run/ppp${i}.pid""

	test -e "${ppppid}" || continue

	kill $(cat "${ppppid}") &>/dev/null;

	wait_pidfile "${ppppid}" || abort "Timeout while waiting for pppd${i} to terminate"
done
sleep 1
pic -w 0 &>/dev/null


# Disable flow control, hangup, carrier detect and local echo
msg info "Setting up tty device"
stty -F ${dev} raw -echo -ixon -ixoff -crtscts clocal -hupcl 2>/dev/null


# Prepare modem to accept firmware file
msg info "Sending firmware update command to modem"
# Cannot use at here as the gpio_daemon is not running anymore
printf "AT^FWLOAD=0\r" >"${dev}"

msg info "Waiting for the modem to be ready"
timeout -t 30 awk '/^>/{exit}' <"${dev}"
test $? -eq 0 || abort "Timeout while waiting for modem"

# Transfer firmware file to modem
msg info "Modem ready, transferring firmware now. Go grab a coffee"
if [ ${verbose} -eq 0 ]; then
	${sx} -k -b "${firmware}" <"${dev}" >"${dev}" 2>/dev/null
else
	${sx} -k -b "${firmware}" <"${dev}" >"${dev}"
fi
test $? -eq 0 || abort "Firmware file transfer failed"


# Wait for the modem to restart. While the device files are gone, the modem
# is actually doing the update
msg info "Waiting for modem to restart"
running=1
retries=600
for i in $(seq ${retries}); do
	if [ ${i} -ge ${retries} ]; then
		abort "Timeout while waiting for modem update"
	fi

	if [ ${running} -ne 0 ]; then
		if [ ! -e "${dev}" ]; then
			msg info "Modem is updating now. Get another coffee"

			running=0

			continue
		fi
	else
		test -e "${dev}" && break
	fi

	sleep 1
done


# The device files returned, wait for firmware update result
msg info "Waiting for modem to report result"
state="$(timeout -t 60 awk '/^\^FWLSTATE:/{print($2); exit}' <"${dev}")"
if [ "${state}" = "90" ]; then
	msg info "Modem update was successful (${state})"
else
	msg error "Modem update failed (${state})"
fi
