/*
 * apf_util.c
 *
 *  Created on: May 10, 2024
 *      Author: tm
 */


#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <sys/types.h>

#include "system.h"
#include "main.h"
#include "microsd.h"
#include "fatfs/source/ff.h"
#include "sleep.h"
#include "sample.h"
#include "string.h"
#include "uartstdio.h"
#include "user_io.h"
#include "uart3.h"
#include "serial.h"
#include "sample.h"
#include "config.h"
#include "apf.h"
#include "test.h"

#include "i2c_bme280.h"
#include "i2c_ina260.h"
#include "i2c_fram.h"

#include "mms_spec.h"
#include "apf.h"
#include "apf_math.h"
#include "apf_numbr.h"
#include "apf_util.h"
#include "board_util.h"
#include "assert.h"
#include "serial_cmd_fxn.h"

extern uint32_t serDbgFlg;          // defd in serial.c

/* Application Build Info */
char const * const bapp  = "MSC3 MoSoS";
char const * const bfile = __FILE__;
char const * const bdate = __DATE__;
char const * const btime = __TIME__;

struct tm DS3234_tm;    // DS3234 time of day as struct tm

struct apfBootInitGlobals apf;
struct apfVolatileGlobals apf_fram;
struct apfCfgGlobals      apf_eeprom;
static_assert((sizeof(apf_eeprom) % 4) == 0, "apf_eeprom size must be evenly divisible by 4");
struct apfIsusArrays      apfArray;

struct apfVolatileGlobals const* get_const_apf_fram(void)
{
    return &apf_fram;
}
struct apfCfgGlobals const* get_const_apf_eeprom(void)
{
    return &apf_eeprom;
}
struct apfIsusArrays const* get_const_apfArray(void)
{
    return &apfArray;
}

struct apfVolatileGlobals* get_apf_fram(void)
{
    return &apf_fram;
}
struct apfCfgGlobals* get_apf_eeprom(void)
{
    return &apf_eeprom;
}
struct apfIsusArrays* get_apfArray(void)
{
    return &apfArray;
}


struct apfRtcTime rtc;

char    logfname[64];
FIL     *dat_file;
FIL     *err_file;


long   size, avail;
ldiv_t hdiv, mdiv;

float  DataMean, DataStdDev;
float  sysTemperature, sysHumidity;
float  sysVoltage, sysCurrent;

extern int dbg_flag;                 // global var used for dbg_printf

extern int mmsDbgFlg; //defd in mms_spec.c


extern double EcoefFile[260][3];

uint32_t check_storage_remaining(void)
{
    uint32_t totSize, freeSpace;

    get_storage_stat(&totSize, &freeSpace);  // size in KBytes

    return(freeSpace);
}



//*****************************************************************************
// File Routines
//*****************************************************************************

int LogErrorSizeHander(void)
{
    CheckFileSize(1);       //1 is for error.log file size check

    return(1);
}

int LogErrorMsg(char const * const emsg, int ecode, uint8_t outp)
{
    const time_t rtc_now = read_hib_rtcc();

    apf_fram.errorLog_errorCnt++;  //used for CalcRecsPending

    if(mmsDbgFlg == 1) { uprintf("DBG: LogErrorMsg imp in progress, in LogErrorMsg apf_util.c");  prn_nl();    }  //APFTODO

    struct tm* rtc_tm = localtime(&rtc_now);

    if( (ecode <= -100) && (ecode >= -199) )
    {
        apf_fram.sys_error_time = rtc_now;

        apf_fram.sys_error_ctr++;

        apf_fram.sys_last_error = ecode;

        fram_apf_vars_store();
    }
    else if( (ecode <= -200) && (ecode >= -299) )
    {
        apf_fram.isus_error_time = rtc_now;

        apf_fram.isus_error_ctr++;

        apf_fram.isus_last_error=ecode;

        fram_apf_vars_store();
    }
    else if( (ecode <= -300) && (ecode >= -399) )
    {
        apf_fram.dura_error_time = rtc_now;

        apf_fram.dura_error_ctr++;

        apf_fram.dura_last_error=ecode;

        fram_apf_vars_store();
    }
    else
    {
        rtc.current_time = rtc_now;
    }

    if( outp == TRUE )
    {
        uprintf("%02d/%02d/%02d %02d:%02d:%02d, %d, %s",
            rtc_tm->tm_mon+1,
            rtc_tm->tm_mday,
            rtc_tm->tm_year+1900,
            rtc_tm->tm_hour,
            rtc_tm->tm_min,
            rtc_tm->tm_sec,
            ecode,
            emsg
        );
        prn_nl();
    }

    if( ! microSD_flg )
    {
        return OK;
    }

    int ret_stat = OK;
    FIL fileObject = { };
    FRESULT fresult = f_open(&fileObject, "ERROR.LOG", FA_OPEN_APPEND | FA_WRITE | FA_READ);  // THOM DEBUG APFTODO
    if(fresult != FR_OK)
    {
        apf_fram.sys_error_time = read_hib_rtcc();   // = RTCGetTime( NULL, NULL );

        ++apf_fram.sys_error_ctr;

        apf_fram.sys_last_error = (int)FILE_OPEN_FAILURE;

        fram_apf_vars_store();

        if(serDbgFlg == 1) { uprintf("DBG LogErrorMsg: error.log f_open failed"); prn_nl(); }

        return ERROR;
    }

    //uprintf("DBG LogErrorMsg: error.log is open"); prn_nl();
    int print_ret = f_printf(&fileObject, "E, %02d/%02d/%02d %02d:%02d:%02d, %d, %s\r\n",
        rtc_tm->tm_mon+1,
        rtc_tm->tm_mday,
        rtc_tm->tm_year+1900,
        rtc_tm->tm_hour,
        rtc_tm->tm_min,
        rtc_tm->tm_sec,
        ecode,
        emsg
    );

    if(print_ret < 0)
    {
        apf_fram.sys_error_time = read_hib_rtcc();   // = RTCGetTime( NULL, NULL );

        ++apf_fram.sys_error_ctr;

        apf_fram.sys_last_error = (int)FILE_OPEN_FAILURE;

        fram_apf_vars_store();

        if(serDbgFlg == 1) { uprintf("DBG LogErrorMsg: error.log f_printf failed"); prn_nl(); }

        ret_stat = ERROR;
    }

    fresult = f_close(&fileObject);
    if(fresult != FR_OK)
    {
        apf_fram.sys_error_time = read_hib_rtcc();   // = RTCGetTime( NULL, NULL );

        ++apf_fram.sys_error_ctr;

        apf_fram.sys_last_error = (int)FILE_OPEN_FAILURE;

        fram_apf_vars_store();

        if(serDbgFlg == 1) { uprintf("DBG LogErrorMsg: error.log f_close failed"); prn_nl(); }

        ret_stat = ERROR;
    }

    return( ret_stat );
}


// called by apf_send_last_nitrate(v), send last "sl" after "t1"
void print_apf_nitrate_sampl(void)
{
    uprintf("print_apf_nitrate_sampl, apf_util.c, Imp es Nada"); prn_nl();


}

// called by apf_send_last_pH(), send last "sl" after "t1"
void print_apf_pH_sampl(void)
{
    uprintf("print_apf_pH_sampl, apf_util.c, Imp es Nada"); prn_nl();
}

void print_isus_header(void)
{
    fram_apfArray_refresh();    //needed for fit numbers
    print_write_isusdura_header(1, 0, ISUS_SENSOR_MODE, NULL);   //print
}



