/****************************************************************************/
/* Copyright 2009 - 2015 MBARI                                              */
/****************************************************************************/
/* Summary  : Main Routine for OASIS5 Expander Board                        */
/* Filename : main.c                                                        */
/* Author   : Robert Herlien (rah)                                          */
/* Project  : OASIS5 Controller                                             */
/* Revision : 1.0                                                           */
/* Created  : 10/28/2015                                                    */
/*                                                                          */
/* MBARI provides this documentation and code "as is", with no warranty,    */
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium  */
/* Research Institute to assist in its use, correction, modification, or    */
/* enhancement. This information should not be published or distributed to  */
/* third parties without specific written permission from MBARI.            */
/*                                                                          */
/****************************************************************************/
/* Modification History:                                                    */
/* 28oct2015 rah - created from main.c in FOCE SensorNode, in turn created  */
/*                 from BRS Chamber Controller main.c                       */
/****************************************************************************/

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "mcc.h"
#include "regs.h"
#include "dig_io.h"
#include "flash_mem.h"
#include "periph.h"
#include "serial.h"
#include "inst_port.h"
#include "config.h"
#include "buffer.h"
#include "sys_timer.h"
#include "misc.h"


/***************************** Modbus includes ******************************/
#include "mb.h"
#include "port.h"
#include "mbport.h"
/***************************** Modbus includes ******************************/

/****************************** Modbus defines ******************************/


/****************************** Modbus variables ****************************/
USHORT mbTimeout = MB_TMOUT_50US;

static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];

static BOOL usGotModbusFrame = FALSE;
static BOOL usUsingDefaultCfg = FALSE;
static USHORT usEnterCharCntr = 0;


/*************************** Modbus implementation **************************/
static char buff[256];
static ConfigData configData;
static int resetFlag = FALSE;

static char *blinkyVals[] =
    {"OFF", "ON", "DEBUG 24 HRS", "DEBUG", "ERROR BLINK", "50% BLINK"};


void menuTask();
void menuBanner();
void menuConfig();
void signon();
BOOL checkMenuSeq();
void menuHelp();
int menuOpt0(int help);
int menuOpt1(int help);
int menuOpt2(int help);
int menuOpt3(int help);
int menuOpt4(int help);
int menuOpt5(int help);
int menuOpt6(int help);
int menuOpt7(int help);
int menuOpt8(int help);
int menuOpt9(int help);


/************************************************************************/
/* Function    : modBusTask                                             */
/* Purpose     : Poll ModBus, count cycles                              */
/* Inputs      : None                                                   */
/* Outputs     : None                                                   */
/************************************************************************/
void modBusTask()
{
    /* poll the modbus stack */
    eMBPoll(); 

    /* Here we simply count the number of poll cycles. */
    if (++usRegInputBuf[3] == 0)
        if (++usRegInputBuf[2] == 0)
            if (++usRegInputBuf[1] == 0)
                ++usRegInputBuf[0];
}


/************************************************************************/
/* Function    : main                                                   */
/* Purpose     : Application main() function                            */
/* Inputs      : None                                                   */
/* Outputs     : Return value (unused)                                  */
/************************************************************************/
int main (void)
{
	int		txIdle;

    /* Allow nested interrupts  */
   _NSTDIS = 0;

    /* init functions */
    SYSTEM_Initialize();        /* mcc.c, from Microchip Code Config    */

    periphInit();               /* Turn off unused peripherals          */
    digInit();                  /* Initialize GPIO                      */
    serInit();
    instPortInit();
    tmrInit();
    miscInit();

    SRbits.IPL = 0;             /* Turn on interrupts   */

    /* read the config struct */
    if ( cfgRead(&configData) != 0 )
    {
        /* set the defaults */
        configData.id = DEFAULT_ID;
        configData.baud = DEFAULT_BAUD;
        configData.blinkyDebug = BLINKY_DEBUG;
        configData.serialNum = 1L;
        configData.menuAtReset = TRUE;
        digSerEnable(CFG_SERPORT);
        configData.pwrAtReset = DFLT_PWR_AT_RESET;

        usUsingDefaultCfg = TRUE;
        
        /* write the config to flash memory */
        cfgWrite(&configData);
    }
    else
        usUsingDefaultCfg = FALSE;

    if (configData.menuAtReset)
    {
        digSerEnable(CFG_SERPORT);
        signon();
    }

    /* set blinky debug mode based on config */
    setBlinkyDebug(configData.blinkyDebug);

#ifdef O5_DB
    digPwrVecSet(configData.pwrAtReset | (1<<CFG_SERPORT));
    // Need relay 0 on to talk to user
#ifdef O5_DB_DBG
    digTx5En();                 // Enable Modbus serial
#endif
#endif

    eMBInit(MB_RTU, (UCHAR)configData.id, 0, configData.baud, MB_PAR_NONE);

    /* Enable the Modbus Protocol Stack. */
    eMBEnable();


    for(;;)
    {
        /* service various functions that need periodic polling */
        tmrTask();
        modBusTask();
        blinkyTask(usGotModbusFrame);

        /* start polling instrument port 
        tasks once modbus messages have started */
        if ( usGotModbusFrame )
		{
            txIdle = instPortTxTask();
#ifndef NO_IDLE
			if (txIdle && serRxIsIdle() && modBusIsIdle())
				Doze();
#endif
		}
        else
        {
            if (checkMenuSeq())
                menuTask();

            /* If the resetFlag is set return from main which will reset the processor */
            if ( resetFlag )
            {
                usEnterCharCntr = 0;
                return(0);
            }
        }
    }
}

