/*
 * serial_cmd_fxn.c
 *
 *  Created on: May 3, 2024
 *      Author: thomm
 */
#include "serial_cmd_fxn.h"

#include "main.h"
#include "system.h"
#include "sleep.h"
#include "sample.h"
#include "serial.h"
#include "config.h"
#include "test.h"
#include "uart3.h"
#include "uartstdio.h"
#include "user_io.h"
#include "serial.h"
#include "board_util.h"
#include "apf.h"
#include "apf_util.h"
#include "apf_math.h"
#include "apf_init.h"
#include "apf_numbr.h"
#include "mms_spec.h"
#include "mms_test.h"
#include "sample.h"
#include "ymodem.h"
#include "zmodem.h"
#include "i2c_fram.h"
#include "apf_test.h"
#include "i2c_ina260.h"
#include "init_defaults.h"
#include "i2c_ds3231.h"
#include "i2c_bme280.h"
#include "vtable.h"

#include "stdint.h"
#include "string.h"
#include "stdbool.h"

extern struct rxStateMach rx0;             // defd in serial.h
extern struct watchDog wdog;                // defd in board_util.h

extern long   size, avail;      //defd in apfutil.c

extern unsigned int databarFlg;
extern unsigned int barfSampleTime;

extern const char* wisdom_arr[];
extern const char* contrib_arr[];

extern int mmsDbgFlg; //defd in mms_spec.c
extern uint32_t fram_apf_vars_retrievedFlg;
extern uint32_t fram_apfArray_retrievedFlg;

uint32_t serRxFlushAfterCmdFlg = 1;       //enabled in MoSoS v5.1.1, disabling serRxFlush may have been causing hangs in v5.1.0, not root caused 22 May 2025


void prn_undefined(void)
{
    uprintf("undefined for this boardType!"); prn_nl();
}
void print_cmd_big_help(void)
{
    uprintf("                 Ping:  W          ACK"); prn_nl();
    uprintf("                 Menu:  M          <menu launches>"); prn_nl();
    uprintf("         Error Counts:  E          ACK,E,3,3033"); prn_nl();
    uprintf("  PwrOn, SysRst, Evnt:  P          ACK,P,03,3937292061"); prn_nl();
    uprintf("       Sample Counter:  S          ACK"); prn_nl();
    uprintf("             Acq Time:  T          ACK"); prn_nl();
    uprintf("       Get MSC Config:  C          H, Datalogger Serial Num , SN:49024..."); prn_nl();
    uprintf("      Initialize Vars:  INIT       ACK,INIT"); prn_nl();
    uprintf("     Integration Time:  TINT,400   ACK,TINT,400"); prn_nl();
#if BOARD_MSC == 3
    uprintf("  Dura=2 Isus=1  Mode:  MODE,1     ACK,MODE,1"); prn_nl();
    uprintf("  Silent Sample Modal:  SS     "); prn_nl();
    uprintf("   Silent Isus Sample:  S1     "); prn_nl();
    uprintf("Silent pH Dura Sample:  S2     "); prn_nl();
    uprintf("    Take Sample Modal:  TS     "); prn_nl();
    uprintf("     Take Isus Sample:  T1     "); prn_nl();
    uprintf("  Take pH Dura Sample:  T2     "); prn_nl();
#endif // #if BOARD_MSC == 3
    wait_consoleTx();
    uprintf("     Send Last sample:  SL     "); prn_nl();
    uprintf("  Send MSC error msgs:  SE     "); prn_nl();
    uprintf("                Sleep:  SLP    "); prn_nl();
    uprintf(" Prepare for PowerOff:  OFF    "); prn_nl();
#if BOARD_MSC == 3
    uprintf("              Set RTC:  RTC,1727464426    "); prn_nl();
    uprintf("              Get RTC:  RTC,?  "); prn_nl();
    uprintf("                 Ping:  FIT    "); prn_nl();

    uprintf("              Set CTD:  CTD,1727464446,20.0000,35.0000,-5.00    "); prn_nl();
    uprintf("              Get CTD:  CTD,?    "); prn_nl();
    uprintf("             Bake,sec:  BAKE,10   ACK,BAKE,CMD,10"); prn_nl();
    uprintf("            Send last:  SL     "); prn_nl();
    uprintf("    Get MSC Error msg:  SE     "); prn_nl(); prn_nl();
    uprintf(" get (Query) MSC type:  Q          ACK,Q,4   Types: 1=NONE, 2=Isus, 3=Dura, 4=Isus+Dura"); prn_nl();
    uprintf("term time = %u msec", cmd_h2m_timeout_get()); prn_nl();
#endif // #if BOARD_MSC == 3
    wait_consoleTx();
}


void print_cmd_help(void)
{

    uprintf("** datalogger setup:  0 for off, 1 for on"); prn_nl();
    uprintf(" time, gettime"); prn_nl();
    uprintf(" time MM/DD/YY HH:MM:SS, settime MM/DD/YY HH:MM:SS");  prn_nl();
    uprintf(" per, period, getperiod");  prn_nl();
    uprintf(" per 20, period 20, setperiod 20");  prn_nl();
    uprintf(" config, getconfig ");  prn_nl();
    uprintf(" config, setconfig");  prn_nl();
    uprintf(" config help");  prn_nl();
    uprintf(" filename, getfilename");  prn_nl();
    uprintf(" filename filename.ext, setfilename filename.ext");  prn_nl();
    uprintf(" microsd, store"); prn_nl();
    uprintf(" align");  prn_nl();
    uprintf("** commands:");  prn_nl();
    uprintf(" help");  prn_nl();
    uprintf(" sleep");  prn_nl();
    uprintf(" deploy");  prn_nl();
    uprintf(" ts");  prn_nl();
    uprintf(" ss");  prn_nl();
    uprintf(" sl");  prn_nl();
    uprintf(" gc");  prn_nl();
    uprintf(" ec");  prn_nl();
    uprintf(" gh");  prn_nl();
    uprintf(" eh");  prn_nl();
    uprintf(" stop, exit, quit, <ctrl-c>");  prn_nl();
    prn_nl();
    uprintf("vrsi set 1    vrsi get    vrsi    vrsi 0   vrsi 2000");


}


void print_cmd_file_help(void)
{
    /*
     *
     * help  : Display list of commands
        h  : Alias for help
        ?  : Alias for help
        cat  : Display contents of a text file
        cd  : Alias for chdir
        chdir  : Change directory
        chkdsk  : Check Disk
        cp  : Copy file
        format  : Format Disk
        ls  : Display list of files
        mkdir  : Make new directory
        mv  : Rename or move a file or directory
        pwd  : Display current working directory
        rm  : Remove a file or directory
        touch  : Create file or update file timestamp
        ys  : Send file via YMODEM
        yr  : Receive file via YMODEM
        exit  : Exit the file viewer
     *
     */
    uprintf("File commands:"); prn_nl(); prn_nl();

    uprintf(" cat    : Display contents of a text file"); prn_nl();
    uprintf(" cd     : Change directory"); prn_nl();
    uprintf(" cp     : Copy file"); prn_nl();
    uprintf(" ls     : Display list of files"); prn_nl();
    uprintf(" mkdir  : Make new directory"); prn_nl();
    uprintf(" mv     : Move a file or directory, aka, rename"); prn_nl();
    uprintf(" pwd    : Print working directory");  prn_nl();
    uprintf(" rm     : Remove a file or directory"); prn_nl();
    uprintf(" touch  : Create file or update file timestamp"); prn_nl();
    uprintf(" ys     : Send file via YMODEM"); prn_nl();
    uprintf(" yr     : Receive file via YMODEM"); prn_nl();

}

#if BOARD_MSC == 3

void apf_apf_vars_debug(void)
{
    prn_nl();

    uprintf("fram_apf_vars_retrievedFlg = %u", fram_apf_vars_retrievedFlg); prn_nl();
    uprintf("fram_apfArray_retrievedFlg = %u", fram_apfArray_retrievedFlg); prn_nl();
    prn_nl();


    uprintf("apf.isus_tcomp_mode    = %u", apf_eeprom.isus_tcomp_mode); prn_nl();
    uprintf("apf.isus_salfit_mode   = %u", apf_eeprom.isus_salfit_mode); prn_nl();
    prn_nl();

    uprintf("apf.isus_pixel_beg     = %u", apf_eeprom.isus_pixel_beg); prn_nl();
    uprintf("apf.isus_pixel_end     = %u", apf_eeprom.isus_pixel_end); prn_nl();

    uprintf("apf_eeprom.lambda_beg         = %u", apf_eeprom.lambda_beg); prn_nl();
    uprintf("apf_eeprom.lambda_end         = %u", apf_eeprom.lambda_end); prn_nl();

    uprintf("apf_eeprom.isus_datpixel_beg  = %u", apf_eeprom.isus_datpixel_beg); prn_nl();
    uprintf("apf_eeprom.isus_datpixel_end  = %u", apf_eeprom.isus_datpixel_end); prn_nl();

    uprintf("apf_eeprom.lambda_dat_beg         = %u", apf_eeprom.lambda_dat_beg); prn_nl();
    uprintf("apf_eeprom.lambda_dat_end         = %u", apf_eeprom.lambda_dat_end); prn_nl();

    uprintf("apf.isus_fit_pixels    = %u", apf_eeprom.isus_fit_pixels); prn_nl();
    prn_nl();

    uprintf("apf.CTD_Time           = %u",   apf.CTD_Time); prn_nl();
    uprintf("apf.CTD_Temp           = %.5f", apf.CTD_Temp); prn_nl();
    uprintf("apf.CTD_Salinity       = %.5f", apf.CTD_Salinity); prn_nl();
    uprintf("apf.CTD_Depth          = %.5f", apf.CTD_Depth); prn_nl();
    prn_nl();

    uprintf("apf.isus_sal           = %.5f", apf.isus_sal); prn_nl();
    uprintf("apf.isus_no3           = %.5f", apf.isus_no3); prn_nl();
    uprintf("apf.isus_hs            = %.5f", apf.isus_hs); prn_nl();
    uprintf("apf.isus_err           = %.5f", apf.isus_err); prn_nl();

    prn_nl();

}

void apf_print_help(uint32_t mode)
{
    if(mode > 0)
    {
        prn_nl(); uprintf("APF Commands: W,M,E,P,S,T,C,Q,INIT,TINT,MODE,SS,S1,S2,SL,TS,T1,T2,SE,SLP,OFF,RTC,FIT,CTD,BAKE,SPECTRA,VER,PROFILE");  prn_nl();
        prn_nl(); uprintf("File Commands: ls, cd, pwd, cat, touch, cp, rm, mkdir, format, yr, ys"); prn_nl();
        prn_nl(); uprintf("pH Commands: time, vrse, vtherm, phext, ib, ik, vk, biaspos, biasneg, tempc, vrsi, phint");  prn_nl();
        prn_nl();
    }
    uprintf("term time = %u msec", cmd_h2m_timeout_get()); prn_nl();
}


/*
 * BAKE,NN command (NN in seconds 1 - 90)
 *    While BAKE is executing, it looks like no other command response will be received (in a delay_sec)
 */
int mms_bake_handler(uint32_t bakeTimeSec)
{
    unsigned int indx;
    unsigned char rxByte;
    int     abortFlg;
    float       fSysVolt, fSysAmp;
    float       fLampVolt, fLampAmp;
    float       fBrdTempC, fBrdPress;
    float       fTimeLeft;
    uint32_t    uiHumidity;
    char        ebuf[128];
    uint32_t    ticks, tickStart;

    //Check if voltage is below threshold
    //NEW, per the MSCInterfaceSpec, check if voltage is below ISUS_V_MIN, if low (spec was 8.0v, but that has been raised to 10v after a stir)
    ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );        //system voltage and current
    if(fSysVolt < sys_data.isus_vin_min_volt)      //ISUS_SYS_V_MIN)
    {
        if(mmsDbgFlg==1) { uprintf("DBG: Input Voltage, %.2f v, is too low, aborting BAKE command.  Current = %.3f a", fSysVolt, fSysAmp);  prn_nl(); }
        mms_lampPwrOff();       //Lamp power should be off, not needed here, but just in case someone was fiddling with test code in thomzone

        sprintf(ebuf, "BAKE command aborted, Sys Vin: %.2f < %.2f, Iin: %.2f mA", fSysVolt, sys_data.isus_vin_min_volt, fSysAmp*1000);
        LOGERRORMSG(ebuf, (int)LAMP_PWR_ON_ABORT_VINMIN, apf_eeprom.sys_message_enable);

        uprintf("NAK,BAKE,STAT"); prn_nl();

        return( ERROR );    //-1
    }

    mms_lampPwrOn();        //takes a couple seconds to power on the lamp (toggling W on and off, not sure why, but that's what was in MSC1, so it is written, so it shall be done)

    ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );        //system voltage and current

#define BAKE_FIBERLITE_MIN_CURRENT  0.250
    if(fSysAmp < BAKE_FIBERLITE_MIN_CURRENT)
    {
        sprintf(ebuf, "BAKE command aborted, Sys Iin: %.2f < %umA", fSysAmp, (uint32_t)(BAKE_FIBERLITE_MIN_CURRENT*1000));
        LOGERRORMSG(ebuf, (int)LAMP_PWR_ON_ABORT_VINMIN, apf_eeprom.sys_message_enable);

        mms_lampPwrOff();       //Lamp power should be off, not needed here, but just in case someone was fiddling with test code in thomzone

        uprintf("NAK,BAKE,STAT"); prn_nl();
        return( ERROR );    //-1
    }

    uprintf("ACK,BAKE,STAT"); prn_nl();

    abortFlg = 0;
    tickStart = Timer_1ms();
    for(indx=0; indx<bakeTimeSec; indx++)
    {
        ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );        //system voltage and current
        if(fSysVolt < sys_data.isus_vin_min_volt)      //ISUS_SYS_V_MIN)
        {
            if(mmsDbgFlg==1) { uprintf("DBG: Input Voltage, %.2f v, is too low, aborting BAKE command.  Current = %.3f a", fSysVolt, fSysAmp);  prn_nl(); }

            mms_lampPwrOff();

            sprintf(ebuf, "BAKE command aborted, Sys Vin: %.2f < %.2f, Iin: %.2f mA", fSysVolt, sys_data.isus_vin_min_volt, fSysAmp*1000);
            LOGERRORMSG(ebuf, (int)LAMP_PWR_ON_ABORT_VINMIN, apf_eeprom.sys_message_enable);

            return( ERROR );    //-1
        }

        ina260_lamp_voltageCurrent( &fLampVolt, &fLampAmp );        //system voltage and current

        if(bme280_read_data() == 1)
        {
            fBrdTempC = bme280_get_fTemperature();
            fBrdPress = bme280_get_fPressure();
            uiHumidity = bme280_get_uiHumidity();
        }

        //delay_sec(1);
        ticks = (Timer_1ms() - tickStart);
        fTimeLeft = (float)(((bakeTimeSec*1000) - ticks)/1000);
        if(ticks > (bakeTimeSec*1000) )
        {
            abortFlg = 1;
        }


        if(UARTrdy() > 0)
        {
            rxByte = getChar();
            switch(rxByte)
            {

                case 'Q':
                case 'q':
                case 'X':
                case 'x':
                case ESC:
                case CTRL_C:
                    abortFlg = 1;
                    uprintf("ACK,BAKE,%u", ticks/1000); prn_nl();   //print how long it did bake
                    break;

                case 'l':
                case 'L':
                    sprintf(ebuf, "ACK,BAKE,STAT,Vin=%.2f, Iin=%.2f, Vuv=%.2f, Iuv=%.2f, TmpC=%.2f, Pres=%.2f, Hum=%u, Endtime %.3f sec", fSysVolt, fSysAmp, fLampVolt, fLampAmp, fBrdTempC, fBrdPress, uiHumidity, fTimeLeft);
                    LOGERRORMSG(ebuf, (int)LAMP_BAKE_DIAG, apf_eeprom.sys_message_enable);
                    uprintf("ACK"); prn_nl(); //let user know the L was good.
                    break;

                case 'w':
                case 'W':
                    uprintf("ACK");  prn_nl();
                    break;

                case '?':
                    uprintf("ACK,BAKE,STAT,Vin=%.2f, Iin=%.2f, Vuv=%.2f, Iuv=%.2f, TmpC=%.2f, Pres=%.2f, Hum=%u, Endtime %.3f sec", fSysVolt, fSysAmp, fLampVolt, fLampAmp, fBrdTempC, fBrdPress, uiHumidity, fTimeLeft);
                    prn_nl();
                    break;

                default:
                    //uprintf("NAK, X to exit BAKE"); prn_nl();
                    break;
            }
        }



        if(abortFlg == 1)
            break;

    }

    mms_lampPwrOff();

    uprintf("ACK,BAKE,DONE"); prn_nl();

    if(abortFlg == 1)
    {
        return( ERROR );
    }
    else
    {
        return 1;
    }

}


void apf_test_dura(uint8_t testNum)
{

    // stream APF dura readings
    while(1)
    {
        apf_take_sample_pH(1);
        if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();

        while(1)
        {
            if( kbhit() )
            {
                return;
            }
        }
    }

}