// for writes, pass in a pointer to an open file ready for writing
int print_write_isusdura_header(uint32_t printFlg, uint32_t writeFlg, uint32_t smode, FIL *fptr)      //FILE *fptr)
{
    int indx;
    uint32_t bndx;
    uint32_t bw;
    FRESULT fresult;

    {
        uint32_t temp_size;
        uint32_t temp_avail;
        FRESULT fr = get_storage_stat(&temp_size, &temp_avail);   //moved to apf_deployment_init()
        if(fr == FR_OK)
        {
            size  = temp_size;
            avail = temp_avail;
        }
        else
        {
            size  = 0;
            avail = 0;
        }

        apf.storage_size = size;
        apf.storage_free = avail;
    }

    memset(prnBuf, 0, SIZEOF_PRNBUF);

    bndx = 0;
    bndx += sprintf((char*)&prnBuf[bndx], "H, Datalogger Serial Num , SN:%04hu", apf_eeprom.sys_serial_num );
    bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, Application      Name , %s", VERSION ); bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, App Build        Date , %s, %s", bdate, btime);  bndx += sprn_nl((char*)prnBuf, bndx);

    struct tm *tp;
    tp = localtime((const time_t*)&apf_fram.sys_reset_time);        //apf_fram.sys_reset_time is set in apf_init_msc() in apf_init.c
    bndx += sprintf((char*)&prnBuf[bndx], "H, 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); bndx += sprn_nl((char*)prnBuf, bndx);

    rtc.current_time = read_hib_rtcc();
    rtc.alarm_time = 0;     // what is rtc.alarm_time used for???  APFTODO
    tp = get_RTC_time();
    rtc.current_time = read_hib_rtcc();
    bndx += sprintf((char*)&prnBuf[bndx], "H, Current  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); bndx += sprn_nl((char*)prnBuf, bndx);

    //tp = localtime(&rtc.alarm_time);
    //tp = get_RTC_time();
    uint32_t u32time = TI_CCS_UNIX_EPOCH_OFFSET;
    tp = localtime(&u32time);
    //rtc.alarm_time = read_hib_rtcc();
    //bndx += sprintf((char*)&prnBuf[bndx], "H, Alarm Time deprecated , "); bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, Serial Inactive Sleep , %u sec", SERIAL_INACTIVITY_TIMEOUT_M2M_SEC);  bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, MSC3 Info             , Tiva 1290 running MoSoS v5");  bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, Storage          Size , %ld", apf.storage_size); bndx += sprn_nl((char*)prnBuf, bndx);

    if(apf.storage_size != 0)
    {
        bndx += sprintf((char*)&prnBuf[bndx], "H, Storage          Left , %.2f%%", ((float)apf.storage_free/(float)apf.storage_size)*100.0f); bndx += sprn_nl((char*)prnBuf, bndx);
    }
    else
    {
        bndx += sprintf((char*)&prnBuf[bndx], "H, Storage          Left , %.2f%%", 0.0f); bndx += sprn_nl((char*)prnBuf, bndx);
    }

    bndx += sprintf((char*)&prnBuf[bndx], "H, WatchDog      Counter , %lu", apf_fram.sys_wdog_ctr); bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, System Reset  Counter , %lu", apf_fram.sys_reset_ctr); bndx += sprn_nl((char*)prnBuf, bndx);

    bndx += sprintf((char*)&prnBuf[bndx], "H, System Error  Counter , %lu", apf_fram.sys_error_ctr); bndx += sprn_nl((char*)prnBuf, bndx);
    bndx += sprintf((char*)&prnBuf[bndx], "H, Last System     Error , %d",  apf_fram.sys_last_error); bndx += sprn_nl((char*)prnBuf, bndx);
    tp = get_RTC_time();
    apf_fram.sys_error_time = read_hib_rtcc();
    bndx += sprintf((char*)&prnBuf[bndx], "H, Last System ErrorTime , %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); bndx += sprn_nl((char*)prnBuf, bndx);

    if(printFlg == 1)
    {
        UARTwrite((char *)prnBuf, bndx);
        wait_consoleTx();       // wait for UARTwrite to complete, trying to fix garbage chars
    }

    //uprintf("DBG: (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = %u) && (writeFlg = %u) ", sys_data.app_cfg[APPCFG_MICROSD_ENABLE], writeFlg); prn_nl();
    if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )
    {
        //APFTODO, even tho this routine is called when a new prof file is made, proly should call f_lseek anyway just in case
        fresult = f_write( fptr, prnBuf, bndx, (UINT*)&bw);

        //APFTODO, check bndx vs bw (bytes written)
        if(fresult != FR_OK)
        {
            if(serDbgFlg == 1) { uprintf("f_write error: %s, bytes attempted = %u, bytes written = %u", StringFromFresult(fresult), bndx, bw);  prn_nl(); }
        }
        else
        {
            //uprintf("\r\nSampling... f_write is good\r\n"); // THOM DEBUG
        }

    }
    else
    {
        if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0) && (writeFlg == 1) )
        {
            if(serDbgFlg == 1) { uprintf("ERR: tried to write to disabled uSD, sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 0"); prn_nl(); }
        }
    }

    memset(prnBuf, 0, SIZEOF_PRNBUF);

    bndx = 0;
    if(smode == ISUS_SENSOR_MODE)
    {
        fram_apfArray_refresh();

        bndx += sprintf((char*)&prnBuf[bndx], "H, ISUS  Error   Counter , %lu", apf_fram.isus_error_ctr); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last ISUS       Error , %d", apf_fram.isus_last_error); bndx += sprn_nl((char*)prnBuf, bndx);

        tp = localtime(&apf_fram.isus_error_time);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last ISUS Error  Time , %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); bndx += sprn_nl((char*)prnBuf, bndx);

        bndx += sprintf((char*)&prnBuf[bndx], "H, ISUS Sample   Counter , %lu", apf_fram.isus_sample_ctr); bndx += sprn_nl((char*)prnBuf, bndx);

        tp = localtime(&apf_fram.isus_sample_time);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last Sample Rec. Time , %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); bndx += sprn_nl((char*)prnBuf, bndx);

        uint32_t lampOnSeconds = apf_fram.isus_lampOn_mSec / 1000L;
        hdiv = ldiv(lampOnSeconds, 3600L);
        mdiv = ldiv(hdiv.rem, 60L);

        bndx += sprintf((char*)&prnBuf[bndx], "H, Fiberlite    Odometer , %02ld:%02ld:%02ld", hdiv.quot, mdiv.quot, mdiv.rem); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Fiberlite      Cycles , %hu", apf_fram.isus_lampOn_ctr); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Spec Intensity LampON , %hu", apf_eeprom.isus_ref_limit); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Spec Integration Time , %lu", apf_eeprom.isus_spec_period);  bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Spec Pre  scans taken , %hu", apf_eeprom.isus_pre_scans); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Spec Lite scans taken , %hu", apf_eeprom.isus_scan_num); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Spec Dark scans taken , %hu", apf_eeprom.isus_dark_num); bndx += sprn_nl((char*)prnBuf, bndx);

        if(apfArray.ZcoefsCnt <= 0)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "H, Zceof Not Loaded      , ZCoefs = %d", apfArray.ZcoefsCnt); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        if(apfArray.EcoefsCnt != 256)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "H, Eceof Not Loaded      , ECoefs = %d", apfArray.EcoefsCnt); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        if(apfArray.EcoefsCnt == 256)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "H, Calibration      Date , %s", apfArray.CalDate); bndx += sprn_nl((char*)prnBuf, bndx);
        }

        bndx += sprintf((char*)&prnBuf[bndx], "H, Sw Calibration   Temp , %.2f", apfArray.SwCalTemp);  bndx += sprn_nl((char*)prnBuf, bndx); //745
        // Next causes pHFET-SOS Crash  //APFTODO
        bndx += sprintf((char*)&prnBuf[bndx], "H, Wavelength Fit  Range , %.3f <-> %.3f", apfArray.LambdaData[apf_eeprom.isus_pixel_beg], apfArray.LambdaData[apf_eeprom.isus_pixel_end]); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Pixel Fit       Range , %d <-> %d", apf_eeprom.isus_pixel_beg, apf_eeprom.isus_pixel_end); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Fitted Concentrations , %d", apf_eeprom.isus_fit_concs); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Baseline        Model , ");
        if(apf_eeprom.isus_bsl_model == 1)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "(ax + b)"); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        if(apf_eeprom.isus_bsl_model == 2)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "(ax^2 + bx + c)"); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        if(apf_eeprom.isus_bsl_model == 3)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "exp(ax + b)"); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        if(apf_eeprom.isus_bsl_model == 4)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "exp(ax^2 + bx + c)"); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        bndx += sprintf((char*)&prnBuf[bndx], "H, Br Temp  Compensation , ");
        if(apf_eeprom.isus_tcomp_mode == TRUE)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "ENABLED"); bndx += sprn_nl((char*)prnBuf, bndx);
        }
        else
        {
            bndx += sprintf((char*)&prnBuf[bndx], "DISABLED"); bndx += sprn_nl((char*)prnBuf, bndx);
        }

        if(printFlg == 1)
        {
            UARTwrite((char *)prnBuf, bndx);
            wait_consoleTx();       // wait for UARTwrite to complete, trying to fix garbage chars
        }

        // write to uSD
        if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )
        {

            //APFTODO, even tho this routine is called when a new prof file is made, proly should call f_lseek anyway just in case
            fresult = f_write( fptr, prnBuf, bndx, (UINT*)&bw);

            //APFTODO, check bndx vs bw (bytes written)
            if(fresult != FR_OK)
            {
                if(serDbgFlg == 1) { uprintf("f_write error: %s, bytes attempted = %u, bytes written = %u", StringFromFresult(fresult), bndx, bw);  prn_nl(); }
            }
            else
            {
                //uprintf("\r\nSampling... f_write is good\r\n"); // THOM DEBUG
            }

        }
        else
        {
            if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0) && (writeFlg == 1) )
            {
                if(serDbgFlg == 1) { uprintf("ERR: tried to write to disabled uSD, sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 0"); prn_nl(); }
            }
        }

        memset(prnBuf, 0, SIZEOF_PRNBUF);

        bndx = 0;
        bndx += sprintf((char*)&prnBuf[bndx], "H, Bromide Term in Model , ");
        //bndx += sprn_nl((char*)prnBuf, bndx); // DEBUG

        if(apf_eeprom.isus_salfit_mode == TRUE)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "will be FIT (to absorbance data)");
            bndx += sprn_nl((char*)prnBuf, bndx);
        }
        else
        {
            bndx += sprintf((char*)&prnBuf[bndx], "will be FIXED (to external CTD value)");
            bndx += sprn_nl((char*)prnBuf, bndx);
        }

        if(apfArray.ZcoefsCnt > 0)
        {
            bndx += sprintf((char*)&prnBuf[bndx], "H, Zeiss Coefficient Vals, ");
            for(indx = 0; indx < 5; indx++)
            {
                if(indx != 4)
                {
                    bndx += sprintf((char*)&prnBuf[bndx], "%e,", apfArray.ZeissCoefs[indx]);
                }
                else
                {
                    bndx += sprintf((char*)&prnBuf[bndx], "%e", apfArray.ZeissCoefs[indx]);
                    bndx += sprn_nl((char*)prnBuf, bndx);
                }
            }
        } //end Zcoefs if

        if(printFlg == 1)
        {
            //UARTwrite((char *)prnBuf, strlen((char *)prnBuf));
            UARTwrite((char *)prnBuf, bndx);
            wait_consoleTx();       // wait for UARTwrite to complete, trying to fix garbage chars
            //delay_msec(2000);

        }

        // write to uSD
        if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )
        {

            //APFTODO, even tho this routine is called when a new prof file is made, proly should call f_lseek anyway just in case
            fresult = f_write( fptr, prnBuf, bndx, (UINT*)&bw);

            //APFTODO, check bndx vs bw (bytes written)
            if(fresult != FR_OK)
            {
                if(serDbgFlg == 1) { uprintf("f_write error: %s, bytes attempted = %u, bytes written = %u", StringFromFresult(fresult), bndx, bw);  prn_nl(); }
            }
            else
            {
                //uprintf("\r\nSampling... f_write is good\r\n"); // THOM DEBUG
            }

        }
        else
        {
            if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0) && (writeFlg == 1) )
            {
                if(serDbgFlg == 1) { uprintf("ERR: tried to write to disabled uSD, sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 0"); prn_nl(); }
            }
        }


    } // end ISUS_MODE

    if(smode == DURA_SENSOR_MODE)
    {
        bndx += sprintf((char*)&prnBuf[bndx], "H, DURA  Error   Counter , %lu", apf_fram.dura_error_ctr); bndx += sprn_nl((char*)prnBuf, bndx);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last DURA       Error , %d", apf_fram.dura_last_error); bndx += sprn_nl((char*)prnBuf, bndx);

        tp = localtime(&apf_fram.dura_error_time);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last DURA Error  Time , %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); bndx += sprn_nl((char*)prnBuf, bndx);

        bndx += sprintf((char*)&prnBuf[bndx], "H, DURA Sample   Counter , %lu", apf_fram.dura_sample_ctr); bndx += sprn_nl((char*)prnBuf, bndx);

        tp = localtime(&apf_fram.dura_sample_time);
        bndx += sprintf((char*)&prnBuf[bndx], "H, Last Sample Rec. Time , %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); bndx += sprn_nl((char*)prnBuf, bndx);

        //bndx += sprintf((char*)&prnBuf[bndx], "H, AtoD Reads per Sample , %hu", apf_eeprom.dura_atod_readings); bndx += sprn_nl((char*)prnBuf, bndx);
        //bndx += sprintf((char*)&prnBuf[bndx], "H, AtoD Reading    Delay , %hu", apf_eeprom.dura_atod_delay); bndx += sprn_nl((char*)prnBuf, bndx);
        //bndx += sprintf((char*)&prnBuf[bndx], "H, Samps per AtoD  Recal , %lu", apf_eeprom.dura_samps_per_cal); bndx += sprn_nl((char*)prnBuf, bndx);

        //printf("dura hdr bndx = %u"); prn_nl();


        if(printFlg == 1)
        {
            wait_consoleTx();       // wait for previous UARTwrite to complete

            UARTwrite((char *)prnBuf, bndx);

            wait_consoleTx();

        }

        // write to uSD
        if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )
        {

            //APFTODO, even tho this routine is called when a new prof file is made, proly should call f_lseek anyway just in case
            fresult = f_write( fptr, prnBuf, bndx, (UINT*)&bw);

            //APFTODO, check bndx vs bw (bytes written)
            if(fresult != FR_OK)
            {
                if(serDbgFlg == 1) {uprintf("f_write error: %s, bytes attempted = %u, bytes written = %u", StringFromFresult(fresult), bndx, bw);  prn_nl(); }
            }
            else  // FILE is open
            {
                //uprintf("\r\nSampling... f_write is good\r\n"); // THOM DEBUG
            }

        }
        else
        {
            if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0) && (writeFlg == 1) )
            {
                if(serDbgFlg == 1) { uprintf("ERR: tried to write to disabled uSD, sys_data.app_cfg[APPCFG_MICROSD_ENABLE] = 0"); prn_nl(); }
            }
        }


    } // end DURA_MODE


    return(1);
}