/****************************************************************************/
/*                             Modbus callbacks                             */
/****************************************************************************/
/*   Input Register callback (used just to return number of ModBus polls)   */
/****************************************************************************/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
        eStatus = MB_ENOREG;

    return eStatus;
}


/****************************************************************************/
/*      Holding Register callback (main ModBus Register interface           */
/****************************************************************************/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          loByte;
    USHORT          hiByte;
    USHORT          reg;

    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    if ( usAddress > REG_HOLDING_MAX )
        return MB_ENOREG;

    /* read mode */
    if ( eMode == MB_REG_READ )
    {
        if ( ((usAddress + usNRegs) > INST_HOLDING_START) && 
             (usAddress <= INST_HOLDING_END) )
        {
            instSnapshotRxAvail();
        }

        while( usNRegs > 0 )
        {
            /* set the reg to zero just in case there is no representation */
            reg = 0;

            if ((usAddress >= MISC_START) && (usAddress <= MISC_END))
                reg = miscRegRead(usAddress, &configData);
            
            else if ((usAddress >= INST_HOLDING_START) && (usAddress <= INST_HOLDING_END))
                reg = instPortReadReg(usAddress);

            /* assign it to the reg buffer */
            *pucRegBuffer++ = (unsigned char)( reg >> 8 );
            *pucRegBuffer++ = (unsigned char)( reg & 0xFF );

            /* increment register index and address */
            --usNRegs;
            ++usAddress;
        }
    }

    /* write mode */
    if ( eMode == MB_REG_WRITE )
    {
        while( usNRegs > 0 )
        {
            /* get a register from the reg buffer */
            hiByte = *(pucRegBuffer++);
            hiByte <<= 8;
            loByte = *(pucRegBuffer++);

            reg = ((hiByte & 0xFF00) + (loByte & 0x00FF));

            if ((usAddress >= MISC_START) && (usAddress <= MISC_END))
                eStatus = miscRegWrite(usAddress, reg, &configData);
            
            else if ( (usAddress >= INST_HOLDING_START) && 
                      (usAddress <= INST_HOLDING_END) )
                instPortWriteReg(usAddress, reg);

            if ((usAddress == MB_TIMEOUT_REG) && (reg >= 35))
            {
                xMBPortTimersInit(reg);
                mbTimeout = reg;
            }

            /* increment register index and address */
            --usNRegs;
            ++usAddress;
        }

        /* If a write is ending then all Tx write 
           transactions are over, so clear all txWriteRequest registers. */
        instPortClearWriteReq();
    }

    return(eStatus);
}


int isBitSet(int bitNum, UCHAR* buff)
{
    return(buff[bitNum/8] & (1 << (bitNum%0x7)));
}

void bitSet(int bitNum, UCHAR* buff, int val)
{
    /* set of clear the bit depending on the val */
    if ( val ) 
        buff[(bitNum / 8)] |= (1 << (bitNum&0x7));
    else
        buff[(bitNum / 8)] &= ~(1 << (bitNum&0x7));

    return;
}