void apf_test_log(uint8_t testNum)
{
    int avail;
    uint32_t  indx;

    //testNum == 1  error.log

    //APFTODO use this for testing error.log, etc.   incomplete, needs work 16apr2025

    avail = 123;

    for(indx=0; indx<10; indx++)
    {
        avail += 5;
        sprintf((char*)eBuf, "FLASH Card is Full with %ld Bytes Remaining", avail);
        LOGERRORMSG((char*)eBuf, (int)FLASH_CARD_FULL, apf_eeprom.sys_message_enable);

    }


}


#ifdef NOCODE
    FR_OK = 0,              /* (0) Function succeeded */
    FR_DISK_ERR,            /* (1) A hard error occurred in the low level disk I/O layer */
    FR_INT_ERR,             /* (2) Assertion failed */
    FR_NOT_READY,           /* (3) The physical drive does not work */
    FR_NO_FILE,             /* (4) Could not find the file */
    FR_NO_PATH,             /* (5) Could not find the path */
    FR_INVALID_NAME,        /* (6) The path name format is invalid */
    FR_DENIED,              /* (7) Access denied due to a prohibited access or directory full */
    FR_EXIST,               /* (8) Access denied due to a prohibited access */
    FR_INVALID_OBJECT,      /* (9) The file/directory object is invalid */
    FR_WRITE_PROTECTED,     /* (10) The physical drive is write protected */
    FR_INVALID_DRIVE,       /* (11) The logical drive number is invalid */
    FR_NOT_ENABLED,         /* (12) The volume has no work area */
    FR_NO_FILESYSTEM,       /* (13) Could not find a valid FAT volume */
    FR_MKFS_ABORTED,        /* (14) The f_mkfs function aborted due to some problem */
    FR_TIMEOUT,             /* (15) Could not take control of the volume within defined period */
    FR_LOCKED,              /* (16) The operation is rejected according to the file sharing policy */
    FR_NOT_ENOUGH_CORE,     /* (17) LFN working buffer could not be allocated or given buffer is insufficient in size */
    FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
    FR_INVALID_PARAMETER    /* (19) Given parameter is invalid */

    FA_READ             Specifies read access to the file. Data can be read from the file.
    FA_WRITE            Specifies write access to the file. Data can be written to the file. Combine with FA_READ for read-write access.
    FA_OPEN_EXISTING    Opens the file. The function fails if the file is not existing. (Default)
    FA_CREATE_ALWAYS    Creates a new file. If the file is existing, the file is truncated and overwritten.
    FA_CREATE_NEW       Creates a new file. The function fails if the file is existing.
    FA_OPEN_ALWAYS      Opens the file. If it is not exist, a new file is created.
    FA_OPEN_APPEND      Same as FA_OPEN_ALWAYS except the read/write pointer is set end of the file.
#endif

// "ftst"
// serial command string is in rx0.buf
void apf_file_test(void)
{
    FIL   coef_fil;

    uprintf("apf_file_test()");   prn_nl();

    fRes = f_open(&coef_fil, "\\ISUS\\ECOEFZER.CAL", FA_READ);   //FR_OK=0, FR_NO_FILE=4
    uprintf("DBG: ISUS\\ECOEFZER.CAL fRes = %d", fRes); prn_nl();
    if(fRes)
    {
        uprintf("DBG: fileopen failed %d", fRes); prn_nl();
        return;
    }
    f_close(&coef_fil);
    return;


}

// "dbg"
void apf_dbg(int state)
{
    mmsDbgFlg = state;

    uprintf("mmsDbgFlg=%d, mms dbg printfs are %s", mmsDbgFlg, (mmsDbgFlg==1? "on" : "off"));
    prn_nl();
}

void apf_serial_term_echo(int state, int prnFlg)
{
    if(state == 0)
    {
        sys_data.echo_flag = 0;
        if(prnFlg) { uprintf("echo off"); prn_nl(); }
    }
    if(state == 1)
    {
        sys_data.echo_flag = 1;
        if(prnFlg) { uprintf("echo on"); prn_nl(); }
    }
    if(state == -1)
    {
        if(sys_data.echo_flag == 0)
        {
            if(prnFlg) { uprintf("echo off"); prn_nl(); }
        }
        else if(sys_data.echo_flag == 1)
        {
            if(prnFlg) { uprintf("echo on"); prn_nl(); }
        }
        else
        {
            if(prnFlg) { uprintf("echo not defd, turning echo on"); prn_nl(); }
            sys_data.echo_flag = 1;
        }

    }
}

// "reade" "readecoef"
void apf_ecoef_read(void)
{
    apf_ecoef_print();
}


void apf_apfArray_debug(void)
{
    uint32_t  indx;
    uint32_t  jndx;

    wait_consoleTx();

    uprintf("SwCalTemp = %.5f", apfArray.SwCalTemp); prn_nl();

    uprintf("ModuleNum = %u", apfArray.ModuleNum); prn_nl();

    uprintf("ZcoefsCnt = %u", apfArray.ZcoefsCnt); prn_nl();

    prn_nl();

    for(indx=0; indx<5; indx++)
    {
        uprintf("C%u' %.5E", indx, apfArray.ZeissCoefs[indx]); prn_nl();

    }
    prn_nl();


    uprintf("apfArray.EcoefsCnt = %u,  apfArray.EcoefFile[][]:", apfArray.EcoefsCnt); prn_nl();
    if( (apfArray.EcoefsCnt > 0) && (apfArray.EcoefsCnt <= 256) )
    {
        for(jndx=0; jndx<3; jndx++)
        {
            prn_nl();  uprintf("  -- apfArray.EcoefFile[0][%u]  --", jndx); prn_nl();
            //delay_msec(200);

            for(indx=0; indx<apfArray.EcoefsCnt; indx++)
            {
                //delay_msec(50);
                wdog_pet();
                uprintf("%.5E", apfArray.EcoefFile[indx][jndx]);
                if((indx+1)%8 == 0 )
                {
                    wait_consoleTx();
                    prn_nl();
                }
                else
                {
                    uprintf(",");
                }
            }

        }

    }
    prn_nl();


    uprintf("apfArray.EcoefsCnt = %u,  apfArray.EcoefData[][]:", apfArray.EcoefsCnt); prn_nl();
    if( (apfArray.EcoefsCnt > 0) && (apfArray.EcoefsCnt <= 256) )
    {
        for(jndx=0; jndx<3; jndx++)
        {
            prn_nl();  uprintf("  -- apfArray.EcoefData[0][%u]  --", jndx); prn_nl();

            for(indx=0; indx<apfArray.EcoefsCnt; indx++)
            {
                wdog_pet();
                if(apf_EcoefData[indx][jndx] <  1.0e-100)
                {
                    apf_EcoefData[indx][jndx] = 0.0;       //uprintf does not handle dnormal  IEE754 not fully supported by the rts lib
                }
                uprintf("%.5E", apf_EcoefData[indx][jndx]);
                if((indx+1)%8 == 0 )
                {
                    wait_consoleTx();
                    prn_nl();
                }
                else
                {
                    uprintf(",");
                    wait_consoleTx();
                }
            }

        }

    }

    prn_nl();

    uprintf("apfArray.LambdaData[]:"); prn_nl();
    if( (apfArray.EcoefsCnt > 0) && (apfArray.EcoefsCnt <= 256) )
    {

        for(indx=0; indx<apfArray.EcoefsCnt; indx++)
        {
            wdog_pet();

            uprintf("%.5E", apfArray.LambdaData[indx]);
            if((indx+1)%8 == 0 )
            {
                wait_consoleTx();
                prn_nl();
            }
            else
            {
                uprintf(",");
            }
        }
    }
    prn_nl();

    uprintf("ZerosCnt = %u,  apfArray.ZeroData[]:", apfArray.ZerosCnt); prn_nl();
    if( (apfArray.ZerosCnt > 0) && (apfArray.ZerosCnt <= 256) )
    {
        for(indx=0; indx<apfArray.ZerosCnt; indx++)
        {

            wdog_pet();
            uprintf("%.5E", apfArray.ZeroData[indx]);
            if((indx+1)%8 == 0 )
            {
                wait_consoleTx();
                prn_nl();
            }
            else
            {
                uprintf(",");
            }
        }
    }
    prn_nl();
    //uprintf("apfArray.TcompCoefs[]:"); prn_nl();
    prn_nl();
    for(indx=0; indx<10; indx++)
    {
        uprintf("TcompCoefs[%u] = %.5E", apfArray.TcompCoefs[indx]); prn_nl();
    }
    prn_nl();

}


//"ecoef"  //
void apf_ecoef_print(void)
{
    uint32_t  indx;

    //load ecoef from fram
    fram_apfArray_refresh();        //fixed bug 24 Jul 2025 v5.1.8

    uprintf("Zcoef Cnt = %u", apfArray.ZcoefsCnt); prn_nl();
    uprintf("Ecoef Cnt = %u", apfArray.EcoefsCnt); prn_nl();
    uprintf("Zeros Cnt = %u", apfArray.ZerosCnt);  prn_nl();
    uprintf("SwCalTemp %.5E", apfArray.SwCalTemp); prn_nl();


    //uprintf("C0' %.5E", apfArray.ZeissCoefs[0]); prn_nl();   //+1.89757E+02   NOTE: does not print leading +,
    //uprintf("C1' %.5E", apfArray.ZeissCoefs[1]); prn_nl();          //+7.81499E-01
    //uprintf("C2' %.5E", apfArray.ZeissCoefs[2]); prn_nl();   //C2' +1.40802E-04
    //uprintf("C3' %.5E", apfArray.ZeissCoefs[3]); prn_nl();   //C3' -3.00259E-07
    //uprintf("C4' %.5E", apfArray.ZeissCoefs[4]); prn_nl();   //+0.0


    prn_nl();
           //1.90080E+02, 0.00000E+00, 0.00000E+00, 0.00000E+00, 9.41317E+03,   0.00000E+00, 0.00000E+00
    uprintf("  Lambda,   EcoefF[n][0], EcoefF[n][1],EcoefF[n][2], ZeroData,     EcoefD[n][1],EcoefD[n][2]"); prn_nl();
    if( (apfArray.EcoefsCnt > 0) && (apfArray.EcoefsCnt <= 256) )
    {

        //for(indx=0; indx < apfArray.EcoefsCnt; indx++)
        for(indx=0; indx < 257; indx++)
        {
            //delay_msec(50);
            wdog_pet();
            uprintf("%03u, ", indx);
            uprintf("%.5E, ", apfArray.LambdaData[indx]);
            //uprintf("%.5E", apfArray.EcoefFile[indx][jndx]); prn_nl();
            uprintf("%.5E, ", apfArray.EcoefFile[indx][0]);  //APFTODO hacking add 1
            uprintf("%.5E, ", apfArray.EcoefFile[indx][1]);
            uprintf("%.5E, ", apfArray.EcoefFile[indx][2]);

            uprintf("%.5E," , apfArray.ZeroData[indx]);
            uprintf("   ");
            uprintf("%.5E, ", apf_EcoefData[indx][0]);
            uprintf("%.5E ", apf_EcoefData[indx][1]);
            prn_nl();
            wait_consoleTx();
        }

    }
    prn_nl();





}


// "readz" "readzcoef"
void apf_zcoef_read(void)
{
    //fram_apf_vars_refresh();
    fram_apfArray_refresh();

    init_zcoef_ecoef_apfArray_into_fram();   // calls LoadIsusZCoefs() apf_util.c, saves array to fram
}

// "zcoef"
// print zcoef in a format that can be copied from serial terminal to file by the user
void apf_zcoef_print(void)
{
    fram_apfArray_refresh();

    uprintf("//* Zeiss MMS-UV, Module Number %u *************************************//", apfArray.ModuleNum); prn_nl();
    uprintf("//* Polynomial Coefficients C#' f(N+1) for pixel to wavelength calculation *//"); prn_nl();
    uprintf("//* DO NOT MODIFY FORMAT, i.e, values MUST be preceded by their coefficient*//"); prn_nl();
//        f_printf(&fil, "//* designator AND comment strings must have either a "//" or "*" in them.  *//"); prn_nl();
//        f_printf(&fil, "//**************************************************************************//"); prn_nl();
    //C0 *= -1.0;

    //uprintf("Zeiss MMS-UV, Module Number %u", apfArray.ModuleNum); prn_nl();
    uprintf("C0' %.5E", apfArray.ZeissCoefs[0]); prn_nl();   //+1.89757E+02   NOTE: does not print leading +,
    uprintf("C1' %.5E", apfArray.ZeissCoefs[1]); prn_nl();          //+7.81499E-01
    uprintf("C2' %.5E", apfArray.ZeissCoefs[2]); prn_nl();   //C2' +1.40802E-04
    uprintf("C3' %.5E", apfArray.ZeissCoefs[3]); prn_nl();   //C3' -3.00259E-07
    uprintf("C4' %.5E", apfArray.ZeissCoefs[4]); prn_nl();   //+0.0
    //uprintf("Zcoef Cnt = %u", apfArray.ZcoefsCnt); prn_nl();
}

void apf_wdog_status(void)
{
    uprintf("DEV: wdog.longest = %u, tot = %u", wdog.tickLongest, wdog.tickTot); prn_nl();  uprintf(">");
}

//"dev"
void apf_print_devStatus(void)
{
    prn_nl(); uprintf("Commands implemented: "); prn_nl();
    prn_nl(); uprintf("  W - ping for ACK"); prn_nl();
    prn_nl(); uprintf("  M - menu"); prn_nl();
    prn_nl(); uprintf("  T2 - needs data checked, CTD values need init and then set by command.  pHext should use apf.ctd values"); prn_nl();
    prn_nl(); uprintf("  BAKE is half baked"); prn_nl();
    prn_nl(); uprintf("  MODE - set get MODE values"); prn_nl();
    prn_nl(); uprintf("  INIT - initialize MSC values"); prn_nl();
    prn_nl(); uprintf("  TINT - set get integration"); prn_nl();
    prn_nl(); uprintf("  C - get Config settings for MSC"); prn_nl();
    prn_nl(); uprintf("  H - Help.  Sets time term for H2M"); prn_nl();
    prn_nl(); uprintf("  SYS - new, Display CF2 power on status"); prn_nl();
    prn_nl(); uprintf("  T1 - nitrate in progress, working on "); prn_nl();
    wait_consoleTx();
    prn_nl(); uprintf("  SL - send last, needs work, store in FRAM?  T1, T2, TS etc"); prn_nl();
    prn_nl(); uprintf("  TS - mode governed: 0 for legacy pH, 1 for nitrate, 2 for pH.  use T1 or T2"); prn_nl();
    prn_nl(); uprintf("  E - get Error counter"); prn_nl();
    prn_nl(); uprintf("  P - get PowerOn counter"); prn_nl();
    prn_nl(); uprintf("  S - get Sample counter"); prn_nl();
    prn_nl(); uprintf("  T - get Time for last sample conversion"); prn_nl();
    prn_nl(); uprintf("  Q - get supported MSC sensors"); prn_nl();
    prn_nl(); uprintf("  SLP - hibernate"); prn_nl();
    prn_nl(); uprintf("  OFF - hibernate sent by APF to prep for power off"); prn_nl();
    prn_nl(); uprintf("  RTC - set get rtc epoch time"); prn_nl();
    prn_nl(); uprintf("  CTD - set get CTD values"); prn_nl();

    wait_consoleTx();

    prn_nl(); uprintf("Commands partial: "); prn_nl();
    prn_nl(); uprintf("  SPECTRA - set get ISUS spectra window values"); prn_nl();
    prn_nl(); uprintf("  FIT - set get isus fit window"); prn_nl();
    prn_nl(); uprintf("  SE - get MSC error log"); prn_nl();
    prn_nl(); uprintf("  CAL - get MSC Ecoefs file"); prn_nl();

    prn_nl(); uprintf("Commands not yet: "); prn_nl();


    prn_nl(); uprintf("Misc: "); prn_nl(); ;

    uint32_t  usd_total, usd_free;

    get_storage_stat(&usd_total, &usd_free);

    prn_nl(); uprintf("DBG serial_cmd_fxn.c MicroSD: %uK bytes total, %uK bytes free", usd_total, usd_free); prn_nl(); prn_nl();

    //prn_nl(); uprintf("Commands not planned: "); prn_nl();
    //prn_nl(); uprintf("  TS is not supported for APF, use T1 or T2"); prn_nl();
    //prn_nl(); uprintf("  MODE is not supported for APF, use T1 or T2, not TS"); prn_nl();



}


void apf_send_last_nitrate(void)
{
    int strSize;

    //uprintf("apf_send_last_nitrate, apf_util.c, Imp in prog %u", apf_fram.sendlast_byteCnt); prn_nl(); //DEBUG

    if(apf_fram.sendlast_byteCnt == 0)
    {
        uprintf("NAK,SL"); prn_nl();
        return;
    }

    //fram_last_samp_retrieve();
    fram_apf_dbuf_retrieve(prnBuf, apf_fram.sendlast_byteCnt);

    //print_sampl();  //format the binary data into prnBuf
    //print_apf_nitrate_sampl();

    //APFTODO, check for null term before printing
    strSize = (int) strlen((char *)prnBuf);

    if( (strSize > 0) && (strSize < SIZEOF_PRNBUF) )
    {
        UARTwrite((char *)prnBuf, strSize);
        prn_nl();
    }
    else
    {
        uprintf("NAK,SL"); prn_nl();
    }

}




