#!/bin/bash
#
#    Copyright (C) MOXA Inc. All rights reserved.
#
#    This software is distributed under the terms of the
#    MOXA License.  See the file COPYING-MOXA for details.
#
#    /sbin/setinterface
#
#    Moxa set interface utility for DA-681C model
#
#    2019-01-28  Elvis CW Yao
#	 new release based on orginal setinterface
#

# FT4232 0x20, UART Port 0:3
UART_GPIO_0=(479 480 481 482)
UART_GPIO_1=(483 484 485 486)
UART_GPIO_2=(487 488 489 490)
UART_GPIO_3=(491 492 493 494)

# FT4232 0x21, UART Port 4:7
UART_GPIO_4=(463 464 465 466)
UART_GPIO_5=(467 468 469 470)
UART_GPIO_6=(471 472 473 474)
UART_GPIO_7=(475 476 477 478)

# FT4232 0x22, UART Port 8:11
UART_GPIO_8=(447 448 489 450)
UART_GPIO_9=(451 452 453 454)
UART_GPIO_10=(455 456 457 458)
UART_GPIO_11=(459 460 461 462)

RS232_MODE=0
RS485_MODE=1
RS422_MODE=2
UNKNOWN_MODE=9
RS232_GPIO_MODE=(1 1 0 0)
RS485_GPIO_MODE=(0 0 0 1)
RS422_GPIO_MODE=(0 0 1 0)

RS232_MODE_STR="Now setting is RS232 mode"
RS485_MODE_STR="Now setting is RS485-2W mode"
RS422_MODE_STR="Now setting is RS422/RS485-4W mode"

UARTCONF="/etc/moxa-configs/setinterface.conf"

declare CUR_UART
declare CUR_MODE

PCBA_SERIAL=0
PCBA_DEFAULT=0
PCBA_RESERVE1=1
PCBA_RESERVE2=2
# Get PCBA board serial number to determine DA-681C model type
# $ kversion -s
# 0 for 2 x RS-232/422/485 ports(DB9) + 10 x RS-485-2W ports(TB)
# 1 for 2 x RS-232/422/485 ports(DB9) + 10 x RS-422/RS-485-2W ports(TB)
# 2 for 2 x RS-232/422/485 ports(DB9) + 10 x RS-232-4W/RS-422/RS-485-2W ports(TB)
get_pcba_serial() {
	serial_num=$(kversion -s)

	if [ -z "$serial_num" ]; then
		# if serial_num is empty, set to default value
		PCBA_SERIAL=$PCBA_DEFAULT
		return
	fi

	PCBA_SERIAL=$serial_num
}

check_root_user() {
	if [ x"$(whoami)" != x"root" ]; then
		echo "Permission denied."
		exit 1
	fi
}

usage() {
	echo -ne "Usage:  setinterface [OPTION]...\n"
	echo -ne " [device-node] [interface-no],\tset or get UART mode for sepcific port\n"
	echo -ne "\t\t\t\te.g. set mode: setinterface /dev/ttyM0 0\n"
	echo -ne "\t\t\t\te.g. get mode: setinterface /dev/ttyM0\n"
	echo -ne "\tdevice-node     - /dev/ttyMn, n = [0:${MAX_GPIO_PORT_NUM}]\n"
	echo -ne "\tinterface-no    - following:\n"
	echo -ne "\tnone - to view now setting\n"
	echo -ne "\t\t0 - set to RS232 interface\n"
	echo -ne "\t\t1 - set to RS485-2W interface\n"
	echo -ne "\t\t2 - set to RS422/RS485-4W interface\n"
	echo -ne " -h,\t show this help page\n"
	echo -ne " -s,\t save current UART mode to file\n"
	exit 0
}

save_status() {
	echo "Save current status to $UARTCONF ..."
	if [[ ! -f "$UARTCONF" ]]; then
		echo "setinterface config file $UARTCONF is not existed. Exit."
		exit 1
	fi

	index=0
	for index in `seq 0 $MAX_GPIO_PORT_NUM`
	do
		get_port_mode $index
		echo "Get port $index mode is $CUR_MODE"
		eval "sed -i -e '/#/!s/PORT${index}=[0-9]/PORT${index}=${CUR_MODE}/g' $UARTCONF"
	done

	exit 0
}

get_port_mode() {
	port=$1
	if [ $port -ge 0 ] && [ $port -le $MAX_GPIO_PORT_NUM ]; then
		exp_board_uart_mode $port
	else
		echo "Target device port $1 is invalid. Exit."
		exit 1
	fi
}

exp_board_uart_mode() {
	port=$1
	mode=$2
	gpio_init $port

	if [ -z "$mode" ]; then
		# get mode
		exp_board_get_uart_mode
		return

	# set mode
	elif [ x"$mode" == x"$RS232_MODE" ]; then
		exp_board_set_uart_mode $RS232_MODE $port
	elif [ x"$mode" == x"$RS485_MODE" ]; then
		exp_board_set_uart_mode $RS485_MODE $port
	elif [ x"$mode" == x"$RS422_MODE" ]; then
		exp_board_set_uart_mode $RS422_MODE $port
	else
		echo "Cannot recognize the mode. Exit."
		exit 1
	fi

	exp_board_uart_mode $port
}

