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

#include "serial.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

// 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();
}
#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
//


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();
    }

    rx0.byte = rxByte;
    //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();
                }

                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( 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();
            }

            exec_main_menu();

            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>
 */


int parse_rx0_buf(void)
{
    int retVal;
    unsigned int num;

    retVal = 0;

    //convert to lower case

    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;
    }

    //---------- A --------------
    //
    if (strncmp((char*)rx0.buf, "ack", 3) == 0)            // Useless ACK command sent by APF11
    {
        if(rx0.cmd == CMD_APF_SET_MODE)
        {

            //APFTODO, wire up ACK handler here
            //apf_fram.sys_sensor_mode = newMode;
            //fram_apf_vars_store();
        }
        if(rx0.cmd == CMD_APF_CTD_SET)
        {


        }

        rx0.cmd = CMD_ACK;
        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- B --------------

    else if (strncmp((char*)rx0.buf, "bake", 4) == 0)            // APF BAKE UV Lamp
    {
        rx0.cmd = CMD_APF_BAKE;
        apf_bake_uv_lamp();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "baud", 4) == 0)            //
    {
        rx0.cmd = CMD_APF_BAUD;
        //apf_baud_get();

        if(rx0.buf[4] == ',' && rx0.buf[5] == '?')
        {
            //uprintf("DBG: rx0.buf[4] = %c, rx0.buf[5] = %c", rx0.buf[4], rx0.buf[5]); prn_nl();   wait_consoleTx();
            apf_baud_get();
        }
        else if(rx0.buf[4] == ',' && rx0.buf[5] == 'h')
        {
            apf_baud_help();
        }
        else if(((rx0.buf[4] == ',') || (rx0.buf[4] == '=')) && rx0.buf[5] >= '0' && rx0.buf[8] <= '9')
        {
            //apf_baud_set(&rx0.buf[5]);
            apf_baud_set();
        }
        else
        {
            apf_baud_get();
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "biasneg", 7) == 0)  )          //
    {

        if(rx0.buf[5] == 's')
        {
            //biasneg set 1
            //012345678901234
            rx0.cmd = CMD_SETCFG_BIASNEG;
            cmd_set_biasneg(rx0.buf[9]);
        }
        else if(rx0.buf[5] == 'g')
        {
            //biasneg get
            rx0.cmd = CMD_GETCFG_BIASNEG;
            cmd_get_biasneg();
        }
        else
        {
            // get count, pass in as num
            rx0.cmd = CMD_TEST_BIASNEG;
            num = 1;
            cmd_test_biasneg(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "biaspos", 7) == 0)  )          //
    {

        if(rx0.buf[8] == 's')
        {
            //biaspos set 1
            //012345678901234
            rx0.cmd = CMD_SETCFG_BIASPOS;
            cmd_set_biaspos(rx0.buf[12]);
        }
        else if(rx0.buf[5] == 'g')
        {
            //biaspos get
            rx0.cmd = CMD_GETCFG_BIASPOS;
            cmd_get_biaspos();
        }
        else
        {
            // get count, pass in as num
            rx0.cmd = CMD_TEST_BIASPOS;
            num = 1;
            cmd_test_biaspos(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }


    //---------- C --------------
    //else if (strncmp((char*)rx0.buf, "cfg", 3) == 0)   //wishful thinking to change "C command to "CFG
    else if((rx0.buf[0] == 'c') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_CFG;
        apf_cfg_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "c1", 2) == 0)
    {
        rx0.cmd = CMD_APF_CFG_ISUS;
        apf_isus_cfg_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "c2", 2) == 0)
    {
        rx0.cmd = CMD_APF_CFG_DURA;
        apf_dura_cfg_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "cal_date,isus", 13) == 0)
    {
        rx0.cmd = CMD_APF_CALDATE;

        if((rx0.buf[13] != '=') && (rx0.buf[13] != ','))
        {
            uprintf("cal_date,isus=%s", apfArray.CalDate); prn_nl();
        }
        else
        {
            cmd_cal_date_isus(&rx0.buf[14]);        //cal_date,isus=04/27/2024
                                                    //012345678901234
        }


        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "cal_date,k0", 11) == 0)
    {
        rx0.cmd = CMD_APF_CALDATE;

        if((rx0.buf[11] != '=') && (rx0.buf[11] != ','))
        {
            uprintf("cal_date,k0=%s", sys_data.k0_Cal_date); prn_nl();
        }
        else
        {
            cmd_cal_date_k0(&rx0.buf[12]);          //cal_date,k0=04/27/2024
                                                    //012345678901234
        }


        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "cal_date,pt", 11) == 0)
    {
        rx0.cmd = CMD_APF_CALDATE;

        if((rx0.buf[11] != '=') && (rx0.buf[11] != ','))
        {
            uprintf("cal_date,pt=%s", sys_data.PT_Cal_date); prn_nl();
        }
        else
        {
            cmd_cal_date_pt(&rx0.buf[12]);          //cal_date,pt=04/27/2024
                                                    //0123456789012
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "cal_date", 8) == 0)
    {
        rx0.cmd = CMD_APF_CALDATE;

        uprintf("cal_date,isus=%s", apfArray.CalDate); prn_nl();
        uprintf("cal_date,k0=%s", sys_data.k0_Cal_date); prn_nl();
        uprintf("cal_date,pt=%s", sys_data.PT_Cal_date); prn_nl();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "cal", 3) == 0)
    {
        rx0.cmd = CMD_APF_CAL;
        apf_cal_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    if( (strncmp((char*)rx0.buf, "config", 6) == 0)  )
    {
        if(rx0.indx > 6)
        {
            if(rx0.buf[7] == 'h')       //config help
            {
                rx0.cmd = CMD_HELPCONFIG;
                cmd_config_help();
            }
            else
            {
                rx0.cmd = CMD_SETCFG_CONFIG;
                cmd_config_set();
            }
        }
        else
        {
            rx0.cmd = CMD_GETCFG_CONFIG;
            cmd_config_get();
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "cat") == 0)
    {
        rx0.cmd = CMD_FILEIO_CAT;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "cd") == 0)
    {
        rx0.cmd = CMD_FILEIO_CD;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "chkdsk") == 0)
    {
        rx0.cmd = CMD_FILEIO_CHKDSK;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "cp") == 0)
    {
        rx0.cmd = CMD_FILEIO_CP;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ctd,?", 5) == 0)
    {
        rx0.cmd = CMD_APF_CTD_GET;
        apf_ctd_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ctd,h", 5) == 0)
    {
        rx0.cmd = CMD_APF_CTD_GET;      //treat help as a get
        apf_ctd_help();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ctd", 3) == 0)
    {
        rx0.cmd = CMD_APF_CTD_SET;
        apf_ctd_set();

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- D --------------

    else if (strncmp((char*)rx0.buf, "diag", 4) == 0)
    {
        rx0.cmd = CMD_APF_DIAG;
        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;
            }
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "dev", 3) == 0)
    {
        rx0.cmd = CMD_APF_DEVSTATUS;
        apf_print_devStatus();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "dbg", 3) == 0)
    {
        rx0.cmd = CMD_APF_DBG;
        if(rx0.buf[4] == '1')
            apf_dbg(1);
        else
            apf_dbg(0);

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "defaults", 3) == 0)
    {
        rx0.cmd = CMD_APF_DEFAULTS;
        apf_defaults();

        rx0.cmd = CMD_APF_DEFAULTS;   //trying to figure out why after defaults, sleep...
        //rx0.cmd = CMD_SLEEP;            //sleep after setting defaults
        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- E --------------
    //else if (strncmp((char*)rx0.buf, "e", 1) == 0)        //Future: "err"
    else if((rx0.buf[0] == 'e') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_GET_ERR;
        apf_err_get();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "e1", 2) == 0)
    {
        rx0.cmd = CMD_APF_GET_ERR;
        apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;
        apf_err_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "e2", 2) == 0)
    {
        rx0.cmd = CMD_APF_GET_ERR;
        apf_fram.sys_sensor_mode = DURA_SENSOR_MODE;
        apf_err_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "echo", 4) == 0)            // echo on
    {
        rx0.cmd = CMD_ECHO;

        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
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "ecoef", 5) == 0)            // receive file (download)
    {
        rx0.cmd = CMD_APF_ECOEF;

        apf_ecoef_print();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "ec", 2) == 0) && (rx0.buf[2] == 0) )           // export calibration coefficients in cal file format
    {
        rx0.cmd = CMD_EXPORTCAL;

        export_pH_sensor_cal_coeff();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "error", 5) == 0)           // print error status, statistics
    {
        rx0.cmd = CMD_APF_ERROR;

        uprintf("error command: Imp Es Nada"); prn_nl();

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- F --------------
    else if( (strncmp((char*)rx0.buf, "filename", 4) == 0) ||   (strncmp((char*)rx0.buf, "getfilename", 6) == 0) )        // getfilename
    {
        if(rx0.indx > 10)
        {
            rx0.cmd = CMD_SETFILENAME;
            cmd_set_filename();
        }
        else
        {
            rx0.cmd = CMD_GETFILENAME;
            cmd_get_filename();
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "filesystem", 5) == 0)  )        // getfilename
    {
        rx0.cmd = CMD_FILESYSTEM;
        cmd_filesystem();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ftst", 4) == 0)
    {
        rx0.cmd = CMD_APF_FILETEST;
        apf_file_test();            //thom_file_test()

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "fit,?", 5) == 0)
    {
        rx0.cmd = CMD_APF_GET_FIT;
        apf_fit_get();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "fit", 3) == 0)
    {
        rx0.cmd = CMD_APF_SET_FIT;
        apf_fit_set();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strcmp(tokens.argv[0], "format") == 0)
    {
        rx0.cmd = CMD_FILEIO_FORMAT;

        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();
                //apf_h2m_mode_set(20);       //file commands, term time set to 2.5*20 = 50sec
            }

        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "fp[", 3) == 0)
    {
        rx0.cmd = CMD_PH_CAL;
        //sys_data.fp[7];
        //fp[6]=12345.678
        //0123456789
        cmd_fp(&rx0.buf[3]);
        retVal = rx0.cmd;
        return(retVal);
    }
    //---------- G --------------
    else if(strncmp((char*)rx0.buf, "gc", 2) == 0)            // get calibration coefficients
    {
        rx0.cmd = CMD_GETCAL;

        get_pH_sensor_cal_coeff();
        retVal = rx0.cmd;
    }

    else if (strncmp((char*)rx0.buf, "getperiod", 6) == 0)
    {
        rx0.cmd = CMD_GETPERIOD;
        cmd_period_get();
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "gh1", 3) == 0)            // get write isus data header
    {
        rx0.cmd = CMD_GETHEADER_ISUS;

        apf_print_isus_header();      //
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "gh2", 3) == 0)            // get write dura data header
    {
        rx0.cmd = CMD_GETHEADER_DURA;

        apf_print_dura_header();      //
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "gh", 2) == 0)            // get data header
    {
        rx0.cmd = CMD_GETHEADER;

        get_header();
        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "gm", 2) == 0)            // get metadata header
    {
        rx0.cmd = CMD_GETMETADATA;

        print_metadata_header( read_hib_rtcc() );
        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- H --------------

    else if(strncmp((char*)rx0.buf, "hh", 2) == 0)            // help
    {
        // uint32_t timeterm;

        rx0.cmd = CMD_BIG_HELP;
        if(rx0.buf[3] == 0x00)
        {
            //mult = 2;
            apf_h2m_mode_set(2);
        }
        if((rx0.buf[3] == 'h') && (rx0.buf[4] == 0x00))
        {
            //mult = 3;
            apf_h2m_mode_set(3);
        }
        if((rx0.buf[3] == 'h') && (rx0.buf[4] == 'h'))
        {
            //mult = 4;
            apf_h2m_mode_set(4);
        }
        // timeterm =
        apf_h2m_timeout_get();
        //uprintf("term time = %u msec", (H2M_DELIM_MS_TIMEOUT*mult)); prn_nl();
        //uprintf("term time = %u msec", timeterm); prn_nl();
        print_cmd_big_help();
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "help,all", 8) == 0)            // help,all
    {
        rx0.cmd = CMD_HELP;

        apf_h2m_mode_set(1);

        cmd_help_all();


        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "help", 4) == 0)            // help
    {
        rx0.cmd = CMD_HELP;

        apf_h2m_mode_set(1);

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


        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "h2m", 2) == 0)            // h2m timing
    {
        uint32_t timeterm;

        rx0.cmd = CMD_APF_H2M;
        timeterm = apf_h2m_timeout_get();
        uprintf("was term time = %u msec", timeterm); prn_nl();
        if(timeterm < 1000)
        {
            apf_h2m_mode_set(1);
        }
        else if (timeterm < 2000)
        {
            apf_h2m_mode_set(2);
        }
        else if (timeterm < 3000)
        {
            apf_h2m_mode_set(3);
        }
        else if (timeterm < 3000)
        {
            apf_h2m_mode_set(3);
        }
        else
        {
            apf_h2m_mode_set(4);
        }
        uprintf("now term time = %u msec", apf_h2m_timeout_get()); prn_nl();
        retVal = rx0.cmd;
        return(retVal);
    }

    // "h"
    //else if(strncmp((char*)rx0.buf, "h2m", 3) == 0)            // h2m timing
    else if((rx0.buf[0] == 'h') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_H2M;
        apf_h2m_mode_set(1);
        apf_print_help(helpCnt);
        helpCnt++;
        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- I --------------
    else if( (strncmp((char*)rx0.buf, "ik", 2) == 0)  )          // Ik Counter electrode current
    {
        //if(rx0.indx > 2)
        if(rx0.buf[3] == 's')
        {
            //ik set 1
            //01234567
            rx0.cmd = CMD_SETCFG_IK;
            cmd_set_ik((unsigned int)rx0.buf[7]);
        }
        else if(rx0.buf[3] == 'g')
        {
            rx0.cmd = CMD_GETCFG_IK;
            cmd_get_ik();
        }
        else
        {
            rx0.cmd = CMD_TEST_IK;
            num = 1;
            //if num >= 3, then vk_std is automatic
            cmd_test_ik(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "ib", 2) == 0)  )          // Ib Substrate current
    {
        //if(rx0.indx > 2)
        if(rx0.buf[3] == 's')
        {
            //ib set 1
            //01234567
            rx0.cmd = CMD_SETCFG_IB;
            cmd_set_ib((unsigned int)rx0.buf[7]);
        }
        else if(rx0.buf[3] == 'g')
        {
            rx0.cmd = CMD_GETCFG_IB;
            cmd_get_ib();
        }
        else
        {
            rx0.cmd = CMD_TEST_IB;
            num = 1;
            //if num >= 3, then ib_std is automatic
            cmd_test_ib(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "init", 4) == 0)
    {
        rx0.cmd = CMD_APF_INIT;
        //apf_init();
        apf_init_persistent_vars();  // defd in serial_cmd_fxn.c   Removes ERROR.LOG

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "isfet_sn", 8) == 0)
    {
        rx0.cmd = CMD_APF_PH;

        if((rx0.buf[8] != '=') && (rx0.buf[8] != ','))
        {
            uprintf("isfet_SN=%s", sys_data.isfet_SN); prn_nl();
        }
        else
        {
            //isfet_sn=
            cmd_isfet_sn(&rx0.buf[9]);        //0123456789012345
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ise_sn", 6) == 0)
    {
        rx0.cmd = CMD_APF_PH;

        //ise_sn=
        //0123456
        if((rx0.buf[6] != '=') && (rx0.buf[6] != ','))
        {
            uprintf("ise_SN=%s", sys_data.ise_SN); prn_nl();
        }
        else
        {
            //ise_sn=
            cmd_ise_sn(&rx0.buf[7]);           //0123456789012345
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    //---------- K --------------
#ifdef NOCODE
    float K2_Cal[MAX_NUMOF_K2_CAL_FIELDS];                // worse case 4th order polynomial
    float fP_Cal[MAX_NUMOF_FP_CAL_FIELDS];               // worse case 7th order polynomial

#endif
    else if ((strncmp((char*)rx0.buf, "k0,h", 4) == 0) || (strncmp((char*)rx0.buf, "pH,h", 4) == 0) )
    {
        rx0.cmd = CMD_PH_CAL;
        cmd_k0_help();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "k0_hcl", 6) == 0)
    {
        rx0.cmd = CMD_PH_CAL;
        //sys_data.k0_HCL;
        //k0_hcl=12345.678
        //01234567
        if((rx0.buf[6] != '=') && (rx0.buf[6] != ','))
        {
            uprintf("k0_hcl=%f", sys_data.k0_HCL); prn_nl();
        }
        else
        {
            cmd_k0_hcl(&rx0.buf[7]);
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "k0_sw", 5) == 0)
    {
        rx0.cmd = CMD_PH_CAL;
        //sys_data.k0_sw;
        //k0_sw,12345.678
        //01234567
        if((rx0.buf[5] != '=') && (rx0.buf[5] != ','))
        {
            uprintf("k0_sw=%f", sys_data.k0_SW); prn_nl();
        }
        else
        {
            cmd_k0_sw(&rx0.buf[6]);
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "k0std_sw", 8) == 0)
    {
        rx0.cmd = CMD_PH_CAL;
        //sys_data.k0std_SW;
        //k0std_sw,12345.678
        //0123456789
        if((rx0.buf[8] != '=') && (rx0.buf[8] != ','))
        {
            uprintf("k0std_sw=%f", sys_data.k0std_SW); prn_nl();
        }
        else
        {
            cmd_k0std_sw(&rx0.buf[9]);
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "k2[", 3) == 0)
    {
        rx0.cmd = CMD_PH_CAL;
        //sys_data.k2[4];
        //k2[3]=12345.678
        //0123456789

        cmd_k2(&rx0.buf[3]);  // handles both 'get' aka query and 'set' aka assign

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- L --------------
    else if (strcmp(tokens.argv[0], "ls") == 0)
    {
        rx0.cmd = CMD_FILEIO_LS;

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

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- M --------------

    else if( (strncmp((char*)rx0.buf, "microsd", 7) == 0) )          // store (microsd or spi flash)
    {
        if(rx0.indx > 7)
        {
            rx0.cmd = CMD_SETSTORE;
            cmd_store_set();
        }
        else
        {
            rx0.cmd = CMD_GETSTORE;
            cmd_store_get();
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "mkdir") == 0)
    {
        rx0.cmd = CMD_FILEIO_MKDIR;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "mode,?", 6) == 0)
    {
        rx0.cmd = CMD_APF_GET_MODE;
        apf_mode_get();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "mode", 4) == 0)
    {
        rx0.cmd = CMD_APF_SET_MODE;
        apf_mode_set();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "m2m", 2) == 0)            // m2m timing
    {
        rx0.cmd = CMD_APF_M2M;

        //012345678
        //m2m,4
        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", apf_h2m_timeout_get());  prn_nl();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "mv", 2) == 0)            // file command move
    {
        rx0.cmd = CMD_FILEIO_MV;

        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();
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    //handle main menu selection
    else if((rx0.buf[0] == 'm') && (rx0.buf[1] == 0))
    {
        if(sys_data.terminal_mode == H2M_MODE)      //don't allow M activate menu in M2M mode
        {
            exec_main_menu();
        }
        return(CMD_MAINMENU);
    }

    //---------- N --------------
    else if ( (strncmp((char*)rx0.buf, "nitrate", 5) == 0) || (strncmp((char*)rx0.buf, "no3", 5) == 0) )
    {
        rx0.cmd = CMD_APF_NITRATE;
        apf_nitrate();

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- O --------------
    else if (strncmp((char*)rx0.buf, "off", 3) == 0)
    {
        rx0.cmd = CMD_APF_OFF;
        apf_off();

        retVal = rx0.cmd;
        return(retVal);
    }

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

    //else if (strncmp((char*)rx0.buf, "pc", 2) == 0)
    else if((rx0.buf[0] == 'p') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_GET_PC;
        apf_pc_get();               // PC Print Counters

        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "period", 6) == 0) )        // getperiod
    {

        //01234567890123456789
        //period=20
       // if( (rx0.indx > 7) || ( (rx0.buf[3]) == ' ') && (rx0.buf[5] >= '0') && (rx0.buf[5] <= '9') ) )
        if( (rx0.buf[6] == ' ') || (rx0.buf[6] == ',') || (rx0.buf[6] == '=') )
        {
            rx0.cmd = CMD_SETPERIOD;
            cmd_period_set();
        }
        else
        {
            rx0.cmd = CMD_GETPERIOD;
            cmd_period_get();
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "profile", 7) == 0) )  //get,set profile count, used to generate data log file names, PROFXXXX.log
    {
        if(rx0.buf[7] == ',')
        {
            switch (rx0.buf[8])
            {
                case '?':
                    rx0.cmd = CMD_GET_PROFILECNT;
                    apf_profileCount_get();
                    break;
                case 'h' :
                    rx0.cmd = CMD_GET_PROFILECNT;
                    apf_profileCount_help();
                    break;
                case 'i' :
                    rx0.cmd = CMD_SET_PROFILECNT;
                    apf_profileCount_inc();      //requires ACK
                    break;
                case 'd' :
                    rx0.cmd = CMD_SET_PROFILECNT;
                    apf_profileCount_dec();      //requires ACK
                    break;
                default :
                    rx0.cmd = CMD_SET_PROFILECNT;
                    apf_profileCount_set();     //requires ACK
                    break;
            }
        }
        else
        {
            rx0.cmd = CMD_GET_PROFILECNT;
            apf_profileCount_get();
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "pwd") == 0)
    {
        rx0.cmd = CMD_FILEIO_PWD;

        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();
            //apf_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "ph_calfile", 10) == 0)
    {
        rx0.cmd = CMD_APF_PH;

        //0123456789012345
        //ph_calfile=
        if((rx0.buf[10] != '=')  && (rx0.buf[10] != ','))
        {
            uprintf("pH_Calfile=%s", sys_data.cal_filename); prn_nl();
        }
        else
        {
            //ph_calfile=
            cmd_dura_cal_file(&rx0.buf[11]);        //0123456789012345
        }


        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "ph", 2) == 0)
    {
        rx0.cmd = CMD_APF_PH;

        apf_pH();

        retVal = rx0.cmd;
        return(retVal);
    }


    //---------- Q --------------

    else if (strncmp((char*)rx0.buf, "qa", 2) == 0)
    {
        rx0.cmd = CMD_QA;
        if(rx0.buf[3] == '1')
        {
            board_qa_and_init(1);       //1 means init FRAM
        }
        else
        {
            board_qa_and_init(0);
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "qconfig,h", 9) == 0)
    {
        rx0.cmd = CMD_APF_Q_SENSORCFG;
        //qconfig,
        //012345678
        //apf_eeprom.sys_sensor_type is set
        //1=no sensor, 2=ISUS only, 3=Dura only, 4=ISUS+Dura
        apf_sensor_type_help();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "qconfig", 7) == 0)
    {
        rx0.cmd = CMD_APF_Q_SENSORCFG;
        //qconfig,
        //012345678
        //apf_eeprom.sys_sensor_type is set
        //1=no sensor, 2=ISUS only, 3=Dura only, 4=ISUS+Dura
        apf_sensor_type_set((uint32_t)(rx0.buf[8] & 0x0f));

        retVal = rx0.cmd;
        return(retVal);
    }
    //else if (strncmp((char*)rx0.buf, "qq", 2) == 0)
    else if((rx0.buf[0] == 'q') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_Q_SENSORCFG;
        apf_sensor_type_get();               // Sensor mode (ISUS, DURAFET)  would like to deprecate this

        retVal = rx0.cmd;
        return(retVal);
    }

    //---------- R --------------

    else if (strncmp((char*)rx0.buf, "readzcoef", 5) == 0)
    {
        rx0.cmd = CMD_APF_READZCOEF;

        apf_zcoef_read();       //reads both zcoefdat.cal and ecoefzer.cal, if files are available

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "readecoef", 5) == 0)
    {
        rx0.cmd = CMD_APF_READECOEF;
        apf_ecoef_read();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "rm", 2) == 0)
    {
        rx0.cmd = CMD_FILEIO_RM;

        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();
            //apf_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "rtc", 3) == 0)
    {
        //rx0.cmd = CMD_APF_GET_RTC;
        //apf_rtc_get();

        //retVal = rx0.cmd;
        //return(retVal);
        //01234567890123
        //rtc,3785523705

        if(rx0.buf[3] == 0)
        {
            rx0.cmd = CMD_APF_GET_RTC;
            apf_rtc_get();
        }
        else if(rx0.buf[3] == ',' && rx0.buf[4] == '?')
        {
            rx0.cmd = CMD_APF_GET_RTC;
            apf_rtc_get();
        }
        else if(rx0.buf[3] == ',' && rx0.buf[4] == 'h')   //"rtc,h"
        {
            rx0.cmd = CMD_APF_GET_RTC;
            apf_rtc_help();
        }
        else
        {
            rx0.cmd = CMD_APF_SET_RTC;
            apf_rtc_set();
        }

        retVal = rx0.cmd;
        return(retVal);

    }


    //---------- S --------------

    //else if( (strncmp((char*)rx0.buf, "samp", 4) == 0)  || (strncmp((char*)rx0.buf, "sample", 6) == 0) )          // sample
    // sample turns on regular sleepless sampling by toggling a flag in main loop
    //sample
    else if( (strncmp((char*)rx0.buf, "sample", 6) == 0) )          // sample
    {
        rx0.cmd = CMD_SAMP_DATABARF;

        //apf_fram.sys_sensor_mode = DURAISUS_SENSOR_MODE;  //DBG: force to BOTH mode
        //uprintf("DBG: DURAISUS_SENSOR_MODE in serial.c");  prn_nl();

        uint32_t loopTime = 5;
        if(rx0.buf[6] == ',' && rx0.buf[7] >= '0' && rx0.buf[7] <= '9')
        {
            //0123456789012345
            //sample,300
            char     *str;
            char     *endptr;

            str = (char*)&rx0.buf[7];

            errno = 0; // Reset errno before calling strtoul
            loopTime = strtoul(str, &endptr, 10);

            if(loopTime > 3 && loopTime < 30000)
            {
                uprintf("sample loop time: %u sec, (%u min, %u sec)", loopTime, loopTime/60, loopTime%60); prn_nl();
            }
            else
            {
                uprintf("sample loop time is out of range [3:30000], setting period to 5 sec"); 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)

        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "sampfas", 7) == 0) )          // sample isus fast - IH 9/17/25.  10/15/25 adding option to print to screen or not dep on last character
        // 1/16/26 undoing the sampfas* to only have sampfast, but with verbose flag 'v'
        // secret, you can still type sampfasa if you want.
        {
            rx0.cmd = CMD_SAMP_DATABARF; // unclear where this is used, just use above
            uint16_t looptime_s = 1;
            uint8_t litScansPerDark = 10;
            int verbose = 0;
            int noarg = 0; // flag meaning no arguments after ',' (or other failure). need to use flags because this condition can be reached in two different places

            // i need to parse two numbers
            char     *pch;
            char     *str;
            char     *endptr;
            pch = strtok ((char*)rx0.buf,","); // the first one returns the sampfast part
            pch = strtok (NULL, ",");
            if (pch == NULL) {
                noarg = 1;
            } else {
                //uprintf ("%s\n",pch); // debug

                // check for h
                if (pch[0] == 'h')
                {
                    cmd_sampfast_help();

                    retVal = rx0.cmd;
                    return(retVal);
                }

                // 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) {
                        noarg = 1;
                    }
                }
            }

            if (noarg == 1) { // failed to parse any arguments, or arguments beyond 'v'
                uprintf("No or invalid arguments given; setting to default sampling interval %ds and  %d light scans per dark", looptime_s, litScansPerDark); prn_nl();
            } else { // everything is already setup here with pch pointing to next number, this used to flow straight from above if/elses

                // 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
                if (pch[0] < 48 || pch[0] > 57) {
                    uprintf("No or invalid sampling interval specified; setting to default %ds", looptime_s); prn_nl();
                } else {
                    errno = 0; // Reset errno before calling strtoul
                    looptime_s = strtoul(pch, &endptr, 10);
                    // further checks and printout are done by the function called
                }

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

            }

            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)


            retVal = rx0.cmd;
            return(retVal);
        }

    else if( (strncmp((char*)rx0.buf, "sampnum", 7) == 0)  )          // sampnum
    {
        if(rx0.buf[8] == 's')
        {
            //sampnum set 1
            //0123456789012
            rx0.cmd = CMD_SETCFG_SAMPNUM;
            cmd_cfg_sampnum_set((unsigned int)rx0.buf[12]);
        }
        else if(rx0.buf[8] == 'g')
        {
            rx0.cmd = CMD_GETCFG_SAMPNUM;
            cmd_cfg_sampnum_get();
        }
        else
        {
            rx0.cmd = CMD_TEST_SAMPNUM;
            if(rx0.indx > 7)
            {
                //read argument (up to 7 digits)
                num = 0;    // force to 0 for now
                cmd_sampnum_set(num);
            }
            else
            {
                num = 1;        // Future: num in this context might mean number of times to read sampnum???
                cmd_sampnum_test(num);
            }

        }
        retVal = rx0.cmd;
        return(retVal);
    }

    //else if (strncmp((char*)rx0.buf, "sc", 2) == 0)
    //"s" command
    else if((rx0.buf[0] == 's') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_GET_SC;
        apf_sc_get();                           // get sample counter

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "scanspectra", 8) == 0)        // scanspec
    {
        rx0.cmd = CMD_APF_SCANSPECTRA;
        //"BAA" BAA for Isus Calibration - input number of light scans per dark scan. in apf_test.c
        //cmd_scan_spectra((uint8_t)atoi((char*)rx0.buf+9)); // use atoi to accept multi-digit numbers
        cmd_scan_spectra(10, 0, 1, 1, 0);
        // for now, hard coded num light between darks (10), otherwise we have to fix command to scanspectra or scanspec
        // sample as fast as possible (0), turn off lamp between scans (1), print (1), don't write (0)
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "sernum", 6) == 0)        // sermi,
    {
        //0123456789012345
        //sernum,123456789
        rx0.cmd = CMD_APF_SERNUM;

        apf_sernum_handler();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "setfilename", 6) == 0)        // setfilename
    {
        rx0.cmd = CMD_SETFILENAME;
        cmd_set_filename();
        retVal = rx0.cmd;
        return(retVal);
    }