void apf_send_last_pH(void)
{
    int strSize;

    //uprintf("apf_send_last_pH, apf_util.c, Imp es Nada"); prn_nl();

    if(apf_fram.sendlast_byteCnt == 0)
    {
        uprintf("DBG: apf_send_last_pH, byteCnt is 0"); prn_nl();
        uprintf("NAK,SL"); prn_nl();
        return;
    }

    fram_apf_dbuf_retrieve(prnBuf, apf_fram.sendlast_byteCnt);       //defd in i2c_fram.c

    //print_apf_pH_sampl();
    strSize = (int) strlen((char *)prnBuf);
    if( (strSize > 0) && (strSize < SIZEOF_PRNBUF) )
    {
        UARTwrite((char *)prnBuf, strSize);
        //prn_nl();
    }
    else
    {
        uprintf("NAK,SL"); prn_nl();
    }


}

// "sys"
void apf_get_sysinfo(void)
{
    DisplaySystemInfo();
}



// 'w' ping response
void apf_print_ping_response(void)
{
    uprintf("ACK");  prn_nl();  // apf spec doesnt mention cr-lf,  CF2 implementation is \n only
}

void apf_bake_uv_lamp(void)
{
    uint32_t bakeSec;
    int     result;
    char *endptr;
    char *str;
    char *strptr;


    //APF: BAKE,NN seconds (1 to 90)
    //MSC: ACK,BAKE,CMD,NN
    //APF: ACK      if MSC does not receive ACK in 3 sec, command is rejected
    //MSC: ACK,BAKE,STAT  or NAK,BAKE,STAT if something is wrong with the lamp
    /*
     * Example: Apf11 sends BAKE,Seconds and MSC immediately sends back ACK,BAKE,CMD,Seconds
     * to acknowledge the command. If both passed values agree, Apf11 sends an ACK. If the
     * MSC doesn’t receive an ACK, within 3 seconds the command is rejected. If the value sent
     * doesn’t pass range checking the MSC will reply with a NAK,BAKE,ErrorMsg. After accepting the command
     * the MSC will acknowledge the state of the lamp cycling by returning
     * ACK,BAKE,STAT if successful or NAK,BAKE,STAT if unsuccessful
     */

    bakeSec = 0;
    str = (char *)&rx0.buf[0];      // skip over bake command
    strtok(str, ",");               // first token is "bake"
    strptr = strtok(NULL, ",");     // second token is bakeSec
    if (strptr == 0)
    {
        uprintf("NAK,BAKE,FormatErr"); prn_nl();
        return;
    }

    errno = 0;
    bakeSec = strtol(strptr, &endptr, 10);
    if (errno == ERANGE)
    {
        uprintf("NAK,BAKE,OverflowErr: %s", strptr); prn_nl();
        return;
    }
    else if ((errno == EINVAL) || (*endptr))
    {
        uprintf("NAK,BAKE,ConvertErr: %s", strptr); prn_nl();
        return;
    }


    if( (bakeSec >= 1) && (bakeSec <= 90) )
    {
        // Good Bake
        uprintf("ACK,BAKE,CMD,%u", bakeSec); prn_nl();

        //wait for ACK
        result = waitACK(GET_ACK_TIMEOUT_MSEC);  //3000
        if(result)
        {
            led_sampling_on();

            if( mms_bake_handler(bakeSec) )
            {
                //uprintf("ACK,BAKE,DONE"); prn_nl();       //APFTODO, in future release change the behaviour of bake
            }

            led_sampling_off();
        }
        else
        {
            uprintf("NAK,BAKE,ACK Failure"); prn_nl();
            LOGERRORMSG("BAKE Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }
    else
    {
        uprintf("NAK,BAKE,RangeErr [1,90]: %u", bakeSec); prn_nl();
    }


    //uprintf("DEBUG Bake for 10 seconds");  prn_nl();
    //mms_bake(10);     // secs, defd in mms_spec.c
    //uprintf("BAKE done");  prn_nl();
}





// 'c1'  "c1"
void apf_isus_cfg_get(void)
{
    print_isus_header();
}

// 'c2'  "c2"
void apf_dura_cfg_get(void)
{
    print_dura_header();
}

// 'c' "c"
void apf_cfg_get(void)
{
    if( apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE )
    {
        print_isus_header();
    }
    else if( apf_fram.sys_sensor_mode == DURA_SENSOR_MODE )
    {
        print_dura_header();
    }
    else
    {
        uprintf("NAK,C,InvalidModeValue: %d", apf_fram.sys_sensor_mode);  prn_nl();
    }

}

//"CAL"
void apf_cal_get(void)
{
    int err;

    if( apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE )
    {
        err = read_print_ecoefs_Cal_file();       //apf_util.c
        if (err > 0)
        {
            //uprintf("ACK,CAL,OK"); prn_nl();
        }
        else
        {
            uprintf("NAK,CAL,ACK Failure"); prn_nl();
            LOGERRORMSG("CAL Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }
    else if( apf_fram.sys_sensor_mode == DURA_SENSOR_MODE )
    {
       err = read_print_pH_Cal_file();       //apf_util.c
       if (err > 0)
       {
           //uprintf("ACK,CAL,OK"); prn_nl();
       }
       else
       {
           uprintf("NAK,CAL,ACK Failure"); prn_nl();
           LOGERRORMSG("CAL Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
       }
    }
    else
    {
        uprintf("NAK,C,InvalidModeValue: %d", apf_fram.sys_sensor_mode);  prn_nl();
    }
}
#else
void serial_term_echo(int state, int prnFlg)
{
    if(state == 0)
    {
        sys_data.echo_flag = 0;
        if(prnFlg) { uprintf("echo off"); prn_nl(); }
    }
    if(state == 1)
    {
        sys_data.echo_flag = 1;
        if(prnFlg) { uprintf("echo on"); prn_nl(); }
    }
    if(state == -1)
    {
        if(sys_data.echo_flag == 0)
        {
            if(prnFlg) { uprintf("echo off"); prn_nl(); }
        }
        else if(sys_data.echo_flag == 1)
        {
            if(prnFlg) { uprintf("echo on"); prn_nl(); }
        }
        else
        {
            if(prnFlg) { uprintf("echo not defd, turning echo on"); prn_nl(); }
            sys_data.echo_flag = 1;
        }
    }
}
#endif // #if BOARD_MSC == 3

//wait for ACK, timeout in msec
// return TRUE if ACK received, FALSE if not ACK, -1 if timedout
int waitACK(uint32_t timeout_ms)
{
    unsigned char uc;
#define SIZEOF_WAITACK_RXBUF  16
    unsigned char rxBuf[SIZEOF_WAITACK_RXBUF];
    uint32_t ticks;
    uint32_t indx;

    uint32_t tickStart = Timer_1ms();

    indx=0;
    while(1)
    {
        if( UARTRxBytesAvail() )
        {
            uc = getChar();

            switch(uc)
            {
            case 'A':
            case 'a':
            case 'C':
            case 'c':
            case 'K':
            case 'k':
                //uprintf("DBG: good char %c", uc); prn_nl();
                rxBuf[indx] = uc;
                indx++;
                break;

            default:
                return(false);
            }

#define SIZEOF_ACK_STRING 3
            if(indx >= SIZEOF_ACK_STRING)
            {
                //compare to ACK
                if ( (strncmp((const char*)rxBuf, "ACK", 3) == 0) || (strncmp((const char*)rxBuf, "ack", 3) == 0) )
                {
                    //handle the 3 cases 1)no termination, 2) cr terminated, 3) cr-lf terminated
                    return (true);
                }

                //if strncmp fails, return false
                return(false);
            }
            tickStart = Timer_1ms();
        }

        ticks = (Timer_1ms() - tickStart);
        if(ticks > timeout_ms)
        {
            //Timed out
            return (false);
        }
    }
}

#if BOARD_MSC == 3

void apf_err_get(void)
{
    unsigned long currentTime;

    if( apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE )
    {
        if (apf_fram.isus_error_time > TI_CCS_UNIX_EPOCH_OFFSET)
            currentTime = apf_fram.isus_error_time - TI_CCS_UNIX_EPOCH_OFFSET;

        uprintf("ACK,E,%lu,%lu", apf_fram.isus_error_ctr, currentTime);   prn_nl();
    }
    else if( apf_fram.sys_sensor_mode == DURA_SENSOR_MODE )
    {
        if (apf_fram.dura_error_time > TI_CCS_UNIX_EPOCH_OFFSET)
            currentTime = apf_fram.dura_error_time - TI_CCS_UNIX_EPOCH_OFFSET;

        uprintf("ACK,E,%lu,%lu", apf_fram.dura_error_ctr, currentTime);   prn_nl();
    }
    else
    {
        if (apf_fram.sys_error_time > TI_CCS_UNIX_EPOCH_OFFSET)
            currentTime = apf_fram.sys_error_time - TI_CCS_UNIX_EPOCH_OFFSET;

        //system errors
        uprintf("ACK,E,%lu,%lu", apf_fram.sys_error_ctr, currentTime);   prn_nl();
    }

}


//CTD,?
//ACK,CTD?,Time,T,S,P
void apf_ctd_get(void)
{
    uprintf("ACK,CTD?,%lu,%.4f,%.4f,%.2f", apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth);  prn_nl();
}

#define MIN_CTD_DATE_VALUE  173445218
void apf_ctd_help(void)
{
    uprintf("Example CTD set command:  CTD,%u,20.0000,35.0000,-5.00, after response send ACK, ", MIN_CTD_DATE_VALUE); prn_nl();
    uprintf("      expected response: ACK,CTD,%u,20.0000,35.0000,-5.00", MIN_CTD_DATE_VALUE); prn_nl();
    uprintf("Example CTD get command:  CTD,?"); prn_nl();
    uprintf("ctd,h"); prn_nl();
}


// CTD,timestamp,T,S,P
//DTE 0> W
//DCE 0> ACK
//DTE 0> CTD,1727464446,20.0000,35.0000,-5.00
//DCE 0> ACK,CTD,1727464446,20.0000,35.0000,-5.00
//DTE 0> ACK
void apf_ctd_set(void)
{
    uint32_t tempTime = apf.CTD_Time;
    float tempTemp = apf.CTD_Temp;
    float tempSalt = apf.CTD_Salinity;
    float tempDepth = apf.CTD_Depth;

    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVarField("CTD", 'E');

    if (varP)
    {
        strtok(strptr, ", ");            // first token gives null terminated command
        strptr = strtok(NULL, ",");     // second token gets to the first parm
        if (varValueSet(varP, strptr) == OK)
        {
            varP = findVarField("CTD", 'T');
            strptr = strtok(NULL, ", ");     // second token gets to the first parm
            if (varValueSet(varP, strptr) == OK)
            {
                varP = findVarField("CTD", 'S');
                strptr = strtok(NULL, ", ");     // second token gets to the first parm
                if (varValueSet(varP, strptr) == OK)
                {
                    varP = findVarField("CTD", 'P');
                    strptr = strtok(NULL, ", ");     // second token gets to the first parm
                    if (varValueSet(varP, strptr) == OK)
                    {
                        if( fram_apf_vars_store() <= 0)
                        {
                            sprintf((char*)prnBuf, "CTD Command FRAM Failure");
                            LOGERRORMSG((char*)prnBuf, (int)FRAM_FAIL, apf_eeprom.sys_message_enable);
                        }
                        else
                            return;
                    }
                }
            }
        }
    }

    // if failure then restore all the vars
    apf.CTD_Time = tempTime;
    apf.CTD_Temp = tempTemp;
    apf.CTD_Salinity = tempSalt;
    apf.CTD_Depth = tempDepth;
}

// test vector
// fit,217.87,228.76
// NAK,FIT,SConvertErr: 217.87
// 216.10,227.20   is off by more than 1.4
void apf_fit_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("FIT");

    if (varP)
    {
        strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, ",= ");     // second token gets to the first parm
        if (varValueSet(varP, strptr) == OK)
        {
            fram_apfArray_refresh();      // load the ecoefs apfArray

            if( apfArray.ZcoefsCnt <= 0 )
            {
                uprintf("NAK,FIT,ZCoef File Error = %u", apfArray.ZcoefsCnt); prn_nl();
                if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();
                //SCIRxFlush();
                return;
            }

            MatchPixelsToLambda(apfArray.LambdaData, apf_eeprom.lambda_beg, apf_eeprom.lambda_end, &apf_eeprom.isus_pixel_beg, &apf_eeprom.isus_pixel_end);

            apf_eeprom.isus_fit_pixels = (apf_eeprom.isus_pixel_end - apf_eeprom.isus_pixel_beg) + 1;

            eeprom_apf_cfg_store();
        }
    }
}

void apf_fit_get(void)
{
    //fram_apf_vars_refresh();
    fram_apfArray_refresh();      // if the array is updated, do nothing, otherwise update from fram

    if( apfArray.ZcoefsCnt <= 0 )
    {
        uprintf("NAK,FIT,ZCoef File Error = %d", apfArray.ZcoefsCnt); prn_nl();
        //SCIRxFlush();
        return;
    }

    // uprintf("ACK,FIT?,%.1lf,%.1lf", CellNumToLambda(apf_eeprom.isus_pixel_beg), CellNumToLambda(apf_eeprom.isus_pixel_end) ); prn_nl();
    uprintf("ACK,FIT?,%.1lf,%.1lf", apfArray.LambdaData[apf_eeprom.isus_pixel_beg], apfArray.LambdaData[apf_eeprom.isus_pixel_end] ); prn_nl();
}

void apf_profileCount_help(void)
{
    uprintf("profile      - get profile counter"); prn_nl();
    uprintf("profile,?    - get profile counter"); prn_nl();
    uprintf("profile,NNNN - set profile counter [0000 to 9999], requires ACK"); prn_nl();
    uprintf("profile,inc  - increment profile counter by 1, requires ACK"); prn_nl();
    uprintf("profile,dec  - decrement profile counter by 1, requires ACK"); prn_nl();
}

void apf_profileCount_get(void)
{
    uprintf("ACK,PROFILE?,%04u", apf_fram.profileCnt); prn_nl();
}

void apf_profileCount_set(void) // xxx check it
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("PROFILE");      // JLD!!! TODO - replace function call for all vars that can be looked up now

    if (varP)
    {
        strptr = strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, ",= ");            // second token gives null terminated first parm
        if (varValueSet(varP, strptr) == OK)
        {
            apf_create_logFile_names();
            fram_apf_vars_store();
        }
    }
}

// APF11 sends PROFILE,INC to increment the profile count
void apf_profileCount_inc(void)
{
    uint32_t  profileCnt;
    int       result;

    profileCnt = apf_fram.profileCnt;

    if(apf_fram.profileCnt < 9999)
    {
        profileCnt++;
    }
    else
    {
        uprintf("NAK,PROFILE,RangeErr [0,9999]: %04u", apf_fram.profileCnt); prn_nl();
        return;
    }

    //APFTODO error check profileCnt
    //if(profileCnt >=0 && profileCnt <= 9999)
    if (profileCnt <= 9999)
    {
        uprintf("ACK,PROFILE,%04u", profileCnt); prn_nl();

        result = waitACK(GET_ACK_TIMEOUT_MSEC);
        if(result)
        {
            apf_fram.profileCnt = profileCnt;

            apf_fram.profileStartTime = read_hib_rtcc();
            apf_create_logFile_names();

            //write apf data
            fram_apf_vars_store();
        }
        else
        {
            uprintf("NAK,PROFILE,ACK Failure"); prn_nl();
            LOGERRORMSG("PROFILE Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }

    }

    //UARTFlushRx();   //SCIRxFlush();
}


// APF11 sends PROFILE,INC to increment the profile count
void apf_profileCount_inc_test(void)
{
    apf_fram.profileCnt++;

    apf_fram.profileStartTime = read_hib_rtcc();
    apf_create_logFile_names();

    //write apf data
    fram_apf_vars_store();

}

// APF11 sends PROFILE,DEC to decrement the profile count by 1
void apf_profileCount_dec(void)
{
    uint32_t  profileCnt;
    int       result;

    profileCnt = apf_fram.profileCnt;

    if(apf_fram.profileCnt > 0)
    {
        profileCnt--;
    }
    else
    {
        uprintf("NAK,PROFILE,RangeErr [0,9999]: %04u", apf_fram.profileCnt); prn_nl();
        return;
    }

    //APFTODO error check profileCnt
    if(profileCnt <= 9999)
    {
        uprintf("ACK,PROFILE,%04u", profileCnt); prn_nl();

        result = waitACK(GET_ACK_TIMEOUT_MSEC);
        if(result)
        {
            apf_fram.profileCnt = profileCnt;

            apf_fram.profileStartTime = read_hib_rtcc();
            apf_create_logFile_names();

            //write apf data
            fram_apf_vars_store();
        }
        else
        {
            uprintf("NAK,PROFILE,ACK Failure"); prn_nl();
            LOGERRORMSG("PROFILE Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }

    //UARTFlushRx();   //SCIRxFlush();

}

//INIT  init apf persistent vars formerly stored in DS3234
void apf_init_persistent_vars(void)
{
    apf_deployment_init();      // defd in apf_init.c

    uprintf("ACK,INIT"); prn_nl();

    if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();   //SCIRxFlush();
}

void apf_baud_get(void)
{
    uprintf("baud=%u", sys_data.baud_rate);  prn_nl();
    wait_consoleTx();
}

// print all metadata commands, used for dumping settings before running defaults
// commmand "settings"
void cmd_print_settings(void)
{
    int indx;

    //todo - read fram and eeprom
    fram_apfArray_refresh();

    print_cmd_version();
    apf_baud_get();
    prn_nl();

    cmd_period_get();
    //apf_vinmin_get();
    uprintf("vinmin,isus=%.1f", sys_data.isus_vin_min_volt); prn_nl();
    uprintf("vinmin,dura=%.1f", sys_data.dura_vin_min_volt); prn_nl();
    prn_nl();

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

    for(indx=0; indx<MAX_NUMOF_FP_CAL_FIELDS; indx++)
    {
        uprintf("fp[%u]=%.5e", indx, sys_data.fP_Cal[indx]); prn_nl();
    }
    for(indx=0; indx<MAX_NUMOF_K2_CAL_FIELDS; indx++)
    {
        uprintf("k2[%u]=%.5e", indx, sys_data.K2_Cal[indx]); prn_nl();
    }


    uprintf("isfet_SN=%s", sys_data.isfet_SN); prn_nl();
    uprintf("ise_SN=%s", sys_data.ise_SN); prn_nl();
    uprintf("k0_hcl=%f", sys_data.k0_HCL); prn_nl();
    uprintf("k0_sw=%f", sys_data.k0_SW); prn_nl();
    uprintf("k0std_sw=%f", sys_data.k0std_SW); prn_nl();



    uprintf("PROFILE,%04u", apf_fram.profileCnt); prn_nl();
    uprintf("pH_Calfile=%s", sys_data.cal_filename); prn_nl();
    //apf_sensor_type_get();

    apf_sernum_msc_get();
    showSernums();

//    apf_sernum_baseboard_get();
//    apf_sernum_ctd_get();
//    apf_sernum_isfet_get();
//    apf_sernum_ise_get();
//    apf_sernum_lamp_get();
//    apf_sernum_mcap_get();
//    apf_sernum_optode_get();
//    apf_sernum_probe_get();
//    apf_sernum_sensor_get();
//    apf_sernum_spec_get();
//    apf_sernum_spec_analog_get();
//    apf_sernum_spec_digital_get();
//    apf_sernum_tiva_get();

    apf_spectra_get();
    uprintf("SPECTRA,%.1lf,%.1lf", apfArray.LambdaData[apf_eeprom.isus_datpixel_beg], apfArray.LambdaData[apf_eeprom.isus_datpixel_end]);  prn_nl();
    uprintf("ACK"); prn_nl();

    //apf_int_time_get();
    uprintf("TINT,%u", apf_eeprom.isus_spec_period); prn_nl();
    uprintf("ACK"); prn_nl();

    //apf_fit_get();
    uprintf("FIT,%.1lf,%.1lf", apfArray.LambdaData[apf_eeprom.isus_pixel_beg], apfArray.LambdaData[apf_eeprom.isus_pixel_end] ); prn_nl();
    uprintf("ACK"); prn_nl();

    //apf_cal_get();  //TODO, expand this without ACK
    //cmd_get_filename();

    //apf_zcoef_print();


    //apf_ecoef_print();

    //get_pH_sensor_cal_coeff();
    //export_pH_sensor_cal_coeff();

    //apf_print_isus_header();
    //apf_print_dura_header();
    //get_header();
    //print_metadata_header( read_hib_rtcc() );

    //cmd_config_get();
    //apf_defaults();   //expand this


    //apf_serial_term_echo(-1, 1);  //maybe

}


//MODE,1   ACK

void apf_mode_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("MODE"); // xxxx check it

    if (varP)
    {
        strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, ",= ");            // second token gives null terminated first parm
        if (varValueSet(varP, strptr) == OK)
        {
            apf_fram.sendlast_byteCnt = 0; // Clear SL command buffer on mode change
            fram_apf_vars_store();
        }
    }
}

// MODE,? Query the MSC MODE
void apf_mode_get(void)
{
    uprintf("ACK,MODE?,%u", apf_fram.sys_sensor_mode); prn_nl();

}

void apf_off(void)
{
    //APFTODO, look at microSD / NAND / FatFS for any pending writes.
    uprintf("ACK,OFF");  prn_nl();
}

void apf_pc_get(void)
{
    // sys_por_ctr is being renamed to sys_wdog_ctr as wdog_ctr will indicate crashes
    uprintf("ACK,P,%lu,%lu,%lu", apf_fram.sys_wdog_ctr, apf_fram.sys_reset_ctr, apf_fram.sys_reset_time - TI_CCS_UNIX_EPOCH_OFFSET); prn_nl();
}

#if 0

void apf_sensor_type_help(void)
{
    uprintf("qconfig=N   N is sensor config returned by Q command:  1 = No Sensor, 2 = ISUS Nitrate, 3 = ISFET pH, 4 = Nitrate + pH");  prn_nl();
}

void apf_sensor_type_set(void)
{
    uint32_t sensorCfg = getIntVal("SENSOR", 1);
    //handle qconfig or qconfig,?
    if( (sensorCfg == 0) || (sensorCfg == 15) ) //'?'
    {
        uprintf("%u", apf_eeprom.sys_sensor_type); prn_nl();
        return;
    }

    if( (sensorCfg < 1) || (sensorCfg > 4) )
    {
        uprintf("Invalid sensor config %u.  1 = No Sensor, 2 = ISUS Nitrate, 3 = ISFET pH, 4 = Nitrate + pH", sensorCfg);  prn_nl();
        return;
    }

    apf_eeprom.sys_sensor_type = sensorCfg;
    if(eeprom_apf_cfg_store() != 0)
    {
        uprintf("Unable to program eeprom in apf_sensor_type_set"); prn_nl();
        LOGERRORMSG("Unable to program eeprom in apf_sensor_type_set", (int)EEPROM_FAIL, apf_eeprom.sys_message_enable);
        return;
    }

    uprintf("Q command MSC Sensor Config: ");
    switch(apf_eeprom.sys_sensor_type)
    {
        case 1:
            uprintf("1 = No Sensor"); prn_nl();
            break;
        case 2:
            uprintf("2 = ISUS Nitrate"); prn_nl();
            break;
        case 3:
            uprintf("3 = ISFET pH"); prn_nl();
            break;
        case 4:
            uprintf("4 = ISUS Nitrate + ISFET pH"); prn_nl();
            break;
        default:
            break;
    }

}
#endif

//"Q command
void apf_sensor_type_get(void)
{
    uprintf("ACK,Q,%u", (unsigned int)apf_eeprom.sys_sensor_type); prn_nl();
}




#define JAN2025     1735693200U
#define APR2025     1744688130U
#define JAN2035     2073024030U
#define JAN2038     2145951330U
#define MAX_UINT32  4294967295U
#define MAX_INT32   2145900000U     //31 Dec 2037
#define MAX_TIME_DATE_VALUE  MAX_INT32     //3979434495U          //10 years from uint32_t epoch time rollover in the year 2096


//The 32-bit epoch time rollover, or Year 2038 Problem (Y2K38), happens when a system using a signed 32-bit integer to count Unix time
//(seconds since Jan 1, 1970) hits its maximum value, 2,147,483,647, at 03:14:07 UTC on January 19, 2038, causing the time to "overflow"
//and wrap around to a negative number, effectively rolling back the date to December 13, 1901, breaking time-sensitive applications.
//The fix involves converting systems to use 64-bit integers for time storage, ensuring software can handle larger values beyond the 2038 deadline

void apf_rtc_help(void)
{
    uint32_t    timenow;

    timenow = read_hib_rtcc();

    uprintf("RTC help:");  prn_nl();
    uprintf("   Set RTC:  RTC,%u", timenow); prn_nl();   //04/14/25 8:35:30
    uprintf("   RTC range[0,%u]", MAX_TIME_DATE_VALUE);
    uprintf("     Warning: RTC is 32bit and has Year 2038 rollover to 1901");  prn_nl();
    uprintf("  Read RTC:  RTC,?"); prn_nl();
    uprintf("See also: time,h"); prn_nl(); prn_nl();
    uprintf("Time now: "); printCurrentTime(); prn_nl();

}

// RTC,1727464426
void apf_rtc_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("RTC");

    if (varP)
    {
        strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, ",= ");            // second token gives null terminated first parm

        if (varValueSet(varP, strptr) == OK)
        {
            const uint32_t timeSet_ti_epoch = tempUInt + TI_CCS_UNIX_EPOCH_OFFSET;

            // This means the time setting is delayed by the ACK response time
            MAP_HibernateRTCSet(timeSet_ti_epoch);

            fram_apf_vars_store();
        }
        else
        {
            uprintf("NAK,RTC,ACK Failure"); prn_nl();
            LOGERRORMSG("RTC Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }
}

//RTC,?
void apf_rtc_get(void)
{
    const uint32_t timestamp = read_hib_rtcc() - TI_CCS_UNIX_EPOCH_OFFSET;

    uprintf("ACK,RTC?,%lu", timestamp ); prn_nl();
}

//SE
void apf_se_xfer(void)
{
    int err;

    // SendRecords returns 0 on ok, 1 on ok and EOF, or 2 on just EOF
    err = SendRecords(ERRORLOG_FILE, &apf_fram.Efpos);
    switch (err)
    {
        case 0:     // response is in SendRecords ending with EOF
        case 1:     // we hit the EOF
                    // both cases the fpos moved so store
            fram_apf_vars_store();              // save the Efpos
            break;
        case 2:
            break;
        default:
            if ( !microSD_flg)      //if the microSD card is not enabled, report error as FILESYS_FAIL
            {
                uprintf("NAK,SE,FILESYS_FAIL", err); prn_nl();
            }
            else
            {
                uprintf("NAK,SE,%d", err); prn_nl();
            }
    }

    if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();   //SCIRxFlush();

    fram_apf_vars_store();
}

void apf_se_get(void)
{
    uprintf("ACK,SE?,%ld", apf_fram.errorLog_errorCnt); prn_nl();  // sys_error_ctr);
}

// get sample counter ("S" command), TGM was hoping to rename the command to SC, but oh well, MSC1 compat rules the roost)
void apf_sc_get(void)
{

    if( apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE )
    {
        uprintf("ACK,S,%lu,%lu", apf_fram.isus_sample_ctr, apf_fram.isus_sample_time - TI_CCS_UNIX_EPOCH_OFFSET);  prn_nl();
    }
    else if( apf_fram.sys_sensor_mode == DURA_SENSOR_MODE )
    {
        uprintf("ACK,S,%lu,%lu", apf_fram.dura_sample_ctr, apf_fram.dura_sample_time - TI_CCS_UNIX_EPOCH_OFFSET);  prn_nl();
    }
    else
    {
        uprintf("NAK,S,InvalidModeValue: %d", apf_fram.sys_sensor_mode);  prn_nl();
    }

}

void apf_slp_sleep(void)
{
    // sleep is handled in serial.c, this function is not needed
    uprintf("ACK,SLP"); prn_nl();
}

void apf_spectra_help(void)
{
    uprintf("Get,Set spectra range for sending over satcomm"); prn_nl();
    uprintf("Get: spectra"); prn_nl();
    uprintf("     spectra,?"); prn_nl();
    uprintf("Set: spectra,217,250"); prn_nl();
    uprintf("Range:      [210,350]"); prn_nl();
    prn_nl();
}

/* SPECTRA,217,250
 * Set reported (see SL command) ISUS spectra data range. This command will be used
 * to implement post-deployment reconfigurability using the float’s remote control mechanism.
 * This typically needs to occur only once, upon initial MSC power up (start of profile). The
 * default (see INIT command) reported spectra data range is [217nm,250nm].
 * Example: Apf11 sends SPECTRA,WindowBegin,WindowEnd and MSC sends back as an
 * acknowledgement, ACK,SPECTRA,WindowBegin,WindowEnd. If both agree, Apf11
 * sends an ACK. If MSC doesn’t receive an ACK, within 3 seconds, the values sent are discarded.
 * If the values sent don’t pass range checking the MSC will reply with a NAK,SPECTRA,ErrorMsg
 *
 * called from serial.c spectra, Expects serial data in rx0.buf[0]
*/
void apf_spectra_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("SPECTRA");

    if (varP)
    {
        strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, ",= ");            // second token gives null terminated first parm
        if (varValueSet(varP, strptr) == OK)
        {
            fram_apfArray_refresh();      // if the array is updated, do nothing, otherwise update from fram

            if( apfArray.ZcoefsCnt <= 0 )
            {
                uprintf("NAK,SPECTRA,ZCoef File Error = %d", apfArray.ZcoefsCnt); prn_nl();
                if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();
                return;
            }

            MatchPixelsToLambda(apfArray.LambdaData, apf_eeprom.lambda_dat_beg, apf_eeprom.lambda_dat_end, &apf_eeprom.isus_datpixel_beg, &apf_eeprom.isus_datpixel_end);

            if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();

            eeprom_apf_cfg_store();
        }
        else
        {
            LOGERRORMSG("SPECTRA Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }
    if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();
}

void apf_spectra_get(void)
{
    // double beg;
    // double end;

    fram_apfArray_refresh();      // if the array is updated, do nothing, otherwise update from fram

//    beg = CellNumToLambda (apf_eeprom.isus_datpixel_beg);
//    end = CellNumToLambda (apf_eeprom.isus_datpixel_end);

    uprintf("ACK,SPECTRA?,%.1lf,%.1lf", apfArray.LambdaData[apf_eeprom.isus_datpixel_beg], apfArray.LambdaData[apf_eeprom.isus_datpixel_end]);  prn_nl();

    if(serRxFlushAfterCmdFlg == 1) UARTFlushRx();   //SCIRxFlush();
    return;

}

void apf_acq_time_get(void)
{

    if( apf.gotSample == TRUE )
    {
        uprintf("ACK,T,%.3f", apf.acquire_time); prn_nl();
    }
    else
    {
        uprintf("NAK,T,-1.0");  prn_nl();
    }
}

// "TINT"
void apf_int_time_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("TINT");

    if (varP)
    {
        strtok(strptr, ", ");            // first token gives null terminated command
        strptr = strtok(NULL, ", ");            // second token gives null terminated first parm
        if (varValueSet(varP, strptr) == OK)
        {
            eeprom_apf_cfg_store();
        }
        else
        {
            LOGERRORMSG("TINT Command ACK Failure", (int)COMMAND_ACK_FAILURE, apf_eeprom.sys_message_enable);
        }
    }
}

void apf_int_time_get(void)
{
    uprintf("ACK,TINT?,%u", apf_eeprom.isus_spec_period); prn_nl();
}

// set human to machine h2m in seconds, H2M_DELIM_MS_TIMEOUT=1000
//void apf_h2m_mode_set(uint32_t mult)
//{
//    //uprintf("ACK,H2M timeout %u ms", H2M_DELIM_MS_TIMEOUT);
//
//    rx0.tick_ms_delim = H2M_DELIM_MS_TIMEOUT * mult;        // 1 sec
//
//    //uprintf("H2M_MODE"); prn_nl();
//    sys_data.terminal_mode = H2M_MODE;
//
//    //turn echoflg on
//    //uprintf("echo on"); prn_nl();
//    apf_serial_term_echo(1, 0); //echo on, no prn
//
//    sys_data.autoSleepSec = 30;
//}

void apf_m2m_mode_set(uint32_t termTimeMult)
{
    //uprintf("ACK,M2M timeout %u ms", M2M_DELIM_MS_TIMEOUT);
    if((termTimeMult >= 1) && (termTimeMult <= 9))
    {
        rx0.tick_ms_delim = M2M_DELIM_MS_TIMEOUT * termTimeMult;   // added slower termTime to aid testing m2m by hand
    }
    else
    {
        rx0.tick_ms_delim = M2M_DELIM_MS_TIMEOUT;
    }


    //uprintf("terminal mode = M2M_MODE"); prn_nl();
    sys_data.terminal_mode = M2M_MODE;

    //turn echoflg on
    //uprintf("echo on"); prn_nl();
    apf_serial_term_echo(0, 0); //echo off, dont print

    sys_data.autoSleepSec = 10;
}

// mode,1 command "C"
void apf_print_isus_header(void)
{
    FIL dat_file = { };

    //TODO open the file.

    //GH1
    //print, dont write
    print_write_isusdura_header(1, 0, ISUS_SENSOR_MODE, &dat_file);             //OutputIsusHeader(dat_file);

}
// mode,2 command "C"
void apf_print_dura_header(void)
{
    FIL dat_file = { };

    //TODO open the file.

    //GH2
    print_write_isusdura_header(1, 0, DURA_SENSOR_MODE, &dat_file);             //OutputDuraHeader(dat_file);
}

// T2 command
//msc1 cf2 TakeDuraSample()
void apf_take_sample_pH(int txFlg)
{
    uint32_t indx;
    uint16_t crc;

    // T2 command
    //int TakeDuraSample(char *dbuf, float *atime, ulong CTD_Time, float CTD_Temp, float CTD_Salinity, float CTD_Depth)
    //struct tm *time_struct;
    //char timestamp[TIMESTAMPLENGTH];
    //struct tm *time_struct;
    struct tm *tp;

    if(rx0.cmd == CMD_TAKESAMPLE)  // if we got here by way of "TS", else it must be T2 or S2
    {
        uprintf("ACK,TS,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_APF_DURA_T2)
    {
        uprintf("ACK,T2,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_APF_DURA_S2)
    {
        uprintf("ACK,S2,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_STARTSAMPLE)
    {
        uprintf("ACK,SS,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }

    get_sample(0,0);   // returns data populated in struct sampl

    if(rx0.cmd == CMD_TAKESAMPLE)
    {
        uprintf("ACK,TS,DAT");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_APF_DURA_S2)
    {
        uprintf("ACK,S2,DAT");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_STARTSAMPLE)
    {
        uprintf("ACK,SS,DAT");  prn_nl();         // cr vs crlf  defd in system.c
    }

    //sampl.timestamp = read_hib_rtcc();
    tp = get_RTC_time();
    //strftime(timestamp, sizeof(timestamp), "%m/%d/%Y %H:%M:%S", tp);

    // variable mapping from get_sample() sampl to apf vars

    apf_fram.dura_sample_ctr++;
    apf.sys_temperature = sampl.fBrdTempC;
    apf.sys_humidity = sampl.uiHumidity;
    apf.sys_current = sampl.fsysAmp;
    apf.sys_voltage = sampl.fVbat_main;
    apf.pH = sampl.pHext;
    apf.backupVoltage = sampl.fBiasPos;                    //what is this? APFTODO
    apf.VrsExt = sampl.fVrse;
    apf.StdDevVrs = sampl.fVrse_std;
    apf.Vk = sampl.fVk;
    apf.StdDevVk = sampl.fVk_std;
    apf.Ik = sampl.fIk;
    apf.Ib = sampl.fIb;

    //zero the prnBuf, trying to fix the bug where the sprintf string gets chopped off
    for(indx=0; indx<300; indx++)       //300 is larger than the 165 chars of T2 output
        prnBuf[indx] = 0;

#define CRC_PRNSTRING_OFFSET    7
    for(indx=0; indx<CRC_PRNSTRING_OFFSET; indx++)
        prnBuf[indx] = ' ';

    indx = CRC_PRNSTRING_OFFSET;  //7
                                                                     //      1    2    3     4    5    6    7   8    9   10   11  12  13  14  15   16   17   18   19   20   21 22 23  24  25
  //indx += (unsigned int)sprintf((char*)&prnBuf[CRC_PRNSTRING_OFFSET], "pH,%02d/%02d/%02d %02d:%02d:%02d,%lu,%.2f,%.4f,%.4f,%lu,%lu,%lu,%.2f,%.2f,%.3f,%.3f,%.4f,%.3f,%+f,%f,%+f,%f,%+.3e,%+.3e",
    indx += (unsigned int)sprintf((char*)&prnBuf[CRC_PRNSTRING_OFFSET], "pH,%02d/%02d/%02d %02d:%02d:%02d,%lu,%.2f,%.4f,%.4f,%lu,%lu,%lu,%.2f,%u,%.3f,%.3e,%.4f,%.3f,%+f,%f,%+f,%f,%.3f,%.3f",
            tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec,   //1 2 3 4 5 6
            apf.CTD_Time, apf.CTD_Depth, apf.CTD_Temp, apf.CTD_Salinity,                        //7 8 9 10
            apf_fram.dura_sample_ctr, apf_fram.sys_wdog_ctr, apf_fram.dura_error_ctr,                           //11 12 13
            apf.sys_temperature, apf.sys_humidity, apf.sys_voltage, apf.sys_current,   //14 15 16 17
            apf.pH, apf.backupVoltage, apf.VrsExt, apf.StdDevVrs, apf.Vk, apf.StdDevVk, apf.Ik, apf.Ib); //18 19 20 21 22 23 24 25

    prnBuf[indx] = 0;
    indx++;

    crc = fCrc16Bit((const unsigned char *)prnBuf + CRC_PRNSTRING_OFFSET);
    sprintf((char*)&prnBuf[0],"0x%04X", crc);
    prnBuf[6] = ',';

    //prnBuf holds the sample for send last "SL"
    apf_fram.sendlast_byteCnt = strlen((const char*)prnBuf);
    fram_apf_dbuf_store(&prnBuf[0], apf_fram.sendlast_byteCnt);     //store for SL send last

    // save persistent info to FRAM
    fram_apf_vars_store();

    if(txFlg > 0)
    {
        uprintf("%s", prnBuf); prn_nl();        //print to console
    }

	prnBuf[apf_fram.sendlast_byteCnt] = '\r';
	prnBuf[apf_fram.sendlast_byteCnt+1] = '\n';
	prnBuf[apf_fram.sendlast_byteCnt+2] = 0x00;

    appendDuraProfileDataToSDCard((char*)prnBuf);
}


void apf_pH(void)
{
    get_sample(0,0);

    apf.dura_pH = sampl.pHext;

    uprintf("%.2lf", apf.dura_pH); prn_nl();

    return; // ((float)apf.dura_pH);
}


// apf_take_sample_nitrate, invoked by "T1" and "S1" and "TS" (mode=1), command, calls TakeIsusSample / mms_takeIsusSample
// txFlg = 0 'start sample' but don't send, use "sl" to send, txFlg = 1 is take sample and send
void apf_take_sample_nitrate(int txFlg)
{
    int     ret;

    if(mmsDbgFlg == 1) { uprintf("Dev in progress on apf_take_sample_nitrate, calls mms_takeIsusSample"); prn_nl(); }
    //cmd T1 TakeIsusSample

    if(rx0.cmd == CMD_TAKESAMPLE)  // if we got here by way of "TS", else it must be T1 or S1
    {
        uprintf("ACK,TS,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_APF_ISUS_T1)
    {
        uprintf("ACK,T1,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_APF_ISUS_S1)
    {
        uprintf("ACK,S1,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }
    if(rx0.cmd == CMD_STARTSAMPLE)
    {
        uprintf("ACK,SS,CMD");  prn_nl();         // cr vs crlf  defd in system.c
    }

    ret = mms_takeIsusSample(&apf.acquire_time, apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth, txFlg);     //defd in mms_spec.c

    if( ret == OK )
    {
        if(txFlg == 0)
        {
            if(rx0.cmd == CMD_TAKESAMPLE)  // if we got here by way of "TS", else it must be T1 or S1
            {
                uprintf("ACK,TS,DAT");  prn_nl();         // cr vs crlf  defd in system.c
            }
#ifdef HANDLEDBYTXFLG
            if(rx0.cmd == CMD_APF_ISUS_T1)
            {
                uprintf("ACK,T1,DAT");  prn_nl();         // cr vs crlf  defd in system.c
            }
#endif
            if(rx0.cmd == CMD_APF_ISUS_S1)
            {
                uprintf("ACK,S1,DAT");  prn_nl();         // cr vs crlf  defd in system.c
            }
            if(rx0.cmd == CMD_STARTSAMPLE)
            {
                uprintf("ACK,SS,DAT");  prn_nl();         // cr vs crlf  defd in system.c
            }

        }
        apf.gotSample = TRUE;
    }
    else
    {
        //if takeIsusSample failed...

        if(rx0.cmd == CMD_TAKESAMPLE)  // if we got here by way of "TS", else it must be T1 or S1
        {
            uprintf("NAK,TS,DAT");  prn_nl();         // cr vs crlf  defd in system.c
        }
        if(rx0.cmd == CMD_APF_ISUS_T1)
        {
            uprintf("NAK,T1,CMD");  prn_nl();         // cr vs crlf  defd in system.c
        }
        if(rx0.cmd == CMD_APF_ISUS_S1)
        {
            uprintf("NAK,S1,CMD");  prn_nl();         // cr vs crlf  defd in system.c
        }
        if(rx0.cmd == CMD_STARTSAMPLE)
        {
            uprintf("NAK,SS,CMD");  prn_nl();         // cr vs crlf  defd in system.c
        }

        apf.gotSample = FALSE;
    }

    mms_flushRx();   //  was SCIRxFlush();
}

void apf_nitrate(void)
{
    int retVal;

    retVal = mms_takeIsusSample(&apf.acquire_time, apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth, 0);

    if (retVal != OK)
    {
        uprintf("mms_takeIsusSample returned ERROR!");   //NO3 command is not part of MSC Interface spec, ok to have error message here
        prn_nl();
    }

    uprintf("No3: %.2lf, Sal: %.2lf", apf.isus_no3, apf.isus_sal); prn_nl();

    return; // ((float)apf.isus_no3);
}
#else
#endif // #if BOARD_MSC == 3

void cmd_h2m_mode_set(uint32_t mult)
{
    rx0.tick_ms_delim = H2M_DELIM_MS_TIMEOUT * mult;        // 1 sec

    sys_data.terminal_mode = H2M_MODE;

    //turn echoflg on
    #if BOARD_MSC == 3
    apf_serial_term_echo(1, 0);         //echo on, no prn
    #else
    serial_term_echo(1, 0);         //echo on, no prn
    #endif

    sys_data.autoSleepSec = 30;
}

// get h2m time term in millisec
// JLD- TGM I see no reason this cant be generic
uint32_t cmd_h2m_timeout_get(void)
{
    //uprintf("ACK,H2M timeout %u ms", H2M_DELIM_MS_TIMEOUT);

    return(rx0.tick_ms_delim);
}

//--------- End of APF float commands (thank god) ---------//

char *versionStr(int maxLen)
{
    static char vString[VERSION_STRING_MAX_LEN];

    snprintf(vString, VERSION_STRING_MAX_LEN, "%s %s, MoSoS V%d.%02d.%02d ", PLATFORM, PLATFORM_COMMENT, VERSION_MAJOR, VERSION_MINOR, REVISION);

#ifdef PREFERSHORTVER  29 May 2026
    if (maxLen < VERSION_STRING_MAX_LEN)
        snprintf(vString, VERSION_STRING_MAX_LEN, "%s MoSoS V%d.%02d.%02d %s", PLATFORM, PLATFORM_COMMENT, VERSION_MAJOR, VERSION_MINOR, REVISION);
    else
        snprintf(vString, VERSION_STRING_MAX_LEN, "%s %s MoSoS V%d.%02d.%02d compiled %s @ %s", PLATFORM, PLATFORM_COMMENT, VERSION_MAJOR, VERSION_MINOR, REVISION,  __DATE__, __TIME__);
#endif

    return vString;
}

#include "githash.h"

void print_cmd_version(void)
{
    uprintf(versionStr(VERSION_STRING_MAX_LEN));
    prn_nl();
    uprintf("git hash: %s", gitHash); prn_nl();
    prn_nl();
}

void cmd_period_get(void)
{
    uprintf("period=%u", sys_data.sampling_period); prn_nl();
}

void cmd_period_set(void)
{
    char *strptr = (char *)&rx0.buf[0];
    tVarEntry *varP = findVar("period");

    if (varP)
    {
        strtok(strptr, ",= ");            // first token gives null terminated command
        strptr = strtok(NULL, " ");            // second token gives null terminated first parm
        if (varValueSet(varP, strptr) == OK)
        {
            eeprom_sys_data_store();
            cmd_period_get();
        }
    }
}

void cmd_time_help(void)
{
    prn_nl();
    uprintf("  Set:  time MM/DD/YY HH:MM:SS"); prn_nl();
    uprintf("  Set:  time 11/07/23 13:45:30"); prn_nl();
    uprintf(" Read:  time"); prn_nl();
    uprintf(" Read:  time,?"); prn_nl();
    uprintf(" Help:  time,h"); prn_nl();        //this
}

void cmd_time_get(void)
{
    unsigned int ticks;

    ticks = read_hib_rtcc();
    print_time_t_Time(ticks);
}

/**
 * Set time from rx0.buf input string
 * Format mm/dd/yyyy hh:mm:ss
 */
void cmd_time_set(void)
{
    int indx;
    int st;
    int month;
    int day;
    int year;
    int hour;
    int minute;
    int second;

    //time variables:
    time_t rawtime;
    struct tm * timeStruct;

    for(indx=4; indx<rx0.indx; indx++)
    {
        if( (rx0.buf[indx] == ' ') || (rx0.buf[indx] == ',') || (rx0.buf[indx] == '='))       // find valid char after 'time' or 'settime'
        {
            // if formatted for time
            st = indx+1;      // start of time string
            int items = sscanf((char*)&rx0.buf[st], "%d/%d/%d %d:%d:%d", &month, &day, &year, &hour, &minute, &second);
            if (items == 6)
            {
                if( second >= 0 && minute >= 0 && hour >= 0 && month >= 1 && day >= 1 && year >= 25)
                {
                    if( second <= 59 && minute <= 59 && hour <= 23 && day <= 31 && month <= 12)
                    {
                        //fill in time.h's tm struct initially (timeStruct just points to time.h's struct)
                        time(&rawtime);
                        //timeStruct = localtime(&rawtime);     //mod 31 Mar 2025
                        timeStruct = get_RTC_time();                           //populate time struct

                        //modify as user desired
                        timeStruct->tm_sec = second;
                        timeStruct->tm_min = minute;
                        timeStruct->tm_hour = hour;
                        timeStruct->tm_mday = day;
                        timeStruct->tm_mon = month - 1;
                        timeStruct->tm_year = year + 100; //  1900;

                        //GMT HAS NO DAYLIGHT SAVINGS
                        timeStruct->tm_isdst = 0;

                        //remake the struct
                        rawtime = mktime(timeStruct);

                        MAP_HibernateRTCSet(rawtime); // Set time if user entered valid time

                        printCurrentTime(); prn_nl();

                        ds3231_set_time();   //program the RTC on the baseboard
                        return;
                    }
                }
            }
            break;
        }
    }

    print_NAK();

}

void cmd_get_filename(void)
{
    uprintf("%s", sys_data.fileName); prn_nl();
}

void cmd_test_vrsi()
{
    unsigned int num = 1;
    //uprintf("Not available");
    test_vrsi(num, 0, 0, 0, 500);
}

void cmdTestVRSE(int num, int arg1, int arg2, int arg3, int arg4)
{
    int indx;

    // TODO, parse trials, for now, set to 10
    for(indx=0; indx<num; indx++)
    {
        test_vrse(num, arg1, arg2, arg3, arg4);
        //look for key and abort
        if(kbhit())
            break;
    }
}

int getFirstArgument(int argNum, int defaultValue)
{
    int num = defaultValue;

    char *str = (char *)&rx0.buf[0];
    char *strptr = strtok(str, ", ");
    while (argNum > 0)
    {
        if (strptr)
        {
            strptr = strtok(NULL, ", ");    // is there an argument
        }
        else
            break;

        argNum--;
    }
    if (strptr)
    {
        num = strtol(strptr, 0, 10);
    }
    return num;
}

// call get Next argument after calling this function
void cmd_test_vrse_std(void)
{
    int num = getFirstArgument(1, 5);
    cmdTestVRSE(num, 1, 10, 0, 500);
}

void cmd_test_vrse(void)
{
//    int num = 5;
//    char *str = (char *)&rx0.buf[0];
//    char *strptr = strtok(str, ", ");
//    if (strptr)
//    {
//        strptr = strtok(NULL, ", ");
//        if (strptr)
//            num = strtol(strptr, 0, 10);
//    }
    int num = getFirstArgument(1, 5);

    cmdTestVRSE(num, 0, 0, 0, 500);
}

void cmd_vrse_std_help(void)
{
    uprintf(""); prn_nl();
}

void cmd_test_ib(void)
{
    unsigned int num = 1;
    //uprintf("Not available");
    test_ib(num, 0, 0, 0, 500);
}

void cmd_test_ik(void)
{
    unsigned int num = 1;
    //uprintf("Not available");
    //void test_ik(unsigned int num, unsigned int stdFlg, unsigned int trials, unsigned int sps, unsigned int msec)
    test_ik(num, 0, 0, 0, 500);     // defd in test.c
}

void cmd_test_vk(void)
{
    unsigned int num = 1;
    //uprintf("Not available");
    test_vk(num, 0, 0, 0, 500);
}

void cmd_test_vk_std(void)
{
    unsigned int num = 1;

    //uprintf("Not available");
    test_vk(num, 1, 5, 0, 500);
}

void cmd_test_vtherm(void)
{
    unsigned int num = 1;
    test_vtherm(num, 0, 0, 0, 500);
}

void cmd_test_vtherm_std(void)
{
    unsigned int num = 1;
    test_vtherm(num, 1, 5, 0, 500);
}

void cmd_test_tempc(void)
{
    test_tempc(1, 0, 0, 0, 500);   // stdFlg will print vtherm and vtherm_std
}

void cmd_test_vbat(void)
{
    display_battery_boardSensors();
}

void cmd_test_biasneg()
{
    unsigned int num = 1;
    test_bias_bat(num, 1, 0, 0);
}

void cmd_test_biaspos(void)
{
    unsigned int num = 1;
    test_bias_bat(num, 0, 1, 0);
}

void cmd_timestamp_test()
{
    char timeStamp[TIMESTAMPLENGTH];
    struct tm *time_struct;
    time_t current_time;
    unsigned long long posixtime_ms;

    if(sys_data.data_cfg[CFG_TIMESTAMP] == TYPE_EPOCH_MSEC)
    {
        //1648165197 - 12365360
        // = 1,635,799,837
        // 1648165439000

        current_time = read_hib_rtcc();
        posixtime_ms = (long long)current_time;     // was read_hib_rtcc();   // cast to unsigned long long ?

        posixtime_ms -= TI_CCS_UNIX_EPOCH_OFFSET;

        posixtime_ms *= 1000LL;      // make it msec
        sprintf(timeStamp, "%13llu", posixtime_ms);
        uprintf("%s", timeStamp);
    }

    if(sys_data.data_cfg[CFG_TIMESTAMP] == TYPE_US_DATETIME)
    {
        time_struct = get_RTC_time();
        strftime(timeStamp, sizeof(timeStamp), "%m/%d/%Y %H:%M:%S", time_struct);        // change format of date/time per Yui  7 jun 2018
        uprintf("%s", timeStamp);

    }

}

void cmd_sampfast_help(void)
{
    uprintf("sampfast            - fast ISUS sampling every 1 seconds, with 10 light scans in between dark scans (default params)"); prn_nl();
    uprintf("sampfast,v          - same as above but 'verbose mode' also outputs all data to screen in addition to writing to file"); prn_nl();
    uprintf("sampfast,[v],2,20   - sample every 2 seconds, with 20 light scans in between dark scans"); prn_nl();
    prn_nl();
    uprintf("sampfast always writes to currently set profile file.  Get and set profile number using 'profile' commands."); prn_nl();
    uprintf("Lamp stays on in between dark scans.  Use 'scanspectra' to turn lamp off between all scans."); prn_nl();
}

void cmd_cfg_sampnum_get(void)
{
    uprintf("%u", sys_data.data_cfg[CFG_SAMPLE_NUM]);
}
#ifdef MERGE_ITEM_TO_SORT_OUT
void cmd_cfg_sampnum_set(unsigned int val)
{
    if((val == '1') || (val == 1))
        sys_data.data_cfg[CFG_SAMPLE_NUM] = 1;
    else
        sys_data.data_cfg[CFG_SAMPLE_NUM] = 0;

    eeprom_sys_data_store();
}

void cmd_sampnum_set(unsigned int num)
{
    sys_samp.current_sample = num;
    uprintf("#%07u", sys_samp.current_sample); prn_nl();

    eeprom_sys_data_store();
}

#endif

void cmd_sampnum_test(void)
{
    //sys_samp.current_sample
    uprintf("#%07u", sys_samp.current_sample); prn_nl();
}

void cmd_config_help(void)
{
    uprintf("cmd_config_help Imp Es Nada");   prn_nl();
}

void cmd_config_get(void)
{
    print_config();
}

void cmd_config_set(void)
{
#ifdef NOCODE
    0 -- Zero (Clear) all config fields
    1 -- Set all config fields
    2 -- Use Case: GLIDER,          (A,B,C,D,L,M,R,T,U)
    3 -- Use Case: mFET Default,    (A,B,C,D,E,F,G,H,I,J,K,R,T,U)
    4 -- Use Case: MpHOx Default,   (A,B,C,D,F,G,H,I,L,M,R,T,U,V,W)
    5 -- Use Case: MpHOx m2m,       (A,C,D,E,F,G,H,I,J,K,N,R,T,U)
    6 -- Use Case: nanoFET Default, (A,B,D,F,G,H,I,L,R,S,T,U)
    7 -- Use Case: MFET WireWalker, (B,G,L,R,T,U)
#endif

    if( (strncmp((const char*)&rx0.buf[7], "glider", 4) == 0)  )
    {
        config_data_fields_glider(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "wirewalker", 6) == 0)  )
    {
        config_data_fields_wirewalker(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "mfet", 4) == 0)  )
    {
        config_data_fields_mfet(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "mphox", 5) == 0)  )
    {
        config_data_fields_mphox(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "m2m mphox", 8) == 0)  )
    {
        config_data_fields_m2m_mphox(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "nanofet", 4) == 0)  )
    {
        config_data_fields_nanofet(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "all", 3) == 0) || (rx0.buf[7] == '1') )
    {
        config_data_fields_all(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "none", 4) == 0)  || (rx0.buf[7] == '0') )
    {
        config_data_fields_none(0);
    }
    else if ( (strncmp((const char*)&rx0.buf[7], "test", 4) == 0)  )
    {
        uprintf("not available");  prn_nl();
    }
    else
    {
        print_NAK();
    }
}

// JLD!!! TGM??? why do we set the enable when asked to get the value?
void cmd_store_get(void)
{
    // uprintf("microsd=%u", sys_data.app_cfg[APPCFG_MICROSD_ENABLE]);
    uprintf("%u", sys_data.app_cfg[APPCFG_MICROSD_ENABLE]);
    // JLD-TGM - I removed this line!!!
    // sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 1;
}

void cmd_store_set(void)
{
    // 012345678
    // store 1
    unsigned char prevMicroSDFlag = sys_data.app_cfg[APPCFG_MICROSD_ENABLE];

    if ( (rx0.buf[6] == '1') || (rx0.buf[8] == '1'))      // store 1 or microsd 1
        sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 1;
    else
        sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 0;

    eeprom_sys_data_store();

    if (prevMicroSDFlag != sys_data.app_cfg[APPCFG_MICROSD_ENABLE])
    {
        microSDInit();
    }
}

#if BOARD_MSC == 3

// APFTODO use 'mode' to guide which samples run
void cmd_test_sample(int loopTime)
{
    //uprintf("DBG: barf = %u, loopTIme = %u", databarFlg, loopTime); prn_nl();

    uprintf("%s:Enter databarfFlg is %s, loopTime=%d", __FUNCTION__, databarFlg? "On" : "Off", loopTime); prn_nl();

    // JLD-TGM toggling this flag disables the sample - why do we care about the barfSampleTime if it gets set to off?
    if(databarFlg == 1)
    {
        barfSampleTime = 5;
        databarFlg = 0;
        if (apf_fram.sys_sensor_mode == DURAISUS_SENSOR_MODE)
        {
            apf_fram.sys_sensor_mode = 1;    // ISUS
            fram_apf_vars_store();
        }
    }
    else
    {
        barfSampleTime = loopTime;
        databarFlg = 1;
    }

    uprintf("%s:EXIT databarfFlg is %s, loopTime=%d", __FUNCTION__, databarFlg? "On" : "Off", loopTime); prn_nl();
}


void apf_baud_help(void)
{
    uprintf("Valid baud rate options:   (Note: 230k, 460k are experimental, 921k does not wake on serial");  prn_nl();
    uprintf("baud,4800");    prn_nl();
    uprintf("baud,9600");    prn_nl();
    uprintf("baud,19200");   prn_nl();
    uprintf("baud,38400");   prn_nl();
    uprintf("baud,57600");   prn_nl();
    uprintf("baud,115200");  prn_nl();
    uprintf("baud,230400");  prn_nl();
    uprintf("baud,460800");  prn_nl();          //this baud rate exceeds capabilities of rs232 transceiver, but seems to work fine
    //uprintf("baud,921600");  prn_nl();        //MSC3 revD baseboard does not wake at this baud rate
}

// JLD- need to merge this into new command table
// uint32_t apf_sernum_msc_set(void)
void apf_sernum_msc_set(void)
{
    uint32_t serNum;
    char *endptr;
    char *str;

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

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

    if(serNum > 0 && serNum <= 9999)
    {
        apf_eeprom.sys_serial_num = serNum;
        uprintf("sernum,msc=%04u", apf_eeprom.sys_serial_num); prn_nl();
        eeprom_apf_cfg_store();
    }
    else
    {
        uprintf("serial number %u, out of range [0001 : 9999]", serNum); prn_nl();
    }
}

void apf_sernum_msc_get(void)
{
    uprintf("%04u", apf_eeprom.sys_serial_num); prn_nl();
}

void apf_sernum_msc_help(void)
{
    uprintf("sernum,msc          - print msc serial number [0001 : 9999]"); prn_nl();
    uprintf("sernum,msc,?        - print msc serial number [0001 : 9999]"); prn_nl();
    uprintf("sernum,msc,1234     - set msc serial number 1234 [0001 : 9999]"); prn_nl();
    uprintf("sernum,msc=1234     - set msc serial number 1234 [0001 : 9999]"); prn_nl();
}

#define NEW_SERNUM_CODING
#ifdef NEW_SERNUM_CODING

typedef struct tSerialCmdRec
{
    char        *name;                  // The command
    int         len;
    uDataPtr    v;
    int         flags;              // any help text that the operator might need
} tSerialNumberRec;

#define MAX_MSC_SN  4
#define STR_TYPE 1
#define INT_TYPE 2

tSerialNumberRec sNTable[] = {
    { "base",           MAX_BOARD_SN,           sys_data.board_SN,             STR_TYPE },
    { "ctd",            MAX_CTD_SN,             sys_data.ctd_SN,               STR_TYPE },
    { "isfet",          MAX_ISFET_SN,           sys_data.isfet_SN,             STR_TYPE },
    { "ise",            MAX_ISE_SN,             sys_data.ise_SN,               STR_TYPE },
    { "lamp",           MAX_LAMP_SN,            sys_data.lamp_SN,              STR_TYPE },
    { "mcap",           MAX_MCAP_SN,            sys_data.mcap_SN,              STR_TYPE },
    { "msc",            MAX_MSC_SN,             &apf_eeprom.sys_serial_num,    INT_TYPE },
    { "optode",         MAX_OPTODE_SN,          sys_data.optode_SN,            STR_TYPE },
    { "phsensor",       MAX_SENSOR_SN,          sys_data.sensor_SN,            STR_TYPE },
    { "probe",          MAX_PROBE_SN,           sys_data.probe_SN,             STR_TYPE },
    { "spec",           MAX_SPEC_SN,            sys_data.spec_SN,              STR_TYPE },
    { "spec_analog",    MAX_SPEC_ANALOG_SN,     sys_data.spec_analog_SN,       STR_TYPE },
    { "spec_digital",   MAX_SPEC_DIGITAL_SN,    sys_data.spec_digital_SN,      STR_TYPE },
    { "tiva",           MAX_TIVA_SN,            sys_data.tiva_SN,              STR_TYPE },
    { NULL, 0, NULL, 0 }
};


void showSernums(void)
{
    tSerialNumberRec *snr = sNTable;

    while (snr->len)
    {
        if (snr->flags & INT_TYPE)
            uprintf("sernum,%s=%u", snr->name, *snr->v.u32);
        else
            uprintf("sernum,%s=%s", snr->name, snr->v.str);
        prn_nl();
        snr++;
    }
    prn_nl();
}

void apf_sernum_help(void)
{
    tSerialNumberRec *snr = sNTable;

    apf_sernum_msc_help();
    while (snr->len)
    {
        uprintf("sernum,%s - print %s serial number %d characters", snr->name, snr->name, snr->len); prn_nl();
        snr++;
    }
    prn_nl();
}
#endif

#if THE_OLD_METHOD


void apf_sernum_help(void)
{

    uprintf("sernum              - print msc serial number [0001 : 9999]"); prn_nl();
    uprintf("sernum,?            - print msc serial number [0001 : 9999]"); prn_nl();
    uprintf("sernum,1234         - set msc serial number 1234 [0001 : 9999]"); prn_nl();
    uprintf("sernum=1234         - set msc serial number 1234 [0001 : 9999]"); prn_nl();
    prn_nl();
    uprintf("sernum,base         - print baseboard serial number, %d characters", MAX_BOARD_SN-1); prn_nl();  //baseboard
    uprintf("sernum,ctd          - print ctd serial number, %d characters", MAX_CTD_SN-1); prn_nl();
    uprintf("sernum,isfet        - print isfet serial number, %d characters", MAX_ISFET_SN-1); prn_nl();
    uprintf("sernum,ise          - print ise serial number, %d characters", MAX_ISE_SN-1); prn_nl();
    uprintf("sernum,lamp         - print lamp serial number, %d characters", MAX_LAMP_SN-1); prn_nl();
    uprintf("sernum,mcap         - print mcap serial number, %d characters", MAX_MCAP_SN-1); prn_nl();
    uprintf("sernum,msc          - print msc serial number, %d characters", 4); prn_nl();
    uprintf("sernum,optode       - print optode serial number, %d characters", MAX_OPTODE_SN-1); prn_nl();
    uprintf("sernum,phsensor     - print pH sensor serial number, %d characters", MAX_SENSOR_SN-1); prn_nl();
    uprintf("sernum,probe        - print probe serial number, %d characters", MAX_PROBE_SN-1); prn_nl();
    uprintf("sernum,spec         - print spec serial number, %d characters", MAX_SPEC_SN-1); prn_nl();
    uprintf("sernum,spec_analog  - print spec analog serial number, %d characters", MAX_SPEC_ANALOG_SN-1); prn_nl();
    uprintf("sernum,spec_digital - print spec digital serial number, %d characters", MAX_SPEC_DIGITAL_SN-1); prn_nl();
    uprintf("sernum,tiva         - print tiva board serial number, %d characters", MAX_TIVA_SN-1); prn_nl();


    prn_nl();
}

//board_SN[MAX_BOARD_SN]
void apf_sernum_baseboard_get(void)
{
    uprintf("sernum,base=%s", sys_data.board_SN); prn_nl();
}

void apf_sernum_baseboard_help(void)
{
    uprintf("sernum,base=[%u chars]", MAX_BOARD_SN-1); prn_nl();
}
void apf_sernum_baseboard_set(void)
{
    //0123456789012345678
    //sernum,base,
    rx0.buf[12+MAX_BOARD_SN-1] = 0x00;
    sprintf(sys_data.board_SN, "%s", &rx0.buf[12]);

    //uprintf("%s %u", sys_data.board_SN, (uint32_t)strlen(sys_data.board_SN)); prn_nl();
    uprintf("sernum,base=%s", sys_data.board_SN); prn_nl();  //Should this be board_SN= ???
    eeprom_sys_data_store();
}

//ctd_SN[MAX_CTD_SN]
void apf_sernum_ctd_get(void)
{
    uprintf("sernum,ctd=%s", sys_data.ctd_SN); prn_nl();
}

void apf_sernum_ctd_help(void)
{
    uprintf("sernum,ctd=[%u chars]", MAX_CTD_SN-1); prn_nl();
}
void apf_sernum_ctd_set(void)
{
    //0123456789012345678
    //sernum,ctd,
    rx0.buf[11+MAX_CTD_SN-1] = 0x00;
    sprintf(sys_data.ctd_SN, "%s", &rx0.buf[11]);

    uprintf("sernum,ctd=%s", sys_data.ctd_SN); prn_nl();
    eeprom_sys_data_store();
}

//isfet_SN[MAX_ISFET_SN]
void apf_sernum_isfet_get(void)
{
    uprintf("sernum,isfet=%s", sys_data.isfet_SN); prn_nl();
}

void apf_sernum_isfet_help(void)
{
    uprintf("sernum,isfet=[%u chars]", MAX_ISFET_SN-1); prn_nl();
}
void apf_sernum_isfet_set(void)
{
    //0123456789012345678
    //sernum,isfet,
    rx0.buf[13+MAX_ISFET_SN-1] = 0x00;
    sprintf(sys_data.isfet_SN, "%s", &rx0.buf[13]);

    uprintf("sernum,ctd=%s", sys_data.isfet_SN); prn_nl();
    eeprom_sys_data_store();
}


//ise_SN[MAX_ISE_SN]
void apf_sernum_ise_get(void)
{
    uprintf("sernum,ise=%s", sys_data.ise_SN); prn_nl();
}

void apf_sernum_ise_help(void)
{
    uprintf("sernum,ise=[%u chars]", MAX_ISE_SN-1); prn_nl();
}
void apf_sernum_ise_set(void)
{
    //0123456789012345678
    //sernum,ise,
    rx0.buf[11+MAX_ISE_SN-1] = 0x00;
    sprintf(sys_data.ise_SN, "%s", &rx0.buf[11]);

    uprintf("sernum,ise=%s", sys_data.ise_SN); prn_nl();
    eeprom_sys_data_store();
}

//lamp_SN[MAX_LAMP_SN]
void apf_sernum_lamp_get(void)
{
    uprintf("sernum,lamp=%s", sys_data.lamp_SN); prn_nl();
}

void apf_sernum_lamp_help(void)
{
    uprintf("sernum,lamp=[%u chars]", MAX_LAMP_SN-1); prn_nl();
}
void apf_sernum_lamp_set(void)
{
    //0123456789012345678
    //sernum,lamp,
    rx0.buf[12+MAX_LAMP_SN-1] = 0x00;
    sprintf(sys_data.lamp_SN, "%s", &rx0.buf[12]);

    uprintf("sernum,lamp=%s", sys_data.lamp_SN); prn_nl();
    eeprom_sys_data_store();
}


//mcap_SN[MAX_MCAP_SN]
void apf_sernum_mcap_get(void)
{
    uprintf("sernum,mcap=%s", sys_data.mcap_SN); prn_nl();
}

void apf_sernum_mcap_help(void)
{
    uprintf("sernum,mcap=[%u chars]", MAX_MCAP_SN-1); prn_nl();
}
void apf_sernum_mcap_set(void)
{
    //0123456789012345678
    //sernum,mcap,
    rx0.buf[12+MAX_MCAP_SN-1] = 0x00;
    sprintf(sys_data.mcap_SN, "%s", &rx0.buf[12]);

    uprintf("sernum,mcap=%s", sys_data.mcap_SN); prn_nl();
    eeprom_sys_data_store();
}

//optode_SN[MAX_OPTODE_SN]
void apf_sernum_optode_get(void)
{
    uprintf("sernum,optode=%s", sys_data.optode_SN); prn_nl();
}

void apf_sernum_optode_help(void)
{
    uprintf("sernum,optode=[%u chars]", MAX_OPTODE_SN-1); prn_nl();
}
void apf_sernum_optode_set(void)
{
    //0123456789012345678
    //sernum,optode,
    rx0.buf[14+MAX_OPTODE_SN-1] = 0x00;
    sprintf(sys_data.optode_SN, "%s", &rx0.buf[14]);

    uprintf("sernum,optode=%s", sys_data.optode_SN); prn_nl();
    eeprom_sys_data_store();
}

//probe_SN[MAX_PROBE_SN]
void apf_sernum_probe_get(void)
{
    uprintf("sernum,probe=%s", sys_data.probe_SN); prn_nl();
}

void apf_sernum_probe_help(void)
{
    uprintf("sernum,probe=[%u chars]", MAX_PROBE_SN-1); prn_nl();
}
void apf_sernum_probe_set(void)
{
    //0123456789012345678
    //sernum,probe,
    rx0.buf[13+MAX_PROBE_SN-1] = 0x00;
    sprintf(sys_data.probe_SN, "%s", &rx0.buf[13]);

    uprintf("sernum,probe=%s", sys_data.probe_SN); prn_nl();
    eeprom_sys_data_store();
}



//sensor_SN[MAX_SENSOR_SN]
void apf_sernum_sensor_get(void)
{
    uprintf("sernum,phsensor=%s", sys_data.sensor_SN); prn_nl();
}

void apf_sernum_sensor_help(void)
{
    uprintf("sernum,phsensor=[%u chars]", MAX_SENSOR_SN-1); prn_nl();
}
void apf_sernum_sensor_set(void)
{
    //0123456789012345678
    //sernum,phsensor,
    rx0.buf[16+MAX_SENSOR_SN-1] = 0x00;
    sprintf(sys_data.sensor_SN, "%s", &rx0.buf[16]);

    uprintf("sernum,phsensor=%s", sys_data.sensor_SN); prn_nl();
    eeprom_sys_data_store();
}


//spec_SN[MAX_SPEC_SN]
void apf_sernum_spec_get(void)
{
    uprintf("sernum,spec=%s", sys_data.spec_SN); prn_nl();
}

void apf_sernum_spec_help(void)
{
    uprintf("sernum,spec=[%u chars]", MAX_SPEC_SN-1); prn_nl();
}
void apf_sernum_spec_set(void)
{
    //0123456789012345678
    //sernum,spec,
    rx0.buf[12+MAX_SPEC_SN-1] = 0x00;
    sprintf(sys_data.spec_SN, "%s", &rx0.buf[12]);

    uprintf("sernum,spec=%s", sys_data.spec_SN); prn_nl();
    eeprom_sys_data_store();
}



//spec_analog_SN[MAX_SPEC_ANALOG_SN]
void apf_sernum_spec_analog_get(void)
{
    uprintf("sernum,spec_analog=%s", sys_data.spec_analog_SN); prn_nl();
}

void apf_sernum_spec_analog_help(void)
{
    uprintf("sernum,spec_analog=[%u chars]", MAX_SPEC_ANALOG_SN-1); prn_nl();
}
void apf_sernum_spec_analog_set(void)
{
    //0123456789012345678
    //sernum,spec_analog,
    rx0.buf[19+MAX_SPEC_ANALOG_SN-1] = 0x00;
    sprintf(sys_data.spec_analog_SN, "%s", &rx0.buf[19]);

    uprintf("sernum,spec_analog=%s", sys_data.spec_analog_SN); prn_nl();
    eeprom_sys_data_store();
}


//spec_digital_SN[MAX_SPEC_DIGITAL_SN]
void apf_sernum_spec_digital_get(void)
{
    uprintf("sernum,spec_digital=%s", sys_data.spec_digital_SN); prn_nl();
}

void apf_sernum_spec_digital_help(void)
{
    uprintf("sernum,spec_digital=[%u chars]", MAX_SPEC_DIGITAL_SN-1); prn_nl();
}
void apf_sernum_spec_digital_set(void)
{
    //012345678901234567890
    //sernum,spec_digital,
    rx0.buf[20+MAX_SPEC_DIGITAL_SN-1] = 0x00;
    sprintf(sys_data.spec_digital_SN, "%s", &rx0.buf[20]);

    uprintf("sernum,spec_digital=%s", sys_data.spec_digital_SN); prn_nl();
    eeprom_sys_data_store();
}



//tiva_SN[MAX_TIVA_SN]
void apf_sernum_tiva_get(void)
{
    uprintf("sernum,tiva=%s", sys_data.tiva_SN); prn_nl();
}

void apf_sernum_tiva_help(void)
{
    uprintf("sernum,tiva=[%u chars]", MAX_TIVA_SN-1); prn_nl();
}
void apf_sernum_tiva_set(void)
{
    //0123456789012345678
    //sernum,tiva,
    rx0.buf[12+MAX_TIVA_SN-1] = 0x00;
    sprintf(sys_data.tiva_SN, "%s", &rx0.buf[12]);

    uprintf("sernum,tiva=%s", sys_data.tiva_SN); prn_nl();
    eeprom_sys_data_store();
}
#endif //  THE_OLD_METHOD

#ifdef NEW_SERNUM_CODING

void apf_sernum_handler(void)
{
    char *sntokP; // = strtok((char *)&rx0.buf[7], ",");
    tSerialNumberRec *snr = sNTable;

    sntokP = strtok((char *)&rx0.buf[7], "=");
    if (sntokP)
    {
        while (snr->len)
        {
            if (strstr(sntokP, snr->name))
            {
                char *equalP = strtok(0, "=");
                if (equalP)
                {
                    if (strlen(equalP) > snr->len)
                    {
                        uprintf("sernum %s max length %d exceeded! [%s]", snr->name, snr->len, equalP); prn_nl();
                    }
                    else
                    {
                        // JLD- this code looks very flexible but was stuffed here to accomodate the only
                        // serialnumber in the table that didnt follow the string pattern that all the others did
                        if (snr->flags & INT_TYPE)
                        {
                            errno = 0; // Reset errno before calling strtoul
                            char *endptr;
                            int serNum = strtoul(equalP, &endptr, 10);
                            if(serNum > 0 && serNum <= 9999)
                            {
                                // apf_eeprom.sys_serial_num = serNum;
                                // uprintf("sernum,msc=%04u", apf_eeprom.sys_serial_num); prn_nl();
                                *snr->v.u32 = serNum;
                                eeprom_apf_cfg_store();

                                uprintf("sernum,%s=%04u", snr->name, *snr->v.u32);
                            }
                            else
                            {
                                uprintf("serial number %u, out of range [0001 : 9999]", serNum); prn_nl();
                            }
                        }
                        else
                        {
                            strcpy(snr->v.str, equalP);
                            eeprom_sys_data_store();
                            uprintf("sernum,%s=%s", snr->name, snr->v.str); prn_nl();
                        }
                        return;
                    }
                }
                else
                {
                    if (snr->flags & INT_TYPE)
                        uprintf("sernum,%s=%04u", snr->name, *snr->v.u32);
                    else
                        uprintf("sernum,%s=%s", snr->name, snr->v.str);
                    prn_nl();
                    return;
                }
            }
            snr++;
        }
        uprintf("sernum %s is undefined!", sntokP); prn_nl();
    }
}
#else
void apf_sernum_handler(void)
{
    if(rx0.buf[6] == 0)
    {
        apf_sernum_msc_get();
        return;
    }

//    //todo, make this sernum,msc or sernum,sys
//    if(rx0.buf[6] == ',')
//    {
//        if(rx0.buf[7] == '?')
//    {
//        apf_sernum_msc_get();
//        return;
//    }

    if(strncmp((char*)&rx0.buf[7], "base", 4) == 0)      //baseboard
    {
        //0123456789012345678
        //sernum,base
        if(rx0.buf[11] == 0)
        {
            apf_sernum_baseboard_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == '?'))
        {
            apf_sernum_baseboard_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == 'h'))
        {
            apf_sernum_baseboard_help();
            return;
        }
        else
        {
            apf_sernum_baseboard_set();
        }

    }
    else if(strncmp((char*)&rx0.buf[7], "ctd", 3) == 0)
    {
        //sernum,ctd
        //0123456789012345678

        //sys_data.ctd_SN[MAX_CTD_SN];
        //uprintf("ctd"); prn_nl();
        if(rx0.buf[10] == 0)
        {
            apf_sernum_ctd_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == '?'))
        {
            apf_sernum_ctd_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == 'h'))
        {
            apf_sernum_ctd_help();
            return;
        }
        else
        {
            apf_sernum_ctd_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "isfet", 5) == 0)
    {
        //0123456789012345678
        //sernum,isfet

        //sys_data.isfet_SN[MAX_ISFET_SN];
        //uprintf("isfet"); prn_nl();
        if(rx0.buf[12] == 0)
        {
            apf_sernum_isfet_get();
            return;
        }
        else if((rx0.buf[12] == ',') &&  (rx0.buf[13] == '?'))
        {
            apf_sernum_isfet_get();
            return;
        }
        else if((rx0.buf[12] == ',') &&  (rx0.buf[13] == 'h'))
        {
            apf_sernum_isfet_help();
            return;
        }
        else
        {
            apf_sernum_isfet_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "ise", 3) == 0)
    {
        //0123456789012345678
        //sernum,ise

        //sys_data.ise_SN[MAX_ISE_SN];
        //uprintf("ise"); prn_nl();
        if(rx0.buf[10] == 0)
        {
            apf_sernum_ise_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == '?'))
        {
            apf_sernum_ise_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == 'h'))
        {
            apf_sernum_ise_help();
            return;
        }
        else
        {
            apf_sernum_ise_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "lamp", 4) == 0)
    {
        //0123456789012345678
        //sernum,lamp

        //sys_data.lamp_SN[MAX_LAMP_SN];
        //uprintf("lamp"); prn_nl();
        if(rx0.buf[11] == 0)
        {
            apf_sernum_lamp_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == '?'))
        {
            apf_sernum_lamp_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == 'h'))
        {
            apf_sernum_lamp_help();
            return;
        }
        else
        {
            apf_sernum_lamp_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "mcap", 4) == 0)
    {
        //0123456789012345678
        //sernum,mcap

        //sys_data.mcap_SN[MAX_MCAP_SN];
        //uprintf("mcap"); prn_nl();
        if(rx0.buf[11] == 0)
        {
            apf_sernum_mcap_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == '?'))
        {
            apf_sernum_mcap_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == 'h'))
        {
            apf_sernum_mcap_help();
            return;
        }
        else
        {
            apf_sernum_mcap_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "msc", 3) == 0)
    {
        //sernum,msc
        //0123456789012345678

        //sys_data.ctd_SN[MAX_MSC_SN];
        //uprintf("ctd"); prn_nl();
        if(rx0.buf[10] == 0)
        {
            apf_sernum_msc_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == '?'))
        {
            apf_sernum_msc_get();
            return;
        }
        else if((rx0.buf[10] == ',') &&  (rx0.buf[11] == 'h'))
        {
            apf_sernum_msc_help();
            return;
        }
        else
        {
            apf_sernum_msc_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "optode", 6) == 0)
    {
        //0123456789012345678
        //sernum,optode

        //sys_data.optode_SN[MAX_OPTODE_SN];
        //uprintf("optode"); prn_nl();
        if(rx0.buf[13] == 0)
        {
            apf_sernum_optode_get();
            return;
        }
        else if((rx0.buf[13] == ',') &&  (rx0.buf[14] == '?'))
        {
            apf_sernum_optode_get();
            return;
        }
        else if((rx0.buf[13] == ',') &&  (rx0.buf[14] == 'h'))
        {
            apf_sernum_optode_help();
            return;
        }
        else
        {
            apf_sernum_optode_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "probe", 5) == 0)
    {
        //0123456789012345678
        //sernum,probe

        //sys_data.probe_SN[MAX_PROBE_SN];
        //uprintf("probe"); prn_nl();
        if(rx0.buf[12] == 0)
        {
            apf_sernum_probe_get();
            return;
        }
        else if((rx0.buf[12] == ',') &&  (rx0.buf[13] == '?'))
        {
            apf_sernum_probe_get();
            return;
        }
        else if((rx0.buf[12] == ',') &&  (rx0.buf[13] == 'h'))
        {
            apf_sernum_probe_help();
            return;
        }
        else
        {
            apf_sernum_probe_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "phsensor", 8) == 0)
    {
        //0123456789012345678
        //sernum,phsensor

        //sys_data.sensor_SN[MAX_SENSOR_SN];
        //uprintf("sensor"); prn_nl();
        if(rx0.buf[15] == 0)
        {
            apf_sernum_sensor_get();
            return;
        }
        else if((rx0.buf[15] == ',') &&  (rx0.buf[16] == '?'))
        {
            apf_sernum_sensor_get();
            return;
        }
        else if((rx0.buf[15] == ',') &&  (rx0.buf[16] == 'h'))
        {
            apf_sernum_sensor_help();
            return;
        }
        else
        {
            apf_sernum_sensor_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "spec_analog", 8) == 0)
    {
        //0123456789012345678
        //sernum,spec_analog

        //sys_data.spec_analog_SN[MAX_SPEC_ANALOG_SN];
        //uprintf("spec_analog"); prn_nl();
        if(rx0.buf[18] == 0)
        {
            apf_sernum_spec_analog_get();
            return;
        }
        else if((rx0.buf[18] == ',') &&  (rx0.buf[19] == '?'))
        {
            apf_sernum_spec_analog_get();
            return;
        }
        else if((rx0.buf[18] == ',') &&  (rx0.buf[19] == 'h'))
        {
            apf_sernum_spec_analog_help();
            return;
        }
        else
        {
            apf_sernum_spec_analog_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "spec_digital", 8) == 0)
    {
        //0123456789012345678
        //sernum,spec_digital

        //sys_data.spec_digital_SN[MAX_SPEC_DIGITAL_SN];
        //uprintf("spec_digital"); prn_nl();
        if(rx0.buf[19] == 0)
        {
            apf_sernum_spec_digital_get();
            return;
        }
        else if((rx0.buf[19] == ',') &&  (rx0.buf[20] == '?'))
        {
            apf_sernum_spec_digital_get();
            return;
        }
        else if((rx0.buf[19] == ',') &&  (rx0.buf[20] == 'h'))
        {
            apf_sernum_spec_digital_help();
            return;
        }
        else
        {
            apf_sernum_spec_digital_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "spec", 4) == 0)
    {
        //0123456789012345678
        //sernum,spec

        //sys_data.spec_SN[MAX_SPEC_SN];
        //uprintf("spec"); prn_nl();
        if(rx0.buf[11] == 0)
        {
            apf_sernum_spec_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == '?'))
        {
            apf_sernum_spec_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == 'h'))
        {
            apf_sernum_spec_help();
            return;
        }
        else
        {
            apf_sernum_spec_set();
        }
    }
    else if(strncmp((char*)&rx0.buf[7], "tiva", 4) == 0)
    {
        //0123456789012345678
        //sernum,tiva
        //sernum,tiva,?
        //sernum,tiva,

        //sys_data.tiva_SN[MAX_TIVA_SN];
        //uprintf("TIVA"); prn_nl();
        if(rx0.buf[11] == 0)
        {
            apf_sernum_tiva_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == '?'))
        {
            apf_sernum_tiva_get();
            return;
        }
        else if((rx0.buf[11] == ',') &&  (rx0.buf[12] == 'h'))
        {
            apf_sernum_tiva_help();
            return;
        }
        else
        {
            apf_sernum_tiva_set();
        }
    }

#ifdef NOCODE
    str = (char *)&rx0.buf[0];
    // Tokenize the string using commas as delimiters
    strtok(str, ",");  // first token CTD command
    strptr2 = strtok(NULL, ",");  // 2nd  token - first argument is type of serial number
    strptr3 = strtok(NULL, ",");  // 3rd token
    strptr4 = strtok(NULL, ",");  // 4th token
    strptr5 = strtok(NULL, ",");  // 5th token

    if ((strptr2 == 0) || (strptr3 == 0) || (strptr4 == 0) || (strptr5 == 0))
    {
        //sernum   return msc serial number
        apf_sernum_msc_get();
        return;
    }
#endif


}
#endif