/****************************************************************************/
/*      Coil callback (Relays, Tx Enables, Blinky)                          */
/****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    int i, coil, val;
    
    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    /* only coils 1 - 12 are represented */
    if ( (usAddress < FIRST_COIL) || 
         (usAddress + usNCoils) > (TOTAL_COILS + FIRST_COIL) )
        return(MB_ENOREG);

    /* perform a coil write(s) */
    if ( eMode == MB_REG_WRITE )
    {
        for (i = 0; i < usNCoils; i++)
        {
            /* calculate the coil number */
            coil = i + usAddress;

            /* see if the bit is set for that coil */
            val = isBitSet(i, pucRegBuffer);

            /* take the appropriate action for that coil */
            switch ( coil )
            {
                /* instrument port power bits */
                case  1: digPwrSet(0, val); break;
                case  2: digPwrSet(1, val); break;
                case  3: digPwrSet(2, val); break;
                case  4: digPwrSet(3, val); break;
                case  5: digPwrSet(4, val); break;

                /* instrument port xcvr bits */
                case  9: if (val) digTx0En(); else digTx0Dis(); break;
                case 10: if (val) digTx1En(); else digTx1Dis(); break;
                case 11: if (val) digTx2En(); else digTx2Dis(); break;
                case 12: if (val) digTx3En(); else digTx3Dis(); break;
                case 13: if (val) digTx4En(); else digTx4Dis(); break;
                case 24: usGotModbusFrame = FALSE;
                    usEnterCharCntr = 0;
                    setBlinkyDebug(configData.blinkyDebug);
                    break;
                default: break;
            }
        }

        return(MB_ENOERR);
    }


    /* perform a coil read(s) */
    if ( eMode == MB_REG_READ )
    {
        /* clear all of the coil bytes in the buffer */
        memset(pucRegBuffer, 0, ((usNCoils / 8) + 1));

        for (i = 0; i < usNCoils; ++i)
        {
            /* calculate the coil number */
            coil = i + usAddress;
            
            switch ( coil )
            {
                /* instrument port power bits */
                case  1: bitSet(i, pucRegBuffer, digPwrIsOn(0)); break;
                case  2: bitSet(i, pucRegBuffer, digPwrIsOn(1)); break;
                case  3: bitSet(i, pucRegBuffer, digPwrIsOn(2)); break;
                case  4: bitSet(i, pucRegBuffer, digPwrIsOn(3)); break;
                case  5: bitSet(i, pucRegBuffer, digPwrIsOn(4)); break;

                /* instrument port xcvr bits */
                case  9: bitSet(i, pucRegBuffer, digTx0IsEnabled()); break;
                case 10: bitSet(i, pucRegBuffer, digTx1IsEnabled()); break;
                case 11: bitSet(i, pucRegBuffer, digTx2IsEnabled()); break;
                case 12: bitSet(i, pucRegBuffer, digTx3IsEnabled()); break;
                case 13: bitSet(i, pucRegBuffer, digTx4IsEnabled()); break;
                default: break;
            }
        }

        return(MB_ENOERR);
    }

    return(MB_EIO);
}

/****************************************************************************/
/*      Discrete Input Callback (not used)                                  */
/****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    return(MB_ENOREG);
}



/****************************************************************************/
/*                              Menu functions                              */
/****************************************************************************/
#define MAX_CHARS   32
int getLine(char* line)
{
    static int char_count = 0;
    static char local_line[MAX_CHARS] = "";
    char terminator = '\r';
    int got_line;
    BYTE b;
    char c;
    int i;
    
    got_line = FALSE;

    if ( serGetByte(CFG_SERPORT, &b) )
    {
        /* echo the characters */
        serPutByte(CFG_SERPORT, b);
        
        /* assign byte to char */
        c = (char)b;
            
        if (c == '\r')
            serPutByte(CFG_SERPORT, (BYTE)'\n');

        if ( c != terminator )
        {
            if ( (c != '\b') && (char_count < MAX_CHARS) )
                local_line[char_count++] = c;
            else if ( char_count > 0 )
                --char_count;

            line[0] = '\0';
        } 
        else
        {
            got_line = TRUE;
            
            /* copy local_line buffer to line */
            for (i = 0; i <= char_count; i++)
                line[i] = local_line[i];
            
            /* terminate the line */
            line[char_count] = '\0';
            
            /* clear the char counter for the new line */
            char_count = 0;
        }
    }

    return got_line;
}

void signon()
{
    digSerEnable(CFG_SERPORT);
    menuBanner();
    if (usUsingDefaultCfg)
        serPutString(CFG_SERPORT, "Failed to read config, using default config\r\n");
    menuConfig();
    menuHelp();
}

