#!/bin/bash
# console-config/src/lib-scripts/timing-serial.sh
#
#  Copyright: ©2013–2015, Güralp Systems Ltd.
#  Author: Laurence Withers <lwithers@guralp.com>
#  License: GPLv3
#
# Serial port integration for timing. We support one NMEA input port in
# direct_gps mode (one port is a software limitation); we support one NMEA
# output port in ntp mode (one port because we can only switch one thing at
# a time).
#
# Several of the modes will clear any input or output ports. This will be
# reported to the user.
#

# Global variables
SERIAL_BAUD=""
CONTROL_LINE=""



# serial_clear_function()
#  Scans all serial ports to see if any are configured for the function given
#  in $1. If so, sets the port function to None and reports this to the user.
#  If desired, a config file basename may be passed in $2; this config file
#  will be skipped.
serial_clear_function() {
    local TEST_FUNCTION="$1"
    local SKIP_CONFIG="$2"
    local CF NAME BASENAME FUNCTION

    for CF in ${SERIAL_CFDIR}/*.cf
    do
        [ -r "${CF}" ] || continue
        BASENAME="`basename "${CF}" .cf`"
        [ "${BASENAME}" = "${SKIP_CONFIG}" ] && continue

        FUNCTION="`cfget "${CF}" function`"
        if [ "${FUNCTION}" = "${TEST_FUNCTION}" ]
        then
            NAME="`cfget "${CF}" name`"
            echo "${NAME} was set to ${TEST_FUNCTION}. Deactivating this port."
            cfset "${CF}" function="None"
            svc "${BASENAME}" restart
        fi
    done

    return 0
}



# serial_clear_xlate()
#  If any serial port is configured as a timestamp input translator, change its
#  function to None. Report this to the user.
serial_clear_xlate() {
    serial_clear_function "Timestamp in" ""
}



# serial_clear_input()
#  If any serial port is configured as an input, change its function to None.
#  Report this to the user.
serial_clear_input() {
    serial_clear_function "NMEA in" ""
}



# serial_clear_output()
#  If any serial port is configured as an output, change its function to None.
#  Report this to the user. If desired, the port with base name $1 is skipped.
serial_clear_output() {
    serial_clear_function "NMEA out" ""
}



timestamp_in_and_svc_restart() {
    local CF PORT FUNCTION_CF
    PORT="$1"

    clear

    # update the serial port config file
    echo "Activating time of day receiver on ${PORT}"
    CF="${SERIAL_CFDIR}/${PORT}.cf"
    cfset "${CF}" function="Timestamp in" || exit 1
    cfset "${CF}" baud="${SERIAL_BAUD}" || exit 1

    # update the program-specific config file
    FUNCTION_CF="${SERIAL_CFDIR}/timestamp-in/${PORT}"
    cat > "${FUNCTION_CF}" <<EOF
# Created by timing-serial.sh on `isodate -se`

input_mode = gpzda # only supported mode
EOF
    [ $? -eq 0 ] || exit 1

    # now activate the new service (ensuring it is turned off on all other
    # ports) with our new config
    serial_clear_function "Timestamp in" "${PORT}"
    svc "${PORT}" restart
}



# serial_select_xlate()
#  Allows the user to choose a serial port for timestamp input (for translation
#  mode). Returns 0 if any port was selected or 1 otherwise. Exits on fatal
#  error.
serial_select_xlate() {
    local RESULT

    while true
    do
        serial_build_port_menu
        if [ -z "${MENU}" ]
        then
            clear
            echo "No serial ports available for input."
           return 1
        fi

        dialog ${MENU_EXTRA_ARGS} \
            --title "Time of day input port" \
            --menu "Select serial port to which time of day string is being \
                sent. This port will be used as a timing source." \
            0 ${DIALOG_DEFAULT_W} 0 \
            "${MENU[@]}" \
            2> "${DIALOG_RESULTFILE}"
        RESULT=$?

        clear
        if [ "${RESULT}" -ne 0 ]
        then
            echo "No input port selected."
            return 1
        fi
        RESULT="`cat "${DIALOG_RESULTFILE}"`"

        serial_select_baud "Select baud rate." || continue
        timestamp_in_and_svc_restart ${RESULT}
        return 0
    done
}



nmea_in_svc_restart()
{
    local CF RESULT

    RESULT=$1

    clear

    echo "Activating NMEA input on ${RESULT}"
    CF="${SERIAL_CFDIR}/${RESULT}.cf"
    cfset "${CF}" function="NMEA in" || exit 1
    cfset "${CF}" baud="${SERIAL_BAUD}" || exit 1
    svc "${RESULT}" restart
}



# serial_select_input()
#  Allows the user to choose a serial port for NMEA input. Returns 0 if any port
#  was selected or 1 otherwise. Exits on fatal error.
serial_select_input() {
    local RESULT

    while true
    do
        serial_build_port_menu
        if [ -z "${MENU}" ]
        then
            clear
            echo "No serial ports available for input."
           return 1
        fi

        dialog ${MENU_EXTRA_ARGS} \
            --title "GPS input port" \
            --menu "Select serial port to which GPS is connected. This \
port will be used as a timing source." \
            0 ${DIALOG_DEFAULT_W} 0 \
            "${MENU[@]}" \
            2> "${DIALOG_RESULTFILE}"
        RESULT=$?

        clear
        if [ "${RESULT}" -ne 0 ]
        then
            echo "No input port selected."
            return 1
        fi
        RESULT="`cat "${DIALOG_RESULTFILE}"`"

        serial_select_baud "Select baud rate. 4800 is the most widely used with \
        Guralp GPS receivers, but 19200 is sometimes used for GLONASS." || continue

        # This has been separated out so it can be called from the serial port
        # config.
        nmea_in_svc_restart ${RESULT}

        serial_clear_function "NMEA in" "${RESULT}"
        return 0
    done
}



# select_control_line()
#  Allows the user to pick an I/O line. Sets CONTROL_LINE. Will look at the
#  value of CONTROL_LINE if set and use this as the default.
select_control_line() {
    local IOLINE DESCRIPTION RESULT PLATFORM_TEXT

    MENU=("none" "Do not switch a control line")
    MENU_EXTRA_ARGS="--default-item none"

    while read -r IOLINE DESCRIPTION
    do
        MENU+=("${IOLINE}")
        [ "${IOLINE}" = "${CONTROL_LINE}" ] && MENU_EXTRA_ARGS="--default-item ${IOLINE}"
        MENU+=("${DESCRIPTION}")
    done < <(ioline -J)

    if [ -z "${MENU[2]}" ]
    then
        # no control lines were available
        CONTROL_LINE="/none"
        return 0
    fi

    case "${BUILD_MACHINE}" in
    CMG-DCM-mk4-eabi)
        PLATFORM_TEXT="Usually, the line \"gps_from_eam\" is used to switch \
between internal NMEA generation for the CD24/DM24 and an external GPS \
receiver. If this option is not present, then the system does not support \
this function without the aid of an external cable."
        ;;
    *)
        PLATFORM_TEXT="This option is normally left set to \"none\"."
        ;;
    esac

    dialog ${MENU_EXTRA_ARGS} \
        --title "Select control line" \
        --menu "Select a control line to switch while this port function \
is active. ${PLATFORM_TEXT}" \
        10 ${DIALOG_DEFAULT_W} 0 \
        "${MENU[@]}" \
        2> "${DIALOG_RESULTFILE}"
    [ $? -eq 0 ] || return 1
    CONTROL_LINE="`cat "${DIALOG_RESULTFILE}"`"
    [ "${CONTROL_LINE}" = "none" ] && CONTROL_LINE="/none"
    return 0
}



# write_nmea_out_config()
#  Updates the ntp-to-nmea configuration in the file "$1", with GPS form results
#  available on stdin.
#
#  $1 is the file path
#  $2 is the source ("NTP" or "timestamp-in")
#
write_nmea_out_config() {
    local CF="$1"
    local MODE="$2"
    local MAX_NTP_ERROR GPS_LATITUDE GPS_LONGITUDE GPS_HEIGHT GPS_GEOID

    read -r MAX_NTP_ERROR
    read -r GPS_LATITUDE
    read -r GPS_LONGITUDE
    read -r GPS_HEIGHT
    read -r GPS_GEOID

    echo "Updating \"${CF}\"..."

    if [ ! -r "${CF}" ]
    then
        cat > "${CF}" <<EOF
# ${CF}
#  Created by $0 at `isodate -me`.

EOF
        [ $? -eq 0 ] || exit 1
    fi

    cfset "${CF}" gps_switch_ioline="${CONTROL_LINE}"  || exit 1
    cfset "${CF}" ntp_max_error="${MAX_NTP_ERROR}"     || exit 1
    cfset "${CF}" latitude="${GPS_LATITUDE}"           || exit 1
    cfset "${CF}" longitude="${GPS_LONGITUDE}"         || exit 1
    cfset "${CF}" height="${GPS_HEIGHT}"               || exit 1
    cfset "${CF}" geoid="${GPS_GEOID}"                 || exit 1
    cfset "${CF}" mode="${MODE}"                       || exit 1

    return 0
}



# Selects the I/O control line to assert when running and sets up the NTP
# position information and then restarts the NMEA out service.
#
#  $1 is the port ID
#  $2 is the source ("NTP" or "timestamp-in", or empty to autodetect)
#
nmea_out_and_svc_restart()
{
    local PORT CF SUBCF
    local MAX_NTP_ERROR GPS_LATITUDE GPS_LONGITUDE GPS_HEIGHT GPS_GEOID

    PORT="$1"
    MODE="$2"

    SUBCF="/etc/conf.d/serial/nmea-out/${PORT}"
    if [ -r "${SUBCF}" ]
    then
        CONTROL_LINE="`cfget "${SUBCF}" gps_switch_ioline`"
        MAX_NTP_ERROR="`cfget "${SUBCF}" ntp_max_error`"
        GPS_LATITUDE="`cfget "${SUBCF}" latitude`"
        GPS_LONGITUDE="`cfget "${SUBCF}" longitude`"
        GPS_HEIGHT="`cfget "${SUBCF}" height`"
        GPS_GEOID="`cfget "${SUBCF}" geoid`"
        [ -z "${MODE}" ] && MODE="`cfget "${SUBCF}" mode`"
    else
        CONTROL_LINE="/none"
        MAX_NTP_ERROR="1000"
        GPS_LATITUDE="0000.0000,N"
        GPS_LONGITUDE="00000.0000,E"
        HEIGHT="00000"
        GEIOD="000"
    fi
    [ -z "${MODE}" ] && MODE="NTP"
    select_control_line || return 1

    dialog --title "GPS output port" \
        --form "The maximum NTP error (in microseconds) is the threshold \
below which the digitiser will attempt to lock to NTP time. If the NTP error \
is above this threshold, the digitiser will not use it as a reference source. \
Under good conditions with a local high-stratum NTP server it takes around \
15-30 minutes for the NTP clock to settle below the default error margin of \
1000us. The GPS location fields are optional; they are reported in the text \
status blocks of the CD24/DM24." \
        27 ${DIALOG_DEFAULT_W} 12 \
        "Maximum NTP error (us, default 1000)"  1 1 "${MAX_NTP_ERROR}"  2 3 20 10 \
        "Latitude (format: \"0000.0000,N\")"    3 1 "${GPS_LATITUDE}"   4 3 20 11 \
        "Longitude (format: \"00000.0000,E\")"  5 1 "${GPS_LONGITUDE}"  6 3 20 12 \
        "Height (format: \"00000\", metres)"    7 1 "${GPS_HEIGHT}"     8 3 20 5 \
        "Geoid (format: \"000\", metres)"       9 1 "${GPS_GEOID}"     10 3 20 3 \
        2> "${DIALOG_RESULTFILE}"
    [ $? -eq 0 ] || return 1

    clear
    cat "${DIALOG_RESULTFILE}" | write_nmea_out_config "${SUBCF}" "${MODE}"

    echo "Activating NMEA output on ${PORT}"
    CF="${SERIAL_CFDIR}/${PORT}.cf"
    cfset "${CF}" function="NMEA out"   || exit 1
    cfset "${CF}" baud="${SERIAL_BAUD}" || exit 1
    svc "${PORT}" restart

    serial_clear_function "NMEA out" "${PORT}"
    return 0
}



# serial_select_output()
#  Allows the user to choose and configure a serial port for NMEA output.
#  Returns 0 if port selected or 1 otherwise. Exits on fatal error.
#
#  $1 is the source ("NTP" or "timestamp-in")
#
serial_select_output() {
    local RESULT PORT MODE

    MODE="$1"

    while true
    do
        serial_build_port_menu
        if [ -z "${MENU}" ]
        then
            clear
            echo "No serial ports available for output."
            return 1
        fi

        dialog ${MENU_EXTRA_ARGS} \
            --title "GPS output port" \
            --menu "Select the output port for NMEA compatible timing \
information. This is used in systems that are synchronised over the network \
and must provide a time source to an attached CD24/DM24 digitiser module. \
Simply select cancel if no output port is required." \
            12 ${DIALOG_DEFAULT_W} 0 \
            "${MENU[@]}" \
            2> "${DIALOG_RESULTFILE}"
        RESULT=$?

        clear
        if [ "${RESULT}" -ne 0 ]
        then
            echo "No output port selected."
            return 1
        fi
        PORT="`cat "${DIALOG_RESULTFILE}"`"

        serial_select_baud "Select baud rate. 4800 is the most widely used with \
        Guralp GPS receivers, but 19200 is sometimes used for GLONASS." || continue

        # This does the remainder of the setup for NMEA out (including restarting of
        # the service) and has been separated off so that it can be called from the
        # serial port configuration so code doesn't have to be repeated. 
        nmea_out_and_svc_restart "${PORT}" "${MODE}"
        if [ $? -eq 0 ]
        then
            return 0
        fi
    done
}



# vim: ts=4:sw=4:expandtab:syntax=sh