void apf_status_get(void)
{

    uprintf("Watchdog    Counter,  apf_fram.sys_wdog_ctr   = %u", apf_fram.sys_wdog_ctr);  prn_nl();     //Was Power Cycle sys_por_ctr
    uprintf("System Reset Counter, apf_fram.sys_reset_ctr  = %u", apf_fram.sys_reset_ctr);   prn_nl();
    uprintf("System Error Counter, apf_fram.sys_error_ctr  = %lu", apf_fram.sys_error_ctr);   prn_nl();
    uprintf("Error Log Counter, apf_fram.errorLog_errorCnt = %lu", apf_fram.errorLog_errorCnt);   prn_nl();
    uprintf("Last System Error, apf_fram.sys_last_error    = %d",  apf_fram.sys_last_error); prn_nl();  // JLD - need to show as signed values
    uprintf("LogFile Counter, apf_fram.profileCnt          = %u",  apf_fram.profileCnt); prn_nl();
    prn_nl();
    //uprintf("File sizes = ",  ); prn_nl();
    //uprintf("Depoy Mode = ???"); prn_nl();
    uprintf("Sensor Mode = %u", apf_fram.sys_sensor_mode); prn_nl();
    //uprintf("Data Mode   = %u", apf_fram.sys_data_mode); prn_nl();
    uprintf("Power Active Timeout Period = %u", apf_eeprom.sys_apf_tmout); prn_nl();
    uprintf("Command Time Terminate = %u msec (%u sec)", cmd_h2m_timeout_get(), cmd_h2m_timeout_get()/1000); prn_nl();
    prn_nl();
    wait_consoleTx();
    uprintf("gotSample = %u", apf.gotSample); prn_nl();
    uprintf("apf.acquire_time           = %.3f", apf.acquire_time); prn_nl();
    uprintf("apf_fram.isus_sample_ctr   = %u", apf_fram.isus_sample_ctr); prn_nl();
    uprintf("apf_fram.isus_sample_time  = %u", apf_fram.isus_sample_time); prn_nl();
    uprintf("apf_fram.isus_sample_time  = %s",  DisplayFormattedTime(apf_fram.isus_sample_time)); prn_nl();
    uprintf("apf_fram.dura_sample_ctr   = %u", apf_fram.dura_sample_ctr); prn_nl();
    uprintf("apf_fram.dura_sample_time  = %u", apf_fram.dura_sample_time); prn_nl();
    uprintf("apf_fram.dura_sample_time  = %s", DisplayFormattedTime(apf_fram.dura_sample_time)); prn_nl();
    prn_nl();
    uprintf("apf_fram.errorLog_errorCnt = %u", apf_fram.errorLog_errorCnt); prn_nl();
    uprintf("apf_fram.sys_error_ctr     = %u", apf_fram.sys_error_ctr); prn_nl();
    uprintf("apf_fram.sys_last_error    = %d", apf_fram.sys_last_error); prn_nl();       // JLD - need to show as signed values
    uprintf("apf_fram.sys_error_time    = %u", apf_fram.sys_error_time); prn_nl();
    uprintf("apf_fram.sys_error_time    = %s", DisplayFormattedTime(apf_fram.sys_error_time)); prn_nl();
    wait_consoleTx();
    uprintf("apf_fram.dura_error_ctr    = %u", apf_fram.dura_error_ctr); prn_nl();
    uprintf("apf_fram.dura_last_error   = %d", apf_fram.dura_last_error); prn_nl();     // JLD - need to show as signed values
    uprintf("apf_fram.dura_error_time   = %u", apf_fram.dura_error_time); prn_nl();
    uprintf("apf_fram.dura_error_time   = %s", DisplayFormattedTime(apf_fram.dura_error_time)); prn_nl();
    uprintf("apf_fram.dura_reset_flag   = %u", apf_fram.dura_reset_flag); prn_nl();
    prn_nl();
    wait_consoleTx();
    uprintf("apf_fram.sendlast_byteCnt  = %u", apf_fram.sendlast_byteCnt); prn_nl();
    uprintf("apf_fram.isus_error_ctr    = %u", apf_fram.isus_error_ctr); prn_nl();
    uprintf("apf_fram.isus_last_error   = %d", apf_fram.isus_last_error); prn_nl();     // JLD - need to show as signed values
    uprintf("apf_fram.isus_error_time   = %u", apf_fram.isus_error_time); prn_nl();
    uprintf("apf_fram.isus_error_time   = %s", DisplayFormattedTime(apf_fram.isus_error_time)); prn_nl();

    uprintf("apf_fram.isus_reset_flag   = %u", apf_fram.isus_reset_flag); prn_nl();
    uprintf("apf_fram.isus_lampOn_mSec  = %u", apf_fram.isus_lampOn_mSec); prn_nl();
    uprintf("apf_fram.isus_lamp_evt     = %u", apf_fram.isus_lampOn_ctr); prn_nl();
    prn_nl();
    wait_consoleTx();

    uprintf("apf_fram.logIsus_fsize     = %u", apf_fram.logIsus_fsize); prn_nl();
    uprintf("apf_fram.logDura_fsize     = %u", apf_fram.logIsus_fsize); prn_nl();

    uprintf("sizeof(sys_data) = %u, sizeof(sys_qa) = %u, sizeof(apf) = %u, sizeof(apf_fram) = %u, sizeof(apf_eeprom) = %u", sizeof(sys_data), sizeof(sys_qa), sizeof(apf), sizeof(apf_fram), sizeof(apf_eeprom) );

//Power Up Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

}