BOOL checkMenuSeq()
{
    BYTE b;

    if (configData.menuAtReset || (usEnterCharCntr >= 3))
        return(TRUE);

    if (serGetByte(CFG_SERPORT, &b))
    {
        digSetSerEnable(CFG_SERPORT, 1);

        if (b == '+')
            usEnterCharCntr++;
        else
            usEnterCharCntr = 0;
    }

    if (usEnterCharCntr >= 3)
    {
        signon();
        return(TRUE);
    }
    return(FALSE);
}

void menuTask()
{
    BYTE b;

    if ( serGetByte(CFG_SERPORT, &b) )
    {
        switch ( b )
        {
            case '0': menuOpt0(0); break;
            case '1': menuOpt1(0); break;
            case '2': menuOpt2(0); return;
            case '3': menuOpt3(0); break;
            case '4': menuOpt4(0); break;
            case '5': menuOpt5(0); break;
            case '6': menuOpt6(0); break;
            case '7': menuOpt7(0); break;
            case '8': menuOpt8(0); break;
            case '9': menuOpt9(0); break;
            case '?':
            case 'h':
            case 'H':
            case '+':
                menuHelp();
                break;
            case '\r': serPutString(CFG_SERPORT, "\r\n"); break;
            default: serPutString(CFG_SERPORT, "Invalid command, ");
                     serPutString(CFG_SERPORT, "try '?' for help.\r\n");
        }
    }

    return;
}

void menuBanner()
{
#ifdef O5_DB
    sprintf(buff, "\r\nOasis5 Daughter Board SW Rev %u.%02u %s, %s\r\n",
            SW_REV_HIGH, SW_REV_LOW, __DATE__, __TIME__);
#else
    sprintf(buff, "\r\nOasis5 I/O Expander SW Rev %u.%02u %s, %s\r\n",
            SW_REV_HIGH, SW_REV_LOW, __DATE__, __TIME__);
#endif
    serPutString(CFG_SERPORT, buff);
    serPutString(CFG_SERPORT, "Type ? for menu\r\n\r\n");
}

void menuConfig()
{
    sprintf(buff, "SLAVE ID:     %u\r\n", configData.id);
    serPutString(CFG_SERPORT, buff);
    sprintf(buff, "BAUD RATE:    %lu\r\n", configData.baud);
    serPutString(CFG_SERPORT, buff);

    sprintf(buff, "SERIAL NUM:   %lu\r\n", configData.serialNum);
    serPutString(CFG_SERPORT, buff);
    if (configData.blinkyDebug < NumberOf(blinkyVals))
        sprintf(buff, "BLINKY:       %s\r\n", blinkyVals[configData.blinkyDebug]);
    else
        sprintf(buff, "BLINKY:       %d\r\n", configData.blinkyDebug);
    serPutString(CFG_SERPORT, buff);

    serPutStringBool(CFG_SERPORT, "MENU @ RESET: ", configData.menuAtReset);
#ifdef O5_DB
    sprintf(buff, "PWR AT RESET: 0x%X\r\n", configData.pwrAtReset);
    serPutString(CFG_SERPORT, buff);
#endif
    serPutString(CFG_SERPORT, "\r\n");
}

void menuHelp()
{
    menuOpt0(1);
    menuOpt1(1);                
    menuOpt2(1);
    menuOpt3(1);
    menuOpt4(1);
    menuOpt5(1);
    menuOpt6(1);
    menuOpt7(1);
    menuOpt8(1);
    menuOpt9(1);
}

int menuOpt0(int help)
{
    if (help)
        serPutString(CFG_SERPORT, "0 - Save config changes and reset\r\n");
    else
    {
        memErase();                     /* erase the old config */

        cfgWrite(&configData);
        
        resetFlag = TRUE;
    }

    return(0);
}

int menuOpt1(int help)
{
    if (help)
        serPutString(CFG_SERPORT, "1 - Show build date and config\r\n");
    else
    {
        menuBanner();
        menuConfig();
    }

    return(0);
}

int menuOpt2(int help)
{
    unsigned int id = 0;

    /* print help string */
    if (help)
        serPutString(CFG_SERPORT, "2 - Set slave address\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Enter SLAVE ID: ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();

            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }

        id = (unsigned int)atoi(buff);

        if ( (id < 1) || (id > 247) )
        {
            sprintf(buff, "Ivalid ID: %u, valid range is [1,247]\r\n", id);
            serPutString(CFG_SERPORT, buff);
            return(-1);
        }

        configData.id = id;
    }

    return(0);
}