exp_board_get_uart_mode() {
	local -a pin_array
	local -a cmp_result

	# fetch GPIO PIN status
	for ((i=0; i<${#CUR_UART[@]}; i++)); do
		pin_array[$i]=$(cat /sys/class/gpio/gpio${CUR_UART[$i]}/value)
	done

	# RS232 comparison
	cmp_result=$(diff_array pin_array[@] RS232_GPIO_MODE[@])
	if [ x"${cmp_result}" == x"0" ]; then
		echo $RS232_MODE_STR
		CUR_MODE=$RS232_MODE
		return
	fi

	# RS485-2W comparison
	cmp_result=$(diff_array pin_array[@] RS485_GPIO_MODE[@])
	if [ x"${cmp_result}" == x"0" ]; then
		echo $RS485_MODE_STR
		CUR_MODE=$RS485_MODE
		return
	fi

	# RS422/485-4W comparison
	cmp_result=$(diff_array pin_array[@] RS422_GPIO_MODE[@])
	if [ x"${cmp_result}" == x"0" ]; then
		echo $RS422_MODE_STR
		CUR_MODE=$RS422_MODE
		return
	fi

	echo "Cannot recognize current mode."
	CUR_MODE=$UNKNOWN_MODE
	exit 1
}

exp_board_set_uart_mode() {
	mode=$1
	port=$2
	case "$mode" in
		$RS422_MODE) # RS-422 mode
			if [ $PCBA_SERIAL -lt $PCBA_RESERVE1 ] && [ $port -gt 1 ]; then
				echo "Operation is not support. Exit."
				exit 1
			fi
			for ((i=0; i<${#CUR_UART[@]}; i++)); do
				echo ${RS422_GPIO_MODE[$i]} > /sys/class/gpio/gpio${CUR_UART[$i]}/value
			done
		;;
		$RS485_MODE) # RS-485 mode
			for ((i=0; i<${#CUR_UART[@]}; i++)); do
				echo ${RS485_GPIO_MODE[$i]} > /sys/class/gpio/gpio${CUR_UART[$i]}/value
			done
		;;
		$RS232_MODE) # RS-232 mode
			if [ $PCBA_SERIAL -lt $PCBA_RESERVE2 ] && [ $port -gt 1 ]; then
				echo "Operation is not support. Exit."
				exit 1
			fi
			for ((i=0; i<${#CUR_UART[@]}; i++)); do
				echo ${RS232_GPIO_MODE[$i]} > /sys/class/gpio/gpio${CUR_UART[$i]}/value
			done
		;;
		*)
			echo "Cannot recognize the mode"
		;;
	esac
}

# 
# This function will compare two arrays value in order to recognize current UART mode. 
#
diff_array()
{
	declare -a arr1=("${!1}")
	declare -a arr2=("${!2}")
	[ ${#arr1[*]} != ${#arr2[*]} ] && { echo arrays different size; exit 1; }
	for ii in ${!arr1[*]}; do
		[ "${arr1[$ii]}" == "${arr2[$ii]}" ] || { echo different element $ii; echo 1 && return; }
	done
	echo 0
}

#
# Init GPIO PIN status, this function will be only executed ONCE
#
gpio_init_action()
{
	# export the gpio if it's not existed
	uart_port=$1
	if [ ! -f /sys/class/gpio/gpio$uart_port/value ]; then
		echo $uart_port > /sys/class/gpio/export
	fi

	# set uart port as output
	direction=$(cat /sys/class/gpio/gpio$uart_port/direction)
	if [ x"$direction" == x"in" ]; then
		echo "out" > /sys/class/gpio/gpio$uart_port/direction
	fi
}

gpio_init()
{
	# get current UART PIN from configuration file
	uart_port=$1
	CUR_UART=($(eval "echo \${"UART_GPIO_${uart_port}[@]"}"))
	
	if [ x"$CUR_UART" == x"" ]; then
		echo "The parameter is incorrect."		
		exit 1
	fi	
	
	for (( i=0; i<${#CUR_UART[@]}; i++ )); do
		gpio_init_action ${CUR_UART[$i]}
	done
}

main() {
	check_root_user

	if [ x"$1" == x"-h" ]; then
		usage
	fi

	if [ x"$1" == x"-s" ]; then
		save_status
	fi

	get_pcba_serial

	port=${1##*/dev/ttyM}
	mode=$2

	if [ ! -e $1 ]; then
		echo "Target device node $1 is not existed. Exit."
		exit 1
	fi

	if [ $port -ge 0 ] && [ $port -le $MAX_GPIO_PORT_NUM ]; then
		exp_board_uart_mode $port $mode
	else
		echo "Target device node $1 is not supported. Exit."
		exit 1
	fi
}

MAX_GPIO_PORT_NUM=$(ls /dev/ttyM* | wc -l)
let 'MAX_GPIO_PORT_NUM=MAX_GPIO_PORT_NUM - 1'

if [ $# -eq 0 ] || [ $# -gt 2 ]; then
	usage
fi

main $1 $2