void apf_status_help(void)
{
    uprintf("MSC1 Logger Setup menu items are available as status"); prn_nl();
    uprintf("status        - print status"); prn_nl();
    uprintf("status,?      - print status"); prn_nl();
    uprintf("status,reset  - reset status variables"); prn_nl();
}

void apf_status_reset(void)
{
    uprintf("apf_status_reset() NEEDS DATA INTEGRITY CHECK, por favor"); prn_nl();

    apf_fram.sys_por_flag = FALSE;

    apf_fram.profileCnt = 0;
    apf_fram.sys_sensor_mode = ISUS_SENSOR_MODE;     //default to MODE 1
    apf_fram.sys_data_mode = SYS_DATAMODE_VAL;
    apf_eeprom.sys_apf_tmout = 10;         //APFTODO, make a #define, see apf_init.c

    apf_fram.sys_last_error = 0;

    apf_fram.sys_error_ctr = 0;
    apf_fram.sys_error_time = 0;
    apf_fram.sys_reset_ctr = 0;
    apf_fram.sys_reset_time = SYS_RESETTIME_VAL;
    apf_fram.sys_wdog_ctr = 0;                           //wdog counter
    apf_fram.sys_file_ctr = SYS_FILECTR_VAL;
    apf_eeprom.sys_sleep_period = SYS_SLEEPPER_VAL;
    apf_eeprom.sys_pump_period = SYS_PUMPPER_VAL;

    apf_fram.dura_error_time = DURA_LASTERRTIME_VAL;
    apf_fram.dura_sample_time = DURA_LASTSAMPTIME_VAL;
    apf_fram.dura_error_ctr = DURA_ERRORCTR_VAL;
    apf_fram.dura_last_error = DURA_LASTERROR_VAL;
    apf_fram.dura_reset_flag = TRUE;                     //shouldnt this be false?  APFTODO
    apf_fram.dura_sample_ctr = DURA_SAMPLECTR_VAL;

    if(fram_apf_vars_store() <= 0)
    {
        LOGERRORMSG("Failed to write to fram in apf_status_reset", (int)FRAM_FAIL, apf_eeprom.sys_message_enable);
    }
    if(eeprom_apf_cfg_store() != 0)
    {
        LOGERRORMSG("Failed to write to eeprom in apf_status_reset", (int)EEPROM_FAIL, apf_eeprom.sys_message_enable);
    }

}