void print_dura_header(void)
{
    //#define ISUS_SENSOR_MODE        1
    //#define DURA_SENSOR_MODE        2
    //print_write_isusdura_header(uint32_t printFlg, uint32_t writeFlg, uint32_t smode)
    print_write_isusdura_header(1, 0, DURA_SENSOR_MODE, NULL);   //print


}

void resetErrorLog(void)
{
    int fresult;

    fresult = f_unlink("ERROR.LOG");
    if (fresult)
    {
        // Error occurred during delete
        // ...
    }

    apf_fram.Efpos = 0;
    // JLD->TGM I think we keep this going instead of clearing ---> apf_fram.sys_error_ctr = 0;
    apf_fram.errorLog_errorCnt = 0;
}

int SendRecords(unsigned int select, FSIZE_t *fOffset)     // uint8_t select
{
    char   *FileName[] = {"OFFLOAD.DAT", "ERROR.LOG"};      //APFTODO
    FIL     fptr = { };
    FSIZE_t fptmp;
    FSIZE_t fpStart = *fOffset;     // JLD - remember where we started

    //OFFLOAD.DAT is not supported in MSC3, ymodem is used, see ys command
    if( select != 1 )
    {
        return( ERROR );
    }

    fRes = f_open(&fptr, FileName[select], FA_READ);
    if (fRes == 4)  // F_NO_FILE?
    {
        uprintf("EOF"); prn_nl();
        return 2;
    }
    if(fRes)
        return( ERROR );

    if (apf_fram.errorLog_errorCnt)
        apf_fram.errorLog_errorCnt--;

    //set file pos to passed value
    if( *fOffset != 0 )
    {
        if( f_lseek(&fptr, *fOffset) != OK )
        {
            f_close(&fptr);
            return( -2 );           // JLD ambiguous issue  from -1
        }
    }

#define DATBUFSIZE  2048
    if( f_gets((char*)sBuf, DATBUFSIZE, &fptr) != NULL )
    {
         UARTwrite((char *)sBuf, strlen((char *)sBuf));         //UARTwrite used instead of 255 limited uprintf
    }

    //read current file pos
    *fOffset = f_tell(&fptr);

    //read EOF file pos
    fptmp = f_size(&fptr);       // get filesize

    f_close(&fptr);
    // There are three return scenarios:
    // 0 - normal case - we printed an message and the file position has moved up
    // 1 - EOF but also printed a message, file position has moved to end of the file.
    // 2 - EOF and nothing printed so the file position is where it was at the start.

    if( *fOffset == fptmp )   // case 1
    {
        // At EOF clear the ERRORs
        resetErrorLog();
        //uprintf("EOF"); prn_nl();     //should not send EOF on the last record, EOF on subsequent 22 May 2025 per Dana Swift
        if (fptmp == fpStart)  // case 2
            return 2;
        else
            return 1;
    }

    return OK;      // case 0
}

