/*
 * adv.c
 * Library for interface with Nortek Vector ADV
 *
 *  Created on: Oct 4, 2021
 *      Author: Irene Hu
 *
 *  Based on MIT code - removes synch pin, recorder functions
 */


#include "adv.h"

// uprintf family (at least, the standard one - this one is defined in user_io.c) has %b, but sprintf doesn't
// https://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)  \
  (byte & 0x80 ? '1' : '0'), \
  (byte & 0x40 ? '1' : '0'), \
  (byte & 0x20 ? '1' : '0'), \
  (byte & 0x10 ? '1' : '0'), \
  (byte & 0x08 ? '1' : '0'), \
  (byte & 0x04 ? '1' : '0'), \
  (byte & 0x02 ? '1' : '0'), \
  (byte & 0x01 ? '1' : '0')

#define TIMESTAMP_FMT "%Y/%m/%d %H:%M:%S"

//#define CTRL_X 24 // as in system.h

extern unsigned long Timer_10ms(bool reset);
extern unsigned long Timer_1ms(bool reset);

unsigned char prnBuf[SIZEOF_PRNBUF+4];      // size is defd in system.h - this copied straight from sample.c


//******************************************************************************
//* Initialize and UART wrapper
//* references to uart are in this section only
//******************************************************************************

void adv_init(void)
// similar to other devices, init function now only sets up the uart port
{
    uart2_init();

    uart2_setup(ADV_BAUD, 16000000);

    uart2_echo_set(0);
}

int adv_open(void)
// setup function for ADV
// return 0 if success, 1 if fail
{
    _adv_flushRx(); // don't know why it kept giving me a blank char even when ADV isn't connected

    adv_sendBreak(); // sometimes it may need to be woken up
    ROM_SysCtlDelay(MILLISECOND*1000); // copied from optode code for a simple delay (other optionis while loop using timer)
    adv_flushPort();

    if (adv_getUserConfig() == 0) {
        uprintf("\nSuccessfully read in ADV's user config info.\n");
        adv_validUserConfig = 1;
        return 0;
    }
    else {
        uprintf("\nDid not succeed in reading ADV's user config info.\n");
        adv_validUserConfig = 0;
        return 1;
    }
}

unsigned char _adv_getc(void)
{
    return(uart2_rx_byte());
}

void _adv_putc(unsigned char c)
{
    uart2_tx_byte(c);
}

int adv_rxBytesAvail(void)
{
    return(uart2_rx_bytes_avail());
}

void _adv_flushRx(void)
{
    uart2_rx_bufr_flush();
}

int _adv_write(const char *pcBuf, uint32_t ui32Len)
{
    return(uart2_write(pcBuf, ui32Len));
}

//******************************************************************************
//* Public Methods - User Menu
//******************************************************************************

void adv_comm_test(void)
// a test menu for the ADV
// includes a lot of stuff from old main.c
{
    uprintf("\nWelcome to the ADV menu!  meow!\n");

    adv_open(); // errors already printed

    int retVal;
    time_t timer;
    time_t currentTime;
    struct tm *time_struct;
    char input;
    char buff[256];

    // for case 1 (battery)
    unsigned int batt;

    // for case 3 (clock)
    struct adv_clock currClock; // memory is now reserved, can access by pointer - this was previously dynamically allocated
    time_t ADVtime;

    // for case 6 (measure)
    double totCtTime_min;
    char fn_append[MAX_FILENAME];
    int charCount;

    int mode;

    // want to test this get mode function
    adv_getMode(buff, &mode);
    uprintf("Current mode is %s.\n", buff);

    // Copied from test menu loop
    while(1)
    {
        uprintf("\n\n\nADV Menu:\n");
        uprintf("1 -- Get and print ADV battery\n");
        uprintf("2 -- Print user config to screen\n");
        uprintf("3 -- Get and print ADV clock\n");   //uprintf("3 -- Communicate with Instrument\n");
        uprintf("4 -- Power down ADV\n");
        uprintf("5 -- Wake up ADV\n");
        uprintf("6 -- Take measurements\n");
        uprintf("7 -- Exit measurement mode - in case things went wrong somewhere\n");
        uprintf("9 -- Exit to Test Menu\n");
        uprintf("\nEnter Selection [9]: ");

        UARTFlushRx();
        timer = 30000 + ROM_HibernateRTCGet();  //30 second

        while(1)
        {
            if( UARTRxBytesAvail() )
            {
                input = get_key();
                break;
            }

            //if( timer <= ROM_HibernateRTCGet() ) sleep(IDLE, 0);
        }

        switch(input)
        {
            // for now, copied from main.c of MIT code
            case '1':
            {   // error with declaring variable after the label (for case), but i want to restrict scope anyways
                // https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement

                // get ADV battery V, output to screen
                retVal = adv_getBatt(&batt);

                // copied from test menu option M, and print_write_metadata_header()
                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                if (retVal != 0) {
                    uprintf("[%s] Error getting ADV battery voltage.\n", buff);
                    break;
                }
                uprintf("[%s] ADV battery voltage is %d mV.\n", buff, batt);
                break;
            }

            case '2':
            {
                // print user config to screen

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                if (adv_validUserConfig)
                {
                    uprintf("[%s] ADV ", buff);
                    adv_printUserConfig(NULL, 1);
                    break;
                }
                else
                {
                    uprintf("[%s] Did not successfully read in ADV user config at initial connection; trying again\n", buff);
                    if (adv_getUserConfig() == 0) {
                        uprintf("Successfully read in ADV's user config info.\n");
                        adv_validUserConfig = 1;

                        currentTime = ROM_HibernateRTCGet();
                        time_struct = localtime(&currentTime);
                        strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                        uprintf("[%s] ADV ", buff);
                        adv_printUserConfig(NULL, 1);
                    }
                    else {
                        currentTime = ROM_HibernateRTCGet();
                        time_struct = localtime(&currentTime);
                        strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                        uprintf("[%s] Did not succeed in reading ADV's user config info.\n", buff);
                        adv_validUserConfig = 0;
                    }

                }

                break;
            }

            case '3':
            {
                // get and print ADV clock

                if (adv_getCurrClock(&currClock) != 0) {
                    currentTime = ROM_HibernateRTCGet();
                    time_struct = localtime(&currentTime);
                    strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);
                    uprintf("[%s] Problem getting ADV clock info.\n", buff);
                    break;
                }
                
                adv_convertClock(&currClock, &ADVtime);

                // slightly different format
                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),"%A, %B %d %Y, %I:%M %p", time_struct);
                uprintf("mphox time: %s\n", buff);
                
                time_struct = localtime(&ADVtime);
                strftime(buff, sizeof(buff),"%A, %B %d %Y, %I:%M %p", time_struct);
                uprintf("ADV time: %s\n", buff);

                break;
            }


            case '4':
            {
                // Power down ADV

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                if (adv_powerDown() != 0) uprintf("[%s] Error powering down ADV.\n", buff);
                else uprintf("[%s] ADV powered down to sleep mode.\n", buff);

                break;
            }

            case '5':
            {
                // Wake up ADV

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                adv_sendBreak();
                uprintf("[%s] Attempting to wake up ADV:\n", buff);

                ROM_SysCtlDelay(MILLISECOND*1000); // copied from optode code for a simple delay (other option is while loop using timer)

                adv_flushPort();

                break;
            }

            case '6':
            {
                // take measurement
                // ... yikes 

                // now that we're operating off menus, need a way to get user input that's not part of the first command
                // copying some code / using functions from the config menu (config.c), eg for changing file name or entering a sampling period
                // unlike config  menu, there is nothing saved in permanent memory and no 'default' or 'last' option
                // user must input something
                // eventually want it to autoincrement files etc, but for test function, user inputs name of file etc

                while(1) {
                    uprintf("\nEnter measurement duration (min): ");
                    if(getUserInput(buff, 20)) // assume this blocks until something is received?
                    {
                        if(sscanf(buff, "%lf", &totCtTime_min) == 1)
                        {
                            if(totCtTime_min <= 0)
                            {
                                uprintf("\nMeasurement duration must be greater than 0.\n");
                                continue;
                            }

                            break;
                        }
                        else // sscanf returned something besides 1
                        {
                            uprintf("\nPlease enter a single number.\n");
                            continue;
                        }
                    }
                    //else break; // no default value, so take this break out
                }

                while(1) { // no default values, so keep asking until get a value

                    uprintf("\nEnter data file name (5 chars, no extension): ");
                    if( charCount = getUserInput(buff, MAX_FILENAME) )
                    {
                        if (charCount > 5) buff[5] = 0; // force it to 5
                        strcpy(fn_append, buff);
                        uprintf("\nFile append sequence: %s\n", fn_append);
                        break;
                    }
                }

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                uprintf("[%s] ", buff);
                // measure function will print the rest of the narration

                retVal = adv_measure(totCtTime_min, fn_append);

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                if (retVal == 1)
                    uprintf("\n[%s] Exited with errors.\n", buff);
                else
                    uprintf("\n[%s] Finished measuring and recording data.\n", buff);


                break;
            }

            case '7':
            {
                // Exit measurement mode - shouldn't need to call this unless things go wrong
                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                // int mode; - declared it up above, for now

                uprintf("[%s] Trying to exit ADV's measurement mode...\n", buff);
                if(adv_exitMeasMode() != 0) {
                    // adv_getMode(buff, &mode);
                    // uprintf("Error trying to exit measurement mode. Current mode is %s.\n", buff);
                    uprintf("Error trying to exit measurement mode. ");
                } else {
                    uprintf("Exited measurement mode. ");
                }
                // want to test this get mode function
                adv_getMode(buff, &mode);
                uprintf("Current mode is %s.\n", buff);

                break;

            }

             default: // Exit
                uprintf("\nPress enter to bring up main menu\n");
                return;

        } // end switch
    } // end while for menu printing
}