void apf_vinmin_get(void)
{
    uprintf("vinmin,isus=%.1f", sys_data.isus_vin_min_volt); prn_nl();
    uprintf("vinmin,dura=%.1f", sys_data.dura_vin_min_volt); prn_nl();
}

void apf_vinmin_isus_get(void)
{
    uprintf("vinmin,isus=%.1f", sys_data.isus_vin_min_volt); prn_nl();
}

void apf_vinmin_dura_get(void)
{
    uprintf("vinmin,dura=%.1f", sys_data.dura_vin_min_volt); prn_nl();
}

void apf_vinmin_help(void)
{
    //       012345678901234
    uprintf("vinmin,dura,8.0      - Vin min threshold for DURA pH"); prn_nl();
    uprintf("vinmin,isus,11.2     - Vin min threshold for ISUS nitrate"); prn_nl();
    prn_nl();
}

void apf_vinmin_isus_set(void)
{
    char    *endptr;
    char    *str;
    float   fVinmin;

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

    errno = 0; // Reset errno
    fVinmin = strtof(str, &endptr);

    if( (fVinmin >= 6.0) && (fVinmin <= 22.0) )
    {
        sys_data.isus_vin_min_volt = fVinmin;
        eeprom_sys_data_store();
        uprintf("vinmin,isus,%.2f stored in eeprom", sys_data.isus_vin_min_volt); prn_nl();
    }
    else
    {
        uprintf("vinmin,isus,%.2f range error [6.0 : 22.0]", sys_data.isus_vin_min_volt); prn_nl();
    }
}