int ReinitializeErrorLogPtrs(void)
{
    int recordCount = 0;
    char *FileName = "ERROR.LOG";
    FIL     fptr = { };

    fRes = f_open(&fptr, FileName, FA_READ);
    if(fRes)
        return( ERROR );

    while (1)
    {
#define DATBUFSIZE  2048
        if( f_gets((char*)sBuf, DATBUFSIZE, &fptr) != NULL )
        {
            recordCount ++;
            // keep the watchdog happy.
            if ((recordCount % 100) == 0)
                wdog_pet();
        }
        else
        {
            break;
        }
    };
    f_close(&fptr);

    if(serDbgFlg == 1) { uprintf("Error Log read has %d records", recordCount); prn_nl(); }
    prn_nl();

    apf_fram.Efpos = 0;
    apf_fram.sys_error_ctr = recordCount;
    apf_fram.errorLog_errorCnt = recordCount;

    return recordCount;
}

int CheckFileSize(unsigned int select)
{
    //char   *ebuf;
    char   *FileName[] = {"\\OFFLOAD.DAT", "\\ERROR.LOG"};
    uint32_t   fileSize;
    char    ebuf[128];

    FIL fileObject;     // File object
    FRESULT fresult;

    if(serDbgFlg == 1) { uprintf("DBG CheckFileSize: in progress modifying...");  prn_nl(); }

    if( ! microSD_flg )
    {
        return ERROR;
    }

    // Unless select is a signed value, compare less than zero is a compiler issue - as it should be
    // opting for clarity here if select is ZERO or ONE ok.  Not ZERO or ONE return ERROR!!!
    if ( !( (select == 0) || (select == 1)) )
    //if( ( select < 0 ) || ( select > 1 ) )
        return( ERROR );

    memset(&fileObject, 0, sizeof(fileObject));

    fresult = f_open(&fileObject, FileName[select], FA_READ);
    if(fresult != FR_OK)
    {
        if(serDbgFlg == 1) { uprintf("DBG CheckFileSize: f_open error: %s", StringFromFresult(fresult)); prn_nl(); }
        return( ERROR ); 
    }

    fileSize = (uint32_t)f_size(&fileObject);
    if(serDbgFlg == 1) { uprintf("DBG CheckFileSize: fileSize = %u", fileSize); prn_nl(); }

    // Close the file
    fresult = f_close(&fileObject);
    if(fresult != FR_OK)
    {
        //APFTODO - write to log
        if(serDbgFlg == 1) { uprintf("DBG CheckFileSize: f_close error: %s", StringFromFresult(fresult)); prn_nl();  }
        return( ERROR );
    }

    if( select == 0 )
    {
        if( fileSize > apf_eeprom.sys_sf_limit*1000L )   //init'd to SYS_SFLIMIT_VAL
        {

            fresult = f_unlink(FileName[select]); // Delete the file "OFFLOAD.DAT"
            if (fresult)
            {
                // Error occurred during delete
                // ...
            }
            else
            {

            }

            sprintf(ebuf, "OFFLOAD.DAT file size = %u, is over the limit of %u", fileSize, apf_eeprom.sys_sf_limit*1000L);
            LogErrorMsg(ebuf, (int)OFFLOAD_FILE_TOO_BIG, apf_eeprom.sys_message_enable);

            return( TRUE );
        }
        else
        {
            return( OK );
        }
    }

    else if( select == 1 )
    {
        if( fileSize > apf_eeprom.sys_ef_limit*1000L )
        {
            resetErrorLog();
            sprintf(ebuf, "ERROR.LOG Too Big %ld Bytes - File Removed", fileSize);
            LogErrorMsg(ebuf, (int)ERROR_FILE_TOO_BIG, apf_eeprom.sys_message_enable);

            return( TRUE );
        }
        else
        {
            return( OK );
        }
    }

    else
    {
        return( ERROR );
    }

}

// pulled from apf_math.c
//unsigned short calcCRC(unsigned char val, unsigned short crc);
//int debugTesting = 0;
int read_print_ecoefs_Cal_file(void)
{
    FIL   coef_fil;
    char    buff[150];
    int     indx;
    int     result;
    unsigned short crc = 0xffff;
    int errorExited = 0;

    //uprintf("read_print_ecoefs_file Imp Es Nada");

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

    /* Read every line and display it */
    indx=0;
    while (f_gets(buff, sizeof(buff), &coef_fil))
    {
        uprintf("%s", buff); //prn_nl();
        char *msg = buff;
        while (*msg)
        {
            crc = calcCRC((0xff & *msg), crc);
            msg++;
        }

        indx++;
        result = waitACK(GET_ACK_TIMEOUT_MSEC);
        if( ! result )     // if no ACK, vals get dumped
        {
            errorExited = 1;
            break;
        }
        // wait_consoleTx();
    }

    f_close(&coef_fil);

    if (errorExited == 0)
    {
        crc = calcCRC(0,calcCRC(0,crc)) & 0xFFFF;
        uprintf("CAL,EOF,0x%04X", crc); prn_nl();
        return 1;
    }

    return 0;
}


// return the EOL string from configuration
char *eolStrs[] = { "\r\n", "\r", "\n", "" };
char *eol_str(void)
{
    if (sys_data.app_cfg[APPCFG_SER_LINETERM_MODE] <= 2)
        return eolStrs[sys_data.app_cfg[APPCFG_SER_LINETERM_MODE]];
    else
        return eolStrs[0];
}

typedef enum eDataType
{
    DTypeUnknown = 0,
    DTypeFloat = 1,
    DTypeEng5 = 2,
    DTypeString = 3,
} eDataType;

typedef struct tTableDisplay
{
    char        *fieldFormat;
    eDataType   dataType;
    void        *dataPtr;
} tTableDisplay;