#ifdef DEPRECATE
    else if(strncmp((char*)rx0.buf, "setperiod", 6) == 0)        // setperiod
    {
        rx0.cmd = CMD_SETPERIOD;
        cmd_period_set();
        retVal = rx0.cmd;
        return(retVal);
    }
#endif
    else if(strncmp((char*)rx0.buf, "settime", 7) == 0)        // settime
    {
        rx0.cmd = CMD_SETTIME;
        cmd_time_set();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "settings", 8) == 0)        // settime
    {
        rx0.cmd = CMD_SETTINGS;
        cmd_print_settings();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "se,?", 4) == 0)
    {
        rx0.cmd = CMD_APF_GET_SE;
        apf_se_get();                           // get number of error messages

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "se?", 3) == 0)        //handle the bad behaviour of AP11
    {
        rx0.cmd = CMD_APF_GET_SE;
        apf_se_get();                           // get number of error messages

        retVal = rx0.cmd;
        return(retVal);
    }
    else if ((strncmp((char*)rx0.buf, "se", 2) == 0) && rx0.buf[2] == 0 )
    {
        rx0.cmd = CMD_APF_XFER_SE;
        apf_se_xfer();                          // send msc error messages

        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "sleep", 5) == 0) || (strncmp((char*)rx0.buf, "nap", 3) == 0) || (strncmp((char*)rx0.buf, "zz", 2) == 0) || (strncmp((char*)rx0.buf, "zzz", 3) == 0)
            || (strncmp((char*)rx0.buf, "quit", 4) == 0) || (strncmp((char*)rx0.buf, "stop", 4) == 0) || (strncmp((char*)rx0.buf, "exit", 4) == 0)  || (strncmp((char*)rx0.buf, "off", 3) == 0)  )
    {
        if(databarFlg == 1)
        {
            rx0.cmd = CMD_APF_DIAG;
            databarFlg = 0;
            retVal = rx0.cmd;
            return(retVal);
        }
        databarFlg = 0;
        rx0.cmd = CMD_SLEEP;
        retVal = rx0.cmd;            // return -1 to exit exec_main_loop in main.c causes sleep
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "slp", 3) == 0)
    {
        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;
        retVal = rx0.cmd;            // return -1 to exit exec_main_loop in main.c causes sleep

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "sl", 2) == 0)           // start sample, needs to have sl within 6 sec
    {
        rx0.cmd = CMD_SENDLAST;

        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();
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "spectra,?", 9) == 0)
    {
        rx0.cmd = CMD_APF_SPECTRA_GET;
        apf_spectra_get();                        // get reported ISUS data range

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "spectra,h", 9) == 0)
    {
        rx0.cmd = CMD_APF_SPECTRA_GET;
        apf_spectra_help();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "spectra", 7) == 0)
    {
        //012345678901234567890
        //spectra,215,250
        if(rx0.buf[7] == 0)     //spectra
        {
            rx0.cmd = CMD_APF_SPECTRA_GET;
            apf_spectra_get();                        // get reported ISUS data range
        }
        else
        {
            rx0.cmd = CMD_APF_SPECTRA_SET;
            apf_spectra_set();
        }

        retVal = rx0.cmd;
        return(retVal);

    }
    else if(strncmp((char*)rx0.buf, "status", 6) == 0)        // system status, MSC1 Logger setup menu, System Reset Counter, System Error Counter
    {
        //0123456789012345
        //sernum,123456789
        rx0.cmd = CMD_APF_SYS_STATUS;

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

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "stat", 4) == 0)  // same as "dev"
    {
        rx0.cmd = CMD_APF_DEVSTATUS;
        apf_print_devStatus();             // serial_cmd_fxn.c

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "sensor_sn", 9) == 0)
    {
        rx0.cmd = CMD_APF_PH;

        //sensor_sn
        //01234567890

        if((rx0.buf[9] != '=')  && (rx0.buf[9] != ','))
        {
            //uprintf("sensor_SN=%s", sys_data.sensor_SN); prn_nl();
            uprintf("sernum,phsensor=%s", sys_data.sensor_SN); prn_nl();
        }
        else
        {
            cmd_sensor_sn(&rx0.buf[10]);           //0123456789012345
        }
                                           //sensor_sn=
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "ss", 2) == 0)           // start sample, needs to have sl to print sample to console
    {
        //flush the receive and transmit buffers
       // UARTFlushRx();
        rx0.cmd = CMD_STARTSAMPLE;
        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
        }
        else
        {
            uprintf("NAK,MODE=%u", apf_fram.sys_sensor_mode); prn_nl();
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "s1", 2) == 0)            // start Isus Sample, send "sl" to receive sample
    {
        rx0.cmd = CMD_APF_ISUS_S1;
        apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;    //set mode = 1 (ISUS)
        apf_take_sample_nitrate(0);      // ISUS sample
        save_apf_vars_fram();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "s2", 2) == 0)            // start Dura pH Sample, sl to send last
    {
        rx0.cmd = CMD_APF_DURA_S2;
        apf_fram.sys_sensor_mode = DURA_SENSOR_MODE;    //set mode=2, pH
        apf_take_sample_pH(0);
        save_apf_vars_fram();
        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "store", 5) == 0) )          // store (microsd or spi flash)
    {
        if(rx0.indx > 5)
        {
            rx0.cmd = CMD_SETSTORE;
            cmd_store_set();
        }
        else
        {
            rx0.cmd = CMD_GETSTORE;
            cmd_store_get();
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "sys", 3) == 0) )          //
    {
        rx0.cmd = CMD_APF_GET_SYSINFO;
        //apf_get_sysinfo();                 // calls DisplaySystemInfo

        retVal = rx0.cmd;
        return(retVal);
    }

