/*
 * serial.c
 *
 *  Created on: Nov 30, 2022
 *      Author: thomm
 */

#include "serial.h"
#include "vtable.h"

#include "apf.h"
#include "apf_util.h"
#include "config.h"
#include "i2c_fram.h"
#include "main.h"
#include "microsd.h"
#include "mms_spec.h"
#include "mms_test.h"
#include "sample.h"
#include "serial_cmd_fxn.h"
#include "sleep.h"
#include "string.h"
#include "system.h"
#include "test.h"
#include "uart3.h"
#include "uartstdio.h"
#include "user_io.h"
#include "ymodem.h"
#include "zmodem.h"
#include "zone_irene.h"
#include "apf_test.h"
#include "apf_util.h"
#include "init_defaults.h"
#include "i2c_ds3231.h"

#include <stdint.h>
#include <stdbool.h>

struct rxStateMach rx0;             // defd in serial.h

extern unsigned int databarFlg;
extern unsigned int barfSampleTime;

uint32_t testVar;
uint32_t newMode;       //global hack for ack handler
uint32_t serDbgFlg = 0;
extern uint32_t helpCnt;
extern int gBootError;  //defd in main

static CMD_Tokens tokens;

#define MULTIPLECRMENU 0  //was 4 //#define MULTIPLECRMENU 3            // 0 to disable multiple carriage returns invoking menu, recommended 3 or 4

extern void microsd_fatfs_hammer_test(unsigned char testNum);
/*
 * Writing a new serial command, for instance >wdog
 *
 * 1) new command is wdog for the purpose of this tutorial.
 * 2) Serial.c in handle_m2m_comm, search case statement for early warning bad char.
 *      if the first letter of your command is in the case statement, no action  Otherwise add both cases of the new letter.
 * 3) The if-else parsing of the commands are in alphabetic order in serial.c.   Search for "w or 'w
 * 4) Make a copy of an existing 'else if' statement, ex., else if( (strncmp(rx0.buf, "vrse", 4) == 0) )  and paste.
 * 5) When pasting, make sure commands are in 'else if' order longest to shortest.
 *     The exception is single letter commands.  Put those first with a char test followed by a test for 0x00 (null)
 * 6) Modifiy the strncmp else if( (strncmp(rx0.buf, "wdog", 4) == 0) ).    Changing 4 to 2 is a shortcut option
 * 7) New commands require a new command opcode.  Open serial.h and go to the bottom of the #define and and add the new one.
 *      #define CMD_APF_WDOG             146  // display watchdog debug info
 *      update the else if with the new command code.
 *      rx0.cmd = CMD_APF_WDOG;
 * 8) Call a new 'logically named' handler function and call it from the else if
 *        apf_wdog_status();
 * 9) Create new protocol command handler in serial_cmd_fxn().c, add proto to serial_cmd_fxn.h
 *      void apf_wdog_status(void) {  }
 * 10) Compile and test.   I'm leaving lots of junk in the code with endless amounts of conditional compile.
 *      If you make a test and test command that's semi useful, err on the side of leaving it in the code.
 *      for tests, uprintf's should have "DBG: " prepended to output strings.  As that will help later when its time to consolidate and shed.
 *
 * Notes: several command parsing libraries were given a cursory look back in 2023 when the command line started.  The simple parsing by cascaded
 *      'else if' provides and easy copy paste modify coding style.   I've left it in place well passed the time I normally would have refactored
 *      or migrated to the use of someone elses nifty library.  Schedule is tight, lots of more important things todo.
 *
 * for a tutorial on 'Adding Menu items' for test and development, see thomzone.c
 *
 * serial_stateMachine on uart0
 * *
 * The expected behavior:
 *  wakeup, command prompt, no menu, be ready to respond to serial commands
 *  perhaps, hit <CR> multiple times and the menu pops up.
 *
*/

extern char dirName[ ];


//reset the system errno variable on each pass
//ERANGE is used in commands that parse passed values, e.g, RTC, CTD, FIT


#define ST_RX_INIT      0
#define ST_RX_MAINMENU  1
#define ST_EXEC_CMD     2       // user types a command
#define ST_RX_CMD       3
#define ST_RX_DATA      4
#define ST_RX_EXEC      5

// Per board requirement #define the Menu Items as needed.
#if BOARD_MSC == 3
    #if USE_MAIN_MENU >= 1
        #define MENU(...)   __VA_ARGS__
    #endif
    #if USE_FS_CMD_TABLE >= 1
        #define MENU(...)   __VA_ARGS__
    #endif
    #define SCXX(...)   __VA_ARGS__
    #define SC3C(...)   __VA_ARGS__
    #define FSYS(...)   __VA_ARGS__
    #define cmd_defaults apf_defaults
#elif BOARD_MPHOX == 1
#if USE_MAIN_MENU >= 1
    #define MENU(...)   __VA_ARGS__
#endif
    #define FSYS(...)   __VA_ARGS__
    #define SCMP(...)   __VA_ARGS__
    #define SCXX(...)   __VA_ARGS__

#elif BOARD_MFET >= 1
    #define FSYS(...)   __VA_ARGS__
    #define SCXX(...)   __VA_ARGS__
#if USE_MAIN_MENU >= 1
    #define MENU(...)   __VA_ARGS__
#endif

#elif BOARD_NANOFET >= 1
    #define SCXX(...)   __VA_ARGS__
#if USE_MAIN_MENU >= 1
    #define MENU(...)   __VA_ARGS__
#endif
#else
    #error  UNKNOWN BOARD  // we shouldnt get this error - unless we added a new board and forget to augment the else if conditional
#endif

//  Add all unused menu line entry macros here - and define them above per HW requirements
#ifndef MENU
    #define MENU(...)       // global menus - obsolete for size and historically kept here only for amusement
#endif
#ifndef SC3C
    #define SC3C(...)       // specific to APF MSC3 board
#endif
#ifndef SCMP
    #define SCMP(...)       // pump actions
#endif
#ifndef SCXX
    #define SCXX(...)       // Standard menu item
#endif
#ifndef FSYS
    #define FSYS(...)   // file system - requires boards with SD cards
#endif

#ifndef cmd_defaults
#define cmd_defaults base_defaults
#endif



#if BOARD_MSC == 3
// call this function after sampling and to store mode changes
void save_apf_vars_fram(void)
{
    //eeprom_sys_data_store();    //save
    //APFTODO, verify this also stores apf variables
     fram_apf_vars_store();
     fram_last_samp_store();
}
#endif

#ifdef OLDCODE
 void save_sys_data_eeprom(void)
 {
     eeprom_sys_data_store();
 }
#endif
void print_prompt(void)
{
	//print prompt in H2M 
    if(sys_data.terminal_mode == H2M_MODE)
    {
        //uprintf("\r\n>");   //no dir in prompt at root
        prn_nl();

        get_pwd_str(&dirName[0]);
        if(dirName[0] == '/' && dirName[1] == 0x00)
        {
            if(gBootError == 1)
            {
                uprintf("><");
            }
            else
            {
                uprintf(">");       //suppress extra chars, don't print /
            }
        }
        else
        {
            uprintf("%s>", dirName);
        }

        //uprintf("%s>", dirName);
        //uprintf("%s\r\n>", dirName);   //prompt
        //uprintf("%s>", dirName);  prn_nl(); //prompt
    }
    // no prompt in M2M_MODE
}

void print_pwron_msg(void)
{
    if(sys_data.terminal_mode == M2M_MODE)
    {
        if(gBootError == 1)
        {
            uprintf("?"); prn_nl(); //power on prompt
        }
        else
        {
            uprintf("!"); prn_nl(); //power on prompt
        }
    }
}

void print_NAK(void)
{
    uprintf("NAK"); prn_nl();
}
void m2m_statemachine_init(void)
{
    unsigned int indx;

    UARTEchoSet(false);

    rx0.state = ST_RX_INIT;
    rx0.cmd = 0;
    rx0.cnt = 0;
    rx0.cr_cnt = 0;
    rx0.indx = 0;
    rx0.tick_ms_start = Timer_1ms();
    rx0.tick_ms_now = rx0.tick_ms_start;
    rx0.tick_ms_delta = 0;
    //rx0.tick_ms_delim = M2M_DELIM_MS_TIMEOUT;

    //uprintf("sm init rx0.tick_ms_delim = M2M\r\n");     // DEBUG

    for(indx=0; indx<MAX_SIZE_RX0_BUF; indx++)
        rx0.buf[indx] = 0;

    print_prompt();

}

// call this routine from an exec loop
// return 0 if nothing happened
//        1 if char typed or command
//       -1 if sleep is commanded
// Notes:
//    give user immediate feedback if typing 'wrong command'
//    use timeout to terminate command entry, also accept 0x0d <cr> as terminating char
//    minimum 2 letter commands, measure time between 1st and 2nd char to determine if m2m (250msec?) or h2m timeout
//
typedef enum eEscapeStates
{
    EscSt8_Idle,
    EscSt8_Started,
    EscSt8_Bracket,
    EscSt8_Complete
} eEscapeStates;

#define MAX_QUEUED_CMD_LENGTH   48          // longer commands are just not entered into the history silly
#define CMD_QUEUE_DEPTH         8           // Must be power of 2 (2,4,8,16 etc... - or change the wrapping MACROs

#define NEXT_ITEM(v)  ((v - 1) & (CMD_QUEUE_DEPTH-1))
#define PREV_ITEM(v)  ((v + 1) & (CMD_QUEUE_DEPTH-1))
#define SOME_ITEM(v,d)  ((v + d) & (CMD_QUEUE_DEPTH-1))


typedef struct tEscapeSM
{
    eEscapeStates   state;
    uint8_t         index;
    uint8_t         count;
    char            command[CMD_QUEUE_DEPTH][MAX_QUEUED_CMD_LENGTH];
} tEscapeSM;

tEscapeSM escSM = {.state = EscSt8_Idle, .index=0, .count=0};

void ruboutExistingCmd(void)
{
    uint8_t idx;
    for (idx=0; idx<rx0.cnt; idx++)
    {
        UARTputc('\b');
        UARTputc(' ');
        UARTputc('\b');
    }

}
void cmdHistoryHandler (void)
{
    uint8_t idx;
    uint8_t cIdx = NEXT_ITEM(escSM.index);
    for (idx=0; idx<escSM.count; idx++)
    {
        uprintf("%s", escSM.command[cIdx]);
        prn_nl();
        cIdx = NEXT_ITEM(cIdx);
    }
}