//******************************************************************************
//* Public Methods - Measure
//******************************************************************************

int adv_measure(double measDuration_min, char* fn_append)
// measure for measDuration_min minutes at whatever frequency is set in ADV settings
// pass in a character array containing a <=5-char append string sequence
// no longer passing in FIL* because i am blowing up the stack
// return 0 if success, 1 if not
{
    uint64_t measDuration_ms = (uint64_t)(measDuration_min * 60 * 1000); // switch from us in MIT code to ms here, to use timers

    // not sure the below still matters
    /*
    // check that continuous measurement is enabled
    if (_adv_config.B1 != 0) {
        cerr << "Vector is in burst, not continuous mode. Download a new deployment file.\n";
        return 1;
    } */

    struct adv_dataHeader datHdrStruct;
    struct adv_probeChkData prbChkStruct;
    struct adv_sysData sysDatStruct;
    struct adv_velocityData velDatStruct;

    char filename[MAX_FILENAME];
    FIL reused_ofs, sysdat_ofs, veldat_ofs; // file objects
    FRESULT fresult;

    // these are for velocity and sys data
    int16_t checkSum;
    int ID;

    // keep track of how many - now that I'm not counting pulses
    int numsys = 0, numvel = 0, numvelerrs = 0;

    // polling for cancel
    int inChar;

    if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] != 1)   // copied from Thom fix 6 Aug 2021
    {
        uprintf("Some lil flag is not set properly and the microsd is not enabled. This fxn relies on writing to files. Goodbye, better luck next time.\n");
        return 1;
    } // rather than only opening files etc if condition is met, just quit now if it isn't

    uprintf("Initiating ADV measurements for %.2f minutes; ADV is set to measure at %d Hz\n", measDuration_min, 512/_adv_config.AvgInterval);
    uprintf("Files will be named [uc / dh / pc / sd / vd]_%s.txt for user config, data header, probe check, system data, and velocity data, respectively.\n", fn_append);

    // start recording - then will need 1 second before header data is available
    char cmd[] = "ST";
    //char cmd[] = "SD"; // 5/15/18 test if it's still streaming to serial
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    if (_adv_receiveAck() != 0) return 1;

    // start time
    // 11/16/21 no longer need this since we're not implementing pauses during startup
    // Timer_1ms(TRUE); // reset 1ms tick - assumes timer not being used for anything else, otherwise need to store value here and calc a difference later

    // need to wait 1 s; while we wait, output user config and file headers
    //FA_OPEN_APPEND is not supported in this fatfs version

    // user config - open, output, and close
    sprintf(filename, "uc_%s.txt", fn_append);
    fresult = f_open(&reused_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        // != FR_OK
        uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
        //error_store("f_open error");
        // how to handle this error condition?
    }
    else  // FILE is open
    {
        // Seek to the end, to append our file
        fresult = f_lseek(&reused_ofs, reused_ofs.fsize);
        if(fresult != FR_OK)
        {
            uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
        }
    }

    adv_printUserConfig(&reused_ofs,0);

    fresult = f_close( &reused_ofs );
    if(fresult != FR_OK)
    {
     uprintf("File %s f_close error: %s\n", filename, StringFromFresult(fresult));
     //error_store("f_close error");
    }

    // system and velocity data - these need to stay open

    sprintf(filename, "sd_%s.txt", fn_append);
    fresult = f_open(&sysdat_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        // != FR_OK
        uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
        //error_store("f_open error");
        // how to handle this error condition?
    }
    else  // FILE is open
    {
        // Seek to the end, to append our file
        fresult = f_lseek(&sysdat_ofs, sysdat_ofs.fsize);
        if(fresult != FR_OK)
        {
            uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
        }
    }

    sprintf(filename, "vd_%s.txt", fn_append);
    fresult = f_open(&veldat_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        // != FR_OK
        uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
        //error_store("f_open error");
        // how to handle this error condition?
    }
    else  // FILE is open
    {
        // Seek to the end, to append our file
        fresult = f_lseek(&veldat_ofs, veldat_ofs.fsize);
        if(fresult != FR_OK)
        {
            uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
        }
    }

    adv_printSysDataHdr(&sysdat_ofs);
    adv_printVelDataHdr(&veldat_ofs);

    sd_fprintf(&sysdat_ofs, "\n");
    sd_fprintf(&veldat_ofs, "\n");
    // found the above function in print_write_metadata_header() in config.c, which I found as i was tracing how timestamps are done and if it's worth it to print timestamps for narration
    // seems much easier to use- built in checks for failure to write etc- than putting strings in a buffer and then printing the buffer
    // buffer thing is nice when you want option to output to screen and/or file, so keep that for the others

    // 1 s after sent command, can get header and probe check data
    //while ( Timer_1ms(FALSE) < 1000) ; // waiting loop
    // this timer was reset where I used to save a 'starttime', so can use its absolute number
    // 11/16/21 this is now removed as it seems to cause timing problems, and is not necessary since functions will wait for data until it appears

    if (adv_getDatHeader(&datHdrStruct) == 1) return 1;
    if (adv_getProbeChkDat(&prbChkStruct) == 1) return 1;

    // need to wait another s; while we wait, output
    // data header files and probe check can open, write, and close now

    sprintf(filename, "dh_%s.txt", fn_append);
    fresult = f_open(&reused_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        // != FR_OK
        uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
        //error_store("f_open error");
        // how to handle this error condition?
    }
    else  // FILE is open
    {
        // Seek to the end, to append our file
        fresult = f_lseek(&reused_ofs, reused_ofs.fsize);
        if(fresult != FR_OK)
        {
            uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
        }
    }

    adv_printDatHeader(&datHdrStruct, &reused_ofs);

    fresult = f_close( &reused_ofs );
    if(fresult != FR_OK)
    {
     uprintf("File %s f_close error: %s\n", filename, StringFromFresult(fresult));
     //error_store("f_close error");
    }

    sprintf(filename, "pc_%s.txt", fn_append);
    fresult = f_open(&reused_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        // != FR_OK
        uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
        //error_store("f_open error");
        // how to handle this error condition?
    }
    else  // FILE is open
    {
        // Seek to the end, to append our file
        fresult = f_lseek(&reused_ofs, reused_ofs.fsize);
        if(fresult != FR_OK)
        {
            uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
        }
    }

    adv_printProbeChkDat(&prbChkStruct, &reused_ofs);

    fresult = f_close( &reused_ofs );
    if(fresult != FR_OK)
    {
     uprintf("File %s f_close error: %s\n", filename, StringFromFresult(fresult));
     //error_store("f_close error");
    }

    // 2 s after command, can get system data
    //while ( Timer_1ms(FALSE) < 2000) ; // waiting loop
    // 11/16/21 this is now removed as it seems to cause timing problems, and is not necessary since functions will wait for data until it appears

    /*uprintf("Troubleshooting: print out 100 incoming hex bytes:\n");
    int i;
    for (i = 0; i < 100; i ++)
    {
        uprintf("%#x ",_adv_getc());
    }*/

    // system data requires a little more processing
    // check we're getting the right structure
    ID = _adv_sortIncomingDataStruct(&checkSum);
    uprintf("Tried to receive and sort first system data packet\n"); // debug
    if (ID == -1) return 1; // means transmission or sync byte failed- error msg already printed
    if (ID != ADV_IDSYS) {
        uprintf("Did not receive correct structure ID. Got %#x\n", ID);
        return 1;
    }
    if (adv_getSysData(&sysDatStruct, checkSum) == 1) return 1;
    numsys++;

    // without synch pulses, Vector will just start measuring
    // we'll estimate this as the starting time, so reset timer here
    Timer_1ms(TRUE);

    // output sys data
#ifdef PRINT_ADV_TIMING
    sd_fprintf(&sysdat_ofs, "0\t");