int menuOpt3(int help)
{
    unsigned long baud;

    /* print help string */
    if (help)
        serPutString(CFG_SERPORT, "3 - Set Modbus baudrate\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Enter BAUD RATE: ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();
        
            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }

        baud = (unsigned long)atol(buff);

        switch ( baud )
        {
            case 19200: break;
            case 38400: break;
            case 57600: break;
            case 115200: break;
            default:    sprintf(buff, "Invalid BAUD RATE: %lu\r\n", baud);
                        serPutString(CFG_SERPORT, buff);
                        serPutString(CFG_SERPORT, "Valid rates are\r\n");
                        serPutString(CFG_SERPORT, "19200\r\n");
                        serPutString(CFG_SERPORT, "38400\r\n");
                        serPutString(CFG_SERPORT, "57600\r\n");
                        serPutString(CFG_SERPORT, "115200\r\n");
                        return(-1);
        }

        configData.baud = baud;
    }

    return(0);
}

int menuOpt4(int help)
{
    int state = 0;

    if (help)
        serPutString(CFG_SERPORT, "4 - Set blinky debug state\r\n");
    else
    {
        serPutString(CFG_SERPORT, "BLINKY DEBUG:\r\n");
        serPutString(CFG_SERPORT, "0 = OFF\r\n");
        serPutString(CFG_SERPORT, "1 = ON\r\n");
        serPutString(CFG_SERPORT, "2 = DEBUG for 24 HRS\r\n");
        serPutString(CFG_SERPORT, "3 = DEBUG ALWAYS\r\n");
        serPutString(CFG_SERPORT, "4 = ERROR BLINK\r\n");
        serPutString(CFG_SERPORT, "5 = 50% DUTY CYCLE\r\n");
        serPutString(CFG_SERPORT, "Enter 0-5:  ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();

            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }
        
        state = atoi(buff);

        if ( (state < 0) || (state > 5) )
        {
            sprintf(buff, "Invalid state: %d\r\n", state);
            serPutString(CFG_SERPORT, buff);
            return(-1);
        }

        configData.blinkyDebug = state;
        setBlinkyDebug(state);
    }

    return(0);
}

int menuOpt5(int help)
{
    if (help)
        serPutString(CFG_SERPORT, "5 - Set board serial number\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Enter Board Serial Number: ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();
        
            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }

        configData.serialNum = (unsigned int)atoi(buff);
    }

    return(0);
}

int menuOpt6(int help)
{
    if (help)
        serPutString(CFG_SERPORT, "6 - Set/Reset print menu at reset\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Print menu at reset? (Y/N):  ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();

            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }
        
        if (toupper(buff[0]) == 'Y')
            configData.menuAtReset = TRUE;
        else if (toupper(buff[0]) == 'N')
            configData.menuAtReset = FALSE;
        else
        {
            serPutString(CFG_SERPORT, "Invalid response\r\n");
            return(-1);
        }

        sprintf(buff, "%setting menu at reset\r\n",
                configData.menuAtReset ? "S" : "Res");
        serPutString(CFG_SERPORT, buff);
    }
    
    return(0);
}

int menuOpt7(int help)
{
#ifdef O5_DB
    if (help)
        serPutString(CFG_SERPORT, "7 - Set Power at Reset\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Power vector at startup, in hex:  ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();
        
            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }

        configData.pwrAtReset = (unsigned int)strtol(buff, NULL, 16);
    }
#endif
    
    return(0);
}

int menuOpt8(int help)
{
#ifndef STANDALONE
    int state = 0;
    
    if (help)
        serPutString(CFG_SERPORT, "8 - Set bootloader flag and reset\r\n");
    else
    {
        serPutString(CFG_SERPORT, "Confirm bootloader flag set?  [0 = No | 1 = Yes]: ");
        
        /* wait for user input */
        while ( !getLine(buff) )
        {
            modBusTask();

            /* if you get a modbus message, bail out of menuOpt */
            if ( usGotModbusFrame )
                return(0);
        }
        
        state = atoi(buff);

        if ( state == 1 )
        {
            serPutString(CFG_SERPORT, "Setting bootloader flag\r\n");
            cfgSetBootFlag();
            resetFlag = TRUE;
        }
#endif
        return(0);
    }


int menuOpt9(int help)
{
    /* unused */
    return(0);
}