void loadCommand(int direction)
{
    ruboutExistingCmd();
    escSM.index = SOME_ITEM(escSM.index, direction);
    rx0.state = ST_EXEC_CMD;
    rx0.cnt = strlen(escSM.command[escSM.index]);
    // rx0.indx = rx0.cnt;
    //uprintf("          [12345678901234567890"); prn_nl();
    //uprintf("loadCommand[%s], cnt=%d", escSM.command[escSM.index], rx0.cnt); prn_nl();
    strcpy((char *)rx0.buf, escSM.command[escSM.index]);
    escSM.state = EscSt8_Complete;
}

void pushCommand(char *currCmd)
{
    if (rx0.cnt < MAX_QUEUED_CMD_LENGTH)
    {
        strcpy(escSM.command[escSM.index], currCmd);
        escSM.index = PREV_ITEM(escSM.index);
        escSM.count++;
        if (escSM.count > CMD_QUEUE_DEPTH)
            escSM.count = CMD_QUEUE_DEPTH;
    }
}

eEscapeStates escapeStringSM(char ch)
{
    switch (escSM.state)
    {
        case EscSt8_Idle :
        case EscSt8_Complete :
            if (ch == ESC)
                escSM.state = EscSt8_Started;
            else
                escSM.state = EscSt8_Idle;
            break;

        case EscSt8_Started :
            if (ch == 91)   // '['
                escSM.state = EscSt8_Bracket;
            else
                escSM.state = EscSt8_Idle;
            break;

        case EscSt8_Bracket :
            switch (ch)
            {
                case 'A' :  // up
                    loadCommand(-1);        // sets state to EscSt8_Complete
                    break;

                case 'B' :  // down
                    loadCommand(1);         // sets state to EscSt8_Complete
                    break;

                case '5' :   // page Up
                case '6' :   // page Down
                case 'C' :   // right
                case 'D' :   // left
                default :
                    escSM.state = EscSt8_Idle;
                    break;
            }
    }

    return escSM.state;
}


int handle_m2m_comm(void)
{
    int rxByte;
    int retVal;
    int timeoutFlg;

    //Insert timebased delimiter check here - if char entry timeout is hit, then insert rx0.byte = 0x0d and bypass UARTrdy() check


    timeoutFlg = 0;
    if(rx0.indx > 0)
    {
        // start is set on first valid char, then reset again on each valid char
        // check timeout delim
        rx0.tick_ms_delta = (Timer_1ms() - rx0.tick_ms_start);

        if(rx0.tick_ms_delta >= rx0.tick_ms_delim)
        {
            //uprintf("timed out\r\n>");   // debug
            rxByte = 0x0d;          // if timeout, insert 0x0d 'end of rx delimiter'
            timeoutFlg = 1;
            rx0.cr_cnt = 0;     // is this needed?
        }

    }

    // if not timed out, then check for rx
    if(timeoutFlg == 0)
    {
        if(UARTrdy() == 0)
        {
            return(0);      //no char available
        }

        rxByte = getChar();
    }


    if (escapeStringSM(rxByte))
    {
        if (escSM.state == EscSt8_Complete)
        {
            uprintf("%s", escSM.command[escSM.index]);
            rx0.state = ST_EXEC_CMD;
            rx0.cmd = 0;
            rx0.indx = rx0.cnt;             //
            escSM.state = EscSt8_Idle;
            rx0.byte = escSM.command[escSM.index][rx0.cnt];
        }
    }
    else
    {
        rx0.byte = rxByte;
    }

    if (rx0.byte > 0)
    {
        //UARTputc(rx0.byte);     // DEBUG
        switch(rx0.state)
        {
            case ST_RX_INIT:
                //m2m_statemachine_init();
                // transition to MAINMENU if <cr> or 'm'
                // transition to CMD if 0xaa or 'U'  (APFTODO 6May2025 - eliminate this more complex m2m serial protocol, relic of the first proto)
                switch(rx0.byte)
                {
                    // command terminator
                    //case 0x0a:  //<LF>          //APFTODO, new 6May2025, check if LF is now handled in Jess test
                        //uprintf("<LF>");   //DBG
                    case 0x0d:  //<CR>
                        print_prompt();
                        //uprintf(" CR Rxd %u\r\n", rx0.cr_cnt);  //DEBUG
                        rx0.cr_cnt++;
                        if(rx0.cr_cnt >= MULTIPLECRMENU+1)
                        {
                            rx0.cr_cnt = 0;
    //APFTODO, consider removing multiple CR carriage return to bring up note about menu  TODO Remove from production / review with UW
    #if MULTIPLECRMENU > 0
                            uprintf("M for menu, H for H2M mode"); prn_nl();
    #ifdef BEHAVIOR_TOSTARTMENU
                            rx0.state = ST_RX_MAINMENU;
                            UARTEchoSet(true);
    #endif
    #endif
                        }
                        return(1);          //got a command delimiter, timeout or <cr>

                    case 0x0a:              //handling cr cr-lf lf termination
                        return(1);          //eat the LF, fixes the NAK when Teraterm is setup for CR-LF termination
                        //break;

                    case 'a':
                    case 'A':
                    case 'b':
                    case 'B':
                    case 'c':
                    case 'C':
                    case 'd':
                    case 'D':
                    case 'e':
                    case 'E':
                    case 'f':
                    case 'F':
                    case 'g':
                    case 'G':
                    case 'h':
                    case 'H':
                    case 'i':
                    case 'I':
                    //case 'j':
                    //case 'J':
                    case 'k':
                    case 'K':
                    case 'l':
                    case 'L':
                    case 'm':
                    case 'M':
                    case 'n':
                    case 'N':
                    case 'o':       //off
                    case 'O':
                    case 'p':
                    case 'P':
                    case 'q':
                    case 'Q':
                    case 'r':
                    case 'R':
                    case 's':
                    case 'S':
                    case 't':
                    case 'T':
                    //case 'u':
                    //case 'U':
                    case 'v':
                    case 'V':
                    case 'w':
                    case 'W':
                    //case 'x':
                    //case 'X':
                    case 'Y':
                    case 'y':
                    case 'z':
                    case 'Z':

                        //uprintf("s %ul\r\n", rx0.tick_ms_start);        // debug

                        // first letter of valid user typed command => Immediate feedback
                        if(sys_data.echo_flag == 1)
                        {
                            UARTputc(rx0.byte);         //echo user typed commands
                        }

                        if(rx0.byte >= 'A' && rx0.byte <= 'Z')
                        {
                            rx0.byte |= 0x20;       //0x41 -> 0x61
                        }
                        rx0.state = ST_EXEC_CMD;
                        rx0.cr_cnt = 0;
                        rx0.cmd = 0;
                        rx0.indx = 0;
                        rx0.tick_ms_start = Timer_1ms();       // set time start after receiving first good char
                        //rx0.cnt = 2;                // THOMREMOVE default is to look for 2 byte commands first
                        rx0.buf[rx0.indx] = rx0.byte;
            //UARTputc(rx0.buf[rx0.indx]);   //DEBUG
                        rx0.indx++;
                        rx0.buf[rx0.indx] = 0;
                        return(1);

                    //case M2M_RX_PREAMBLE_HACK:   //'U'
                    //case M2M_RX_PREAMBLE_TXT:   //'<'
                    case M2M_RX_PREAMBLE:       // 0xAA
                        rx0.state = ST_RX_CMD;
                        rx0.cmd = 0;
                        rx0.indx = 0;
                        rx0.cr_cnt = 0;
                        rx0.tick_ms_start = Timer_1ms();       // set time start after receiving first good char
                        return(1);

                    default:
                        print_NAK();
                        rx0.state = ST_RX_INIT;
                        m2m_statemachine_init();
                        return(1);
                        //break;

                }
    #ifdef WARNING_ABATEMENT_UNREACHABLE
                break;
    #endif

            case ST_EXEC_CMD:
                //if(rx0.byte == 0x0d)        // exec command terminated by 0x0d
                if( (rx0.byte == 0x0d) || (rx0.byte == 0x0a) )       // exec command terminated by 0x0d <cr> or <lf>, new 6May2025
                {
                    if(sys_data.terminal_mode == H2M_MODE)
                    {
                        prn_nl();
                    }
                    pushCommand((char *)rx0.buf);

                    retVal = parse_rx0_buf();   // exec command parse
                    if(retVal == 0)             // bad or no command
                    {
                        print_NAK();
                    }
                    else if( (retVal == CMD_SLEEP) || (retVal == CMD_APF_OFF) )   //APFTODO  Sleep and Off are the same for now 3 Oct 2024
                    {
                        //return(-1);
                        return(M2M_GOTO_SLEEP); //-1
                    }
    #ifdef REMOVECARBONLAB_MODE0
                    else if( (retVal == CMD_TAKESAMPLE) && (apf_fram.sys_sensor_mode == 0))    // sleep after sample
                    {
                        //return(-1);
                        return(M2M_GOTO_SLEEP); //-1
                    }
                    else if( (retVal == CMD_SENDLAST) && (apf_fram.sys_sensor_mode == 0))   // sleep after send last if in CarbonLab legacy mode
                    {
                        //return(-1);
                        return(M2M_GOTO_SLEEP); //-1
                    }
    #endif
                    else if(retVal == CMD_MAINMENU)
                    {
                        rx0.state = ST_RX_INIT;
                        m2m_statemachine_init();
                    }
                    else
                    {
                        rx0.state = ST_RX_INIT;
                        m2m_statemachine_init();

                        return(retVal);         //
                    }

                    rx0.state = ST_RX_INIT;
                    m2m_statemachine_init();
                }
                else if(rx0.byte == '\b')       // backspace
                {

                    UARTputc('\b');
                    UARTputc(' ');
                    UARTputc('\b');
                    if(rx0.indx > 0)
                        rx0.indx--;
                    rx0.buf[rx0.indx] = 0;
                    rx0.tick_ms_start = Timer_1ms();       // reset time start
                }
                else
                {

                    if(sys_data.echo_flag == 1)
                    {
                        UARTputc(rx0.byte);         //echo user typed commands and carriage return
                    }

                    if(rx0.byte >= 'A' && rx0.byte <= 'Z')
                    {
                        rx0.byte |= 0x20;       //0x41 -> 0x61   //tolower for all alpha
                    }
                    rx0.buf[rx0.indx] = rx0.byte;
                    if(rx0.indx < MAX_SIZE_RX0_BUF-1)
                        rx0.indx++;
                    rx0.buf[rx0.indx] = 0;
                    rx0.tick_ms_start = Timer_1ms();       // reset time start
                    //Thom todo on checking this condition

                    //uprintf("s %ul\r\n", rx0.tick_ms_start);        // DEBUG
                }
                break;

            case ST_RX_MAINMENU:
    #if BOARD_MSC == 3
                if( apf.isus_spec_pwr == TRUE )
                {
                    mms_specPwrOff();       //SpecPwrOff( SpecPort );
                    prn_nl(); uprintf("  Spectrometer Power was left ON! Powering DOWN..."); prn_nl();
                }
                if( apf.isus_lamp_pwr == TRUE )
                {
                    mms_lampPwrOff();         //LampPwrOff();
                    prn_nl(); uprintf("  Fiberlight Power was left ON! Powering DOWN..."); prn_nl();
                }
    #endif // BOARD_MSC == 3

    #if USE_MAIN_MENU >= 1
                exec_main_menu();
    #endif

                rx0.state = ST_RX_INIT;
                m2m_statemachine_init();
                //UARTEchoSet(true);          // NEW 26 Apr 2024, causes double return...  Trying to solve the Menu lack of echo problem
                return(1);
    #ifdef WARNING_ABATEMENT_UNREACHABLE
                break;
    #endif

            case ST_RX_CMD:
                switch(rx0.byte)
                {
                    //case M2M_RX_PREAMBLE_TXT:   //'U'  Note: with this active, no command, preamble to equalize noisy comm channel
                    case M2M_RX_PREAMBLE:       // 0xAA
                        // handle multiple preamble by doing nothing
                        rx0.tick_ms_start = Timer_1ms();       // reset time start
                        break;

                    default:
                        if( ((rx0.byte >= 'A') && (rx0.byte <= 'Z')) || ((rx0.byte >= 'a') && (rx0.byte <= 'z')) )
                        {
                            rx0.buf[rx0.indx] = rx0.byte & 0x5f;   // make lower case 0101 1111   42 vs 62  0100 0110
                            rx0.buf[rx0.indx] = rx0.byte | 0x20;   // make lower case 0101 1111   42 vs 62  0100 0110  0010 0000
                            //UARTputc(rx0.buf[rx0.indx]);         // DEBUG tolower
                            if(rx0.indx < MAX_SIZE_RX0_BUF-1)
                                rx0.indx++;
                            rx0.buf[rx0.indx] = 0;      // null terminate for string ops
                        }
                        else  // For now, valid commands are all ascii alphabet
                        {
                            // handle non alpha characters here...  For now, re-init
                            rx0.state = ST_RX_INIT;
                            m2m_statemachine_init();
                        }
                        break;
                }
                break;

            case ST_RX_DATA:
                break;
            case ST_RX_EXEC:
                break;
            default:
                rx0.state = ST_RX_INIT;
                m2m_statemachine_init();
                break;
        }
    }

    return 0;
}