tTableDisplay phCALFileDisplay[] =
{
#ifdef NOCODE
    { "k0_SW=%f",           DTypeFloat, &sys_data.k0_SW },
    { "k0_HCL=%f",          DTypeFloat, &sys_data.k0_HCL },
    { "k0std_SW=%f",        DTypeFloat, &sys_data.k0std_SW },
    { "k2[0]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[0] },
    { "k2[1]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[1] },
    { "k2[2]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[2] },
    { "k2[3]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[3] },
    { "fp[0]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[0] },
    { "fp[1]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[1] },
    { "fp[2]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[2] },
    { "fp[3]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[3] },
    { "fp[4]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[4] },
    { "fp[5]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[5] },
    { "fp[6]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[6] },
    { "fp[7]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[7] },
    { "fp[8]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[8] },
    { "fp[9]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[9] },
    { "service date = %s",  DTypeString, sys_data.service_date },
    { "PT Cal date = %s",   DTypeString, sys_data.PT_Cal_date },
    { "k0 Cal date = %s",   DTypeString, sys_data.k0_Cal_date },
    { "cal filename = %s",  DTypeString, sys_data.cal_filename },
    { "board_SN = %s",      DTypeString, sys_data.board_SN },
    { "mcap_SN = %s",       DTypeString, sys_data.mcap_SN },
    { "sensor_SN = %s",     DTypeString, sys_data.sensor_SN },
    { "isfet_SN = %s",      DTypeString, sys_data.isfet_SN },
    { "package name = %s",  DTypeString, sys_data.package_name },
    { "",                   DTypeUnknown, NULL }
#endif
    { "sensor_SN = %s",     DTypeString, sys_data.sensor_SN },
    { "pH_CalFile = %s",   DTypeString, sys_data.cal_filename },
    { "PT_Cal_date = %s",   DTypeString, sys_data.PT_Cal_date },
    { "k0_Cal_date = %s",   DTypeString, sys_data.k0_Cal_date },
    { "k0_SW=%f",           DTypeFloat, &sys_data.k0_SW },
    { "k2[0]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[0] },
    { "k2[1]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[1] },
    { "k2[2]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[2] },
    { "k2[3]=%.5e",         DTypeEng5,  &sys_data.K2_Cal[3] },
    { "fp[0]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[0] },
    { "fp[1]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[1] },
    { "fp[2]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[2] },
    { "fp[3]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[3] },
    { "fp[4]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[4] },
    { "fp[5]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[5] },
    { "fp[6]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[6] },
    { "fp[7]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[7] },
    { "fp[8]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[8] },
    { "fp[9]=%.5e",         DTypeEng5,  &sys_data.fP_Cal[9] },
    { "",                   DTypeUnknown, NULL }

};


// returns 1 on OK
// returns 0 on ERROR
int read_print_pH_Cal_file(void)
{
    char    buff[150];
    char    *msg;
    int     result;
    unsigned short crc = 0xffff;
    tTableDisplay *tTab = phCALFileDisplay;
    float *fptr;

    //pH calibration coefficients will eventually be read in on the command line and persisted as variables in sys_data.  see system.h for defs
    //the CAL command will read each of the following one line at a time
    //the implementation will call a uprintf() that formats the variable appropriate to it's data type

    while (tTab->dataType != DTypeUnknown)
    {
        switch (tTab->dataType)
        {
            case DTypeFloat :
            case DTypeEng5 :
                fptr = (float *)tTab->dataPtr;
                sprintf(buff, tTab->fieldFormat, *fptr);
                break;

            case DTypeString :
                sprintf(buff, tTab->fieldFormat, (char *)tTab->dataPtr);
                break;

            default :
                sprintf(buff, "%s UNKNOWN table data type", tTab->fieldFormat);
                break;
        }

        strcat(buff, eol_str());        //add appropriate line term so it is included in CRC
        uprintf("%s", buff);  //prn_nl();
        msg = buff;
        while (*msg)
        {
            crc = calcCRC((0xff & *msg), crc);
            msg++;
        }
        result = waitACK(GET_ACK_TIMEOUT_MSEC);

        if ( !result )
        {
            return 0;
        }
        // next table entry
        tTab++;
    }

    crc = calcCRC(0,calcCRC(0,crc)) & 0xFFFF;
    uprintf("CAL,EOF,0x%04X", crc); prn_nl();

    return 1;
}

#ifdef ZEISSFILE
        /* Zeiss MMS-UV, Module Number 153900 *************************************/
        /* Polynomial Coefficients C#' f(N+1) for pixel to wavelength calculation */
        /* DO NOT MODIFY FORMAT, i.e, values MUST be preceded by their coefficient*/
        /* designator AND comment strings must have either a "/" or "*" in them.  */
        /**************************************************************************/
        C0' +1.89757E+02
        C1' +7.81499E-01
        C2' +1.40802E-04
        C3' -3.00259E-07
        C4' +0.0
#endif
//Note: LoadIsusZCoefs should NOT be called at boot time, this fxn only called through user issued command (defaults, readz, reade (readz calls reade)
int LoadIsusZCoefs(double *ZeissCoefs)
{
    FIL   coef_fil;
    int     coefCnt;
    int     gotModNum;
    uint32_t modNum;
    char    *endptr;
    char    ebuf[128];

#define BUFFER_SIZE 256
#define MAX_COEFFICIENTS 5 // Based on your example

    FRESULT fr;
    char buffer[BUFFER_SIZE];
    double coefficient[MAX_COEFFICIENTS];


    coefCnt = 0;
    gotModNum = 0;
    modNum = 0;

    fr = f_open(&coef_fil, "\\ISUS\\ZCOEFDAT.CAL", FA_READ | FA_OPEN_EXISTING);
    if(fr != FR_OK)
    {
        //uprintf("ZCOEFdat.txt fRes = %d", (int)fr);

        sprintf(ebuf, "Can't open \\ISUS\\ZCOEFDAT.CAL");
        LogErrorMsg(ebuf, (int)INIT_ERROR, apf_eeprom.sys_message_enable);

        prn_nl(); uprintf("Error: Can't open \\isus\\zcoefdat.cal!"); prn_nl();
        return( -1 );
    }

    apfArray.ModuleNum = 0;

    // Read the file line by line
    while (f_gets(buffer, BUFFER_SIZE, &coef_fil))
    {
        //uprintf("DBG f_gets = %s", buffer); prn_nl();
        // Skip comment lines
        if (strstr(buffer, "/") != NULL || strstr(buffer, "*") != NULL)
        {
            if(gotModNum == 0)
            {
                ///* Zeiss MMS-UV, Module Number 153900 *************************************/
                //01234567890123456789012345678901

                const char *moduleNumberStart = strstr(buffer, "Module Number ");
                if (moduleNumberStart == NULL)
                {
                    uprintf("moduleNumberStart == NULL"); prn_nl();
                    continue;
                }

                moduleNumberStart += strlen("Module Number ");

                //char *endPtr;
                modNum = strtoul(moduleNumberStart, &endptr, 10);

                // Check if strtoul successfully parsed a number
                if (moduleNumberStart == endptr)
                {
                    uprintf("moduleNumberStart == endPtr, no Mod Num found"); prn_nl();
                    continue;
                }

                gotModNum = 1;
                apfArray.ModuleNum = modNum;

            }

            continue;
        }
        if (coefCnt == MAX_COEFFICIENTS)
        {
            uprintf("%s: ignoring additional file content: %s", __FUNCTION__, buffer); prn_nl();
            break;
        }
        // Parse coefficient name and value
        coefficient[coefCnt] = 0.0;     // for the case where the coef is actually 0.0
        coefficient[coefCnt] = strtod(&buffer[3], NULL);        //strtod rets 0.0 if string is not valide

        if( coefficient[coefCnt] != 0.0 )
        {
            apfArray.ZeissCoefs[coefCnt] = coefficient[coefCnt];
            //uprintf("DBG: affarr zeiss [%u] %f", apfArray.ZeissCoefs[coefCnt]);   prn_nl();
            coefCnt++;
        }
        else
        {
            // for the 0.0 case
            apfArray.ZeissCoefs[coefCnt] = 0.0;
            coefCnt++;
        }
    }

    if(coefCnt == 3)
    {
        apfArray.ZeissCoefs[3]=0.0; //clear possible residual from an "new" spec with 3 coefs
        apfArray.ZeissCoefs[4]=0.0; //clear possible residual from an "new" spec with 3 coefs
    }
    if(coefCnt == 4)
    {
        apfArray.ZeissCoefs[4]=0.0;  //clear possible residual from an "old" spec with 5 coefs
    }

    //APFTODO somehow coefCnt is 4 when it should be 5, cuz last number is 0.0

    apfArray.ZcoefsCnt = coefCnt;

    //fram_apf_vars_store();      // to store ZcoefsCnt, perhaps move cnt to the array APFTODO
    fram_apfArray_store();

    //APFTODO -check if store is successful by reading back and comparing

    // Close the file
    f_close(&coef_fil);
    return(coefCnt);
}

int LoadDuraFetIIITCoefs(double *Coefs)
{
    FIL   coef_file;
    char   *coef_ptr;
    char buff[150];
    int i;

    // open Steinhart-Hart coefficients
    fRes = f_open(&coef_file, "C:\\DURA\\SHHCOEFS.CAL", FA_READ);
    if(fRes)
    {
        uprintf("\n  Can't open SHHCOEFS.CAL!\n");
        return( -1 );
    }
    else
    {
        i=0;
        do
        {
            f_gets(buff, sizeof(buff), &coef_file);
            if( (strchr(buff,'/')) || (strchr(buff,'*')) )
                continue;
            coef_ptr = strstr(buff,"A");
            if(coef_ptr != NULL)
            {
                coef_ptr += 3;
                if( (strtod(coef_ptr, NULL) != 0.0) && (i < 4) )
                {
                    Coefs[i] = strtod(coef_ptr, NULL);
                    i++;
                }
            }
        } while( !f_eof(&coef_file) );
    } // end else

    f_close(&coef_file);

    return(i);      //should return 4

}




#define ABORT           0x03
#define BKSP            0x08
#define REDISP          0x12
#define RESTRT          0x15


/*******************************************************************************
**      QueryDateTime           Query user for the Date and Time
**
**      Return TRUE for all replies except ctrl-C.  Print prompt string (verbatim)
**      followed by the default reply (either the current system calander time if
**      defTime is TRUE or the calander time contained in the tm structure.
**      Returns result in struct tm, but does not set the system clock.
**
**      NOTES:
**              1.      carriage return is not echoed.
**              2.      ctrl-C always return FALSE
**              3.      input format is compatable with CrossCut's Paste Time & Date (cmd-D)
**
**      Typical Usage:
**              struct tm       t;
**              if (! QueryDateTime("\nCurrent Date and Time", TRUE, &t))
**                      ... error processing for ctrl-C ...
**              SetTimeTM(&t, NULL);    \* set the system time *\
*******************************************************************************/
short QueryDateTime(char *prompt, short defTime, struct tm *tm)
        {
        char            buf[40];
        char            def[40];
        time_t          secs;

        while (1)
                {
                printf(prompt);
                if (defTime)
                        secs = time(NULL);
                else
                        secs = mktime(tm);

                strftime(def, sizeof(def), "%m/%d/%Y %H:%M:%S", localtime(&secs));

                printf(" [%s] ? ", def);

                if (! InputLine(buf, (short) sizeof(buf)))
                        return (FALSE);

                if (! *buf)                     /* just return keyed */
                        strcpy(buf, def);       /* copy default and fall through */

                /* *c is NULL ptr for the "/" and ":" seperators */
                if (sscanf(buf, "%d%*c%d%*c%d%*c%d%*c%d%*c%d",
                        &tm->tm_mon, &tm->tm_mday, &tm->tm_year,
                        &tm->tm_hour, &tm->tm_min, &tm->tm_sec))
                        {
                        tm->tm_mon--;           /* adjust to month 0 - 11 */
                        if(tm->tm_year >= 2000)
                           tm->tm_year = (tm->tm_year - 2000) + 100;
                        else
                           tm->tm_year = (tm->tm_year - 1900);
                        return (TRUE);
                        }

                if (*prompt != '\n')
                        putchar('\n');  /* reprompt and try again */
                }

#ifdef WARNING_ABATEMENT_UNREACHABLE
            return (TRUE);
#endif

        }       /* QueryDateTime() */


/*******************************************************************************
**      QueryYesNo              Query user for a yes or no reply.
**
**      Return TRUE only for a Yes reply (which may be return only default if defYes
**      is TRUE).  Print prompt string (verbatim) followed by (Yes/No) followed by
**      default reply character inside square brackets, followed by a question mark.
**
**      NOTES:
**              1.      carriage return is not echoed.
**              2.      ctrl-C always return FALSE
*******************************************************************************/
//short QueryYesNo(ptr prompt, short defYes)
short QueryYesNo(char *prompt, short defYes)
        {
        char    reply;
        char    buf[100];

        strncpy(buf, prompt, sizeof(buf) - 10);
        strcat(buf, " (Yes/No)");
        if (! QueryChar(buf, defYes ? 'Y' : 'N', "YN", &reply))
                return (FALSE);
        return ((short) (reply == 'Y'));

        }       /* QueryYesNo() */


/*******************************************************************************
**      QueryChar               Query user for a character reply.
**
**      Return TRUE for all replies except ctrl-C.  Print prompt string (verbatim)
**      followed by optional default character inside square brackets, followed by
**      a question mark.  Return default character if just return pressed, otherwise
**      scan reply accepting only characters from scanSet.
**
**      NOTES:
**              1.      carriage return is not echoed.
**              2.      If the scanSet contains no lower case characters, the input will be
**                      converted to upper case.
**              3.      Same as 2 but opposite case sense.
**
*******************************************************************************/
//short QueryChar(ptr prompt, short defChar, ptr scanSet, char *reply)
short QueryChar(char *prompt, short defChar, char *scanSet, char *reply)
{
    char    c, buf[2];
    short   forceUpper = FALSE, forceLower = FALSE;
    char *pss;
    pss = scanSet;      //ptr             pss = scanSet;

    if (scanSet)
        {
        forceUpper = forceLower = TRUE;
        while (c = *pss++)
        if (isupper(c))
            forceLower = FALSE;
        else if (islower(c))
            forceUpper = FALSE;
        }

    while (1)
        {
        printf(prompt);
        if (defChar)
            printf(" [%c]", *reply = defChar);
//              printf(" ? ");

        if (! InputLine(buf, (short) sizeof(buf)))
            return (FALSE);

        if ((! *buf) && defChar)
            return (TRUE);

        if (forceUpper)
            *reply = toupper(*buf);
        else if (forceLower)
            *reply = tolower(*buf);
        else
            *reply = *buf;

        if (scanSet)
            if (strchr(scanSet, *reply))
                return (TRUE);

        if (*prompt != '\n')
            putchar('\n');  /* reprompt and try again */
        }

}       /* QueryChar() */



/*******************************************************************************
**      QueryNum                Query user for a numeric value.
**
**      Return TRUE for all replies except ctrl-C.  Print prompt string (verbatim)
**      followed by optional default value (if defFmt in non-NULL and not a NULL
**      string) inside square brackets, followed by a question mark.  Return default
**      value if just return pressed, otherwise scan reply using scanFmt string.
**
**      NOTES:
**              1. defFmt and scanFmt are scanf specifiers (e.g. "%ld", "lu", "%lx")
**              2. carriage return is not echoed.
*******************************************************************************/
int QueryNum(char *prompt, char *defFmt, char *scanFmt, unsigned long *value)     // ptr prompt
{
    char    buf[20];

    while (1)
    {
        uprintf(prompt);
        if (defFmt)
            if (*defFmt)
            {
            uprintf(" [");
            uprintf(defFmt, *value);
            uprintf("]");
            }
        uprintf(" ? ");

        if (! InputLine(buf, (short) sizeof(buf)))
            return (FALSE);

        if (! *buf)                     /* just return keyed */
            return (TRUE);

        if (sscanf(buf, scanFmt, value))
            return (TRUE);

        if (*prompt != '\n')
            putchar('\n');  /* reprompt and try again */
    }

}       /* QueryNum() */


/*******************************************************************************
**      QueryFloat              Query user for a float value.
**
**      Return TRUE for all replies except ctrl-C.  Print prompt string (verbatim)
**      followed by optional default value (if defFmt in non-NULL and not a NULL
**      string) inside square brackets, followed by a question mark.  Return default
**      value if just return pressed, otherwise scan reply using scanFmt string.
**
**      NOTES:
**              1. defFmt and scanFmt are scanf specifiers (e.g. "%f", "%e" )
**              2. carriage return is not echoed.
*******************************************************************************/
int QueryFloat(char *prompt, char *defFmt, char *scanFmt, float *value)
{
char    buf[20];

    while (1)
        {
        printf(prompt);
        if (defFmt)
                if (*defFmt)
                        {
                        printf(" [");
                        printf(defFmt, *value);
                        printf("]");
                        }
        printf(" ? ");

        if (! InputLine(buf, (short) sizeof(buf)))
                return (FALSE);

        if (! *buf)                     /* just return keyed */
                return (TRUE);

        if (sscanf(buf, scanFmt, value))
                return (TRUE);

        if (*prompt != '\n')
                putchar('\n');  /* reprompt and try again */
        }

}       /* QueryFloat() */


/*******************************************************************************
**      InputLine               Input line from keyboard with minimal editing features
**
**      Returns TRUE for all input except ctrl-C and returs NULL terminated string
**      in buffer.  Does not echo or include the terminating return character.
**
**      Typical Usage:
**              char    myline[40];
**              if (! InputLine(myline, sizeof(myline)))
**                      ... error processing for ctrl-C ...
**
**
**      Edit Commands:
**              ctrl-C 0x03             cancel operation, return FALSE
**              ctrl-H 0x08             backspace and erase
**              ctrl-R 0x12             re-display current line
**              ctrl-U 0x15             clear line and start again
**
*******************************************************************************/
int InputLine(char *linebuf, short linelen)
{
    short   i;
    char    ch;
    char    *bufptr;
    int     intChr;

    bufptr = linebuf;

    while (1)
        {
        //fflush(stdout);
        UARTFlushTx(TRUE);
//                i = SerTimedGetByte(30000L);    //30sec timeout
//        i = SCIRxGetCharWithTimeout(30000L);    //30sec timeout
        intChr = UARTgetc_timed(30000);   //30second timeout
        //uprintf("SCIRxGetCharWithTimeout needed, apf_util.c\r\n");
        if(intChr == -1)
        {
            prn_nl(); uprintf("Input Timeout!"); prn_nl();
            return(FALSE);
        }

        ch = (unsigned char) intChr;
        switch (ch)
            {
            case '\r' :
            case '\n' :
                *bufptr = '\0';
                return (TRUE);

            case BKSP :
                if (bufptr > linebuf)
                    {
                    bufptr--;
                    putchar(BKSP);
                    }
                break;

            case '\0' :
            case ABORT :
                *linebuf = '\0';
                return (FALSE);

            case REDISP :
                //UARTputc('\n');
                prn_nl();
                for (i = 0; &linebuf[i] < bufptr; i++)
                    UARTputc(linebuf[i]);
                break;

            case RESTRT :
                while (bufptr > linebuf)
                    {
                    bufptr--;
                    UARTputc(BKSP);
                    }
                break;
#ifdef WARNING_ABATEMENT_UNREACHABLE
                *linebuf = '\0';
                return (FALSE);
#endif

            default :
                if (bufptr < linebuf + linelen-1)
                    {
                    *bufptr++ = ch;
                    UARTputc(ch);
                    }
                else
                    {
                    bufptr[-1] = ch;
                    UARTputc(BKSP);
                    UARTputc(ch);
                    }
                break;
            }
        }

}       /* InputLine() */


/*******************************************************************************
**      kbflush         Flush any keyboard characters pending and return TRUE if any
**                              were available.
*******************************************************************************/
//short kbflush()
int kbflush(void)
{
    short   avail;

    UARTFlushRx();

    avail = kbhit();

    return (avail);

}       /* kbflush() */



//================================================================================================


//******************************************************************************
// Serial I/O Routines
///*****************************************************************************


//APFTODO - move the serial i/o routines to uartstdio
/*
* UART_rx_byte_timeout
*   timeout in millisec
*   return > 255 if timeout
*   return unsigned char (0 to 255), cast to uchar
*/
uint32_t UART_rx_byte_timeout(uint32_t timeout_ms)
{
    unsigned char uc;
    uint32_t retVal;

    const unsigned long tickStart = Timer_1ms();

    while(1)
    {
        if( UARTrdy() == 0 )
        {
            //uc = uart3_rx_byte();
            uc = UARTgetc();
            retVal = (uint32_t)uc;
            return(retVal);
        }

        if( is_timer_1ms_expired(tickStart, timeout_ms) )
        {
            retVal = 256;       // any number greater than 255 is an error
            return(retVal);
        }

    }

}

//unsigned short SerGetsTmout(char *buf, unsigned short buflen, unsigned short seconds)
int UART_GetStr_timeout(unsigned char *rxPtr, uint32_t bufLen, uint32_t timeout_msec)
{

    int indx;
    uint32_t val1;
    unsigned char uc;

    indx = 0;

    while(1)
    {
        val1 = UART_rx_byte_timeout( timeout_msec );
        if(val1 <= 255)
        {
            uc = (unsigned char)val1;
            *rxPtr = uc;
            rxPtr++;
            *rxPtr = 0x00;
            indx++;

            if((uc == 0x0d) || (uc == 0x0a) || (uc == 0x00) )  //cr=0x0d, lf=0x0a
            {
                *rxPtr = 0x00;       // null terminate
                indx++;
                return(indx);   //return byte count
            }


        }
        else  // timeout
        {
            return(-1);
        }
    }


}


int safegets(char *buffer, int n)
{
    char *ptr;               // ptr used for finding '\n'

    fgets(buffer,n,stdin);   // Get n chars
    buffer[n+1]='\0';        // Set last char to null
    ptr=strchr(buffer,'\n'); // If string contains '\n'
    if(ptr!=NULL)            // Change char to '\0'
        ptr[0]='\0';

    //SCIRxFlush();            // Flush the UART buffer

    return strlen(buffer);   // Return the length

}

/**
 * @file
 * @brief Implementation of #strupr() function.
 *
 * @date 06.08.2011
 * @author Gerald Hoch
 */

char *strupr (char *string)
{
    char * cp;

    for (cp=string; *cp; ++cp)
    {
        if ('a' <= *cp && *cp <= 'z')
            *cp += 'A' - 'a';
    }

    return(string);
}

int MatchACK(char *buf)
{
    char   *srchptr;

    if( (srchptr=strchr(buf,0x0D)) != NULL ) // If string contains CR Change char to '\0'
        *srchptr='\0';
    if( (srchptr=strchr(buf,0x0A)) != NULL ) // If string contains LF Change char to '\0'
        *srchptr='\0';

    strupr(buf);

    if( (strstr(buf, "ACK") != NULL) && (strlen(buf) == 3) )
        return( ACK );
    else if( (strstr(buf, "NAK") != NULL) && (strlen(buf) == 3) )
        return( NAK );
    else
        return( ERROR );

}

//******************************************************************************
// Time Routines
//******************************************************************************


int DayOfYear(int yy, int mm, int dd)
{
    int K;

    /* Check for leap year */
    if ((yy % 4 == 0) && ((yy < 1583) || (yy % 100 != 0) || (yy % 400 == 0)))
        K = 1;
    else
        K = 2;

    return (275 * mm / 9) - K * ((mm + 9) / 12) +  (dd - 30);

}

int DayOfWeek(int yy, int mm, int dd)
{

    if (mm < 3) { // convert months to 2...14
        mm += 12;
        yy -= 1;
    }

    return (((dd + (((mm + 1) * 26) / 10) + yy + (yy / 4) + (6 * (yy / 100)) + (yy / 400)) - 1) % 7);
}



//******************************************************************************
// Output Routines
//******************************************************************************



void DisplaySystemInfo(void)
{
    char SpecFirmware[128];
    char   *srchptr = NULL;
    uint8_t spec_was_on;
    // int ret;
    float volts, amps, temp, humi, dew_point;

    spec_was_on = apf.isus_spec_pwr;

    //ret = mms_specPwrOn();

    uprintf("DisplaySystemInfo Imp in progress, spec_was_on power state needs work, apf_util.c"); prn_nl();

    // ret =
    mms_check_mmsDig_version();   //defd mms_spec.c, print mms Digital version, turns spec on, sends 'V', turns spec off

    if( spec_was_on == FALSE )
        mms_specPwrOff();

    prn_nl(); prn_nl(); uprintf("  MBARI Serial Number : %04hu", apf_eeprom.sys_serial_num); prn_nl();

    prn_nl(); uprintf("  MSC3 App: %s Date: %s %s", strupr((char *)bfile), bdate, btime);  prn_nl();

    uprintf("  uSD Card: Size=%ld Avail=%.2f%%", size, (float)(((float)avail/(float)size)*100.0)); prn_nl(); prn_nl();

    DisplayTimes();

    volts = 0.0;
    amps = 0.0;
    ina260_system_voltageCurrent(&volts, &amps);

    prn_nl();
    uprintf("  System Batt Voltage = %.3f", volts); prn_nl();
    uprintf("  System Load Current = %.2e", amps); prn_nl();

    bme280_ReadTempHumidity(&temp, &humi);
    //dew_point = calc_dewpoint(humi, temp);

    dew_point = bme280_calc_dewpoint(temp, humi);   // Arg order reversed to match ReadTempHumidity

    uprintf("  Housing Temperature = %+06.2fC = %+06.2fF",temp, CtoF(temp) );  prn_nl();
    uprintf("  Housing    Humidity = %5.1f%%  = %5.1fC (DP)", humi, dew_point);  prn_nl();

    uprintf("  Watchdog      Count = %lu", apf_fram.sys_wdog_ctr);                prn_nl();      //Was Power Cycle
    uprintf("  System Reset  Count = %lu", apf_fram.sys_reset_ctr);              prn_nl();

    uprintf("  System Error  Count = %lu", apf_fram.sys_error_ctr);              prn_nl();
    uprintf("  Last System   Error = %d",  apf_fram.sys_last_error);             prn_nl();
    uprintf("  Last Error     Time = %s", ctime( &apf_fram.sys_error_time) );    prn_nl();

    uprintf("  ISUS Error    Count = %lu", apf_fram.isus_error_ctr);             prn_nl();
    uprintf("  Last ISUS     Error = %d",  apf_fram.isus_last_error);            prn_nl();
    uprintf("  Last Error     Time = %s", ctime( &apf_fram.isus_error_time) );   prn_nl();

    uprintf("  DURA Error    Count = %lu", apf_fram.dura_error_ctr);             prn_nl();
    uprintf("  Last DURA     Error = %d",  apf_fram.dura_last_error);            prn_nl();
    uprintf("  Last Error     Time = %s", ctime( &apf_fram.dura_error_time) );   prn_nl();

    //DS1683_RdElapsedTimer( &isus_lamp_elp );
    //DS1683_RdEventCounter( &isus_lamp_evt );
    uint32_t lampOnSeconds = apf_fram.isus_lampOn_mSec / 1000L;
    hdiv = ldiv(lampOnSeconds, 3600L);
    mdiv = ldiv(hdiv.rem, 60L);
    uprintf("  Fiberlite  Odometer = %02ld:%02ld:%02ld = %lu secs", hdiv.quot, mdiv.quot, mdiv.rem, lampOnSeconds);  prn_nl();
    uprintf("  Fiberlite    Cycles = %hu", apf_fram.isus_lampOn_ctr);       prn_nl();

    if( srchptr != NULL )
    {
        prn_nl(); uprintf("  Spectrometer Status = %s", srchptr);   prn_nl();
    }
    else
    {
        prn_nl(); uprintf("  Spectrometer Status = %s", SpecFirmware);     prn_nl();
    }

    if( apfArray.ZcoefsCnt > 0 )
    {
        prn_nl();  uprintf("  Zeiss Coef File Found %d coefs loaded.", apfArray.ZcoefsCnt);   prn_nl();
    }
    else
    {
        prn_nl(); uprintf("  Zeiss Coef File Not Found!");  prn_nl();
    }

    if( apfArray.EcoefsCnt > 0 )
    {
        uprintf("  Ecoef File Found %d coefs loaded.", apfArray.EcoefsCnt);  prn_nl();
    }
    else
    {
        uprintf("  Ecoef File Not Found!");  prn_nl();
    }


}


char *DisplayFormattedTime(time_t ptime)
{
    static char formattedTimeString[48];
    static char *wdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
    struct tm     *tmptr;

    tmptr = localtime(&ptime);

    tmptr->tm_wday = DayOfWeek( (tmptr->tm_year + 1900), (tmptr->tm_mon + 1), tmptr->tm_mday );
    tmptr->tm_yday = DayOfYear( (tmptr->tm_year + 1900), (tmptr->tm_mon + 1), tmptr->tm_mday );

    sprintf(formattedTimeString, "%s %d %02d/%02d/%04d %02d:%02d:%02d", wdays[tmptr->tm_wday], tmptr->tm_yday,
           tmptr->tm_mon+1, tmptr->tm_mday, tmptr->tm_year + 1900,
           tmptr->tm_hour, tmptr->tm_min, tmptr->tm_sec);

    return formattedTimeString;
}

void DisplayLambdaInfo(FIL *fptr)
{
    int i;

    if(fptr == NULL)       //if(fptr == FNULL)           //APFTODO, fix the check for NULL
        return;

    if( (apfArray.ZcoefsCnt > 0) || (apfArray.EcoefsCnt == 256) ) {  //wavelength info SHOULD be loaded
        f_printf(fptr, "L,");
        for(i = 1; i <= 256; i++) {
            if(i != 256)
                f_printf(fptr, "%.1f,", apfArray.LambdaData[i]);
            else
                f_printf(fptr, "%.1f\n", apfArray.LambdaData[i]);
        }
    }

}

void DisplayByteVal(char *msgstr, uint8_t ByteVal)
{
    int i;
    uint8_t temp;

    temp = ByteVal;

    printf("%s = 0x%02Xh = ", msgstr, temp);

    for(i = 0; i < 8; i++) {
        if((temp & 0x80) != 0)
            printf("1");
        else
            printf("0");
        temp = temp << 1;
    }

    printf("b\n");

}


//******************************************************************************
// Math Routines
//******************************************************************************

int CalcAbs(double *ZeroVals, unsigned short *IntenVals, double *AbsVals, double DCMean, unsigned short fit_pixels)
{
    int i, ret;
    double Inten, Trans;

    ret = 0;

    //printf("CalcAbs Input %lf, %hu, %lf\n", ZeroVals[1], IntenVals[0], DCMean);

    for(i = 1; i <= fit_pixels; i++) {
        Inten = (double)IntenVals[i] - DCMean;
        if( Inten > 0.0 ) {
            Trans = ZeroVals[i] / Inten;
            AbsVals[i] = log10(Trans);
        }
        else{
            AbsVals[i] = -1.0;
            ++ret;
        }
    }

    if( ret == 0 )
        return( (int)fit_pixels );
    else
        return( ret );

}