#endif
    adv_printSysData(&sysDatStruct, &sysdat_ofs);
    sd_fprintf(&sysdat_ofs, "\n");

    // very different from other code - no longer in charge of measurements, just sit and wait for data
    // error condition: do not abort if something goes wrong
    // TODO: rethink how to handle errors, when pH data stream is also coming in
    // based off of read recorder file code

    // for debugging
    uprintf("\nStarting to measure.\n");

    while( Timer_1ms(FALSE) < measDuration_ms)
    {
        // check for velocity and system data
        ID = _adv_sortIncomingDataStruct(&checkSum);

        switch (ID) {
            case -1: // transmission or synch byte failed
            {
                break;
            }

            case ADV_IDSYS: //system data
            {
                // other code has somethign to throw out incomplete data structures - ??

                if (adv_getSysData(&sysDatStruct, checkSum) == 1) {
                    uprintf("Failed to get system data %d; continuing to check for more data\n", numsys++);
                    break;
                }
#ifdef PRINT_ADV_TIMING
                sd_fprintf(&sysdat_ofs, "%.4f\t",(double)Timer_1ms(FALSE)/1000);
#endif
                adv_printSysData(&sysDatStruct, &sysdat_ofs);
                sd_fprintf(&sysdat_ofs, "\n");
                numsys++;

                // since we're not coordinating synch pulses, ok to go back to top of loop and check again for velocity
                // rather than having to read the velocity immediately

                break;

            }

            case ADV_IDVEL: //velocity data
            {
                // other code has somethign to throw out incomplete data structures - ??

                if (adv_getVelData(&velDatStruct, checkSum) == 1) {
                    uprintf("Failed to get velocity data %d; continuing to check for more data\n", numvel++);
                    break;
                }

                while (velDatStruct.Count != numvel % 256) {
                    uprintf("Velocity data point #%d not read in, read in point #%d.\n", numvel, (int)(velDatStruct.Count));
                    numvel++;
                    numvelerrs++;
                }
#ifdef PRINT_ADV_TIMING
                sd_fprintf(&veldat_ofs, "%.4f\t",(double)Timer_1ms(FALSE)/1000);
#endif
                adv_printVelData(&velDatStruct, &veldat_ofs, numvel); // it starts at 0
                sd_fprintf(&veldat_ofs, "\n");
                numvel++;

                break;
            }

            default:
            {
                uprintf("Got synch byte, but ID byte received (%#x) does not correspond to system or velocity data. Ignoring these bytes, continuing to check for more data.\n", ID);
                break;
            }
        } // end switch

        // poll for a ctrl-X to quit - copied partly from optode code
        if(UARTRxBytesAvail())
        {
            inChar = UARTgetc();
            if(inChar == CTRL_X)
            {
                uprintf("Ctrl-x received, aborting measurement.\n");
                break;
            }
        }

        // go back to top of loop, keep checking for data until timeout

    }

    // all done measuring!
    uint64_t elapsedTime = Timer_1ms(FALSE);

    uprintf("Total %d system data and %d velocity data received, plus %d velocity readin errors, over a period of %.2f min\n", numsys, numvel-numvelerrs, numvelerrs, (double)(elapsedTime)/60000);

    // close files
    fresult = f_close( &sysdat_ofs );
    if(fresult != FR_OK)
    {
        uprintf("System data f_close error: %s\n", StringFromFresult(fresult));
        //error_store("f_close error");
    }

    fresult = f_close( &veldat_ofs );
    if(fresult != FR_OK)
    {
        uprintf("Velocity data f_close error: %s\n", StringFromFresult(fresult));
        //error_store("f_close error");
    }

    return adv_exitMeasModeNoNarration();
}


//******************************************************************************
//* Public Methods - Controller Commands
//******************************************************************************

int adv_flushPort()
// flush port, printing out everything that's coming, in ASCII
// returns 1 if there was nothing there, 0 otherwise
{
    if (!adv_rxBytesAvail()) return 1;
    uprintf("Received from ADV:\n");
    while (adv_rxBytesAvail()) {
        uprintf("%c",_adv_getc());
    }

    return 0;
}


void adv_sendBreak()
// send a break to the ADV
{
    unsigned long time1;

    char breakCmd[] = "@@@@@@"; //a null terminated string
    char breakCmd2[] = "K1W%!Q";

    _adv_write(breakCmd, 6);

    time1 = Timer_10ms(TRUE);; // reset 10ms tick - assumes timer not being used for anythign else, otherwise need 2 time variables and calc a difference

    while ( time1 < 10) //need to wait 100 ms
        time1 = Timer_10ms(FALSE);

    _adv_write(breakCmd2, 6);
}


int adv_powerDown()
// puts instrument in sleep mode.  Returns 0 if success, 1 if not
{
    char cmd[] = "PD";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    if (_adv_receiveAck() != 0) return 1;

    return 0;
}


int adv_getBatt(unsigned int *battV)
// stores battery voltage (mV) in battV
// return 0 if success, 1 if fail
{
    if (!battV) return 1;

    char cmd[] = "BV";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    // port from c++ - it no longer returns -1 if no byte, it just blocks
    // manual implementation of a timeout, since it's reasonable that there would be some dead time after sending command
    unsigned long time1;
    Timer_10ms(TRUE); // reset 10ms tick - assumes timer not being used for anything else, otherwise need 2 time variables and calc a difference

    while (adv_rxBytesAvail() < 2) {
        time1 = Timer_10ms(FALSE);
        if ( time1 > CMD_TIMEOUT_MS/10) {
            uprintf("Bytes not available on port.\n");
            return 1;
        }
    }

    *battV = (unsigned int)_adv_getTwoBytes();
    if (_adv_receiveAck() != 0) return 1;

    return 0;
}


int adv_getMode(char *response, int *mode)
// stores mode in string pointed to by response - for C, must pass in a char array that's long enough
// longest response is 41 char + null (= 42), for the no Ack received message
// also stores number of mode in intResponse
// returns 0 if success, 1 if fail
{
    if (!response) return 1;

    char cmd[] = "II";

    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    // port from c++ - it no longer returns -1 if no byte, it just blocks
    // manual implementation of a timeout, since it's reasonable that there would be some dead time after sending command
    // check for 2 bytes bc assumes there's an extra char; otherwise, code risks blocking for it
    unsigned long time1;
    Timer_10ms(TRUE); // reset 10ms tick - assumes timer not being used for anything else, otherwise need 2 time variables and calc a difference

    while (adv_rxBytesAvail() < 2) {
        time1 = Timer_10ms(FALSE);
        if ( time1 > CMD_TIMEOUT_MS/10) {
            uprintf("Bytes not available on port.\n");
            return 1;
        }
    }

    *mode = _adv_getc();

    _adv_getc(); // discard extra char

    switch (*mode) {
        case 0x00:
        {
            strcpy(response,"Firmware upgrade mode");
            break;
        }

        case 0x01:
        {
            strcpy(response,"Measurement mode");
            break;
        }

        case 0x02:
        {
            strcpy(response,"Command mode");
            break;
        }

        case 0x04:
        {
            strcpy(response,"Data retrieval mode");
            break;
        }

        case 0x05:
        {
            strcpy(response,"Confirmation mode");
            break;
        }

        default:
        {
            strcpy(response,"[Error in getting mode]");
            return 1;
            //break;
        }
    }

    if (_adv_receiveAck() != 0) {
        strcpy(response,"[Error in getting mode - no Ack received]");
        return 1;
    }

    return 0;
}


int adv_exitMeasMode()
// go from measurement mode to command mode by sending a break followed by an MC
// returns 0 if succeed, 1 if fail
{
    adv_sendBreak();
    _adv_flushRx(); // if I haven't been clearing the input buffer, there's data there

    ROM_SysCtlDelay(MILLISECOND*1000); // copied from optode code for a simple delay (other option is while loop using timer)

    adv_flushPort();

    uprintf("\n\nSending MC.\n");

    char cmd[] = "MC";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    if (_adv_receiveAck() != 0) return 1;

    ROM_SysCtlDelay(MILLISECOND*1000);

    adv_flushPort();

    return 0;
}


int adv_exitMeasModeNoNarration()
// go from measurement mode to command mode by sending a break followed by an MC
// thisi version doesn't print out all the ADV's responses, it just clears them
// returns 0 if succeed, 1 if fail
{
    adv_sendBreak();
    _adv_flushRx(); // if I haven't been clearing the input buffer, there's data there

    ROM_SysCtlDelay(MILLISECOND*1000); // copied from optode code for a simple delay (other option is while loop using timer)

    // it should send back: "Confirm:"
    if (!adv_rxBytesAvail()) return 1;
    _adv_flushRx();

    // now send the MC
    char cmd[] = "MC";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    if (_adv_receiveAck() != 0) return 1;

    ROM_SysCtlDelay(MILLISECOND*1000);

    // now comes te welcome message.  We're going to throw that out too
    if (!adv_rxBytesAvail()) return 1;
    _adv_flushRx();

    // to be sure, check that it's in command mode
    // if it's still in measurement mode, this would probably just fail and cause weird errors
    int mode;
    char response[42];
    adv_getMode(response, &mode);

    if (mode != 2) return 1; // whatever calls this function can check the mode again if it wants

    return 0;
}


int adv_getCurrClock(struct adv_clock *clockStruct)
// get current clock and store in the ADV clock passed in
// Returns 0 if success, 1 if not
{
    if (!clockStruct) return 1;

    char cmd[] = "RC";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    // port from c++ - it no longer returns -1 if no byte, it just blocks
    // manual implementation of a timeout, since it's reasonable that there would be some dead time after sending command
    unsigned long time1;
    Timer_10ms(TRUE); // reset 10ms tick - assumes timer not being used for anything else, otherwise need 2 time variables and calc a difference

    while (adv_rxBytesAvail() < 6) {
        time1 = Timer_10ms(FALSE);
        if ( time1 > CMD_TIMEOUT_MS/10) {
            uprintf("Bytes not available on port.\n");
            return 1;
        }
    }

    char clockByteStream[6];
    int i;

    for (i=0; i<6; i++) {
        clockByteStream[i] = _adv_getc();
    }

    if (_adv_receiveAck() != 0) return 1;

    _adv_clockBytesToStruct(clockByteStream, clockStruct);

    return 0;
}