// return 0 if no command
// return CMD_xxx otherwise
// return -1 to exit exec_main_loop
//
/* config:
 *  - time, gettime
 *  - settime
 *  - period, getperiod
 *  - setperiod
 *  - config, getconfig      datafields,
 *  - setconfig
 *  - filename, getfilename
 *  - setfilename
 *  - align
 *  commands:
 *  - help
 *  - sleep
 *  - ts
 *  - ss
 *  - sl
 *  - gc
 *  - ec
 *  - gh
 *  - eh
 *  - stop, exit, quit, <ctrl-c>
 */

void cmdAckHandler(void)
{

}
// #if BOARD_MSC == 3

void cmdHHHandler(void)
{
    switch (rx0.buf[3])
    {
        case '2' :
            cmd_h2m_mode_set(2);
            break;
        case '3' :
            cmd_h2m_mode_set(3);
            break;
        case '4' :
            cmd_h2m_mode_set(4);
            break;
        default :
            cmd_h2m_mode_set(1);
            break;
    }
    uprintf("term time = %u msec", cmd_h2m_timeout_get()); prn_nl();
    helpCnt++;
}

void cmdH2mHandler(void)
{
    uint32_t timeterm = cmd_h2m_timeout_get();
    uprintf("was term time = %u msec", timeterm); prn_nl();
    if(timeterm < 1000)
    {
        cmd_h2m_mode_set(1);
    }
    else if (timeterm < 2000)
    {
        cmd_h2m_mode_set(2);
    }
    else if (timeterm < 3000)
    {
        cmd_h2m_mode_set(3);
    }
    else if (timeterm < 3000)
    {
        cmd_h2m_mode_set(3);
    }
    else
    {
        cmd_h2m_mode_set(4);
    }
    uprintf("now term time = %u msec", cmd_h2m_timeout_get()); prn_nl();
}

#if BOARD_NANOFET == 1
// There is no SD Card so none apply
#else