//1) serial.c, copy paste and modify an 'else if( (strncmp((char*)rx0.buf, "
//2) edit serial.h, create a new CMD_APF_GET_SYSINFO
//3) create a handler / function in serial_cmd_fxn.c

    //---------- T --------------
    //else if (strncmp((char*)rx0.buf, "ta", 2) == 0)
    //"t"
    else if((rx0.buf[0] == 't') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_GET_TA;
        apf_acq_time_get();                        // get acquisition time for last sample

        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "tempc", 5) == 0)  )          // compute temperature in celsius
    {
        if(rx0.buf[6] == 's')
        {
            //tempc set 1
            //012345678901
            rx0.cmd = CMD_SETCFG_TEMPC;
            cmd_set_tempc((unsigned int)rx0.buf[10]);
        }
        else if(rx0.buf[6] == 'g')
        {
            rx0.cmd = CMD_GETCFG_TEMPC;
            cmd_get_tempc();
        }
        else
        {
            rx0.cmd = CMD_TEST_TEMPC;
            num = 1;
            cmd_test_tempc(num);

        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "timestamp", 9) == 0)  )          // timestamp
    {
        //if(rx0.indx > 9)
        if(rx0.buf[10] == 's')
        {
            //timestamp set 1
            //012345678901234
            rx0.cmd = CMD_SETCFG_TIMESTAMP;
            cmd_timestamp_set((unsigned int)rx0.buf[14]);
        }
        else if(rx0.buf[10] == 'g')
        {
            rx0.cmd = CMD_GETCFG_TIMESTAMP;
            cmd_timestamp_get();
        }
        else
        {
            rx0.cmd = CMD_TEST_TIMESTAMP;
            num = 1;
            cmd_timestamp_test(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "timeds", 6) == 0)
    {
        rx0.cmd = CMD_GETTIME;
        cmd_ds3231_time_get();          //defd in i2c_ds3231.c

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "time,h", 6) == 0)
    {
        rx0.cmd = CMD_GETTIME;
        cmd_time_help();                        // print time help

        retVal = rx0.cmd;
        return(retVal);
    }
    else if( (strncmp((char*)rx0.buf, "time", 4) == 0) ||  (strncmp((char*)rx0.buf, "gettime", 5) == 0) )          // gettime
    {
        // time 11/07/23 13:45:30<cr>
        if(rx0.indx > 6)
        {
            rx0.cmd = CMD_SETTIME;
            cmd_time_set();
        }
        else
        {
            rx0.cmd = CMD_GETTIME;
            cmd_time_get();
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if (strncmp((char*)rx0.buf, "tint,?", 6) == 0)
    {
        rx0.cmd = CMD_APF_GET_TINT;
        apf_int_time_get();                        // get integration time

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "tint", 4) == 0)
    {
        rx0.cmd = CMD_APF_SET_TINT;
        apf_int_time_set();                        // set integration time

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strncmp((char*)rx0.buf, "test", 4) == 0)
    {
        rx0.cmd = CMD_APF_TEST_SENSOR;

        if( (rx0.buf[4] == ',') && (rx0.buf[5] == 'h') )
        {
            apf_test_print_help();          //apf_cmd_test_help();
        }
        else if (strncmp((char*)&rx0.buf[5], "array", 3) == 0)       //"test,array" "test,arr"
        {
            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();
            }
        }
        else if (strncmp((char*)&rx0.buf[5], "dura", 4) == 0)
        {
            apf_test_dura(rx0.buf[10] & 0x0f);
        }
        else if (strncmp((char*)&rx0.buf[5], "fram", 4) == 0)
        {
            if(rx0.buf[10] == '1')
            {
                fram_size_check();
            }
            else
            {
                fram_soak_test();
            }

        }
        else if (strncmp((char*)&rx0.buf[5], "file", 4) == 0)
        {
            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
            }

        }
        else if (strncmp((char*)&rx0.buf[5], "errord", 6) == 0)     //insert a dura error in the error.log
        {
            //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]);
            }
        }
        else if (strncmp((char*)&rx0.buf[5], "errori", 6) == 0)     //insert an isus error in the error.log
        {
            //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]);
            }
        }
        else if (strncmp((char*)&rx0.buf[5], "error", 5) == 0)
        {
            //012345678901234567890
            //test,error,this is an error string<cr><lf>
            if((rx0.buf[10] == ',') && (rx0.buf[11] >= ' '))
            {
                apf_error_log_add(&rx0.buf[11]);
            }
        }
        else if (strncmp((char*)&rx0.buf[5], "iv", 2) == 0)
        {
            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();
                }
            }
        }
        else if (strncmp((char*)&rx0.buf[5], "probe", 5) == 0)
        {
            apf_test_probe();
        }
        else if (strncmp((char*)&rx0.buf[5], "spec", 4) == 0)
        {
            apf_test_spec((uint8_t)atoi((char*)rx0.buf+10)); // use atoi to accept multi-digit numbers
        }		
        else if (strncmp((char*)&rx0.buf[5], "lamp", 4) == 0)
        {
            apf_test_lamp();
        }
        else if (strncmp((char*)&rx0.buf[5], "log", 3) == 0)
        {
            apf_test_log(rx0.buf[10] & 0x0f);
        }
        else if (strncmp((char*)&rx0.buf[5], "vars", 3) == 0)       //"test,vars"  "test,var"
        {
            apf_apf_vars_debug();
        }
        else if (strncmp((char*)&rx0.buf[5], "wdog", 4) == 0)       //"test,wdog
        {
            apf_test_wdog();            //cause phfetsos crash, then look for error.log entry
        }
        else
        {
            apf_test_print_help();
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if (strcmp(tokens.argv[0], "touch") == 0)
    {
        rx0.cmd = CMD_FILEIO_TOUCH;

        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();
            //apf_h2m_mode_set(20);       //file commands, term term set to 2.5*20 = 50sec
        }

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "ts", 2) == 0)            // take sample
    {
        rx0.cmd = CMD_TAKESAMPLE;
#ifdef NOCODE
        if(apf_fram.sys_sensor_mode == 0)        // 0 is now CarbonLab get_sample format, BUG FIX 15 May 2025, remove this for MSC1 compat
        {
            pump_and_sample(1,1);
        }
#endif
        if(apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE)
        {
            if(sys_data.msc_op == MSC1_OP)
            {
                apf_take_sample_nitrate(0);          // per Dana Swift, APF11 release needs to operate MSC1 compatible
            }
            else
            {
                apf_take_sample_nitrate(1);          // same as T1, calls mms_takeIsusSample "T1"
            }
        }
        else if(apf_fram.sys_sensor_mode == DURA_SENSOR_MODE)
        {
            if(sys_data.msc_op == MSC1_OP)
            {
                apf_take_sample_pH(0);          // per Dana Swift, APF11 release needs to operate MSC1 compatible, ts is 'silent sample', requires sl
            }
            else
            {
                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();
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "t0", 2) == 0)            // take pH sample (CarbonLab style)
    {
        rx0.cmd = CMD_TAKESAMPLE_T0;
        apf_fram.sys_sensor_mode = 0;    //update mode
        pump_and_sample(1,1);      //
        save_apf_vars_fram();
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "t1", 2) == 0)            // take Isus Sample
    {
        rx0.cmd = CMD_APF_ISUS_T1;
        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
        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "t2", 2) == 0)            // take Dura pH Sample
    {
        rx0.cmd = CMD_APF_DURA_T2;
        apf_fram.sys_sensor_mode = DURA_SENSOR_MODE;    //update mode=2
        apf_take_sample_pH(1);
        save_apf_vars_fram();                                     // save mode setting to FRAM (find out where FRAM is stored in take sample pH
        retVal = rx0.cmd;
        return(retVal);
    }



    //---------- V --------------
    else if( (strncmp((char*)rx0.buf, "vbat", 4) == 0)  )          // vbat
    {

        if(rx0.buf[5] == 's')
        {
            //vbat set 1
            rx0.cmd = CMD_SETCFG_VBAT;
            cmd_set_vbat(rx0.buf[9]);
        }
        else if(rx0.buf[5] == 'g')
        {
            //vbat get
            rx0.cmd = CMD_GETCFG_VBAT;
            cmd_get_vbat();
        }
        else
        {
            // get count, pass in as num
            rx0.cmd = CMD_TEST_VBAT;
            num = 1;
            cmd_test_vbat(num);

            //vbat or vbat nnnnnn (doforever if 0)
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "version", 3) == 0)            // help
    {
        rx0.cmd = CMD_GETVERSION;
        print_cmd_version();
        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "vinmin", 6) == 0)            // vin threshold for isus and dura
    {
        //012345678901234567890
        //vinmin,isus,11.2
        rx0.cmd = CMD_APF_VINMIN;

        if(rx0.buf[6] == ',' && rx0.buf[7] == '?')
        {
            apf_vinmin_get();
        }
        else if(rx0.buf[6] == ',' && rx0.buf[7] == 'h')
        {
            apf_vinmin_help();
        }
        //else if(rx0.buf[6] == ',' && rx0.buf[7] == 'i' && rx0.buf[8] == 's' && rx0.buf[9] == 'u' && rx0.buf[10] == 's' && rx0.buf[11] == ',' && rx0.buf[12] >= '0' && rx0.buf[12] <= '9')
        else if(rx0.buf[6] == ',' && rx0.buf[7] == 'i' && rx0.buf[8] == 's' && rx0.buf[9] == 'u' && rx0.buf[10] == 's' && rx0.buf[12] >= '0' && rx0.buf[12] <= '9')
        {
            apf_vinmin_isus_set();
        }
        //else if(rx0.buf[6] == ',' && rx0.buf[7] == 'd' && rx0.buf[8] == 'u' && rx0.buf[9] == 'r' && rx0.buf[10] == 'a' && rx0.buf[11] == ',' && rx0.buf[12] >= '0' && rx0.buf[12] <= '9')
        else if(rx0.buf[6] == ',' && rx0.buf[7] == 'd' && rx0.buf[8] == 'u' && rx0.buf[9] == 'r' && rx0.buf[10] == 'a' && rx0.buf[12] >= '0' && rx0.buf[12] <= '9')
        {
            apf_vinmin_dura_set();
        }
        else
        {
            apf_vinmin_get();
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    // parse vk_std ahead of vk
    else if( (strncmp((char*)rx0.buf, "vk_std", 6) == 0)  )          // Vk_std Counter electrode current
    {
        if(rx0.buf[7] == 's')
        {
            //vk_std set 1
            //012345678901
            rx0.cmd = CMD_SETCFG_VK_STD;
            cmd_set_vk_std((unsigned int)rx0.buf[11]);
        }
        else if(rx0.buf[7] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VK_STD;
            cmd_get_vk_std();
        }
        else
        {
            rx0.cmd = CMD_TEST_VK_STD;
            num = 1;
            //if num >= 3, then vk_std is automatic
            cmd_test_vk_std(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "vk", 2) == 0)  )          // Vk Counter electrode voltage
    {
        //if(rx0.indx > 2)
        if(rx0.buf[3] == 's')
        {
            //vk set 1
            //01234567
            rx0.cmd = CMD_SETCFG_VK;
            cmd_set_vk((unsigned int)rx0.buf[7]);
        }
        else if(rx0.buf[3] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VK;
            cmd_get_vk();
        }
        else
        {
            rx0.cmd = CMD_TEST_VK;
            num = 1;
            //if num >= 3, then vk_std is automatic
            cmd_test_vk(num);

        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "vrsi_std", 6) == 0)  )          // vrse_std
    {
        //if(rx0.indx > 8)
        if(rx0.buf[9] == 's')
        {
            //vrsi_std set 1
            //01234567890123
            rx0.cmd = CMD_SETCFG_VRSI_STD;
            cmd_set_vrsi_std((unsigned int)rx0.buf[13]);
        }
        else if(rx0.buf[9] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VRSI_STD;
            cmd_get_vrsi_std();
        }
        else
        {
            rx0.cmd = CMD_TEST_VRSI_STD;
            num = 1;
            cmd_test_vrsi_std(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "vrse_std", 6) == 0)  )          // vrse_std,get vrse_std,set vrse_std,h
    {
        //if(rx0.indx > 8)
        if(rx0.buf[9] == 's')
        {
            rx0.cmd = CMD_SETCFG_VRSE_STD;
            cmd_set_vrse_std((unsigned int)rx0.buf[13]);
        }
        else if(rx0.buf[9] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VRSE_STD;
            cmd_get_vrse_std();
        }
        else if(rx0.buf[9] == 'h')
        {
            //rx0.cmd = CMD_GETCFG_VRSE_STD;
            cmd_vrse_std_help();
        }
        else
        {
            rx0.cmd = CMD_TEST_VRSE_STD;
            //num = 1;
            num = rx0.buf[9] & 0x0f;
            if(num == 0)
                num = 1;
            cmd_test_vrse_std(num);
        }
        retVal = rx0.cmd;
        return(retVal);
    }

    //vrsi
    //vrsi 1
    //vrsi set 1
    //vrsi get
    //vrsi test (t)
    //vrsi testcontinuous (c)
    else if( (strncmp((char*)rx0.buf, "vrsi", 4) == 0) )        // getvrsi
    {
        //if((rx0.indx > 5) && (rx0.buf[5] >= '0') && (rx0.buf[5] <= '1'))
        if(rx0.buf[5] == 's')
        {
            //vrsi set 1
            //0123456789
            rx0.cmd = CMD_SETCFG_VRSI;
            cmd_set_vrsi((unsigned int)rx0.buf[9]);
        }
        else if(rx0.buf[5] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VRSI;
            cmd_get_vrsi();
        }
        else
        {
            rx0.cmd = CMD_TEST_VRSI;
            num = 1;
            cmd_test_vrsi(num);
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    //vrse
    //vrse 1
    //vrse test (t)
    //vrse testcontinuous (c)
    else if( (strncmp((char*)rx0.buf, "vrse", 4) == 0) )        // getvrse
    {
        //if((rx0.indx > 5) && (rx0.buf[5] >= '0') && (rx0.buf[5] <= '1'))
        if(rx0.buf[5] == 's')
        {
            //vrse set 1
            //0123456789
            rx0.cmd = CMD_SETCFG_VRSE;
            cmd_set_vrse((unsigned int)rx0.buf[9]);
        }
        else if(rx0.buf[5] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VRSE;
            cmd_get_vrse();
        }
        else
        {
            rx0.cmd = CMD_TEST_VRSE;
            num = 1;
            cmd_test_vrse(num);

        }

        retVal = rx0.cmd;
        return(retVal);
    }
    // parse vtherm_std before vtherm
    else if( (strncmp((char*)rx0.buf, "vtherm_std", 9) == 0)  )          // Vk Counter electrode voltage
    {
        //if(rx0.indx > 6)
        //vtherm_std set 1
        //012345678901234567
        if(rx0.buf[11] == 's')
        {
            // vtherm set 1
            // 012345678901
            rx0.cmd = CMD_SETCFG_VTHERM_STD;
            cmd_set_vtherm_std((unsigned int)rx0.buf[15]);
        }
        else if(rx0.buf[11] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VTHERM_STD;
            cmd_get_vtherm_std();
        }
        else
        {
            rx0.cmd = CMD_TEST_VTHERM_STD;
            num = 1;
            cmd_test_vtherm_std(num);

        }
        retVal = rx0.cmd;
        return(retVal);
    }

    else if( (strncmp((char*)rx0.buf, "vtherm", 6) == 0)  )          // Vk Counter electrode voltage
    {
        //if(rx0.indx > 6)
        if(rx0.buf[7] == 's')
        {
            // vtherm set 1
            // 012345678901
            rx0.cmd = CMD_SETCFG_VTHERM;
            cmd_set_vtherm((unsigned int)rx0.buf[11]);
        }
        else if(rx0.buf[7] == 'g')
        {
            rx0.cmd = CMD_GETCFG_VTHERM;
            cmd_get_vtherm();
        }
        else
        {
            rx0.cmd = CMD_TEST_VTHERM;
            num = 1;
            cmd_test_vtherm(num);

        }
        retVal = rx0.cmd;
        return(retVal);
    }


    //---------- W --------------

    //else if (strncmp((char*)rx0.buf, "ww", 2) == 0)            // APF ping response
    // "w"  "W"
    else if((rx0.buf[0] == 'w') && (rx0.buf[1] == 0))
    {
        rx0.cmd = CMD_APF_PING;
        apf_print_ping_response();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "wdog", 4) == 0)
    {
        rx0.cmd = CMD_APF_WDOG;
        apf_wdog_status();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "wisd", 4) == 0)
    {
        rx0.cmd = CMD_WISD;
        cmd_wisdom_get();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strncmp((char*)rx0.buf, "yooster", 4) == 0)
    {
        rx0.cmd = CMD_YOOSTER;
        cmd_yooster();

        retVal = rx0.cmd;
        return(retVal);
    }
    //---------- Y --------------

    else if((rx0.buf[0] == 'y') && (rx0.buf[1] == 0))
    {
        //DEBUG command, m2m mode with long term time is the suspect
        rx0.cmd = CMD_APF_PING;
        uprintf("term time = %u", rx0.tick_ms_delim); prn_nl();
        //apf_print_termtime();

        retVal = rx0.cmd;
        return(retVal);
    }
    else if(strcmp(tokens.argv[0], "ys") == 0)            // send file (upload from target to host)
    {
        rx0.cmd = CMD_YMODEM_SEND;

        //apf_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
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strcmp(tokens.argv[0], "yr") == 0)            // receive file (download)
    {
        rx0.cmd = CMD_YMODEM_RECEIVE;

        //apf_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
        }

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "zcoef", 5) == 0)            // receive file (download)
    {
        rx0.cmd = CMD_APF_ZCOEF;

        apf_zcoef_print();

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strncmp((char*)rx0.buf, "zr", 2) == 0)            // receive file (download)
    {
        rx0.cmd = CMD_ZMODEM_RECEIVE;

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

        retVal = rx0.cmd;
        return(retVal);
    }

    else if(strcmp(tokens.argv[0], "zs") == 0)           // send file (upload)
    {
        rx0.cmd = CMD_ZMODEM_SEND;
        /*
        if( zmodem_send(tokens.argc, tokens.argv) != 0)
        {
            // fail
        }
        else
        {
            // success
        }
        */

        retVal = rx0.cmd;
        return(retVal);
    }
    else
    {
        //APFTODO rx0.cmd = 0xff  added 19Mar2025

        rx0.cmd = CMD_NOTDEFD;      // 0x00
        retVal = rx0.cmd;
    }


    return(retVal);

}

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;
}