void apf_vinmin_dura_set(void)
{
    char    *endptr;
    char    *str;
    float   fVinmin;

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

    errno = 0; // Reset errno
    fVinmin = strtof(str, &endptr);

    if( (fVinmin >= 6.0) && (fVinmin <= 22.0) )
    {
        sys_data.dura_vin_min_volt = fVinmin;
        eeprom_sys_data_store();
        uprintf("vinmin,dura,%.2f stored in eeprom", sys_data.dura_vin_min_volt); prn_nl();
    }
    else
    {
        uprintf("vinmin,dura,%.2f range error [6.0 : 22.0]", sys_data.dura_vin_min_volt); prn_nl();
    }
}

#endif // #if BOARD_MSC == 3


void cmd_baud_get(void)
{
    uprintf("%u", sys_data.baud_rate);  prn_nl();
    wait_consoleTx();
}

void cmd_baud_set(void)
{
    uint32_t baudRate;
    int      result;
    uint32_t indx;
    char     *str;
    char     *endptr;

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

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

    if (errno == ERANGE)
    {
        uprintf("Error: Baud value out of range");  prn_nl();
        return; // (sys_data.baud_rate);     // return old baud rate
    }
    else if (errno == EINVAL)
    {
        uprintf("Error: No conversion possible");  prn_nl();
        return; // (sys_data.baud_rate);     // return old baud rate
    }

    int bIdx = 1;
    while (bIdx <= 8)
    {
        if (baudRate == BaudRateList[bIdx])
            break;
        bIdx++;
    }
    if (bIdx == 9)
    {
        uprintf("NAK,BAUD,%u", baudRate); prn_nl();
        return; // (sys_data.baud_rate);     // return old baud rate
    }

    uprintf("Change serial terminal baud rate (minicom, TeraTerm, etc), then type ACK"); prn_nl();
    uprintf("If ACK is not received in 10 sec at the new rate, MoSoS reverts to previous baud"); prn_nl();

    wait_consoleTx();
    UARTFlushRx();   //clear junk from baud change
    UARTStdioConfig(0, baudRate, 16000000);     //change to new baud rate
    delay_msec(10);
    UARTFlushRx();  // clear junk from baud change

#define BAUDRETRY      8                // JLD bumped from 5 - I found this difficult to complete set to 5.
    for(indx=0; indx<BAUDRETRY; indx++)
    {
        result = waitACK(4000);
        if(result)
        {
            sys_data.baud_rate = baudRate;
            uprintf("baudrate = %u", sys_data.baud_rate);  prn_nl();
            // Write the sys_data struct into EEPROM.
            if(eeprom_sys_data_store())
            {
                prn_nl(); uprintf("Error storing system data."); prn_nl();
            }
            else
            {
                prn_nl(); uprintf("System data stored..."); prn_nl();
            }

            return; // (sys_data.baud_rate);
        }
        else
        {
            //typo or timeout
        }

        uprintf("Try again %u, type ACK for baud = %u", (BAUDRETRY-indx), baudRate); prn_nl();
    }

    wait_consoleTx();
    UARTFlushRx();
    UARTStdioConfig(0, sys_data.baud_rate, 16000000);     //change to new baud rate
    delay_msec(10);
    UARTFlushRx();
    uprintf("Baud restored to %u", sys_data.baud_rate); prn_nl();
    wait_consoleTx();

    return; // (sys_data.baud_rate);
}