int adv_getUserConfig()
// get the user config info, parse and write into internal struct _adv_adv_config
// return 0 if successful, 1 if unsuccessful
// note these assume that lsb/msb stuff is grouped by field, so revisit if this isn't true
{
    int i;

    char cmd[] = "GC";
    if (_adv_sendTwoCharCmd(cmd) == 1) return 1;

    // for checksum
    int16_t checkSum;
    int16_t recWord;
    int16_t *iterator;

    // check we're getting the right structure
    int ID = _adv_sortIncomingDataStruct(&checkSum);
    if (ID == -1) return 1; // means transmission or sync byte failed- error msg already printed
    if (ID != 0x00) {
        uprintf("Did not receive correct structure ID. Got %#x\n", ID);
        return 1;
    }

    if ((recWord = _adv_getTwoBytes()) != 256) {
        uprintf("Problem with size bytes.  Got %d words.\n", recWord);
        return 1;
    }
    checkSum += recWord;

    // now walk through the return feed and parse it into _adv_config

    _adv_config.T1 = (uint16_t)_adv_getTwoBytes();
    _adv_config.T2 = (uint16_t)_adv_getTwoBytes();
    _adv_config.T3 = (uint16_t)_adv_getTwoBytes();
    _adv_config.T4 = (uint16_t)_adv_getTwoBytes();
    _adv_config.T5 = (uint16_t)_adv_getTwoBytes();
    _adv_config.NPings = (uint16_t)_adv_getTwoBytes();
    _adv_config.AvgInterval = (uint16_t)_adv_getTwoBytes();
    _adv_config.NBeams = (uint16_t)_adv_getTwoBytes();
    _adv_config.TimCtrlReg = (uint16_t)_adv_getTwoBytes();

    for (i=0; i<4; i++) {
    // PwrCtrlReg (obsolete), A1, B0, B1
        checkSum += _adv_getTwoBytes();
    }

    _adv_config.CompassUpdRate = (uint16_t)_adv_getTwoBytes();
    _adv_config.CoordSystem = (uint16_t)_adv_getTwoBytes();
    _adv_config.NBins = (uint16_t)_adv_getTwoBytes();
    _adv_config.BinLength = (uint16_t)_adv_getTwoBytes();
    _adv_config.MeasInterval = (uint16_t)_adv_getTwoBytes();

    // for checksum - for the 14 2-byte fields I just stored
    iterator = (int16_t *)&(_adv_config.T1);
    for (i=0; i<14; i++) {
        checkSum += iterator[i];
    }

    // back to reading
    for (i=0; i<6; i++) {
        _adv_config.DeployName[i] = _adv_getc(); // this apparently DOES go in order
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(_adv_config.DeployName[i-1], _adv_config.DeployName[i]);
    }

    _adv_config.WrapMode = (uint16_t)_adv_getTwoBytes();
    // for checksum
    checkSum += (int16_t)_adv_config.WrapMode;

    // back to reading - clock
    char clockByteStream[6];
    for (i=0; i<6; i++) {
        clockByteStream[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(clockByteStream[i-1], clockByteStream[i]);
    }
    _adv_clockBytesToStruct(clockByteStream, &(_adv_config.clockDeploy));

    // a 4-byte word coming up...
    recWord = _adv_getTwoBytes();
    _adv_config.DiagInterval = (uint32_t)(recWord);
    checkSum += recWord;
    recWord = _adv_getTwoBytes();
    _adv_config.DiagInterval = _adv_config.DiagInterval + 65536 * (uint32_t)(recWord);
    checkSum += recWord;

    _adv_config.Mode = (uint16_t)_adv_getTwoBytes();
    _adv_config.AdjSoundSpeed = (uint16_t)_adv_getTwoBytes();
    _adv_config.NSampDiag = (uint16_t)_adv_getTwoBytes();
    _adv_config.NBeamsCellDiag = (uint16_t)_adv_getTwoBytes();
    _adv_config.NPingsDiag = (uint16_t)_adv_getTwoBytes();
    _adv_config.ModeTest = (uint16_t)_adv_getTwoBytes();
    _adv_config.AnaInAddr = (uint16_t)_adv_getTwoBytes();
    _adv_config.SWVersion = (uint16_t)_adv_getTwoBytes();

    // for checksum - for the 8 2-byte fields I just stored
    iterator = (int16_t *)&(_adv_config.Mode);
    for (i=0; i<8; i++) {
        checkSum += iterator[i];
    }

    checkSum += _adv_getTwoBytes(); // Spare

    for (i=0; i<180; i++) {
        _adv_config.VelAdjTable[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(_adv_config.VelAdjTable[i-1], _adv_config.VelAdjTable[i]);
    }

    for (i=0; i<180; i++) {
        _adv_config.Comments[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(_adv_config.Comments[i-1], _adv_config.Comments[i]);
    }

    for (i=0; i<8; i++) {
    // Mode, DynPercPos, T1, T2, T3, NSamp, A1, B0
        checkSum += _adv_getTwoBytes();
    }

    _adv_config.B1 = (uint16_t)_adv_getTwoBytes();

    checkSum += _adv_getTwoBytes(); //Spare

    _adv_config.AnaOutScale = (uint16_t)_adv_getTwoBytes();
    _adv_config.CorrThresh = (uint16_t)_adv_getTwoBytes();

    checkSum += _adv_getTwoBytes(); //Spare

    _adv_config.TiLag2 = (uint16_t)_adv_getTwoBytes();

    // for checksum - for the 5 2-byte fields I just stored
    iterator = (int16_t *)&(_adv_config.B1);
    for (i=0; i<4; i++) {
        checkSum += iterator[i];
    }

    for (i=0; i<15; i++) {
    // 30 bytes of spare
        checkSum += _adv_getTwoBytes();
    }

    for (i=0; i<8; i++) {
    // 16 bytes of QualConst
        checkSum += _adv_getTwoBytes();
    }

    if (checkSum != _adv_getTwoBytes()) // checksum is NOT included in checksum calc
        _adv_config.checksumFlag = 1; // failed
    else
        _adv_config.checksumFlag = 0; // ok

    return _adv_receiveAck();
}



int adv_convertClock(const struct adv_clock *clock, time_t *clockTime)
// convert ADV clock structure to a time_t
// returns the time as a time_t
// returns 0 if success, 1 if fail
{
    if (!clock) return 1;
    if (!clockTime) return 1;

    // use current time to get time zone and offset
    //time_t currTime = time(NULL);
    //struct tm *tmStruct = localtime(&currTime);
    struct tm *tmStruct = get_RTC_time();
    // luckily struct tm is a C thing, not a C++ thing, so we'll keep it
    // sample.c uses it as well
    // it's in <time.h>. which is included in <system.h>
    // sample.c uses get_RTC_time(), which uses localtime() to convert a time_t to local time
    // time(NULL) is the function that gets you current time, replaced by a ROM call in this system

    tmStruct->tm_sec = (int)_adv_BCDToChar(clock->cSecond);
    tmStruct->tm_min = (int)_adv_BCDToChar(clock->cMinute);
    tmStruct->tm_hour = (int)_adv_BCDToChar(clock->cHour);
    tmStruct->tm_mday = (int)_adv_BCDToChar(clock->cDay);
    tmStruct->tm_mon = (int)_adv_BCDToChar(clock->cMonth);
    tmStruct->tm_year = (int)_adv_BCDToChar(clock->cYear);
    tmStruct->tm_isdst = -1; // mktime will try to determine

   // adjust for specifics of struct tm
    tmStruct->tm_mon -= 1;
    if (tmStruct->tm_year < 90) tmStruct->tm_year += 100;

    *clockTime = mktime(tmStruct);

    return 0;
}



int adv_getDatHeader(struct adv_dataHeader *datHdrStruct)
// get a Vector data header, starting from beginning
// stores in datHdrStruct
// returns 0 if success, 1 if fail
{
    if (!datHdrStruct) return 1;

    int i;

    // for checksum
    int16_t checkSum;
    int16_t recWord;

    // check we're getting the right structure
    int ID = _adv_sortIncomingDataStruct(&checkSum);
    if (ID == -1) return 1; // means transmission or sync byte failed- error msg already printed
    if (ID != ADV_IDHDR) {
        uprintf("Did not receive correct structure ID. Got %#x\n", ID);
        return 1;
    }

    if ((recWord = _adv_getTwoBytes()) != 21) {
        uprintf("Problem with size bytes.  Got %d words.\n", recWord);
        return 1;
    }

    checkSum += recWord;

    // now walk through the return feed and parse it in

    char clockByteStream[6];
    for (i=0; i<6; i++) {
        clockByteStream[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(clockByteStream[i-1], clockByteStream[i]);
    }
    _adv_clockBytesToStruct(clockByteStream, &(datHdrStruct->timestamp));

    datHdrStruct->NRecords = (uint16_t)_adv_getTwoBytes();
    checkSum += (int16_t)(datHdrStruct->NRecords);

    for (i=0; i<(NUMBEAMS+1); i++) {
        datHdrStruct->Noise[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int((datHdrStruct->Noise)[i-1], (datHdrStruct->Noise)[i]);
    }
    for (i=0; i<(NUMBEAMS+1); i++) {
        datHdrStruct->Correlation[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int((datHdrStruct->Correlation)[i-1], (datHdrStruct->Correlation)[i]);
    }

    for (i=0; i<10; i++) {
    // 20 bytes of spare
        checkSum += _adv_getTwoBytes();
    }

    if (checkSum != _adv_getTwoBytes())  // checksum is NOT included in checksum calc
        datHdrStruct->checksumFlag = 1; // failed
    else
        datHdrStruct->checksumFlag = 0; // ok

    return 0;
}


int adv_getProbeChkDat(struct adv_probeChkData *prbChkStruct)
// get a Vector probe check data, starting from beginning
// stores in prbChkStruct
// returns 0 if success, 1 if fail
{
    if (!prbChkStruct) return 1;

    int i, j;

    // for checksum
    int16_t checkSum;
    int16_t recWord;

    // check we're getting the right structure
    int ID = _adv_sortIncomingDataStruct(&checkSum);
    if (ID == -1) return 1; // means transmission or sync byte failed- error msg already printed
    if (ID != ADV_IDPCK) {
        uprintf("Did not receive correct structure ID. Got %#x\n", ID);
        return 1;
    }

    if ((recWord = _adv_getTwoBytes()) != (5 + PCHK_NUMSAMPLES*NUMBEAMS/2)) { // amp data are 1 byte each, so 2 per word
        uprintf("Problem with size bytes.  Got %d words.\n", recWord);
        return 1;
    }

    checkSum += recWord;

    // now walk through the return feed and parse it in

    prbChkStruct->Samples = (uint16_t)_adv_getTwoBytes();
    checkSum += (int16_t)(prbChkStruct->Samples);

    prbChkStruct->FirstSample = (uint16_t)_adv_getTwoBytes();
    checkSum += (int16_t)(prbChkStruct->FirstSample);

    for (i=0; i<NUMBEAMS; i++) {
        for (j=0; j<PCHK_NUMSAMPLES; j++) {
            (prbChkStruct->Amp)[i][j] = _adv_getc();
            if ((j/2)*2 != j) checkSum += _adv_twoBytes_to_int((prbChkStruct->Amp)[i][j-1], (prbChkStruct->Amp)[i][j]);
        }
    }

    if (checkSum != _adv_getTwoBytes()) // checksum is NOT included in checksum calc
        prbChkStruct->checksumFlag = 1; // failed
    else
        prbChkStruct->checksumFlag = 0; // ok

    return 0;
}


int adv_getVelData(struct adv_velocityData *velDatStruct, int16_t checkSum)
// get a Vector velocity data, starting from offset 2 (assumes it's been properly ID'ed)
// stores in velDatStruct
// also need to pass in checkSum from the first two bytes
// returns 0 if success, 1 if fail
{
    if (!velDatStruct) return 1;

    int i;

    // for checksum
    int16_t *iterator = (int16_t *)&(velDatStruct->AnaIn2LSB);

    // now walk through the return feed and parse it in

    velDatStruct->AnaIn2LSB = (uint8_t)_adv_getc();
    velDatStruct->Count = (uint8_t)_adv_getc();
    velDatStruct->PressureMSB = (uint8_t)_adv_getc();
    velDatStruct->AnaIn2MSB = (uint8_t)_adv_getc();
    velDatStruct->PressureLSW = (uint16_t)_adv_getTwoBytes();
    velDatStruct->AnaIn1 = (uint16_t)_adv_getTwoBytes();
    for (i=0; i<NUMBEAMS; i++) {
        velDatStruct->Vel[i] = _adv_getTwoBytes();
    }
    for (i=0; i<NUMBEAMS; i++) {
        velDatStruct->Amp[i] = (uint8_t)_adv_getc();
    }
    for (i=0; i<NUMBEAMS; i++) {
        velDatStruct->Corr[i] = (uint8_t)_adv_getc();
    }

    for (i=0; i<10; i++) {
    //structure is 24 bytes (12 words), omit sync/ID and checksum
        checkSum += iterator[i];
    }

    if (checkSum != _adv_getTwoBytes())  // checksum is NOT included in checksum calc
        velDatStruct->checksumFlag = 1; // failed
    else
        velDatStruct->checksumFlag = 0; // ok

    return 0;
}


int adv_getSysData(struct adv_sysData *sysDatStruct, int16_t checkSum)
// get a Vector system data, starting from offset 2 (assumes it's been properly ID'ed)
// stores in sysDatStruct
// also need to pass in checkSum from the first two bytes
// returns 0 if success, 1 if fail
{
    if (!sysDatStruct) return 1;

    int i;

    // for checksum
    int16_t recWord;
    int16_t *iterator = (int16_t *)&(sysDatStruct->Battery);

    if ((recWord = _adv_getTwoBytes()) != 14) {
        uprintf("Problem with size bytes.  Got %d words.\n", recWord);
        return 1;
    }
    checkSum += recWord;

    // now walk through the return feed and parse it in

    char clockByteStream[6];
    for (i=0; i<6; i++) {
        clockByteStream[i] = _adv_getc();
        if ((i/2)*2 != i) checkSum += _adv_twoBytes_to_int(clockByteStream[i-1], clockByteStream[i]);
    }
    _adv_clockBytesToStruct(clockByteStream, &(sysDatStruct->timestamp));

    sysDatStruct->Battery = (uint16_t)_adv_getTwoBytes();
    sysDatStruct->SoundSpeed = (uint16_t)_adv_getTwoBytes();
    sysDatStruct->Heading = _adv_getTwoBytes();
    sysDatStruct->Pitch = _adv_getTwoBytes();
    sysDatStruct->Roll = _adv_getTwoBytes();
    sysDatStruct->Temperature = _adv_getTwoBytes();
    sysDatStruct->Error = _adv_getc();
    sysDatStruct->Status = _adv_getc();
    sysDatStruct->AnaIn = (uint16_t)_adv_getTwoBytes();

    for (i=0; i<8; i++) {
    //structure is 28 bytes (14 words), omit sync/ID, size, 3 word clock, checksum
        checkSum += iterator[i];
    }

    if (checkSum != _adv_getTwoBytes())  // checksum is NOT included in checksum calc
        sysDatStruct->checksumFlag = 1; // failed
    else
        sysDatStruct->checksumFlag = 0; // ok

    return 0;
}


//******************************************************************************
//* Public Methods - Other Commands
//******************************************************************************

int adv_printClock(const struct adv_clock *clock, FIL* ofs)
// prints clock in month, day, yr, hr, min, sec format to ofs
// separated by tabs - as used in printing out data structures
// returns 0 if success, 1 if fail
{
    if (!clock) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    int year = (int)_adv_BCDToChar(clock->cYear);
    if (year < 90) year += 100;
    year += 1900;

    // file output is different in C and C++
    // follow model of existing code by moving my numbers to a buffer and then printing the buffer
    // use separate lines to keep it cleaner, could do it all in one line too
    // f_puts would save some trouble, but seems not to be implemented here...
    indx += sprintf(&prnBuf[indx], "%d\t", year);
    indx += sprintf(&prnBuf[indx], "%d\t", (int)_adv_BCDToChar(clock->cMonth));
    indx += sprintf(&prnBuf[indx], "%d\t", (int)_adv_BCDToChar(clock->cDay));
    indx += sprintf(&prnBuf[indx], "%d\t", (int)_adv_BCDToChar(clock->cHour));
    indx += sprintf(&prnBuf[indx], "%d\t", (int)_adv_BCDToChar(clock->cMinute));
    indx += sprintf(&prnBuf[indx], "%d\t", (int)_adv_BCDToChar(clock->cSecond));

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
      if(fresult != FR_OK)
      {
          uprintf("f_write error: %s\n", StringFromFresult(fresult));
          // StringFromFresult is in microsd.c
          return 1;
      }

    return 0;
}


int adv_printUserConfig(FIL* ofs, int txFlg)
// output all of it
// return 0 if success, 1 if failure (unable to write)
// 10/26/21 need to be able to output this to screen, and for C, can no longer pass cout as the filestream
// so we'll use txFlg as fast_sample() does - pass in 1 to output to screen
// and give it NULL for the ofs to not output that
{
    if (ofs == NULL & txFlg != 1) // well i don't know what you're doing in this case
        return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    time_t deployTime;
    int result = adv_convertClock(&(_adv_config.clockDeploy), &deployTime);
    if (result == 1) {
        uprintf("ADV convert clock failed\n");
        return 1;
    }

    struct tm *time_struct = localtime(&deployTime);
    unsigned char deployTimeStr[256];

    strftime(deployTimeStr, sizeof(deployTimeStr), "%A, %B %d %Y, %I:%M %p", time_struct);

    // file output is different in C and C++
    // follow model of existing code by moving my numbers to a buffer and then printing the buffer
    // use separate lines to keep it cleaner, could do it all in one line too
    // f_puts would save some trouble, but seems not to be implemented here...
    indx += sprintf(&prnBuf[indx], "Deployment configuration information:\n");
    indx += sprintf(&prnBuf[indx], "\tTransmit pulse length (counts) [T1]:\t%d\n", _adv_config.T1);
    indx += sprintf(&prnBuf[indx], "\tBlanking distance (counts) [T2]:\t%d\n", _adv_config.T2);
    indx += sprintf(&prnBuf[indx], "\tReceive length (counts) [T3]:\t%d\n", _adv_config.T3);
    indx += sprintf(&prnBuf[indx], "\tTime between pings (counts) [T4]:\t%d\n", _adv_config.T4);
    indx += sprintf(&prnBuf[indx], "\tTime between burst sequences (counts) [T5]:\t%d\n", _adv_config.T5);
    indx += sprintf(&prnBuf[indx], "\tNumber of beam sequences per burst [NPings]:\t%d\n", _adv_config.NPings);
    indx += sprintf(&prnBuf[indx], "\t512 / Sampling Rate [AvgInterval]:\t%d\n", _adv_config.AvgInterval);
    indx += sprintf(&prnBuf[indx], "\tNumber of Beams [NBeams]:\t%d\n", _adv_config.NBeams);
    indx += sprintf(&prnBuf[indx], "\tTiming controller information [TimCtrlReg]:\n");
    indx += sprintf(&prnBuf[indx], "\t\tProfile:\t");
    if ( (_adv_config.TimCtrlReg >> 1) & 0x01)  { indx += sprintf(&prnBuf[indx], "continuous\n"); }
        else    { indx += sprintf(&prnBuf[indx], "single\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tMode:\t");
    if ( (_adv_config.TimCtrlReg >> 2) & 0x01)  { indx += sprintf(&prnBuf[indx], "continuous\n"); }
        else    { indx += sprintf(&prnBuf[indx], "burst\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tPower level:\t%d\n", (_adv_config.TimCtrlReg >> 5) & 0x03);
    indx += sprintf(&prnBuf[indx], "\t\tSynchout position:\t");
    if ( (_adv_config.TimCtrlReg >> 7) & 0x01)  { indx += sprintf(&prnBuf[indx], "end of sample\n"); }
        else    { indx += sprintf(&prnBuf[indx], "middle of sample\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tSample on synch:\t");
    if ( (_adv_config.TimCtrlReg >> 8) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled, rising edge\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tStart on synch:\t");
    if ( (_adv_config.TimCtrlReg >> 9) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled, rising edge\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\tCompass update rate [CompassUpdRate]:\t%d\n", _adv_config.CompassUpdRate);
    indx += sprintf(&prnBuf[indx], "\tCoordinate system [CoordSystem]:\t");
    if (_adv_config.CoordSystem == 0)   { indx += sprintf(&prnBuf[indx], "ENU\n"); }
        else if (_adv_config.CoordSystem == 1)  { indx += sprintf(&prnBuf[indx], "XYZ\n"); }
        else if (_adv_config.CoordSystem == 2)  { indx += sprintf(&prnBuf[indx], "BEAM\n"); }
        else    { indx += sprintf(&prnBuf[indx], "Error in reading\n"); }
    indx += sprintf(&prnBuf[indx], "\tNumber of cells [NBins]:\t%d\n", _adv_config.NBins);
    indx += sprintf(&prnBuf[indx], "\tCell size [BinLength]:\t%d\n", _adv_config.BinLength);
    indx += sprintf(&prnBuf[indx], "\tMeasurement interval [MeasInterval]:\t%d\n", _adv_config.MeasInterval);
    indx += sprintf(&prnBuf[indx], "\tRecorder deployment name [DeployName]:\t%s\n",_adv_config.DeployName); // the one time the conversion to C has been useful
    indx += sprintf(&prnBuf[indx], "\tRecorder wrap mode [WrapMode]:\t");
    if (_adv_config.WrapMode == 0)  { indx += sprintf(&prnBuf[indx], "no wrap\n"); }
        else if (_adv_config.WrapMode == 1)   { indx += sprintf(&prnBuf[indx], "wrap when full\n"); }
        else    { indx += sprintf(&prnBuf[indx], "Error in reading\n"); }
    indx += sprintf(&prnBuf[indx], "\tDeployment start time [clockDeploy]:\t%s\n", deployTimeStr);
    indx += sprintf(&prnBuf[indx], "\tNumber of secs btwn diagnostics measurements [DiagInterval]:\t%d\n", _adv_config.DiagInterval);

    // we're going to exceed the print buffer size, so print it out now
    if (txFlg) {
        bw = UARTwrite(prnBuf, strlen(prnBuf));
        //uprintf("\nUARTwrite returned %d\n", bw);
        UARTFlushTx(FALSE);
    }
    if (ofs != NULL) {
        fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
        if(fresult != FR_OK)
        {
            uprintf("f_write error: %s\n", StringFromFresult(fresult));
            // StringFromFresult is in microsd.c
        }
    }

    indx = 0;

    indx += sprintf(&prnBuf[indx], "\tMode [Mode]: \n");
    indx += sprintf(&prnBuf[indx], "\t\tUse user spec'd sound speed:\t");
    if ( _adv_config.Mode & 0x01)  { indx += sprintf(&prnBuf[indx], "yes\n"); }
        else    { indx += sprintf(&prnBuf[indx], "no\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tDiagnostics/wave mode:\t");
    if ( (_adv_config.Mode >> 1) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tAnalog output mode:\t");
    if ( (_adv_config.Mode >> 2) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tOutput format:\t");
    if ( (_adv_config.Mode >> 3) & 0x01)  { indx += sprintf(&prnBuf[indx], "ADV\n"); }
        else    { indx += sprintf(&prnBuf[indx], "Vector\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tScaling:\t");
    if ( (_adv_config.Mode >> 4) & 0x01)  { indx += sprintf(&prnBuf[indx], "0.1 mm\n"); }
        else    { indx += sprintf(&prnBuf[indx], "1 mm\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tSerial output:\t");
    if ( (_adv_config.Mode >> 5) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tStage:\t");
    if ( (_adv_config.Mode >> 5) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tOutput power for analog input:\t");
    if ( (_adv_config.Mode >> 9) & 0x01)  { indx += sprintf(&prnBuf[indx], "enabled\n"); }
        else    { indx += sprintf(&prnBuf[indx], "disabled\n"); }
    indx += sprintf(&prnBuf[indx], "\tUser input sound speed adjustment factor [AdjSoundSpeed]:\t%d\n", _adv_config.AdjSoundSpeed);
    indx += sprintf(&prnBuf[indx], "\t# samples in diagnostics mode [NSampDiag]:\t%d\n", _adv_config.NSampDiag);
    indx += sprintf(&prnBuf[indx], "\t# beams/cell number to measure in diagnostics mode [NBeamsCellDiag]:\t%d\n", _adv_config.NBeamsCellDiag);
    indx += sprintf(&prnBuf[indx], "\t\t# pings in diagnostics/wave mode [NPingsDiag]:\t%d\n", _adv_config.NPingsDiag);
    indx += sprintf(&prnBuf[indx], "\tMode test [ModeTest]: \n");
    indx += sprintf(&prnBuf[indx], "\t\tCorrect using DSP filter:\t");
    if ( _adv_config.TimCtrlReg & 0x01)  { indx += sprintf(&prnBuf[indx], "filter\n"); }
        else    { indx += sprintf(&prnBuf[indx], "no filter\n"); }
    indx += sprintf(&prnBuf[indx], "\t\tFilter data output:\t");
    if ( (_adv_config.TimCtrlReg >> 1) & 0x01)  { indx += sprintf(&prnBuf[indx], "only correction part\n"); }
        else    { indx += sprintf(&prnBuf[indx], "total corrected velocity\n"); }
    indx += sprintf(&prnBuf[indx], "\tAnalog input address [AnaInAddr]:\t%d\n", _adv_config.AnaInAddr);
    indx += sprintf(&prnBuf[indx], "\tSoftware version [SWVersion]:\t%d\n", _adv_config.SWVersion);

    /*
        "\tVelocity adjustment table [VelAdjTable]:\t" ;
    for (i=0; i<180; i++) {
        ofs << (int)_adv_config.VelAdjTable[i] << " ";
    }
    ofs << "\n" <<
        "\tFile comments [Comments]:\t";
    for (i=0; i<180; i++) {
        ofs << (int)_adv_config.Comments[i] << " ";
    }
    ofs << "\n";
    */

    indx += sprintf(&prnBuf[indx], "\tNumber of samples per burst [B1]:\t%d\n", _adv_config.B1);
    indx += sprintf(&prnBuf[indx], "\tAnalog output scale factor [AnaOutScale]:\t%d\n", _adv_config.AnaOutScale);
    indx += sprintf(&prnBuf[indx], "\tCorrelation threshold for resolving ambiguities [CorrThresh]:\t%d\n", _adv_config.CorrThresh);
    indx += sprintf(&prnBuf[indx], "\tTransmit pulse length (counts) second lag [TiLag2]:\t%d\n", _adv_config.TiLag2);
    indx += sprintf(&prnBuf[indx], "\nChecksum flag (1=fail):\t%d", (int)(_adv_config.checksumFlag));

    if (txFlg) {
        bw = UARTwrite(prnBuf, strlen(prnBuf));
        //uprintf("\nUARTwrite returned %d\n", bw);
        UARTFlushTx(FALSE); // 11/16/21 this is absolutely necessary to avoid incomplete printouts
    }
    if (ofs != NULL) {
        fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
        if(fresult != FR_OK)
        {
            uprintf("f_write error: %s\n", StringFromFresult(fresult));
            // StringFromFresult is in microsd.c
            return 1;
        }
    }

    return 0;

}


void _adv_flushTx(bool bDiscard)
{
    uart2_tx_bufr_flush(bDiscard);
}


unsigned char _adv_BCDToChar(unsigned char cBCD)
// (for clock) convert from BCD to char - from Sys Integration manual p69
{
    unsigned char c;
    //cBCD = min(cBCD, 0x99);
    //c = min('a','b');
    cBCD = (cBCD>0x99)?0x99:cBCD;
    c = (cBCD & 0x0f);
    c += 10 * (cBCD >> 4);

    return c;
}



int adv_printDatHeader(struct adv_dataHeader *datHdrStruct, FIL* ofs)
// outputs contents of datHdrStruct to ofs
// returns 0 if success, 1 if fail
{
    if (!datHdrStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    time_t timestampTimeT;
    adv_convertClock(&(datHdrStruct->timestamp), &timestampTimeT);
    struct tm *parsedTime = localtime(&timestampTimeT);
    char timestampStr[256];
    strftime(timestampStr, 256, "%A, %B %d %Y, %I:%M %p", parsedTime);

    indx += sprintf(&prnBuf[indx], "Vector velocity data header:\n");
    indx += sprintf(&prnBuf[indx], "\tTimestamp:\t%s\n", timestampStr );
    indx += sprintf(&prnBuf[indx], "\tNumber of velocity samples:\t%d\n", datHdrStruct->NRecords);
    indx += sprintf(&prnBuf[indx], "\tNoise amplitudes:\n");
    indx += sprintf(&prnBuf[indx], "\t\tBeam 1:\t%d\n", (uint16_t)(datHdrStruct->Noise)[0]);
    indx += sprintf(&prnBuf[indx], "\t\tBeam 2:\t%d\n", (uint16_t)(datHdrStruct->Noise)[1]);
    indx += sprintf(&prnBuf[indx], "\t\tBeam 3:\t%d\n", (uint16_t)(datHdrStruct->Noise)[2]);
    indx += sprintf(&prnBuf[indx], "\tNoise correlations:\n");
    indx += sprintf(&prnBuf[indx], "\t\tBeam 1:\t%d\n", (uint16_t)(datHdrStruct->Correlation)[0]);
    indx += sprintf(&prnBuf[indx], "\t\tBeam 2:\t%d\n", (uint16_t)(datHdrStruct->Correlation)[1]);
    indx += sprintf(&prnBuf[indx], "\t\tBeam 3:\t%d\n", (uint16_t)(datHdrStruct->Correlation)[2]);
    indx += sprintf(&prnBuf[indx], "\nChecksum flag (1=fail):\t%d", (int)(datHdrStruct->checksumFlag));

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


int adv_printProbeChkDat(struct adv_probeChkData *prbChkStruct, FIL* ofs)
// outputs system data given in prbchkStruct to ofs
// this DOES print the header
// returns 0 if success, 1 if fail
{
    if (!prbChkStruct) return 1;

    int i, j, k, j_max;
    int numblocks = 4; // 300 pcheck points; 100 points uses 1200 chars (for sample# > 100)
    int sizePerBlock = round(PCHK_NUMSAMPLES / 4);
    // 11/17/21 keep needing to break pcheck into more blocks, as it is blowing up the 1024-char printout buffer
    // so switch to a loop to break it up

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    indx += sprintf(&prnBuf[indx], "Probe check data:\n");
    indx += sprintf(&prnBuf[indx], "Samples:\t%d\n", prbChkStruct->Samples);
    indx += sprintf(&prnBuf[indx], "First sample:\t%d\n", prbChkStruct->FirstSample);
    indx += sprintf(&prnBuf[indx], "Checksum flag (1=error):\t%d\n", (int)(prbChkStruct->checksumFlag));

    indx += sprintf(&prnBuf[indx], "Sample\tAmp B1 (counts)\tAmp B2 (counts)\tAmp B3 (counts)");
    // newline now at beginning of next line, so i can consolidate lines of code

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    for (k=1; k<=numblocks; k++) {
        if (k == numblocks) j_max = PCHK_NUMSAMPLES;
        else j_max = sizePerBlock * k;

        indx = 0;

        for (j=sizePerBlock*(k-1); j<j_max; j++) {
            indx += sprintf(&prnBuf[indx], "\n%d", j+1);
            for (i=0; i<NUMBEAMS; i++) {
                indx += sprintf(&prnBuf[indx], "\t%d", (uint16_t)(prbChkStruct->Amp)[i][j]);
            }
        }

        fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
        if(fresult != FR_OK)
        {
            uprintf("f_write error: %s\n", StringFromFresult(fresult));
            // StringFromFresult is in microsd.c
            return 1;
        }
    }

    return 0;

}


int adv_printVelDataHdr(FIL* ofs)
// print the header for the velocity data as printed by printVelData
// does NOT print \n at end
// returns 0 if success, 1 if fail
{
    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

#ifdef PRINT_ADV_TIMING
    indx += sprintf(&prnBuf[indx], "Elapsed time when rcvd (s)\t");
#endif
    indx += sprintf(&prnBuf[indx], "#\tVel (B1/X/E) (m/s)\tVel (B2/Y/N) (m/s)\tVel (B3/Z/U) (m/s)\t");
    indx += sprintf(&prnBuf[indx], "Ampl (B1) (counts)\tAmpl (B2) (counts)\tAmpl (B3) (counts)\t");
    indx += sprintf(&prnBuf[indx], "Corr (B1) (%%)\tCorr (B2) (%%)\tCorr (B3) (%%)\t");
    indx += sprintf(&prnBuf[indx], "Analog 1 (counts)\tAnalog 2 (counts)\tPressure (dBar)\tChecksum");

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


// no function overloading in C, just comment this out for now
/*
int adv_printVelData(struct velocityData *velDatStruct, FIL* ofs)
// outputs velocity data given in velDatStruct to ofs
// does NOT print \n at end
// returns 0 if success, 1 if fail
{
    if (!velDatStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    double vScaleFactor = 0.001;
    if ( (_adv_config.Mode >> 4) & 0x01) vScaleFactor = vScaleFactor / 10; // 0.1 mm, otherwise 1 mm

    // add 1 to count so it doesn't start at 0
    indx += sprintf(&prnBuf[indx], "%d\t%d\t", (uint16_t)velDatStruct->Count + 1, velDatStruct->Vel[0] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%d\t%d\t", velDatStruct->Vel[1] * vScaleFactor, velDatStruct->Vel[2] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%d\t%d\t", (uint16_t)velDatStruct->Amp[0], (uint16_t)velDatStruct->Amp[1]);
    indx += sprintf(&prnBuf[indx], "%d\t%d\t", (uint16_t)velDatStruct->Amp[2], (uint16_t)velDatStruct->Corr[0]);
    indx += sprintf(&prnBuf[indx], "%d\t%d\t", (uint16_t)velDatStruct->Corr[1], (uint16_t)velDatStruct->Corr[2]);
    indx += sprintf(&prnBuf[indx], "%d\t", (65536*velDatStruct->PressureMSB + velDatStruct->PressureLSW) * .001);
    indx += sprintf(&prnBuf[indx], "%d", (int)(velDatStruct->checksumFlag));

    fresult = f_write( &ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}
*/


int adv_printVelData(struct adv_velocityData *velDatStruct, FIL* ofs, int counter)
// outputs velocity data given in velDatStruct to ofs
// does NOT print \n at end
// returns 0 if success, 1 if fail
// this version accepts an integer counter to print in place of 8-bit count stored in velocity struct (rolls over at 256)
{
    if (!velDatStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    double vScaleFactor = 0.001;
    if ( (_adv_config.Mode >> 4) & 0x01) vScaleFactor = vScaleFactor / 10; // 0.1 mm, otherwise 1 mm

    // for .f types, precision field specifies number of digits after decimal point
    // https://docwiki.embarcadero.com/RADStudio/Sydney/en/Printf_Precision_Specifiers
    // C automatically just printed out number of sig digits for me
    // Vector seems to print 4 for velocity and 3 for pressure

    indx += sprintf(&prnBuf[indx], "%d\t%.4f\t", counter, velDatStruct->Vel[0] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%.4f\t%.4f\t", velDatStruct->Vel[1] * vScaleFactor, velDatStruct->Vel[2] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->Amp[0], (uint16_t)velDatStruct->Amp[1]);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->Amp[2], (uint16_t)velDatStruct->Corr[0]);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->Corr[1], (uint16_t)velDatStruct->Corr[2]);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->AnaIn1, (velDatStruct->AnaIn2MSB << 8) + velDatStruct->AnaIn2LSB);
    indx += sprintf(&prnBuf[indx], "%.3f\t", ((velDatStruct->PressureMSB << 16) + velDatStruct->PressureLSW) * .001);
    indx += sprintf(&prnBuf[indx], "%d", (int)(velDatStruct->checksumFlag));

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


int adv_printVelDataHdr_wSNR(FIL* ofs)
// print the header for the velocity data as printed by printVelData_wSNR
// wSNR version has columns for SNR as calc'ed based on noise amplitudes
// does NOT print \n at end
{

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

#ifdef PRINT_ADV_TIMING
    indx += sprintf(&prnBuf[indx], "Elapsed time when rcvd (s)\t");
#endif
    indx += sprintf(&prnBuf[indx], "#\tVel (B1/X/E) (m/s)\tVel (B2/Y/N) (m/s)\tVel (B3/Z/U) (m/s)\t");
    indx += sprintf(&prnBuf[indx], "Ampl (B1) (counts)\tAmpl (B2) (counts)\tAmpl (B3) (counts)\t");
    indx += sprintf(&prnBuf[indx], "SNR (B1) (dB)\tSNR (B2) (dB)\tSNR (B3) (dB)\t");
    indx += sprintf(&prnBuf[indx], "Corr (B1) (%%)\tCorr (B2) (%%)\tCorr (B3) (%%)\t");
    indx += sprintf(&prnBuf[indx], "Analog 1 (counts)\tAnalog 2 (counts)\tPressure (dBar)\tChecksum");

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


// no function overloading in C, just comment this out for now
/*
int adv_printVelData_wSNR(struct velocityData *velDatStruct, struct dataHeader *datHdrStruct, FIL* ofs)
// outputs velocity data given in velDatStruct to ofs
// has columns for SNR as calc'ed based on noise amplitudes in datHdrStruct
// does NOT print \n at end
// returns 0 if success, 1 if fail
{
    if (!velDatStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    double vScaleFactor = 0.001;
    if ( (_adv_config.Mode >> 4) & 0x01) vScaleFactor = vScaleFactor / 10; // 0.1 mm, otherwise 1 mm

    // add 1 to count so it doesn't start at 0
    ofs << (uint16_t)velDatStruct->Count + 1 << "\t" << velDatStruct->Vel[0] * vScaleFactor << "\t" <<
        velDatStruct->Vel[1] * vScaleFactor << "\t" << velDatStruct->Vel[2] * vScaleFactor << "\t" <<
        (uint16_t)velDatStruct->Amp[0] << "\t" << (uint16_t)velDatStruct->Amp[1] << "\t" <<
        (uint16_t)velDatStruct->Amp[2] << "\t" << fixed << setprecision(1) << //want SNRs to print with 1 decimal point only
        (velDatStruct->Amp[0] - datHdrStruct->Noise[0]) * SNR_SCALE << "\t" <<
        (velDatStruct->Amp[1] - datHdrStruct->Noise[1]) * SNR_SCALE << "\t" <<
        (velDatStruct->Amp[2] - datHdrStruct->Noise[2]) * SNR_SCALE << "\t";
    ofs.unsetf(ios::floatfield); ofs.precision(6);
    ofs << (uint16_t)velDatStruct->Corr[0] << "\t" << (uint16_t)velDatStruct->Corr[1] << "\t" <<
        (uint16_t)velDatStruct->Corr[2] << "\t" <<
        (65536*velDatStruct->PressureMSB + velDatStruct->PressureLSW) * .001 << "\t" <<
        (int)(velDatStruct->checksumFlag);

    return 0;

}
*/


int adv_printVelData_wSNR(struct adv_velocityData *velDatStruct, struct adv_dataHeader *datHdrStruct, FIL* ofs, int counter)
// outputs velocity data given in velDatStruct to ofs
// has columns for SNR as calc'ed based on noise amplitudes in datHdrStruct
// does NOT print \n at end
// returns 0 if success, 1 if fail
// this version accepts an integer counter to print in place of 8-bit count stored in velocity struct (rolls over at 256)
{
    if (!velDatStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    double vScaleFactor = 0.001;
    if ( (_adv_config.Mode >> 4) & 0x01) vScaleFactor = vScaleFactor / 10; // 0.1 mm, otherwise 1 mm

    // for .f types, precision field specifies number of digits after decimal point
    // https://docwiki.embarcadero.com/RADStudio/Sydney/en/Printf_Precision_Specifiers
    // C automatically just printed out number of sig digits for me
    // Vector seems to print 4 for velocity and 3 for pressure
    // SNR is calculated by me and I forced it to 1 decimal point

    indx += sprintf(&prnBuf[indx], "%d\t%.4f\t", counter, velDatStruct->Vel[0] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%.4f\t%.4f\t", velDatStruct->Vel[1] * vScaleFactor, velDatStruct->Vel[2] * vScaleFactor);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->Amp[0], (uint16_t)velDatStruct->Amp[1]);
    indx += sprintf(&prnBuf[indx], "%u\t", (uint16_t)velDatStruct->Amp[2]);
    indx += sprintf(&prnBuf[indx], "%.1f\t", (velDatStruct->Amp[0] - datHdrStruct->Noise[0]) * SNR_SCALE);
    indx += sprintf(&prnBuf[indx], "%.1f\t", (velDatStruct->Amp[1] - datHdrStruct->Noise[1]) * SNR_SCALE);
    indx += sprintf(&prnBuf[indx], "%.1f\t", (velDatStruct->Amp[2] - datHdrStruct->Noise[2]) * SNR_SCALE);
    indx += sprintf(&prnBuf[indx], "%u\t", (uint16_t)velDatStruct->Corr[0]);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->Corr[1], (uint16_t)velDatStruct->Corr[2]);
    indx += sprintf(&prnBuf[indx], "%u\t%u\t", (uint16_t)velDatStruct->AnaIn1, (velDatStruct->AnaIn2MSB << 8) + velDatStruct->AnaIn2LSB);
    indx += sprintf(&prnBuf[indx], "%.3f\t", ((velDatStruct->PressureMSB << 16) + velDatStruct->PressureLSW) * .001);
    indx += sprintf(&prnBuf[indx], "%d", (int)(velDatStruct->checksumFlag));

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


int adv_printSysDataHdr(FIL* ofs)
// print the header for the system data as printed by printSysData
// does NOT print \n at end
{
    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

#ifdef PRINT_ADV_TIMING
    indx += sprintf(&prnBuf[indx], "Elapsed time when rcvd (s)\t");
#endif
    indx += sprintf(&prnBuf[indx], "Mon\tDay\tYr\tHr\tMin\tSec\tErr Code\tStatus code\tBatt V (V)\t");
    indx += sprintf(&prnBuf[indx], "Soundspeed (m/s)\tHeading (degrees)\tPitch (degrees)\tRoll (degrees)\t");
    indx += sprintf(&prnBuf[indx], "Temp (degrees)\tChecksum");

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}


int adv_printSysData(struct adv_sysData *sysDatStruct, FIL* ofs)
// outputs system data given in sysDatStruct to ofs
// does NOT print /n at end
// returns 0 if success, 1 if fail
{
    if (!sysDatStruct) return 1;

    // file output variables
    FRESULT fresult;
    int bw;
    uint32_t indx = 0;

    adv_printClock(&(sysDatStruct->timestamp), ofs);

    // number of decimal points is based on the Nortek output and the C++ output (they match)
    // first, it's binary
    indx += sprintf(&prnBuf[indx], BYTE_TO_BINARY_PATTERN"\t", BYTE_TO_BINARY(sysDatStruct->Error));
    indx += sprintf(&prnBuf[indx], BYTE_TO_BINARY_PATTERN"\t", BYTE_TO_BINARY(sysDatStruct->Status));
    indx += sprintf(&prnBuf[indx], "%.1f\t%.1f\t", sysDatStruct->Battery * 0.1, sysDatStruct->SoundSpeed * 0.1);
    indx += sprintf(&prnBuf[indx], "%.1f\t%.1f\t", sysDatStruct->Heading * 0.1, sysDatStruct->Pitch * 0.1);
    indx += sprintf(&prnBuf[indx], "%.1f\t%.2f\t", sysDatStruct->Roll * 0.1, sysDatStruct->Temperature * 0.01);
    indx += sprintf(&prnBuf[indx], "%d", (int)(sysDatStruct->checksumFlag));

    fresult = f_write( ofs, prnBuf, strlen(prnBuf), (UINT*)&bw);
    if(fresult != FR_OK)
    {
        uprintf("f_write error: %s\n", StringFromFresult(fresult));
        // StringFromFresult is in microsd.c
        return 1;
    }

    return 0;

}



//******************************************************************************
//* Private Methods
//******************************************************************************

int _adv_sendTwoCharCmd(char *cmdToADV)
// send a two-character command to ADV.  cmdToADV is a 2-char null-terminated string
// return 0 for success, 1 for fail (only fails if input format was incorrect)
{
    // check the input
    if (cmdToADV[2] != '\0') return 1;

    // swap the characters because of little Endian-ness
    //char temp = cmdToADV[1];
    //cmdToADV[1] = cmdToADV[0];
    //cmdToADV[0] = temp;

    _adv_write(cmdToADV, 2);

    return 0;
}


int _adv_receiveAck()
// receive ADV's response of Ack or Nack
// return 0 if Ack, 1 if Nack, and 2 if something else
{
    Timer_10ms(TRUE); // reset 10ms tick - assumes timer not being used for anything else, otherwise need 2 time variables and calc a difference

    while (adv_rxBytesAvail() <= 0) {
        if ( Timer_10ms(FALSE) > ACK_TIMEOUT_MS/10) {
            uprintf("Timed out waiting for ADV ack.\n");
            return 2;
        }
    }

    char char1 = _adv_getc();
    char char2 = _adv_getc();

    if (char1 == ADV_ACK && char2 == ADV_ACK) return 0;
    if (char1 == ADV_NACK && char2 == ADV_NACK) return 1;

    return 2;
}


int16_t _adv_twoBytes_to_int(unsigned char byte1, unsigned char byte2)
// turns 2 byte hex response as might be received from ADV into a number
// Little Endian, so byte1 is the less significant byte
{
    return (int16_t)(byte2) * 256 + (int16_t)byte1;
}


int16_t _adv_getTwoBytes()
// get next word from ADV (2 bytes read as one int)
// returns 0 if error
// C porting issue - getc() will block if no byte, rather than returning -1 as previously used function.  need to check for bytes available?
// ideally we just have a built in timeou
// don't want to waste time checking for bytes available every single time this is called, so try omitting it and see how it goes
{
    /*
    int recByte1 = serialGetchar(_fdADVComm);
    int recByte2 = serialGetchar(_fdADVComm);

    if (recByte1 == -1 || recByte2 == -1) {
        cerr << "Byte not available on port.\n";
        return 0;
    }

    return _twoBytes_to_int((char)recByte1, (char)recByte2);
    */

    // saves processing time...?
    return _adv_twoBytes_to_int(_adv_getc(),_adv_getc());

}


int _adv_sortIncomingDataStruct(int16_t *checkSum)
// reads incoming two bytes, meant to correspond to start of an incoming data structure
// first byte should be sync byte
// returns an ID # (byte 2) to identify which structure is coming in, or -1 if sync byte didn't match or either byte didn't read properly
// ID#s match those given by Nortek
// stores a starting checksum value in *checkSum: 0xb58c + first two bytes read as one word
{
    *checkSum = ADV_CHECKSUM_BASE;
    unsigned char recByte, recByte2;

    // first byte is sync byte- same for all
    // port from c++ - it no longer returns -1 if no byte, it just blocks.  so instead, first check for bytes available
    // do both bytes together, and allow a timeout - this is often called right after sending the command
    // reasonable that there would be some dead time
    unsigned long time1;
    time1 = Timer_10ms(FALSE); // allows timer to be in use

    while (adv_rxBytesAvail() < 2) {
        if ( Timer_10ms(FALSE) - time1 > CMD_TIMEOUT_MS/10) {
            uprintf("Bytes not available on port.\n");
            return -1;
        }
    }

    recByte = _adv_getc();
    if (recByte != ADV_SYNCBYTE) {
        uprintf("Did not receive correct sync byte. Got %#x\n", recByte);
        return -1;
    }

    recByte2 = _adv_getc();

    (*checkSum) += _adv_twoBytes_to_int(recByte, recByte2);

    return recByte2;

}


int _adv_clockBytesToStruct(char *clockByteStream, struct adv_clock *clockStruct)
// convert 6-byte stream repr clock in BCD to a clock struct (both passed in)
// according to sys integration manual p. 12, this is the order they come in...
// return 0 if success, 1 if fail
{
    if (!clockStruct) return 1;

    clockStruct->cMinute = clockByteStream[0];
    clockStruct->cSecond = clockByteStream[1];
    clockStruct->cDay = clockByteStream[2];
    clockStruct->cHour = clockByteStream[3];
    clockStruct->cYear = clockByteStream[4];
    clockStruct->cMonth = clockByteStream[5];

    return 0;
}