const char* ser_commands_arr[] =
{
    "bake",
    "baud",
    "biasneg",
    "biaspos",
    "c",
    "c1",
    "c2",
    "cal_date,isus",
    "cal_date,k0",
    "cal_date,pt",
    "cal",
    "config",
    "cat",
    "cd",
    "chkdsk",
    "cp",
    "ctd",
    "defaults",
    "e",
    "e1",
    "e2",
    "echo",
    "ecoef",
    "ec",
    "error",
    "filename",
    "fit",
    "format",
    "fp[0]",
    "gc",
    "gh1",
    "gh2",
    "gh",
    "gm",
    "h",
    "hh",
    "help",
    "help,all"
    "h2m",
    "ik",
    "ib",
    "init",
    "isfet_sn",
    "ise_sn",
    "k0,h",
    "ph,h",
    "k0_hcl",
    "k0_sw",
    "k0std_sw",
    "k2[0]",
    "ls",
    "microsd",
    "mkdir",
    "mode",
    "m2m",
    "mv",
    "m",
    "nitrate",
    "no3",
    "off",
    "p",
    "period",
    "profile",
    "pwd",
    "ph_calfile",
    "ph",
    "qconfig",
    "q",
    "readz",
    "reade",
    "rm",
    "rtc",
    "sample",
    "sampnum",
    "s",
    "scanspectra",
    "sernum",
    "sernum,base",
    "sernum,ctd",
    "sernum,isfet",
    "sernum,ise",
    "sernum,lamp",
    "sernum,mcap",
    "sernum,msc",
    "sernum,optode",
    "sernum,phsensor",
    "sernum,probe",
    "sernum,spec",
    "sernum,spec_analog",
    "sernum,spec_digital",
    "sernum,tiva",
    "se",
    "sleep",
    "zz",
    "stop",
    "quit",
    "slp",
    "sl",
    "spectra",
    "status",
    "sensor_sn",
    "ss",
    "s1",
    "s2",
    "store",
    "sys",
    "t",
    "tempc",
    "timestamp",
    "timeds",
    "time",
    "tint",
    "test",
    "test,array",
    "test,dura",
    "test,fram",
    "test,file",
    "test,errord",
    "test,errori",
    "test,error",
    "test,iv",
    "test,probe",
    "test,spec",
    "test,lamp",
    "test,log",
    "test,vars",
    "test,wdog",
    "touch",
    "ts",
    "t0",
    "t1",
    "t2",
    "vbat",
    "ver",
    "vinmin",
    "vinmin,isus",
    "vinmin_dura",
    "vk_std",
    "vk",
    "vrsi",
    "vrsi_std",
    "vrse",
    "vtherm",
    "vtherm_std",
    "w",
    "wdog",
    "y",
    "ys",
    "yr",
    "zcoef",
    0
};







/*
 * 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
 *
 *
 */