void cmd_baud_help(void)
{
    uprintf("Valid baud rate options:   (Note: 230k, 460k are experimental, 921k does not wake on serial");  prn_nl();
    int bIdx = 1;
    while (bIdx <= 8)
    {
        uprintf("baud,%u", BaudRateList[bIdx]);    prn_nl();
        bIdx++;
    }
}

void cmd_wisdom_get(void)
{
    uint32_t indx, indx2;

    srand((unsigned) read_hib_rtcc());

    // generate number 0 to 61
    indx = rand() % 62;  // todo, how to get the number of strings in wisdom_arr
    indx2 = strlen((char *)wisdom_arr[indx]);
    indx2 /= 15;

    uprintf("  %s", wisdom_arr[indx]); prn_nl();

    wait_consoleTx();

}

void cmd_yooster(void)
{
    uint32_t indx;

    //uprintf("yooster Imp Es Nada"); prn_nl();
    indx=0;
    for(indx=0; indx<100; indx++)
    {
        if(contrib_arr[indx] == 0)
        {
            prn_nl();
            break;
        }
        uprintf("  %s", contrib_arr[indx]); prn_nl();
        delay_msec(200);
    }
    //List contributors

}

#if BOARD_MSC == 3

void apf_error_log_add(unsigned char *errStr)
{
    char errBuf[256];

    sprintf(errBuf, "%s", errStr);
    LOGERRORMSG(errBuf, (int)ERROR_LOG_TEST, apf_eeprom.sys_message_enable);
}

void apf_error_log_dura_add(unsigned char *errStr)
{
    char errBuf[256];

    sprintf(errBuf, "%s", errStr);
    LOGERRORMSG(errBuf, (int)ERROR_LOG_DURA_TEST, apf_eeprom.sys_message_enable);
}

void apf_error_log_isus_add(unsigned char *errStr)
{
    char errBuf[256];

    sprintf(errBuf, "%s", errStr);
    LOGERRORMSG(errBuf, (int)ERROR_LOG_ISUS_TEST, apf_eeprom.sys_message_enable);
}

void apf_test_wdog(void)
{

    uprintf("test,wdog:  enable watchdog by removing jumper, code will loop for 2 seconds without wdog_pet");  prn_nl();
    while(1)
        ;

}

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

#endif // #if BOARD_MSC == 3

void cmd_k0_help(void)
{
    uprintf("k0_hcl=%f", sys_data.k0_HCL); prn_nl();
    uprintf("k0_sw=%f", sys_data.k0_SW); prn_nl();
    uprintf("k0std_sw=%f", sys_data.k0std_SW); prn_nl();
    int idx;
    for(idx=0; idx<MAX_NUMOF_K2_CAL_FIELDS; idx++)
    {
        uprintf("k2[%d]=%.5e", idx, sys_data.K2_Cal[idx]);
        prn_nl();
    }

    for(idx=0; idx<MAX_NUMOF_FP_CAL_FIELDS; idx++)
    {
        uprintf("fp[%d]=%.5e", idx, sys_data.fP_Cal[0]);
        prn_nl();
    }
}

#if BOARD_MSC == 3
//void cmd_cal_date_isus(void)
//{
//    //cal_date,isus=04/27/2024
//    //0123456789012345678901
//    unsigned char *dateStr = strchr((char *)rx0.buf,'=');
//    int size;
//    if (dateStr)
//        dateStr++;
//
//    size = strlen((char *)dateStr);
//    if(size < MAX_ISUS_CAL_DATE)
//    {
//        fram_apfArray_refresh();
//
//        sprintf(apfArray.CalDate, "%s", dateStr);
//        uprintf("k0_Cal_date=%s", apfArray.CalDate); prn_nl();
//        fram_apfArray_store();
//    }
//    else
//    {
//        uprintf("cal_date,isus string too long, max %u.  %s", MAX_ISUS_CAL_DATE-1, dateStr); prn_nl();
//    }
//
//}
#endif // if BOARD_MSC == 3