void cmdCatHandler(void)
{
    if( Cmd_cat(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1)
        {
            uprintf("DBG: cat call failed");
            prn_nl();
        }
    }
    else
    {
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}
void cmdCdHandler(void)
{
    if( Cmd_cd(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: cd call failed"); prn_nl(); }
    }
    else
    {
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}
void cmdChkdskHandler(void)
{
    if( Cmd_chkdsk(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: chkdsk call failed"); prn_nl(); }
    }
}
void cmdCpHandler(void)
{
    if( Cmd_cp(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: cp call failed"); prn_nl(); }
    }
    else
    {
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}


void cmdFormatHandler(void)
{
    int response = yesOrNoMenuChoiceEcho("format will erase the uSD card, do you wish to proceed? [N]? ", NO);
    if(response == YES)
    {
        if( Cmd_format(tokens.argc, tokens.argv) != FR_OK )
        {
            uprintf("DBG: format call failed"); prn_nl();
        }
        else
        {
            //uprintf("DBG: format call ok"); prn_nl();
            //cmd_h2m_mode_set(20);       //file commands, term time set to 2.5*20 = 50sec
        }
    }
}
void cmdLsHandler(void)
{
    if( Cmd_ls(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: ls call failed"); prn_nl();  }
    }
    else
    {
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}

void cmdMkdirHandler(void)
{
    if( Cmd_mkdir(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: mkdir call failed"); prn_nl(); }
    }
    else
    {
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}

void cmdMvHandler(void)
{
    if( Cmd_mv(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: mv call failed"); prn_nl(); }
    }
    else
    {
        //uprintf("DBG: mv call ok"); prn_nl();
    }
}


void cmdPwdHandler(void)
{
    if( Cmd_pwd(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: pwd call failed"); prn_nl(); }
    }
    else
    {
        //uprintf("DBG: pwd call ok"); prn_nl();
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}


void cmdRmHandler(void)
{
   if( Cmd_rm(tokens.argc, tokens.argv) != FR_OK )
   {
       if(serDbgFlg == 1) { uprintf("DBG: rm call failed"); prn_nl(); }
   }
    else
    {
        //uprintf("DBG: rm call ok"); prn_nl();
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}


void cmdTouchHandler(void)
{
    if( Cmd_touch(tokens.argc, tokens.argv) != FR_OK )
    {
        if(serDbgFlg == 1) { uprintf("DBG: touch call failed"); prn_nl(); }
    }
    else
    {
        //uprintf("DBG: touch call ok"); prn_nl();
        cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
    }
}
#endif  // Else not NanoFet

void cmdDiagHandler(void)
{
// JLD!!! help diag does the diag,h and there isnt any other action with diag, so why not just make it a toggle possibly also rename it "diagt"
//    if(rx0.buf[5] == 'h' || rx0.buf[5] == 'H')
//    {
//        uprintf("diag,t - toggle diagnostic sample timing on/off"); prn_nl();
//    }
//    //diag,t => enable diagnostic timing
//    if(rx0.buf[5] == 't' || rx0.buf[5] == 'T')
//    {
        //
        if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1)
        {
            uprintf("Diagnostic Sample Timing is OFF"); prn_nl();
            sys_data.app_cfg[APPCFG_SAMPLING_DIAG] = 0;
        }
        else
        {
            uprintf("Diagnostic Sample Timing is ON"); prn_nl();
            sys_data.app_cfg[APPCFG_SAMPLING_DIAG] = 1;
        }
        // JLD!!! does this need to be saved to EEPROM?
//    }
}
#if BOARD_MSC == 3
void apfDbgHandler(void)
{
    apf_dbg(rx0.buf[4] == '1');
}
void apfIsusErrorHandler(void)
{
    apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;
    apf_err_get();
}
void apfDuraErrorHandler(void)
{
    apf_fram.sys_sensor_mode = DURA_SENSOR_MODE;
    apf_err_get();
}
void apfEchoHandler(void)
{
    if(rx0.buf[6] == 'n')
    {
        apf_serial_term_echo(1, 1);         // set echo flag, (don't echo the characters typed.  This is MSC1 default)
    }
    else if(rx0.buf[6] == 'f')
    {
        apf_serial_term_echo(0, 1);         // clr echo flag
    }
    else
    {
        apf_serial_term_echo(-1, 1);       // print state of echo flag
    }
}

#endif // BOARD_MSC == 3


#if BOARD_MSC == 3
void cmdM2mHandler(void)
{
    if( (rx0.buf[3] == ',') && (rx0.buf[4] >= '1') && (rx0.buf[4] <= '9') )
    {
        apf_m2m_mode_set(rx0.buf[4] & 0x0f);   //pass in 1 to 9
    }
    else
    {
        apf_m2m_mode_set(1);
    }

    uprintf("term time = %u msec", cmd_h2m_timeout_get());  prn_nl();
}
#endif // BOARD_MSC == 3

#if USE_MAIN_MENU >= 1
void cmdMHandler(void)
{
    if(sys_data.terminal_mode == H2M_MODE)      //don't allow M activate menu in M2M mode
    {
        exec_main_menu();
    }
}
#endif

void getMetaHeaderHandler(void)
{
    print_metadata_header( read_hib_rtcc() );
}

void cmdQAHandler (void)
{
    if(rx0.buf[3] == '1')
    {
        board_qa_and_init(1);       //1 means init FRAM
    }
    else
    {
        board_qa_and_init(0);
    }
}


#if BOARD_MSC == 3
void cmdSampleHandler(void)
{
    uint32_t loopTime = 5;  // default to 5 seconds
    char     *str;
    char     *endptr;

    //0123456789012345
    //sample,300

    str = (char*)&rx0.buf[7];
    // If there is an argument - parse it for loopTime
    if (*str > ' ')
    {
        errno = 0; // Reset errno before calling strtoul
        loopTime = strtoul(str, &endptr, 10);

        if((loopTime >= 3) && (loopTime <= 3600))
        {
            uprintf("sample loop time: %u sec, (%u min, %u sec)", loopTime, loopTime/60, loopTime%60); prn_nl();
        }
        else
        {
            uprintf("sample loop time (%s) is out of range [3:3600], setting period to 5 sec", str); prn_nl();
            loopTime = 5;
        }
    }

    if(databarFlg == 0)
    {
        uprintf("sample mode is %u, loop time %u sec.   Type sample again to toggle sampling off", apf_fram.sys_sensor_mode, loopTime); prn_nl();
    }
    else
    {
        uprintf("background sample mode is OFF, mode was %u", apf_fram.sys_sensor_mode);  prn_nl();
    }

    cmd_test_sample(loopTime);     //databarFlg is toggled in this routine, executed in main (search databarFlg)
}

void cmdStartSampleHandler(void)
{
    //flush the receive and transmit buffers
    // UARTFlushRx();

    if(apf_fram.sys_sensor_mode == 0)        // 0 is now CarbonLab get_sample format
    {
        pump_and_sample(0,1);       // 10 Apr 2022 printFlg=0, writeFlg=1
        fram_last_samp_store();     // defd in i2c_fram.c
    }
    else if(apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE)
    {
        apf_take_sample_nitrate(0);          // same as S1, calls mms_takeIsusSample "S1"   sample stored in fram
    }
    else if(apf_fram.sys_sensor_mode == DURA_SENSOR_MODE)
    {
        apf_take_sample_pH(0);          // same as S2, calls get_sample but does not print, stores in fram for retrieval by SL "S2"  sample stored in fram
    }
#if BOARD_MSC == 3
    else if (apf_fram.sys_sensor_mode == DURAISUS_SENSOR_MODE)
    {
        apf_take_sample_nitrate(0);
        prn_nl();
        apf_take_sample_pH(0);
        prn_nl();
    }
#endif // BOARD_MSC == 3
    else
    {
        uprintf("NAK,MODE=%u", apf_fram.sys_sensor_mode); prn_nl();
    }
}

void cmdStatusHandler(void)
{
    if(rx0.buf[6] == ',' && rx0.buf[7] == 'r' && rx0.buf[8] == 'e')
    {
        apf_status_reset();
    }
    else
    {
        apf_status_get();
    }
}

void cmdSlpHandler(void)
{
    rx0.cmd = CMD_APF_SLP;
    apf_slp_sleep();      // MSC suspend mode = SLP  APFTODO, what about processor sleep here, what is current draw with rs232 wakeup by uart peripheral

    //do what is done for "sleep" command (see above)
    rx0.cmd = CMD_SLEEP;
}

void cmdSendLastHandler (void)
{
    if(apf_fram.sys_sensor_mode == 0)        // 0 is now CarbonLab get_sample format
    {
        //uprintf("serial.c sl apf_send_last_sample"); prn_nl();  //DEBUG
        if(sys_data.msc_op != MSC1_OP)
        {
            send_last_sample();     //defd in sample.c (CarbonLab send last)
        }
    }
    if(apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE)
    {
        //uprintf("serial.c sl apf_send_last_nitrate"); prn_nl();  //DEBUG
        apf_send_last_nitrate();          // defd in apf_util.c
    }
    if(apf_fram.sys_sensor_mode == DURA_SENSOR_MODE)
    {
        //uprintf("serial.c sl apf_send_last_ph"); prn_nl();
        apf_send_last_pH();
    }
}
#endif // #if BOARD_MSC == 3

void cmdSleepHandler(void)
{
    if(databarFlg == 1)
    {
        rx0.cmd = CMD_APF_DIAG;
        databarFlg = 0;
    }
    else
    {
        databarFlg = 0;
    }
}

void ymodemSendHandler(void)
{
    cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec

    if( ymodem_send(tokens.argc, tokens.argv) != 0)
    {
         // fail
    }
    else
    {
         // success
    }
}

#if BOARD_MPHOX > 0 || BOARD_MFET > 1 || BOARD_NANOFET > 0
void cmd_t2_handler(void)
{
#if BOARD_NANOFET > 0
    get_sample(1,0);       // prnFlg, writeFlg
#else
    pump_and_sample(1,1);
#endif
}

void cmdTsHandler(void)
{
#if BOARD_NANOFET > 0
    get_sample(1,0);       // prnFlg, writeFlg
#else
    pump_and_sample(1,1);
#endif
}

#endif

#if BOARD_MSC == 3
void cmdIsusSensorModeHandler (void)
{
    apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;    //set mode = 1 (ISUS)
    apf_take_sample_nitrate(0);      // ISUS sample
    save_apf_vars_fram();
}
void cmdDuraSensorModeHandler(void)
 {
     apf_fram.sys_sensor_mode = DURA_SENSOR_MODE;    //set mode=2, pH
     apf_take_sample_pH(0);
     save_apf_vars_fram();
 }

void cmd_t1_handler(void)
{
   apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;    //update mode
   apf_take_sample_nitrate(1);      // ISUS sample
   save_apf_vars_fram();                         // save mode to FRAM
}



void cmd_t0_handler(void)
{
    apf_fram.sys_sensor_mode = 0;    //update mode
    pump_and_sample(1,1);      //
    save_apf_vars_fram();
}

void cmdTsHandler(void)
{

    if(apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE)
    {
        apf_take_sample_nitrate(sys_data.msc_op);           // per Dana Swift, APF11 release needs to operate MSC1 compatible
                                                            // same as T2, calls get_sample "T2" and returns result
    }
    else if(apf_fram.sys_sensor_mode == DURA_SENSOR_MODE)
    {
        apf_take_sample_pH(sys_data.msc_op);                // per Dana Swift, APF11 release needs to operate MSC1 compatible, ts is 'silent sample', requires sl
        // apf_take_sample_pH(1);                           // same as T2, calls get_sample "T2" and returns result
    }
    else
    {
        uprintf("NAK,TS,InvalidModeValue: %u", apf_fram.sys_sensor_mode); prn_nl();
    }
}

void cmdVinMinHandler(void)
{
    const char *argstr = (const char *) &rx0.buf[7];
    if (strstr(argstr, "isus"))
    {
        apf_vinmin_isus_set();
    }
    if (strstr(argstr, "dura"))
    {
        apf_vinmin_dura_set();
    }
    else
    {
        apf_vinmin_get();
    }
}

void apfErrorAddD (void)
{
    //012345678901234567890
    //test,error,this is an error string<cr><lf>
    if((rx0.buf[11] == ',') && (rx0.buf[12] >= ' '))
    {
        apf_error_log_dura_add(&rx0.buf[12]);
    }
}
void apfErrorAddI (void)
{
    //012345678901234567890
    //test,error,this is an error string<cr><lf>
    if((rx0.buf[11] == ',') && (rx0.buf[12] >= ' '))
    {
        apf_error_log_isus_add(&rx0.buf[12]);
    }
}
void apfErrorAdd (void)
{
    //012345678901234567890
    //test,error,this is an error string<cr><lf>
    if((rx0.buf[10] == ',') && (rx0.buf[11] >= ' '))
    {
        apf_error_log_add(&rx0.buf[11]);
    }
}
void apfTestSpec (void)
{
    apf_test_spec((uint8_t)atoi((char*)rx0.buf+10));    // use atoi to accept multi-digit numbers
}

void apfTestLog (void)
{
    apf_test_log(rx0.buf[10] & 0x0f);
}

void apfTestArray (void)
{
     if( (rx0.buf[10] == ',') && (rx0.buf[11] == 'd') )      //destroy EcoefsCnt
     {
         //01234567890123
         //test,array,d
         //badoink the ecoefcnt
         apfArray.EcoefsCnt = 255;       //set EcoefsCnt to be other than 256 to simulate NOT having ecoefzer.cal arrays loaded into fram
                                         // this is to enable 'new board' testing and to check if spectrum is printed in mms_scanSpectra / mms_takeIsusSample
         uprintf("Destroyed (ram) apfArray.EcoefsCnt = 255, run T1 to see if spectra is printed, No3 is -1.0");  prn_nl();
     }
     else if( (rx0.buf[10] == ',') && (rx0.buf[11] == 'r') )     //restore
     {
         apfArray.EcoefsCnt = 256;
         uprintf("Restored (ram) apfArray.EcoefsCnt = 256, assumes defaults was run and ECOEFZER.CAL was read into FRAM");  prn_nl();
     }
     else
     {
         apf_apfArray_debug();
     }
}

void apfDuraTest (void)
{
    apf_test_dura(rx0.buf[10] & 0x0f);
}

void apfFRamTest(void)
{
    if(rx0.buf[10] == '1')
    {
        fram_size_check();
    }
    else
    {
        fram_soak_test();
    }
}

#endif // BOARD_MSC == 3

void cmdIVTest(void)
{
    if (strncmp((char*)&rx0.buf[7], "t", 1) == 0) {                 // ivt vs just iv
        if((rx0.buf[9] >= '!') && (rx0.buf[9] <= '~')) {            // need it to be some real character
            log_current_voltage((char*)rx0.buf+9, 1);               //defd in zone_irene.c
            // remaining contents of rx0.buf should be a null-terminated string that will get used as filename
        } else {
            uprintf("Please follow 'test,ivt,' with a filename."); prn_nl();
        }
    } else {
        if((rx0.buf[8] >= '!') && (rx0.buf[8] <= '~')) {            // need it to be some real character
            log_current_voltage((char*)rx0.buf+8, 0);               //defd in zone_irene.c
            // remaining contents of rx0.buf should be a null-terminated string that will get used as filename
        } else {
            uprintf("Please follow 'test,iv,' with a filename."); prn_nl();
        }
    }
}

void cmdFileTest (void)
{
    if((rx0.buf[10] >= '0') && (rx0.buf[10] <= '9'))
    {
        microsd_fatfs_hammer_test(rx0.buf[10] & 0x0f);            //defd in thom.c
    }
    else
    {
        microsd_fatfs_hammer_test(0);            //defd in thom.c
    }
}

// forward reference for the table help function
void cmdTestHelp(void);

typedef struct tTestCmdRec
{
    char        *keyword;               // The command
    char        kwdLen;                 // portion of string that needs to match
    void        (*func)(void);          // function that handles the test
    char        *helpText;              // any help text that the operator might need
} tTestCmdRec;

tTestCmdRec testCmd[] = {
     { "sampnum",    7,  cmd_sampnum_test,      "" },
     { "timestamp",  9,  cmd_timestamp_test,    "" },
SC3C({ "array",      3,  apfTestArray,          "test array print or with ,d or ,r EcoefsCnt destroyed d=255, r=256" },)
SC3C({ "dura",       4,  apfDuraTest,           "dura pH test" },)
SC3C({ "fram",       4,  apfFRamTest,           "fram soak test or size check with ,1" },)
     { "file",       4,  cmdFileTest,           "microsd FATfs hammer_test [0123] 0=Big File, 1=Many Files, 2=Many Dirs+Files, 3=Long Dir and File names" },
     { "iv",         2,  cmdIVTest,             "current and voltage test iv[t],<filename> t=slow log, test,ivt,sylvester.txt" },
SC3C({ "probe",      5,  apf_test_probe,        "probe i2c bus"},)
SC3C({ "spec",       4,  apfTestSpec,           "spectrometer test <N>" }, )     // (uint8_t)atoi((char*)rx0.buf+10)); // use atoi to accept multi-digit numbers
SC3C({ "lamp",       4,  apf_test_lamp,         "lamp test" }, )
SC3C({ "log",        3,  apfTestLog,            "test log <N>" }, )      // (rx0.buf[10] & 0x0f);
SC3C({ "vars",       3,  apf_apf_vars_debug,    "print variables" }, )
SC3C({ "wdog",       4,  apf_test_wdog,         "cause system crash, then look for error.log entry" }, )
SC3C({ "errord",     6,  apfErrorAddD,          "insert a string in dura.log =>  test errord <string>" }, )
SC3C({ "errori",     6,  apfErrorAddI,          "insert a string in isus.log => test errori <string>" }, )
SC3C({ "error",      5,  apfErrorAdd,           "insert a string in error.log =>  test error this is the story" }, )
     { "help",       4,  cmdTestHelp,           "" },
     { "",           0,  0 }
};

tTestCmdRec a2dCmd[] = {
     { "biasneg",    7,  cmd_test_biasneg,      "" },
     { "biaspos",    7,  cmd_test_biaspos,      "" },

     { "ib",         2,  cmd_test_ib,           "" },
     { "ik",         2,  cmd_test_ik,           "" },
     { "tempc",      5,  cmd_test_tempc,        "" },
     { "vbat",       4,  cmd_test_vbat,         "" },
     { "vk",         2,  cmd_test_vk,           "" },
     { "vk_std",     6,  cmd_test_vk_std,       "" },
     { "vtherm",     5,  cmd_test_vtherm,       "" },
     { "vtherm_std", 10, cmd_test_vtherm_std,   "" },
     { "vrsi",       4,  cmd_test_vrsi,         "vrsi A2D" },
     { "vrse",       4,  cmd_test_vrse,         "vrse A2D" },
     { "vrse_std",   8,  cmd_test_vrse_std,     "" },
     { "",           0,  0 }
};

void cmdTestHelp(void)
{
    char *str = (char *)(&rx0.buf[0]);
    int cmdLen = strlen(str);
    strtok(str, ", ");                // skip over command
    char *strptr = strtok(NULL, ", ");       // is there an arg?
    tTestCmdRec *tCmd = testCmd;
    int table = 1;                 // second table of "test" parms to display/search
    int printCount = 1000;
    int found = 1;
    if (strptr)
    {
        printCount = 1;
        found = 0;
    }

    while ((printCount > 0) && (tCmd->kwdLen > 0))
    {
        if (!found)
        {
            if ((strncmp(strptr, tCmd->keyword, tCmd->kwdLen) == 0))
            {
                found = 1;
            }
        }
        if (found)
        {
            if (table == 1)
            {
                uprintf("test %s %s", tCmd->keyword, tCmd->helpText);
                prn_nl();
            }
            else // (table == 2)
            {
                uprintf("%s <parm>\r\n\t%s\r\n", tCmd->keyword, tCmd->helpText);
                uprintf("\t%s,cfg=<value>\r\n\t%s,sps=<value>\r\n\t%s,trials=<value>", tCmd->keyword,tCmd->keyword,tCmd->keyword,tCmd->keyword);
                prn_nl();
            }
            printCount--;
        }

        wait_consoleTx();
        tCmd++;
        // If we hit the end of table1 move on
        if ((table == 1) && (tCmd->kwdLen == 0))
        {
            tCmd = a2dCmd;
            table = 2;
        }
    }
}
// This is the function that handles the "test" command in the main command table
// A command like wdog already exists there, so in order for the test to get here
// it must be announced (proceeded) with test,wdog or test wdog or test=wdog, etc
// Tests that need no announcement like vrse, or vrse, or vk get to the table by the
// following function runSomeTest
void cmdTestHandler(void)
{
    tTestCmdRec *tCmd = testCmd;

    while (tCmd->kwdLen > 0)
    {
        if (strncmp((char*)&rx0.buf[5], tCmd->keyword, tCmd->kwdLen) == 0)
        {
            strcpy((char *)&rx0.buf[0],(char *)&rx0.buf[5]);   // move command down for argument processing
            tCmd->func();
            return;
        }
        tCmd++;
    }
}

// This function will parse the command line and return OK if there was a test to be run
// it does not care if the test succeeds or fails, just if the command found something (with proper formatted arguments)
int runSomeTest(void)
{
    tTestCmdRec *tCmd = a2dCmd;
    char *cptr = (char*)&rx0.buf[0];
    char *cfgPos = strstr(cptr, ",cfg");
    char *gainPos = strstr(cptr, ",gain");
    char *trialsPos = strstr(cptr, ",trials");
    int status = ERROR;

    while (tCmd->kwdLen > 0)
    {
        if (strncmp(cptr, tCmd->keyword, tCmd->kwdLen) == 0)
        {
            // It appears that the effort is to set the sps for this test
            //  So let it be written
            if (strstr(&cptr[tCmd->kwdLen+1], "sps"))
            {
                // so let it be done.
                cptr[tCmd->kwdLen] = '_';
                status = setDataVar();  // if we find the variable in the table then we will handle setting it there
            }
            // Or perhaps we try to set the config value?
            else if (cfgPos)           //   01234
            {   //  reformat the command var,cfg=xx
                //  to                   var=<value>
                strcpy(cfgPos,cfgPos+4);      // move it down over the "cfg"
                status = setDataVar();          // if we find the variable in the table then we will handle setting it there
            }
            else if (trialsPos)
            {   //  reformat the command var,cfg=xx
                //  to                   var=<value>
                cptr[tCmd->kwdLen] = '_';
                status = setDataVar();  // if we find the variable in the table then we will handle setting it there
            }
            else if (gainPos)
            {
                // If there is a gain configuration item then it should be added to the vtable,
                // or if there is another
                cptr[tCmd->kwdLen] = '_';
                status = setDataVar();  // if we find the variable in the table then we will handle setting it there
            }
            else        // If there was no matching argument call the function
            {
                tCmd->func();
            }

            if (status == OK)
            {
                rx0.cmd = CMD_SETCFG_CONFIG;    // JLD-TGM  Do these need to be differentiated
            }

            return status;
        }
        tCmd++;
    }
    return status;
}

void cmdSetVerbose (void)
{
    if (rx0.buf[5] == '0')
    {
        sys_data.output = NORMAL;
        prn_nl(); prn_nl(); uprintf("Output mode now: NORMAL");
    }
    else
    {
        sys_data.output = VERBOSE;
        prn_nl(); prn_nl(); uprintf("Output mode now: VERBOSE");
    }
}

void cmdDeploy(void)
{
    // JLD-TGM - should the sample period get stored?  Should all vars changed via the vtTable method be "stored"" as well???
    // Adding this here since the sample period is not saved automatically.
    char     *str;
    char     *endptr;
    uint32_t loopTime;
    char askForNotes = 1;
    char *notesStr = 0;
    int32_t mode = 0;
    char needEEStore = 0;
    //0123456789012345
    //deploy loopTime,deployMode,deployment Note

    str = (char*)&rx0.buf[7];
    // If there is an argument - parse it for loopTime
    if (*str > ' ')
    {
        errno = 0; // Reset errno before calling strtoul
        loopTime = strtoul(str, &endptr, 10);

        if((loopTime < 3) || (loopTime > 3600))
        {
            uprintf("Loop time (%d) out of range [0-3600], set to %d seconds", loopTime, sys_data.sampling_period);   prn_nl();
        }
        else
        {
            sys_data.sampling_period = loopTime;
            needEEStore = 1;
        }
#if BOARD_MSC == 3
        if ((*endptr == ' ') || (*endptr == ','))
        {
            str = endptr+1;
            mode = strtoul(str, &endptr, 10);
            if (mode < 0 || mode > 3)
            {
                uprintf("mode out of range [0-3], setting to 1", mode);     prn_nl();
                mode = 1;
            }
            else
            {
                apf_fram.sys_sensor_mode = mode;
                fram_apf_vars_store();
            }
            if ((*endptr == ' ') || (*endptr == ','))
            {
                notesStr = endptr+1;
                if (strlen(notesStr) > 5)
                {
                    strcpy(sys_data.deploy_note, notesStr);
                    needEEStore = 1;
                }
                else
                {
                    uprintf("Some note appeared (%s) but need more than 5 chars", notesStr); prn_nl();
                }
            }
        }
#endif
    }

    uprintf("Deploy Loop time: %u sec\r\nmode: %d", sys_data.sampling_period, mode);
    prn_nl();

    if (notesStr)
    {
        askForNotes = 0;    // no need to ask in the start_deploy function
        uprintf("Notes: %s", notesStr);
        prn_nl();
    }
    if (needEEStore)
        eeprom_sys_data_store();

    // Adding this here to see the notes and other input that is not echoed otherwise
    if (askForNotes)
        UARTEchoSet(true);

    deploy_start(askForNotes);
}

void cmdNote(void)
{
    UARTEchoSet(true);
    enter_deployment_note(30);
    UARTEchoSet(false);
}

#if BOARD_MSC == 3
void cmdScanSpectraHandler (void)
{
    int verbose = 0;
    uint16_t looptime_s = 1;
    uint8_t litScansPerDark = 10;
    char *pch;
    char *endptr;
    strtok ((char*)rx0.buf, ",");    // the first one returns the sampfast part
    pch = strtok (NULL, ",");

    if (pch == NULL)
    {
        cmd_scan_spectra(10, 0, 1, 1, 0);           // JLD--TGM seems like this ought be different than the verbose option below
        return;
    }

// JLD - This should be handled by command table parser
//    // check for h
//    if (pch[0] == 'h')
//    {
//        cmd_sampfast_help();
//        return;
//    }

    // check for v - verbose mode, outputs data to screen
    if (pch[0] == 'v')
    {
        verbose = 1;
        // check for more arguments
        pch = strtok (NULL, ",");
        if (pch == NULL) {
            cmd_scan_spectra(10, 0, 1, 1, 0);
            return;
        }
    }

    // check for non-number and label it an error if so.  strtoul would return 0 which is a valid argument, so can't check for 0 result
    errno = 0; // Reset errno before calling strtoul
    looptime_s = strtoul(pch, &endptr, 10);
    if (errno) {
        uprintf("No or invalid sampling interval specified; setting to default %ds", looptime_s); prn_nl();
    }

    // even if that had a problem, move onto next token
    pch = strtok (NULL, ",");
    errno = 0; // Reset errno before calling strtoul
    litScansPerDark = strtoul(pch, &endptr, 10);
    if (errno) {
        uprintf("No or invalid number of light scans per dark specified; setting to default %d", litScansPerDark); prn_nl();
    }


    cmd_scan_spectra(litScansPerDark, looptime_s, 0, verbose, 1);

    // does NOT turn off between light scans (0), print is optional depending on 'v', writes (1)
}
#endif // #if BOARD_MSC == 3

void cmdYHandler(void)
{
    uprintf("term time = %u", rx0.tick_ms_delim); prn_nl();
    //apf_print_termtime();
}

void ymodemRecvHandler(void)
{
    cmd_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec

    calFileFlg = 0;
    if( ymodem_receive(tokens.argc, tokens.argv) != 0)
    {
        // fail
    }
    else
    {
        // success
    }
}

void cmdHelpHandler(void)
{
    cmd_h2m_mode_set(1);

    if(rx0.buf[5] == 'f')
    {
        print_cmd_file_help();
    }
    else
    {
        print_cmd_help();
    }
}

//void zmodemRecvHandler(void)
//{
//    calFileFlg = 0;
//    if( zmodem_receive(tokens.argc, tokens.argv) != 0)
//    {
//        // fail
//    }
//    else
//    {
//         // success
//    }
//}

void commandHelp(void);

#define MAX_OPS     3

typedef struct tSerialCmdRec
{
    char        *keyword;               // The command
    char        kwdLen;                 // portion of string that needs to match
    char        *opts;                  // optional sub-commands ?=get, h=help, *=set
    uint8_t     cmds[MAX_OPS];          // The system enumeration - might be an integer!
    void        (*func[MAX_OPS])(void); // function that handles. 1 per opts or 1 if opts is NULL
    char        *helpText;              // any help text that the operator might need
} tSerialCmdRec;


void cmdNotImplemented (void)
{
    uprintf("error command: Imp Es Nada", (char *)(&rx0.buf[0]));
    prn_nl();
}

tSerialCmdRec serCmd[] =
{
     // --------- A --------------
     { "ack",       3,  "",     {CMD_ACK},                      { cmdAckHandler },              "useless ACK command sent by APF11" },

     //---------- B --------------
SC3C({ "bake",      4,  "*",    {CMD_APF_BAKE},                 { apf_bake_uv_lamp },           "UV Lamp on for NN sec" },)
     { "baud",      4,  "?h*",  {CMD_APF_BAUD,  CMD_APF_BAUD,   CMD_APF_BAUD},
                                {cmd_baud_get, cmd_baud_help,  cmd_baud_set },                  "set baud rate" },
     // --------- C --------------
SC3C({ "c1",        2,    "",   {CMD_APF_CFG_ISUS},             { apf_isus_cfg_get },           "" },)
SC3C({ "c2",        2,    "",   {CMD_APF_CFG_DURA},             { apf_dura_cfg_get },           "" },)
// SC3C({ "cal_date,is", 11, "*",  {CMD_APF_CALDATE},              { cmd_cal_date_isus},           "" },)
// SC3C({ "cal_date,k0", 11, "*",  {CMD_APF_CALDATE},              { cmd_cal_date_k0},             "" },)
//SC3C({ "cal_date,pt", 11, "*",  {CMD_APF_CALDATE},              { cmd_cal_date_pt},             "" },)
SC3C({ "cal",       3,    "",   {CMD_APF_CAL,0,0},              { apf_cal_get},                 "get MSC calibration" },)
FSYS({ "cat",       3,    "*",  {CMD_FILEIO_CAT},               { cmdCatHandler },              "cat a file" },)
FSYS({ "cd",        2,    "*",  {CMD_FILEIO_CD},                { cmdCdHandler },               "change dir" },)
FSYS({ "chkdsk",    6,    "",   {CMD_FILEIO_CHKDSK},            { cmdChkdskHandler },           "" },)
     { "config",    6,    "?h*",{CMD_GETCFG_CONFIG, CMD_HELPCONFIG, CMD_SETCFG_CONFIG},
                                {cmd_config_get, cmd_config_help, cmd_config_set },             "" },
FSYS({ "cp",        2,    "*",  {CMD_FILEIO_CP},                { cmdCpHandler },               "copy file" },)
SC3C({ "ctd",       3,    "?h*",{CMD_APF_CTD_GET, CMD_APF_CTD_GET, CMD_APF_CTD_SET},
                                {apf_ctd_get, apf_ctd_help, apf_ctd_set},                       "get, set CTD vals" },)
SC3C({ "c",         1,  "",     {CMD_APF_CFG},                  { apf_cfg_get },                "get MSC config" },)

     //---------- D --------------
     { "deploy",    6,    "*",  {CMD_DEPLOY},                   { cmdDeploy },                  "start deploy,loopTime,mode,deployment Note" },
SC3C({ "dev",       3,  "",     {CMD_APF_DEVSTATUS},            { apf_print_devStatus },        "show developement state" },)
SC3C({ "dbg",       3,  "*",    {CMD_APF_DBG},                  { apfDbgHandler },              "set debug flag" },)
SCXX({ "def",       3,  "",     {CMD_APF_DEFAULTS},             { cmd_defaults },               "set defaults" },)
SC3C({ "diag",      4,  "",     {CMD_APF_DIAG},                 { cmdDiagHandler },             "toggle diagnostic sample timing on/off" },)

     //---------- E --------------
SC3C({ "e1",        2,  "",     {CMD_APF_GET_ERR},              { apfIsusErrorHandler },        "ISUS error counts" },)
SC3C({ "e2",        2,  "",     {CMD_APF_GET_ERR},              { apfDuraErrorHandler },        "DURA error counts" },)
SC3C({ "echo",      4,  "*",    {CMD_ECHO},                     { apfEchoHandler },             "" },)
SC3C({ "ecoef",     5,  "",     {CMD_APF_ECOEF},                { apf_ecoef_print },            "print coefficients file" },)
     { "ec",        2,  "",     {CMD_EXPORTCAL},                { export_pH_sensor_cal_coeff }, "export calibration coefficients in cal file format" },
SC3C({ "error",     5,  "",     {CMD_APF_ERROR},                { cmdNotImplemented },          "" },)
     { "exit",      4,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "" },
SC3C({ "e",         1,  "",     {CMD_APF_GET_ERR},              { apf_err_get },                "get error and event counts" },)

     //---------- F --------------
// MENU({ "files",     5,  "",     {CMD_FILESYSTEM},               { cmd_filesystem },             "" },)
SC3C({ "fit",       3,  "?*",   {CMD_APF_GET_FIT, CMD_APF_SET_FIT},
                                {apf_fit_get, apf_fit_set },                                    "get, set ISUS fit Window" },)
FSYS({ "format",    6,  "",     {CMD_FILEIO_FORMAT},            { cmdFormatHandler },           "" },)
SC3C({ "ftst",      4,  "",     {CMD_APF_FILETEST},             { apf_file_test },              "" },)

     //---------- G --------------
     { "gc",        2,  "",     {CMD_GETCAL},                   { get_pH_sensor_cal_coeff },    "get calibration coefficients" },
     { "getfil",    6,  "",     {CMD_GETFILENAME},              { cmd_get_filename },           "" },
     { "getper",    6,  "",     {CMD_GETPERIOD},                { cmd_period_get },             "" },
     { "get",       3,  "*",    {CMD_GETCFG_CONFIG},            { cmd_get_var },                "get <symbol>" },

SC3C({ "gh1",       3,  "",     {CMD_GETHEADER_ISUS},           { apf_print_isus_header },      "get ISUS header" },)
SC3C({ "gh2",       3,  "",     {CMD_GETHEADER_DURA},           { apf_print_dura_header },      "get DURA header" },)
     { "gh",        2,  "",     {CMD_GETHEADER},                { get_header },                 "get old header" },
     { "gm",        2,  "",     {CMD_GETMETADATA},              { getMetaHeaderHandler },       "get metadata header" },

     // --------- H --------------
     { "help",      4,  "*",    {CMD_HELP},                     { commandHelp },                "help,<cmd> for specific help" },
SCXX({ "hh",        2,  "*",    {CMD_APF_H2M,0,0},              { cmdHHHandler },               "" },)
     { "hist",      4,  "",     {CMD_APF_H2M,0,0},              { cmdHistoryHandler },          "command history" },
SCXX({ "h2m",       2,  "",     {CMD_APF_H2M},                  { cmdH2mHandler },              "" },)
SCXX({ "h",         1,  "",     {CMD_APF_H2M,0,0},              { cmdHHHandler },               "" },)

     //---------- I --------------
SC3C({ "init",      4,  "",     {CMD_APF_INIT},                 { apf_init_persistent_vars },   "init MSC for deployment" },)
//SC3C({ "isfet_sn",  8,  "*",    {CMD_APF_PH},                   { cmd_isfet_sn },               "set FET SN" },)
//SC3C({ "ise_sn",    6,  "*",    {CMD_APF_PH},                   { cmd_ise_sn },                 "set E SN" },)

     //---------- K --------------

     //---------- L --------------
     { "logt",      4,  "",     {CMD_SPARE1},                   { cmdNotImplemented },          "" },
FSYS({ "ls",        2,  "*",    {CMD_FILEIO_LS},                { cmdLsHandler },               "" },)

     //---------- M --------------
SC3C({ "m2",        2,  "",     {CMD_APF_M2M},                  { cmdM2mHandler },              "" },)
     { "microsd",   7,  "?*",   {CMD_GETSTORE, CMD_SETSTORE},
                                {cmd_store_get, cmd_store_set},                                 "store (microsd or spi flash)" },
FSYS({ "mkdir",     5,  "*",    {CMD_FILEIO_MKDIR},             { cmdMkdirHandler },            "" },)
SC3C({ "mode",      4,  "?*",   {CMD_APF_GET_MODE, CMD_APF_SET_MODE},
                                {apf_mode_get, apf_mode_set },                                  "get, set Sensor Mode" },)
FSYS({ "mv",        2,  "*",    {CMD_FILEIO_MV},                { cmdMvHandler },               "" },)

MENU({ "m",         1,  "",     {CMD_MAINMENU},                 { cmdMHandler },                "" },)

     //---------- N --------------
     { "nap",       3,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "" },
SC3C({ "nitra",     5,  "",     {CMD_APF_NITRATE},              { apf_nitrate },                "" },)
SC3C({ "no3",       3,  "",     {CMD_APF_NITRATE},              { apf_nitrate },                "" },)
     { "note",      4,  "",     {CMD_HELP},                     { cmdNote },                    "Debug - deployment note" },

     // --------- O --------------
SC3C({ "off",       3,  "",     {CMD_APF_OFF},                  { apf_off },                    "prep MSC for shutdown" },)

     //---------- P --------------

//     { "period",    6,  "?*",   {CMD_GETPERIOD, CMD_SETPERIOD},             // JLD-TGM - this was a real headache to allow first 3 chars...
//                                {cmd_period_get, cmd_period_set},                               "" },
//SC3C({ "ph_calfile",10, "*",    {CMD_APF_PH},                   { cmd_dura_cal_file },          ""},)
     { "ph,h",      4,  "",     {CMD_PH_CAL},                   { cmd_k0_help },                "" },
SC3C({ "ph",        2,  "",     {CMD_APF_PH},                   { apf_pH },                     "" },)
SC3C({ "profile,dec",11,"",     {CMD_SET_PROFILECNT},           { apf_profileCount_dec },       "decrement profile counter" },)
SC3C({ "profile,inc",11,"",     {CMD_SET_PROFILECNT},           { apf_profileCount_inc },       "increment profile counter" },)
SC3C({ "profile",   7,  "?h*",  {CMD_GET_PROFILECNT, CMD_GET_PROFILECNT, CMD_SET_PROFILECNT},
                                {apf_profileCount_get, apf_profileCount_help, apf_profileCount_set }, "get, set profile counter" },)
FSYS({ "pwd",       3,  "",     {CMD_FILEIO_PWD},               { cmdPwdHandler},               "" },)
SCMP({ "pump",      4,  "*",    {CMD_PUMP_SETUP},               { pump_sequencer_setup },       "pump sequence settings" },)
SC3C({ "p",         1,  "",     {CMD_APF_GET_PC},               { apf_pc_get},                  "get power cycle count" },)

     //---------- Q --------------
     { "qa",        2,  "*",    {CMD_QA},                       { cmdQAHandler },               "" },
//SC3C({ "qconfig,h", 9,  "",     {CMD_APF_Q_SENSORCFG},          { apf_sensor_type_help },       "sensor Help" },)
//SC3C({ "qconfig",   7,  "*",    {CMD_APF_Q_SENSORCFG},          { apf_sensor_type_set },        "set Sensor" },)
     { "quit",      4,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "go to sleep" },
SC3C({ "q",         1,  "",     {CMD_APF_Q_SENSORCFG},          { apf_sensor_type_get },        "get MSC supported sensors" },)

     //---------- R --------------
SC3C({ "reade",     5,  "",     {CMD_APF_READECOEF},            { apf_ecoef_read },             "" },)
SC3C({ "readz",     5,  "",     {CMD_APF_READZCOEF},            { apf_zcoef_read },             "" },)
FSYS({ "rm",        2,  "*",    {CMD_FILEIO_RM},                { cmdRmHandler },               "" },)
SC3C({ "rtc",       3,  "?h*",  {CMD_APF_GET_RTC, CMD_APF_GET_RTC, CMD_APF_SET_RTC},
                                {apf_rtc_get, apf_rtc_help, apf_rtc_set },                      "get, Set MSC real time clock" },)

     // --------- S --------------
SC3C({ "s1",        2,  "",     {CMD_APF_ISUS_S1},              { cmdIsusSensorModeHandler },   "start ISUS sample" },)
SC3C({ "s2",        2,  "",     {CMD_APF_DURA_S2},              { cmdDuraSensorModeHandler },   "start DURA sample" },)
SC3C({ "sample",    6,  "*",    {CMD_SAMP_DATABARF},            { cmdSampleHandler },           "sample <looptime>" },)
SC3C({ "scanspec",  8,  "h*",   {CMD_APF_SCANSPECTRA,CMD_APF_SCANSPECTRA},
                                                                { cmd_sampfast_help, cmdScanSpectraHandler },      "" },)
// SC3C({ "sensor_sn", 9,  "",     {CMD_APF_PH},                   { cmd_sensor_sn },              "" },)
SC3C({ "sernum",    6,  "?h*",  {CMD_APF_SERNUM,CMD_APF_SERNUM,CMD_APF_SERNUM},
                                {apf_sernum_msc_get, apf_sernum_help, apf_sernum_handler },     "" },)
SC3C({ "settings",  8,  "?h*",  {CMD_SETTINGS},                 { cmd_print_settings },         "print setting details" },)
     { "set",       3,  "*",    {CMD_SETCFG_CONFIG},            { cmd_set_var },                "set <symbol>" },
// JLD these are redundant commands file xx, period xx, time xx all do the same job
//     { "setfil",    6,    "*",{CMD_SETFILENAME},              { cmd_set_filename },           "" },
//     { "setper",    6,    "*",{CMD_SETPERIOD},                { cmd_period_set },             "" },
//     { "settime",   5,    "*",{CMD_SETTIME},                  { cmd_time_set },               "" },

SC3C({ "se",        2,  "?*",   {CMD_APF_GET_SE, CMD_APF_XFER_SE},
                                {apf_se_get, apf_se_xfer},                                      "send MSC error messages" },)
     { "sleep",     5,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "sleep command" },
     { "stop",      4,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "" },
SC3C({ "slp",       3,  "",     {CMD_APF_SLP},                  { cmdSlpHandler },              "apf sleep command" },)
SC3C({ "sl",        2,  "",     {CMD_SENDLAST},                 { cmdSendLastHandler },         "send Last sample" },)
SC3C({ "spectra",   7,  "?h*",  {CMD_APF_SPECTRA_GET,CMD_APF_SPECTRA_GET,CMD_APF_SPECTRA_SET},
                                {apf_spectra_get, apf_spectra_help, apf_spectra_set },          "set ISUS spectra data range" },)
SC3C({ "status",    6,  "?h*",  {CMD_APF_SYS_STATUS,CMD_APF_SYS_STATUS,CMD_APF_SYS_STATUS},
                                {apf_status_get,apf_status_help,cmdStatusHandler },             "" },)
SC3C({ "stat",      4,  "",     {CMD_APF_DEVSTATUS},            { apf_print_devStatus },        "" },)

SC3C({ "ss",        2,  "",     {CMD_STARTSAMPLE},              { cmdStartSampleHandler },      "" },)
     { "store",     5,  "?*",   {CMD_GETSTORE, CMD_SETSTORE},
                                {cmd_store_get, cmd_store_set},                                 "" },
SC3C({ "sys",       3,  "",     {CMD_APF_GET_SYSINFO},          { apf_get_sysinfo },            "" },)
SC3C({ "s",         1,  "",     {CMD_APF_GET_SC},               { apf_sc_get },                 "get MSC sample count" },)

     //---------- T --------------
SC3C({ "t0",        2,  "",     {CMD_TAKESAMPLE_T0},            { cmd_t0_handler },             "take pump sample, set MODE=0" },)
SC3C({ "t1",        2,  "",     {CMD_APF_ISUS_T1},              { cmd_t1_handler },             "take ISUS sample, set MODE=1" },)
//SC3C({ "t2",        2,  "",     {CMD_APF_DURA_T2},              { cmd_t2_handler },             "take DURA sample, set MODE=2" },)
   { "t2",        2,  "",     {CMD_APF_DURA_T2},              { cmd_t2_handler },             "take DURA sample, set MODE=2" },
SC3C({ "ta",        2,  "",     {CMD_APF_GET_TA},               { apf_acq_time_get },           "" },)
     { "test",      4,  "h*",   {CMD_APF_TEST_SENSOR,CMD_APF_TEST_SENSOR },
                                {cmdTestHelp, cmdTestHandler },                         "" },
     { "timeds",    6, "",      {CMD_GETTIME},                  { cmd_ds3231_time_get },        "" },
     { "time",      4, "?h*",   {CMD_GETTIME, CMD_GETTIME, CMD_SETTIME},
                                {cmd_time_get, cmd_time_help, cmd_time_set },                   "" },
SC3C({ "tint",      4, "?*",    {CMD_APF_GET_TINT, CMD_APF_SET_TINT},
                                {apf_int_time_get, apf_int_time_set },                          "get, set spec integration time" },)
FSYS({ "touch",     5, "*",     {CMD_FILEIO_TOUCH},             { cmdTouchHandler },            "touch a file" },)
//SC3C({ "ts",        2, "",      {CMD_TAKESAMPLE},               { cmdTsHandler },               "take a sample" },)
      { "ts",        2, "",      {CMD_TAKESAMPLE},               { cmdTsHandler },               "take a sample" },
SC3C({ "t",         1, "",      {CMD_APF_GET_TA},               { apf_acq_time_get },           "get sample time" },)

     //---------- V --------------
     { "var",       3, "",      {CMD_GETVERSION},               { cmd_show_vars },              "" },

     { "verb",      4, "*",     {CMD_GETVERSION},               { cmdSetVerbose },              "set the verbosity 0-NORMAL, 1-VERBOSE" },
     { "ver",       3, "",      {CMD_GETVERSION},               { print_cmd_version},           "" },
SC3C({ "vinmin",    6, "?h*",   {CMD_APF_VINMIN,CMD_APF_VINMIN,CMD_APF_VINMIN},
                                {apf_vinmin_get,apf_vinmin_help,cmdVinMinHandler},              "vinmin get, set" },)
// SC3C({ "vrse",      4, "*",     {CMD_TEST_VRSE},                { cmd_test_vrse },              "vrse [run count default=5]" },)
     //---------- W --------------
SC3C({ "wdog",      4,  "",     {CMD_APF_WDOG},                 { apf_wdog_status },            "" },)
     { "wisd",      4,  "",     {CMD_WISD},                     { cmd_wisdom_get },             "" },
SC3C({ "w",         1,  "",     {CMD_APF_PING},                 { apf_print_ping_response },    "" },)

     //---------- Y --------------
     { "yoos",      4,  "",     {CMD_YOOSTER},                  { cmd_yooster },                "" },
     { "yr",        2,  "*",    {CMD_YMODEM_RECEIVE},           { ymodemRecvHandler },          "YModem recv yr" },
     { "ys",        2,  "*",    {CMD_YMODEM_SEND},              { ymodemSendHandler },          "YModem send ys <filename> <blocksize, default 1K" },
     { "y",         1,  "",     {CMD_APF_PING},                 { cmdYHandler },                "" },

     //---------- Z --------------
SC3C({ "zcoef",     5,  "",     {CMD_APF_ZCOEF},                { apf_zcoef_print },            "" },)
     { "zr",        2,  "",     {CMD_ZMODEM_RECEIVE},           { cmdNotImplemented },          "" },
     { "zs",        2,  "*",    {CMD_ZMODEM_SEND},              { cmdNotImplemented },          "" },
     { "zz",        2,  "",     {CMD_SLEEP},                    { cmdSleepHandler },            "" },
     { "",          0,  "",     {0,0,0},                        { NULL,NULL,NULL },             "End of Table" }
};

int parse_rx0_buf(void)
{
    tSerialCmdRec *pCmd = serCmd;
    int retVal = -1;
    int cmdLen = strlen((char *)(&rx0.buf[0]));
    int optIdx;
    int cmdIdx;
    int match = 0;

    if( build_argc_argv((char const * )rx0.buf, &tokens) < 0 )
    {
        if(serDbgFlg == 1) { uprintf("DBG: build_argc_argv failed"); prn_nl(); }
        return 0;
    }

    if(tokens.argc == 0)
    {
        if(serDbgFlg == 1) { uprintf("DBG: No input tokens"); prn_nl(); }
        return 0;
    }

    while (pCmd->kwdLen > 0)
    {
        int cmpValue = strncmp((char*)rx0.buf, pCmd->keyword, pCmd->kwdLen);
        if (cmpValue == 0)            // Useless ACK command sent by APF11
        {
            rx0.cmd = CMD_NOTDEFD;          // if it gets set by the function caller then don't set it to the table value
            if (cmdLen > pCmd->kwdLen)
            {
                cmdIdx = pCmd->kwdLen;
                while ((rx0.buf[cmdIdx] >= 'a' && rx0.buf[cmdIdx] <= 'z'))
                    cmdIdx++;

                char ch = rx0.buf[cmdIdx];

                if ((ch==',') || (ch=='=') || (ch=='[') || (ch==' '))
                {
                    cmdIdx++;
                    // This loop steps through the character options for the command, if it matches ? or h or something else it calls the corresponding function
                    // The '*' option is a wild card that allows arguments (any number of them), that get parsed by the function called
                    for(optIdx=0; optIdx<strlen(pCmd->opts); optIdx++)
                    {
                        // set command requires wild-card option *
                        if (pCmd->opts[optIdx] == '*')
                        {
                            match = 1;
                        }
                        // the options need to be cleared so that arguments can match space or comma
                        else if (rx0.buf[cmdIdx] == pCmd->opts[optIdx])
                        {
                            strcpy((char *)(&rx0.buf[0]), (char *)(&rx0.buf[cmdIdx]));
                            match = 1;
                        }
                        // array indices match
                        else if (pCmd->opts[optIdx] == '[')
                        {
                            match = 1;
                        }
                        if (match)
                        {
                            pCmd->func[optIdx]();
                            if (rx0.cmd == CMD_NOTDEFD)
                                rx0.cmd = pCmd->cmds[optIdx];
                            break;
                        }
                    }
                }
                else
                {
                    tVarEntry *varP = findVar((char *)&rx0.buf[0]);
                    if (varP)
                    {
                        showVar(varP);
                        return CMD_MAINMENU;
                    }
                    else  /// just try the first option
                    {
                        pCmd->func[0]();
                        rx0.cmd = pCmd->cmds[0];
                        retVal = rx0.cmd;
                    }
                    break;
                }
            }
            else
            {
                // Not sure why I added this here - it calls the first function if we have matched a subset of the table string defintion?
                // Debug here Jim
                pCmd->func[0]();
                if (rx0.cmd == CMD_NOTDEFD)
                    rx0.cmd = pCmd->cmds[0];
            }
            retVal = rx0.cmd;
            break;
        }
        else
        {
            if (cmpValue < 0)
            {
                // if the first letter has past the table, then give up
                // this is necessary since the longer commands MUST BE AHEAD of shorter commands"
                if (rx0.buf[0] < pCmd->keyword[0])
                {
                    break;
                }
            }
        }
        pCmd++;
    }

    // Heres where we get if we scanned through the entire table and found no matches
    if (retVal < 0)
    {
        int status;
        // per last minute changes this will use the test table above and run anything that
        // might be there ahead of the next section that will check if the command is a variable to be set
        status = runSomeTest();
        if (status == OK)
        {
            uprintf("\r\nTest was performed");
            prn_nl();

            retVal = rx0.cmd;
        }
        else    // check the var table (vtable.c)
        {
            status = setDataVar();  // if we find the variable in the table then we will handle setting it there
            if (status == OK)
            {
                rx0.cmd = CMD_SETCFG_CONFIG; // JLD!!! just a punt - what are these used for in all the values that get set?
            }
            else
            {
                if (status == ERROR)
                    uprintf("Command %s Failed to perform.", rx0.buf);
                else
                    uprintf("Command not found? %s", rx0.buf);
                prn_nl();
                rx0.cmd = CMD_NOTDEFD;      // 0x00
            }
            retVal = rx0.cmd;
        }
    }

    return(retVal);
}

// The command table will be listed in multiple columns defined by this constant.
// For a narrower or wider view, NUM_COLUMNS may be decreased or increased accordingly.
#define NUM_COLUMNS 4

void commandHelp(void)
{
    tSerialCmdRec *pCmd = serCmd;
    char *str = (char *)(&rx0.buf[0]);
    int cmdLen = strlen(str);
    int colCnt = 0;
    int listOnly = 0;
    strtok(str, ", ");                // skip over command
    char *strptr = strtok(NULL, ", ");       // is there an arg?

    if (strptr == NULL)
    {
        uprintf("help <cmd> for specific details."); prn_nl();
        uprintf("commands are:"); prn_nl();
    }
    else if (strncmp(strptr, "list", 4) == 0)
    {
        listOnly = 1;
        strptr = NULL;
    }
    // count the number of commands to do a vertically ordered list instead of right to left. (easier to read).
    int cmdCount = 0;
    int row  = 0;
    while (pCmd->kwdLen > 0)
    {
        cmdCount++;
        pCmd++;
    }

    int rowCount = (cmdCount + (NUM_COLUMNS-1)) / NUM_COLUMNS;
    pCmd = serCmd;
    int cmdsListed = 0;
    while (cmdsListed < cmdCount)
    {
        if (strptr)
        {
            if ((strncmp(strptr, pCmd->keyword, pCmd->kwdLen) == 0))
            {
                uprintf("%s[%s] %s",  pCmd->keyword, pCmd->opts, pCmd->helpText);
                prn_nl();
                char *helpAvailable = strchr(pCmd->opts, 'h');
                if (helpAvailable)
                {
                    int fIdx = helpAvailable - pCmd->opts;
                    if (fIdx < 3)
                        pCmd->func[fIdx]();
                }
                break;
            }
            pCmd++;
        }
        else
        {
            if (listOnly)
            {
                if (strlen(pCmd->opts))
                    uprintf("%-12s [%3s] | %s",  pCmd->keyword, pCmd->opts,  pCmd->helpText);
                else
                    uprintf("%-12s       | %s",  pCmd->keyword, pCmd->helpText);
                prn_nl();
                wait_consoleTx();
                cmdsListed++;
                pCmd++;
            }
            else
            {
                uprintf("%12s [%3s] ",  pCmd->keyword, pCmd->opts);
                colCnt++;
                cmdsListed++;
                if (colCnt == NUM_COLUMNS)
                {
                    prn_nl();
                    wait_consoleTx();
                    // bump the row and find the next start of the row
                    row++;
                    colCnt = 0;
                    pCmd = serCmd + row;        // Adjust to the row start offset from the head
                }
                else
                {
                    // get the next in the row position by traversing the number of items per column
                    int nextRowStart = 0;
                    while(nextRowStart < rowCount)
                    {
                        nextRowStart++;
                        pCmd++;
                        // At the end of the list some columns do not fill the row
                        if (pCmd->kwdLen == 0)
                        {
                            prn_nl();               // just a cr will do
                            wait_consoleTx();
                            row++;
                            colCnt = 0;             // same as starting a new row
                            pCmd = serCmd + row;    // find the start of the row
                            break;
                        }
                    }
                }
            }
        }
    }
    prn_nl();
}


int build_argc_argv(const char* str, CMD_Tokens* const out_tokens)
{
    if( ! (str && out_tokens) )
    {
        return 0;
    }

    // Copy input command line so we don't have to modify it
    // We could perhaps have two versions of this function, one that operates in-place
    // This may matter more with LFN support in FatFS increasing filename length
    out_tokens->cmd_buf[0] = '\0';
    strncat(out_tokens->cmd_buf, str, MAX_SIZE_RX0_BUF);

    // For now, delim on ascii space only. strtok merges adjacent delims. We could consider eating tabs too
    const char delim[] = " ";

    out_tokens->argc = 0;
    char* token_start_ptr = strtok(out_tokens->cmd_buf, delim);
    while( token_start_ptr && (out_tokens->argc < CMD_TOKENS_MAX_ARGC) )
    {
        out_tokens->argv[out_tokens->argc] = token_start_ptr;
        out_tokens->argc++;

        token_start_ptr = strtok(NULL, delim);
    }
    out_tokens->argv[out_tokens->argc] = NULL; // argv is 1 element longer than CMD_TOKENS_MAX_ARGC, so we can always NULL term per C11 5.1.2.2.1

    return 1;
}


/*
 * old commands still supported.  gdata and dump have not been tested in a long time:
 *      gdata,
 *      ts,
 *      stop,
 *      dump,
 *      samp_byte,
 *      gh,
 *      gm,
 *      sl,
 *      ss,
 *      pumpon,
 *      pumpoff,
 *      go,
 *      resume,
 *      exit
 *      Set Config Glider
 *      Set Congig MFET
 *
 * New Commands for Float use case (MSC3)
 *
 * W,M,E,P,S,T,C,Q,TS,T1,T2,SL,SE,SLP,OFF,RTC,FIT,CTD,MODE,INIT,BAKE,SPECTRA
 *
 *
 * Notes from the MSC Spec (v1.0)
 * The CF2-based MSC remains powered throughout the entire profile phase but remains in a low-power sleep state except to measure samples.
 * Allowing the MSC to be powered-down between samples is the preferred mode but CF2-like compatibility will be retained as a fall-back, if needed.
 * The MSC will be responsible for implementing its own real time clock (RTC); the RTC of the MSC will be synchronized to the Apf11’s at the start of each profile.
 *
 * Apf11 to reconfigure the MSC using two-way iridium communication. However, in general, it will be the responsibility of the MSC to maintain any required configuration parameters in its own non-volatile storage.
 *
 * The MSC does not communicate directly with the Sbe41cp. Instead, the Apf11 queries the CTD and subsequently passes the salinity (S), temperature (T), and pressure (P) to the MSC
 * MSC should respond to a brown-out condition (ie., voltage less than 8V) by aborting any sample-in-progress
 *
 * Any character received by the sensor should cause it to wake and enter a command processing loop.  If no characters are received for 30 seconds
 *
 * Commands and responses are case insensitive
 *
 * Unrecognized: NAK,ReceivedCmd,ErrorMsg
 *
 * Arguments of commands should be validated by checking for type, count, and range
 *
 *
 */
