/*
 * MoSoS:  MBARI Ocean Sensor Operating System software for MFET, MpHOx, NanoFET, and MSC3 boards.
 *
 * Copyright (c) 2025 MBARI
 *
 * Licensed under open source MIT License.
 *
 * Thom Maughan (TM), MBARI, tm@mbari.org (starting 2/2018)
 *
 * Contributors are acknowledged at the bottom of main.c.
 *
 */

#include "main.h"

#include "stdint.h"
#include "system.h"
#include "sleep.h"

#include "init.h"
#include "test.h"
#include "config.h"

#include "user_io.h"
#include "microsd.h"
#include "fatfs/source/ff.h"
#include "ads1248_iso.h"
#include "ads1248_noniso.h"
#include "sleep.h"
#include "board_util.h"
#include "sample.h"
#include "uartstdio.h"
#include "serial.h"
#include "serial_cmd_fxn.h"
#include "apf.h"
#include "apf_init.h"
#include "apf_util.h"
#include "i2c_fram.h"

#include "assert.h"

// TM4c123g is Little Endian

extern int ina260_system_voltageCurrent( float *volts, float *amps );

void exec_main_menu(void);
void print_main_menu(void);
void exec_main_loop(void);
void deploy(deploy_state state, unsigned int wokebyserialFlg);
void help_menu(void);
void eeprom_firmware_qa_check(void);

void state_dump(char *tag);      // diagnostic
uint32_t compute_nextWakeUp(void);
uint32_t exec_command(uint32_t timeout);

extern void thom_zone_menu(void);       // defd in thomzone.c


/*
 * Global System variables.
 */
extern struct rxStateMach rx0;
extern uint32_t adc_data[];
extern char Optode_buff[];
extern float fBiasPos;  // used for read_bias_bat();
extern float fBiasNeg;

struct systemData sys_data;
static_assert((sizeof(sys_data) % 4) == 0, "sys_data size must be evenly divisible by 4");
struct qaData sys_qa;

struct systemSamplingData sys_samp;         // sys variables updated with each sample
struct sampData sampl;          // defd in system.h

unsigned char sBuf[SIZEOF_SBUF+4];      // defd in system.h, scratchpad buffer
unsigned char eBuf[SIZEOF_EBUF+4];

FIL FileObject;
FRESULT fRes;       //was fr;          // defd in main.c

int dbg_flag = 1;                 // global var used for dbg_printf
int ads1248noniso_flg = 0;

unsigned int databarFlg = 0;        //background sample mode flag
unsigned int barfSampleTime = 5;

uint32_t helpCnt = 0;

int calFileFlg = 0;             // calFileFlg is used for ymodem to receive and parse a cal file without a filesystem for storage - config.c, ymodem.c

int gBootError = 0;             // set to 1 if retrieve of NV structs fails in init()



extern void printSiliconDetails(void);
/*
 * main.c
 *
 * init
 * no-os exec loop, cooperative multitasking
 * sleep is deep hibernate which results in power off, reset via Wake (button or serial wake)
 *
 */
int main(void)
{
    uint32_t wakeSource;
    uint32_t retVal;

    // Since these parts have peripheral code in a mask rom, the peripheral library needs to know the silicon revision
    // to know which functions to add to flash and which to map to mask rom
    // as-is, we can't support building for multiple silicon revisions at the same time
    // we would need to have a build target per-revision
    // this will catch if the build target and silicon host do not match
    if( ! check_silicon_revision_matches_tiva_build() )
    {
        // probably can't call this since the uart isn't initialized yet
        // crash_mosos();

        // instead we will just spin on wfi with interrupts off
        _disable_interrupts();
        _dsb();
        _isb();

        for(;;)
        {
            // this should park us in a low power mode, since interrupts are turned off
            // wait for the external watchdog to reset us
            //APFTODO, figure out how to notify users of this problem.   This is potentially dangerous code when out on an APF Argo Profiling Float, for 7+ years

            // Spinning on WFI can crash, as not all peripherals are set up correctly for sleep mode
            // TODO: configure needed peripherals to be awake in sleep using MAP_SysCtlPeripheralSleepEnable
            // See tm4c manual caution in section 5.2.6.2
            // __wfi();
        }
    }

    // Set timezone to UTC, otherwise TI CCS thinks we are in US central time and gmtime is wrong
    {
        _tz.daylight = 0;
        _tz.timezone = 0;
        sprintf(_tz.tzname,  "%s", "UTC");
        sprintf(_tz.dstname, "%s", "STD");
    }

	if( init() <= 0 )		                // Initialize pins and ports and vars  calls init_msc which calls init_apf_global_vars
	{
	    int indx;

	    for(indx=0; indx<10; indx++)
	    {
	        //BLINK fatal warning for 5 seconds
	        led_power_on();             //led_green_on();     // power
	        led_sampling_off();
	        delay_msec(250);
	        led_power_off();
	        led_sampling_on();
            delay_msec(250);
	    }

        // Get ready to retry
        _disable_interrupts();
        _dsb();
        _isb();

        // Software reset
        MAP_SysCtlReset();

        // This should not be reached
        for(;;)
        {

        }
	}

	// printSiliconDetails();
	//uncommment the following line if baud is kooked and needs to be restored to 9600
	//apf_defaults();

	//override the mode retrieved from NV mem
#if BOARD_MSC == 3
	if(sys_data.msc_op == MSC1_OP)
	{
	    apf_fram.sys_sensor_mode = 0;   //MSC1 inits to zero after sleep and off
	}
#endif // #if BOARD_MSC == 3
	// Boot time measurement: from 3.3v at 3.25 to led_power_on, power on reset delay plus init();
	led_power_on();             //led_green_on();     // power
	led_sampling_on();          // turn sampling/red led on momentarily for oscope trigger of init timing
	led_sampling_off();

#if BOARD_NANOFET > 0
	//config_data_fields_nanofet(1);       //uncomment this line to force config to defaults
#endif

    print_pwron_msg();      // defd in serial.c, print !

#if BOARD_MSC == 3
    sys_rts(0);         // Assert CF2 RTS output (CTS input to APF, etc)
#endif // if BOARD_MSC == 3

	//printCurrentEpochTime();  //DEBUG  BUG in DEBUG, uncomment the following line, buildloadrun, then comment out
	//init_sys_data_default();     // TEMP, can't stop sampling

	//sys_data.diag = 0;   // Thomz DEBUG Menu's
	dbg_flag = sys_data.debug_flag;
	//wokebyserialFlg = 0;            // flag used during RTC deploy wakeup to handle case when woke by serial and return back to deployment 30 Dec 2021

	// Symbols for board builds are define in Project Properties (right click project, Select properties,
	// then select Build->ARM Compiler->Predefined Symbols.  New: Edit the BOARD_XXX and define = 1.  Only 1 board
	// should be =1, the deselected board is =0
#if BOARD_MFET == 1
    uprintf("\r\n***** ERROR: BOARD_MFET = 1 is NOT supported in this firmware version *****\r\n");
#endif
#if BOARD_MFET == 2
    dbg_printf("\r\n***** BOARD_MFET = 2 *****\r\n");
#endif
#if BOARD_MPHOX == 1
    dbg_printf("\r\n***** BOARD_MpHOx = 1 *****\r\n");
    uprintf("\r\n***** BOARD_MpHOx = 1 ***** Compiled on %s @ %s", __DATE__, __TIME__); prn_nl();
#endif
#if BOARD_NANOFET == 1
    dbg_printf("\r\n***** BOARD_NANOFET = 1 *****\r\n");
#endif
#if BOARD_MSC == 2
    dbg_printf("\r\n***** BOARD_MSC = 2  MpHOXrB + MSC2base *****\r\n");
#endif
#if BOARD_MSC == 3
#ifdef PART_TM4C123GH6PM
    dbg_printf("\r\n***** BOARD_MSC = 3  MSC3 Tiva 123 + MSC3 baseboard *****\r\n");
#endif
#ifdef PART_TM4C1290NCPDT
    dbg_printf("\r\n***** BOARD_MSC = 3  MSC3 Tiva 1290 + MSC3 baseboard *****\r\n");
#endif
#endif

    /*
     *
     * Re-write of the top level state machine to implement the following without as many layers
     * SLEEP/IDLE - state for a pre-deployment system
     *    - woke up by serial or reset switch
     * DEPLOY - after configuration of sampling parameters, this is the lower power state woke by
     *     A) real time clock based on sampling period
     *     B) console serial,  typically by the user to 'stop' rtc based sampling
     * COMMAND -  (polled mode) sampling period = 0
     *     A) console serial,  when sampling period = 0, it is in COMMAND, ie, polled mode and responds to 'ts' or 'sl'   STOP to get it out of DEPLOY/COMMAND mode
     * Use Cases:
     *    LABINSTRUMENT - wired, power cycled at times, typically polled. does not need to log to uSD.
     *    DATALOGGER - battery powered, self timed RTC based logging to uSD, insure the battery does not get too low to prevent uSD corruption
     *    'GLIDER" - power is being cycled.  power on, polled get_sample, save to uSD, respond to ts, sleep.  When the glider is on a down cast, power is turned off
     *    MOORED
     *    UNDERWAY SYSTEM
     *    LRAUV - streamed output
     *
     */

    //state_dump();  // THOM DEBUG

    // if in wakertc mode and woke by keystroke
    //  -  X seconds to type command
    //  -  nextWakeUp is computed, goes back to sleep
    // if in wakegpio mode and woke by keystroke
    //  - X seconds to receive command or 'human serial terminal input'
    // what woke me?  WAKERTC or WAKECONSOLE (aka WakeGPIO)
    wakeSource = wake_handler();   // was sleep_handler

    //exec loop
    while(1)
    {

        //
        switch(sys_samp.state)
        {
            case DEPLOYED:      // In datalogging via RTC wakeup or console wakeup polled sampling period
                // if deployed and woke by console, user wants to run a command
                if(wakeSource == WAKE_CONSOLE)  // serial console wokeup during RTC wakeup, at issue is with Aligned to hour sampling, a sample is taken on return thru deploy(DEPLOYED)
                {
                    dbg_printf("\r\nMAIN: DEPLOYED, calling exec_command\r\n");

                    // For now, keep the old exec_command for use in having limited commands during deployment mode.
                    retVal = exec_command(5);        // 5 second console char timeout, return 0 to stop deployment

                    if(retVal == 0)
                    {
                        // user has stopped the deployment
                        sys_samp.state = IDLE;
                        // this returns to the while(1) with IDLE which should result in exec_main_menu
                    }
                    else
                    {
                        //wokebyserialFlg = 1;        // Wake source is console, not aborted by user so resume deployment
                        if (sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] == EXEC_DEPLOY)
                        {
                            dbg_printf("\r\nMAIN: calling deploy(DEPLOYED) after key command\r\n");
                            deploy(DEPLOYED, 1);        // wokebyserialFlg = 1, does not return (calls sleep)
                        }

                        if (sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] == EXEC_FASTSAMP)
                        {
                            databarf_loop();    //test_get_sample_loop();     // start with this for now...
                            sys_samp.state = IDLE;
                        }

                    }
                }

                if(wakeSource == WAKE_RTC)
                {
                    if (sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] == EXEC_DEPLOY)
                    {
                        // RTC wakeup
                        dbg_printf("\r\nMAIN: calling deploy(DEPLOYED)\r\n");
                        deploy(DEPLOYED, 0);               // deploy(DEPLOYED) should not return, pwrOff/sleep is called
                    }

                    if (sys_data.app_cfg[APPCFG_EXEC_SWITCHERONI] == EXEC_FASTSAMP)
                    {
                        databarf_loop();
                        sys_samp.state = IDLE;
                    }
                }

                if(wakeSource == WAKE_NOTSET)
                {
                    dbg_printf("\r\n(wakeSource == WAKE_NOTSET)\r\n");      // bug fix, code gets into weird state if firmware is downloaded while it is in deployment mode. 4 Aug 2021
                    //uprintf("\r\nmain.c main() (wakeSource == WAKE_NOTSET)\r\n");
                    sys_samp.state = IDLE;
                    eeprom_sys_data_store();
                }
                break;

            case IDLE:
                // wakeup path when idle, or WAKEUP_RTC is aborted by exec_command
                dbg_printf("\r\nMAIN: state = IDLE\r\n");
            default:
                if(sys_samp.state != IDLE)
                    uprintf("ERROR in sys_samp.state = %u", sys_samp.state);
                sys_samp.state = IDLE;

                break;
        }

        // while(1) for handling comms
        m2m_statemachine_init();    // init serial state machine, turn off echo in ISR, print prompt

        //exec_main_loop(TIMEOUT_EXEC_MAIN_SEC);      // mainloop   100 (140)  APFTODO, m2m timeout is 10sec, h2m timeout is 100sec (30?)
        exec_main_loop();      // mainloop   100 (140)  APFTODO, m2m timeout is 10sec, h2m timeout is 100sec (30?)


        if(sys_samp.state == IDLE)
        {
            sleep(IDLE, 0);
        }
        else
        {
            deploy(DEPLOYED, 1);      // 1 says woke by serial, does not take sample
        }

        //hmmm
        prn_nl(); uprintf("life after switch");  prn_nl();

    }  // end of while(1)




}


void print_main_menu(void)
{
    eeprom_firmware_qa_check();     // Print noisy warning about EEPROM and firmware mismatch 22 Jul 2021

#if BOARD_MFET >= 2 || BOARD_MPHOX >= 1
    if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0)        //if(microSD_flg == 0)  6 Aug 2021
    {
        uprintf("\n\rMicroSD card is disabled, store=1 to ENABLE");
        prn_nl();
    }

    float fVbat = read_vbat();     //  print a warning if Vin is too low, especially for ymodem transfers  Bug fix 11 Mar 2022
    if( fVbat < (sys_data.dura_vin_min_volt + 0.005) )
    {
        prn_nl(); prn_nl(); uprintf("Voltage input is low (%.2fv < threshold %.2fv, avoid file operations", fVbat, sys_data.dura_vin_min_volt);  prn_nl();
    }
#endif

    // Print out the menu:
    //uprintf("\r\n\r\n\r\nMain Menu -- %s -- Will sleep in %u seconds.   Vin = %.2f", VERSION, DEPLOY_DELAY_SEC, fVbat);      // VERSION is defd in system.h
    prn_nl(); prn_nl(); prn_nl(); uprintf("Main Menu -- %s -- Will sleep in %u seconds", versionStr(VERSION_STRING_MAX_LEN), DEPLOY_DELAY_SEC);      // VERSION is defd in system.h


    // better workflow mapping, addresses some of the new lab user comments
    prn_nl(); uprintf("1 -- Configure");
    prn_nl(); uprintf("2 -- Test");
    prn_nl(); uprintf("3 -- Sleep");
    prn_nl(); uprintf("4 -- Deploy");
    prn_nl(); uprintf("5 -- Files");
    prn_nl(); uprintf("6 -- Commandline");
    prn_nl(); uprintf("7 -- ThomZone");
    prn_nl(); uprintf("H -- Help");
    prn_nl(); uprintf("Enter Selection: ");

}


void eeprom_firmware_qa_check(void)
{
    //check eeprom version against active firmware version
    int version_match;

    version_match = strncmp(sys_data.firmware_version, versionStr(0), sizeof(versionStr(0)));

    if(version_match != 0)
    {

		prn_nl(); prn_nl(); prn_nl(); prn_nl(); prn_nl(); 
        uprintf("************* ERROR: EEPROM data check failed (firmware upgrade) ***********************");  prn_nl();
        prn_nl();
        uprintf("*  EEPROM layout must match firmware, please run EEPROM copy / edit procedure  ");  prn_nl();
        prn_nl();
        uprintf("*  Version: %s, does not match firmware: %s", sys_data.firmware_version, versionStr(0));   prn_nl(); //         uprintf("*  QA: %s, does not match firmware: %s\r\n*\r\n", sys_data.qa.firmware_version, VERSION);
        prn_nl();
        uprintf("*  Run EEPROM data procedure:  ");  prn_nl();
        prn_nl();
        uprintf("*     1-Config Menu -> Y -- Advanced -> E -- Edit/Copy EEPROM sys_data ");  prn_nl();
        prn_nl();
        uprintf("*   or");  prn_nl();
        prn_nl();
        uprintf("*     1-Config Menu -> Y -- Advanced -> D -- assign Defaults to sys_data and sys_samp ");  prn_nl();
        prn_nl();
        uprintf("*   or");  prn_nl();
        prn_nl();
        uprintf("*     6-Command Line:    h   >defaults");  prn_nl();
        prn_nl();
        uprintf("****************************************************************************************");  prn_nl();
    }


}



// main loop
// handle serial communications
// handle time out
//void exec_main_loop(uint32_t timeout)       //APFTODO, remove arg, timeout is set inside while(1)
void exec_main_loop(void)       //APFTODO, remove arg, timeout is set inside while(1)
{

    uint32_t    startTicks;
    uint32_t    elapsedTicks;
    uint32_t    timeout;

    int         retVal;

#ifdef NOCODE
    uprintf("\r\nexec_main_loop");

    if(sys_samp.state == DEPLOYED)
        uprintf("  Deployed\r\n");
    if(sys_samp.state == IDLE)
        uprintf("  Idle\r\n");
#endif

#if BOARD_MSC == 3
    apf_m2m_mode_set(1);        //250msec * 1 for m2m terminal timeout
#endif // if BOARD_MSC == 3

    startTicks = Timer_1ms();
    while(1)
    {
        wdog_pet();     // defd board_util.c   wdog_pet called in delay_msec, sec, usec also.   1.6sec wdog, insure it's called in less than 1.0 sec worse

        // handle serial commands
        retVal = handle_m2m_comm();

        //retVal is >= 1 when a command is successful, a command is terminated, a valid char is queued,
        if(retVal >= 1) //character received
        {
            // reset timeout after a serial command is executed
            startTicks = Timer_1ms();


        }
        //if(retVal < 0)
        if(retVal == M2M_GOTO_SLEEP)   //-1
        {
            //uprintf("DBG: handle_m2m_comm(), retVal = %d, heading to sleep", retVal); prn_nl();  wait_consoleTx();
            // return from exec and deep sleep
            return;
        }
        //if retVal is 0, nothing received, keep running timeout
        if(sys_data.terminal_mode == H2M_MODE)
        {

            timeout = SERIAL_INACTIVITY_TIMEOUT_H2M_SEC;
#ifdef NOCODE
            //check if serial term time is less than serial inactivity timeout
            if(timeout*1000 < rx0.tick_ms_delim)
            {
                LOGERRORMSG("H2M Serial inactivity timeout less than time terminated input", (int)SERIAL_TIME_ERROR, apf_eeprom.sys_message_enable);
                rx0.tick_ms_delim = H2M_DELIM_MS_TIMEOUT;
            }
#endif
        }
        else    //M2M_MODE
        {

            timeout = SERIAL_INACTIVITY_TIMEOUT_M2M_SEC;

            //check if serial term time is less than serial inactivity timeout
            if(timeout*1000 < rx0.tick_ms_delim)
            {
                LOGERRORMSG("M2M Serial inactivity timeout less than time terminated input", (int)SERIAL_TIME_ERROR, apf_eeprom.sys_message_enable);
                rx0.tick_ms_delim = M2M_DELIM_MS_TIMEOUT;       //this assumes SERIAL_INACTIVITY_TIMEOUT_M2M_SEC*1000 < M2M_DELIM_MS_TIMEOUT
            }

        }



        // handle timeout for serial inactivity
        elapsedTicks = (Timer_1ms() - startTicks) / 1000UL;
        if( elapsedTicks > timeout )
        {
            //uprintf("DBG: handle_m2m_comm() elapsedTicks > timeout, heading to sleep"); prn_nl();  wait_consoleTx();
            // return from exec and head to deep sleep (hibernate)
            return; // TIMELIMITPASSED;  goto sleep
        }
        //handle timeout

        //handle continuous sample background thread - used for APF testing of file system
        //elapsedTicks = (Timer_1ms() - startTicks) / 1000UL;
        if( (elapsedTicks > barfSampleTime) && databarFlg == 1)
        {
            //databarf mode is implemented here...  continuous pH data   APFTODO, make this pH and ISUS data with apf.mode variable
            startTicks = Timer_1ms();       //this sets timing for background AND resets timeout for inactivity

#if BOARD_MSC == 3
            uprintf("%s: sensor mode = %d", __FUNCTION__, apf_fram.sys_sensor_mode);
            prn_nl();
            //TODO, pass in print=0, write=1 except when checking sample status
            if(apf_fram.sys_sensor_mode == ISUS_SENSOR_MODE)   //1
            {
                apf_take_sample_nitrate(1);
                prn_nl();
                //calls mms_takeIsusSample(&apf.acquire_time, apf_fram.CTD_Time, apf_fram.CTD_Temp, apf_fram.CTD_Salinity, apf_fram.CTD_Depth, 1);     //defd in mms_spec.c
            }
            else if(apf_fram.sys_sensor_mode == DURA_SENSOR_MODE)
            {
                apf_take_sample_pH(1);
                prn_nl();
            }
            else if (apf_fram.sys_sensor_mode == DURAISUS_SENSOR_MODE)   //mode=3 is only valid when databarFlg =1
            {
                // Test code: fill up file system
                apf_profileCount_inc_test();

                float volts, amps;
                ina260_system_voltageCurrent( &volts, &amps );        //system voltage and current

                uprintf("DBG: profile count = %u", apf_fram.profileCnt);  //prn_nl();
                uprintf(", Vin = %.2f, Iin = %.3f", volts, amps);  prn_nl();
                    //lamp power off

                apf_take_sample_nitrate(1);
                prn_nl();
                apf_take_sample_pH(1);
                prn_nl();
            }
            else
#endif // #if BOARD_MSC == 3
            {
                //pump_and_sample(1,1);
                get_sample(1,1);
                prn_nl();
            }

        }

    }

}


#if USE_MAIN_MENU >= 1

void exec_main_menu(void)
{
    char    input;

    UARTEchoSet(true);      // New 3 Jun 2024

    while(1)
    {
        print_main_menu();

        // Get user's selection, sleep after 30 sec timeout
        UARTFlushRx();

        // timer = DEPLOY_DELAY_SEC + read_hib_rtcc(); // 30 second delay | timer = 300 + read_hib_rtcc();
        const unsigned long t_0 = Timer_1ms();

        // main menu doforever loop
        while(1)
        {
            if( UARTRxBytesAvail() )
            {
                //input = get_key();      // key <CR>
                input = get_key_m2m();      // key <CR>
                //retVal = get_key_m2m();      // key <CR>
                //if(retVal >= ' ')
                  //  input = (char)retVal

                break;    // break from while(1)
            }

            const unsigned long t_i = Timer_1ms();
            if( (t_i - t_0) > (DEPLOY_DELAY_SEC*1000UL) )        // time has expired
            {
                // THOM 23Apr2019 - timeout on menu is resulting in a bug when SP is in sleep and a spurious character wakes it up, this results in 'deploy'
                switch(sys_samp.state)
                {
                    case DEPLOYED:      // RTC wakeup sampling period
                        // if woke up by spurious character and was deployed, go back to sleep
                        dbg_printf("\r\nEMM: DEPLOYED, calling deploy(DEPLOYED)\r\n");  // THOM DEBUG
                        deploy(DEPLOYED, 1);  // THOM new 7May2019
                        break;

                    case IDLE:
                    default:
                        dbg_printf("\r\nexec_main_menu:***** SLEEP *****\r\n");  // THOM DEBUG
                        sleep(IDLE, 0);
                        break;
                }

            }  // end of if(timeout)

        } // end of while(1) looking for key and timeout

        switch(input)
        {
#if USE_CONFIGURATION_MENU >= 1
            case '1':
                configuration_menu();
                break;
#endif
            case '2':
                test_menu();
                break;

            case '3':
                sleep(IDLE, 0);
                break;

            case '4':
                //deploy(START);
                deploy_start(1);
                break;

            //case '8':
              //  exec_command(5);
                //break;
#if USE_FS_CMD_TABLE >= 1
            case '5':
#if BOARD_MFET >= 2 || BOARD_MPHOX >= 1
            {
                float fVbat = read_vbat();     //  print a warning if Vin is too low, especially for ymodem transfers  Bug fix 11 Mar 2022
                if( fVbat < (sys_data.dura_vin_min_volt + 0.005) )
                {
                    prn_nl(); prn_nl(); uprintf("Voltage input is low (%.2fv < threshold %.2fv, avoid file operations!!!", fVbat, sys_data.dura_vin_min_volt); prn_nl(); 
                }

                if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0)   // Fix 6 Aug 2021
                {
                    prn_nl(); 
					uprintf("MicroSD card disabled, file commands are not available"); prn_nl();
                    uprintf("   To enable, use 1-Config -> A-App Config to edit app_cfg[APPCFG_MICROSD_ENABLE]=1");  prn_nl();
                }
                else
                {
                    if(microSD_flg == 1)
                    {
                        viewFiles();
                    }
                    else
                    {
                        prn_nl(); uprintf("MicroSD card not detected, file commands are not available"); prn_nl();
                    }
                }
            }
#endif
#if BOARD_NANOFET > 0
                prn_nl(); uprintf("MicroSD card is not available on NanoFET, file commands are not available"); prn_nl();
#endif
#if BOARD_MSC == 3
                if(microSD_flg == 1)
                {
                    viewFiles();
                }
                else
                {
                    prn_nl(); uprintf("MicroSD card not detected, file commands are not available"); prn_nl(); 
                }
#endif
                break;
#endif

            case 'h':
            case 'H':
                help_menu();
                break;

            case 'c':
            case 'C':
            case '6':
                //uprintf("/r/n>"); //prompt
                return;     // return from exec_main_menu
                //break;

            case '7':
                thom_zone_menu();
                break;


            default:
                // invalid char is received, then menu repaint
                //uprintf(" char = %02x ", input);
                prn_nl();
                return;     // return from exec_main_menu (if you hit enter, go back to commandline, per Jess 13 mar 2025
                //break;
        }

    }

}

#if 0
void new_exec_main(uint8_t input)
{
    switch(input)
    {
#if USE_CONFIGURATION_MENU >= 1
        case '1':
            configuration_menu();
            break;
#endif

        case '2':
            test_menu();
            break;

        case '3':
            sleep(IDLE, 0);
            break;

        case '4':
            //deploy(START);
            deploy_start();
            break;

        case '8':
            exec_command(5);
            break;

#if USE_FS_CMD_TABLE >= 1
        case '5':
#if BOARD_MFET >= 2 || BOARD_MPHOX >= 1
        {
            float fVbat = read_vbat();     //  print a warning if Vin is too low, especially for ymodem transfers  Bug fix 11 Mar 2022
            if( fVbat < (sys_data.dura_vin_min_volt + 0.005) )
            {
                prn_nl(); prn_nl(); uprintf("Voltage input is low (%.2fv < threshold %.2fv, avoid file operations!!!", fVbat, sys_data.dura_vin_min_volt); prn_nl(); 
            }

            if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 0)   // Fix 6 Aug 2021
            {
                prn_nl(); uprintf("MicroSD card disabled, file commands are not available"); prn_nl(); 
                uprintf("   To enable, use 1-Config -> A-App Config to edit app_cfg[APPCFG_MICROSD_ENABLE]=1"); prn_nl(); 
            }
            else
            {
                if(microSD_flg == 1)
                {
                    viewFiles();
                }
                else
                {
                    prn_nl(); uprintf("MicroSD card not detected, file commands are not available"); prn_nl(); 
                }
            }
        }
#endif
#if BOARD_NANOFET > 0
            prn_nl(); uprintf("MicroSD card is not available on NanoFET, file commands are not available"); prn_nl(); 
#endif
            break;
#endif

        case 'h':
        case 'H':
            help_menu();
            break;

        default:
            // if an invalid char is received, then reprint
            print_main_menu();
            break;
    }
}

#endif  // 0

#endif // USE_CONFIG_MENU >= 1

// Thom TODO this is used for sl command, need to fix this hack
int print_sampBuf(void)
{
    //return(uprintf("%s", sampBuf));

    int size;
    size = strlen((const char *)prnBuf);
    UARTwrite((const char *)prnBuf, size);


    return(size);
}


// get_data() is called by exec_command()
void get_data(void)
{
    int samp_byte;
    //int pumponFlg;
    char buff[81];
    FIL fileObject;
    FRESULT fresult;
    UINT br;
    bool line_end;
    //time_t current_time;        // new

    //pumponFlg = 0;

    UARTEchoSet(false);     // Turn off console echo so key hit doesn't mess up screen output

    // Open a file
    fresult = f_open(&fileObject, sys_data.fileName, FA_READ|FA_OPEN_ALWAYS);
    if(fresult != FR_OK)
    {
        uprintf("Error.txt f_open error: %s", StringFromFresult(fresult));  prn_nl();
    }

    // Seek to the next byte in data file since last gdata
    fresult = f_lseek(&fileObject, sys_samp.next_gdata_fptr);

    if(fresult != FR_OK)
    {
        uprintf("Error.txt f_lseek error: %s", StringFromFresult(fresult));  prn_nl();
    }

    // Print out the records
    line_end = false;

    do
    {
        fresult = f_read(&fileObject, buff, 80, &br);
        if(fresult != FR_OK)
        {
            uprintf("Error.txt f_read error: %s", StringFromFresult(fresult));   prn_nl();
        }

        buff[br] = '\0';

        // Wait till there is room in tx buffer, then print the buffer
        while( UARTTxBytesFree() <= 80 );
        uprintf("%s", buff);

        // Stop reading and printing if user hits key
        if(kbhit())
        {
            // Print out any remaining characters of the last sample
            do
            {
                fresult = f_read(&fileObject, buff, 80, &br);
                {
                    uprintf("Error.txt f_read error: %s", StringFromFresult(fresult));  prn_nl();
                }

                for(samp_byte = 0; samp_byte < br; samp_byte++)
                {
                    uprintf("%c", buff[samp_byte]);

                    if( buff[samp_byte] == '\n' )   // Look for the end of the line
                    {
                        line_end = true;
                        break;
                    }
                }
            } while(br == 80 && !line_end);

            break;
        }

    } while (br == 80 && !line_end);        // Keep doing this until the end of the file

    sys_samp.next_gdata_fptr = fileObject.fptr;     // Store the current file position for next gdata command
    UARTEchoSet(true);
}


/*
 * exec_command() is the execution loop for serial command processing when in 'deployed mode', limited commands (legacy support).
 * return 0 if user 'stops' deployment
 */

uint32_t exec_command(uint32_t timeout)
{
    //time_t start_time;
    int charCount, gdata, ts, stop, quit, dump, gh, gm, sl, ss, go, resume, exit, gc, ec, which;  //, to, tc;   // to, tc added for Dale Graves nsFOCE Jan 2020 Thom, nsFOCE team changed requirements so it was removed.
#if BOARD_MPHOX >= 1
    int pumpon, pumpoff;
#endif
    int pumponFlg;
    char buff[81];
    time_t current_time;        // new

    pumponFlg = 0;

    if(sys_data.app_cfg[APPCFG_DEPLOY_UARTECHO] == 0)
    {
        UARTEchoSet(false);  // 18 Aug 2021 Disable UART echo in deployment - this is for m2m / labview to reduce the amount of echo char parsing along with 0x0d, 0x0a echo
        // echoSet = false also disables backspace mgmt in isr
    }

    while(1)    // Command loop
    {
        if(sys_data.output == VERBOSE || sys_data.test_mode)
        {
            prn_nl();
#if BOARD_MPHOX >= 1
            uprintf("Enter command (ts, ss, gh, gm, gc, gdata, pumpon, pumpoff, stop): ");
#else
            uprintf("Enter command (ts, ss, gh, gm, gc, gdata, stop): ");
#endif
        }

        //flush the receive and transmit buffers
        UARTFlushRx();

        // get command with timeout in seconds
        charCount = getUserInputTimeLimit(buff, sizeof(buff), timeout);

        // If the time limit passed...
        if(charCount == TIMELIMITPASSED)
        {
            if(sys_data.output == VERBOSE) { prn_nl(); prn_nl(); uprintf("No command, resuming..."); }
            if(pumponFlg == 1)
            {
                pwr_aux_pwr1_off();
                uprintf("AUX-PWR1 OFF (no serial activity after %u sec, sleeping)", timeout);  prn_nl();
            }

            return(1);      // new 21 Jul 2021, this takes it back to sleep
        }
        else
        {
            // Parse command
            gdata = strncmp(buff, "gdata", 5);      // get data
            ts = strncmp(buff, "ts", 2);            // take sample
            ss = strncmp(buff, "ss", 2);            // start sample, needs to have sl within 6 sec
            gc = strncmp(buff, "gc", 2);            // get calibration coefficients
            ec = strncmp(buff, "ec", 2);            // export calibration coefficients in cal file format
            gh = strncmp(buff, "gh", 2);            // get data header
            gm = strncmp(buff, "gm", 2);            // get metadata header
#if BOARD_MPHOX >= 1
            pumpon = strncmp(buff, "pumpon", 6);
            pumpoff = strncmp(buff, "pumpoff", 7);
#endif
            dump = strncmp(buff, "dump", 4);        // dump
            go = strncmp(buff, "go", 2);            // go / resume
            resume = strncmp(buff, "resume", 6);    // resume
            stop = strncmp(buff, "stop", 4);        // stop deployment (sampling)
            exit = strncmp(buff, "exit", 4);        // stop deployment (sampling)
            quit = strncmp(buff, "quit", 4);        // stop deployment (sampling)
            which = strncmp(buff, "which", 4);      // print which loop, exec_command() the deploy loop handler in this instance (self wakeup on schedule datalogging)


            if( (gdata == 0) && (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) )    // Display samples since last gdata command 6 Aug 2021 Fixed
            {
                get_data();
            }

            // Take a sample and continue with deployment
            else if( ts == 0 )
            {
                //get_sample(1,1);
                pump_and_sample(1,1);       // 10 Apr 2022
                return(1);

            }
            else if( ss == 0 )
            {
                //flush the receive and transmit buffers
                UARTFlushRx();

                //get_sample(0,1);
                pump_and_sample(0,1);       // 10 Apr 2022

                // Wait for command 6 seconds, then back to sleep (and dump the data)
                charCount = getUserInputTimeLimit(buff, sizeof(buff), 6);

                if(charCount == TIMELIMITPASSED)  // if retVal is -1, timeout occurred
                {
                    if(sys_data.output == VERBOSE) {prn_nl(); prn_nl(); uprintf("No command, resuming deployment..."); }
                    return(1);
                }
                else
                {
                    // Parse command
                    sl = strncmp(buff, "sl", 2);            // send last sample, must have sent ss first
                    if( sl == 0 )
                    {
                        print_sampBuf();
                        wait_consoleTx();

                        return(1);

                    }
                    else
                    {
                        prn_nl(); uprintf("expected sl, received %s", buff); prn_nl(); 
                        // no return or break, should put the code back to looking for next command
                    }
                }

            }
            // Get cal coeff and dump to terminal
            else if( gc == 0 )
            {
                get_pH_sensor_cal_coeff();
                return(1);
            }
            // export cal coeff in cal file format
            else if( ec == 0 )
            {
                export_pH_sensor_cal_coeff();
                return(1);
            }
            // Get header info and dump to terminal
            else if( gh == 0 )
            {
                get_header();
                return(1);
            }
            // Get header info and dump to terminal
            else if( gm == 0 )
            {
                // THOM TODO:  Bug, sending gm while in DEPLOYED mode stops the sampling
                current_time = read_hib_rtcc();       // THOM TODO: add var called sys_data.deployment_time 20 Apr 2021
                print_metadata_header(current_time);

                return(1);      // return 1 says sleep appropriately

            }
#if BOARD_MPHOX >= 1
            else if( pumpon == 0 )
            {
                pumponFlg = 1;
                uprintf("AUX-PWR1 ON");  prn_nl();
                pwr_aux_pwr1_on();
                //return(1);              // new 21 Jul 2021
            }
            else if( pumpoff == 0 )
            {
                pumponFlg = 0;
                uprintf("AUX-PWR1 OFF");  prn_nl();
                pwr_aux_pwr1_off();
                //return(1);              // new 21 Jul 2021
            }
#endif
            // Exit deployment and return to main menu
            else if( (stop == 0) || (exit == 0) || (quit == 0))
            {

                sys_samp.state = IDLE;
                UARTEchoSet(true);
                fram_sys_samp_store();  // update sys_samp.state in FRAM
                return(0);
            }
            else if( dump == 0)
            {
                // state_dump("XXX");
                // with no return, more commands can be entered
                //return(1);
            }
            else if( go == 0 )
            {
                return(1);
            }
            else if( resume == 0 )
            {
                return(1);
            }
            // Invalid command
            else
            {
                if(sys_data.output == VERBOSE) {  prn_nl(); uprintf("Invalid command"); }    // Goes back to top of command loop
                uprintf("NAK");  prn_nl();
            }
        }
        dbg_printf("\r\nbottom of command loop\r\n");   // THOM debug
    }   // End of command loop

#ifdef WARNING_ABATEMENT_UNREACHABLE
    return(1);
#endif
}

uint32_t compute_nextWakeUp(void)
{
    time_t current_time;

    // Calculate the next sample time
    if(sys_data.sample_aligned)     // Sample time hour aligned?
    {
        current_time = read_hib_rtcc();
        //sys_data.deploy_time = current_time;        // new 20 Apr 2021, removed 25 May 2021

        sys_samp.nextWakeUp = (current_time / 3600) * 3600;         // Find last hour

        // THOM TODO: 2 Jul ??? this code needs an error check to insure time is not whacked - have seen a hang here
        do
        {
            sys_samp.nextWakeUp += sys_data.sampling_period;
        } while(sys_samp.nextWakeUp < current_time);
    }
    else
    {
        sys_samp.nextWakeUp += sys_data.sampling_period;   // Not hour aligned
    }
    return(sys_samp.nextWakeUp);

}


void deploy_start(char askForNotes)
{
    time_t current_time, usersTime;

    char buff[100];
    int response;
    uint32_t temp;

    // THOM 17Mar2022, comment out: sys_data.test_mode = 0;         // SBE pump will run if not in test mode

    // Set custom start time, test mode, removed before 2018...

    // Start immediately
    current_time = read_hib_rtcc();

    sys_samp.nextWakeUp = current_time;
    sys_data.deploy_time = current_time + 1;        //added +1 to prevent time rollover  25 May 2021



#ifdef ALIGN_FIRSTSAMPLE   // Per Yui, this is OK to have the first sample 'not aligned', it's useful to see the first sample right away especially if on a 30 min schedule
    if(sys_data.sample_aligned)     // Sample time hour aligned?
    {
        current_time = read_hib_rtcc();
        //sys_data.deploy_time = current_time;        // new 20 Apr 2021, removed 25 May 2021

        sys_samp.nextWakeUp = (current_time / 3600) * 3600;         // Find last hour
        //sys_samp.nextWakeUp = ((current_time+1) / 3600) * 3600;    // new 26 May 2021


        // THOM TODO: 2 Jul ??? this code needs an error check to insure time is not whacked - have seen a hang here
        do
        {
            sys_samp.nextWakeUp += sys_data.sampling_period;
        } while(sys_samp.nextWakeUp < current_time);
    }
#endif

    //THOM 17Mar2022 commentout: sys_samp.pump_seq_state = 1;   // initialize pump sequence variable  THOM TODO - is this the right init?
    pump_and_sample_start();

    if(sys_data.pump1_ontime > 0)       // THOM todo, this should be pump_seq_type != 0
    {
        prn_nl(); uprintf("SBE pump will run and must be immersed at start time!"); prn_nl(); 

        prn_nl(); uprintf(" Delayed start feature is disabled... Next pHFET-SOS release should have it, feedback on delay parm entry is requested. "); prn_nl(); 

        sys_data.test_mode = 0;
		prn_nl(); prn_nl(); 
        response = yesOrNoMenuChoice("    Pump Test Mode ([Y] allows testing without activating pump)? (Y/N) [N]? ", NO);
        if(response == YES)
        {
            prn_nl(); uprintf("Pump test mode active"); prn_nl(); 
            sys_data.test_mode = 1;
        }

        // get response
        //response = yesOrNoMenuChoice("\r\n\r\n    Do you wish to enter a pump delayed start? (Y/N) [N]? ", NO);
        sys_data.start_pump_time = 0;       // new 10 Apr 2022, appears to be non-initialized, this is the means for a delay before pump starts
        sys_samp.start_pump_cntdown = 0;    // new 10 Apr 2022, try to fix broken statemachine
		prn_nl(); prn_nl(); 
        response = yesOrNoMenuChoice("    Enter pump delayed start? (Y/N) [N]? ", NO);
        if(response == YES)
        {
            // enter as a delay in min
			prn_nl(); prn_nl(); 
            response = yesOrNoMenuChoice("    Pump start delay entry in Minutes (Y) or DateTime (N)? (Y/N) [Y]? ", YES);
            if(response == YES)
            {
                sys_data.start_pump_time = 0;     // in seconds
                temp = 0;
                //uprintf("\r\nEnter delay [%u] min: ", sys_data.start_pump_time/60);     // sys_samp.start_pump_cntdown
                prn_nl(); uprintf("Enter delay [%u] min: ", temp);
                if(getUserInput(buff, 5))
                {
                    if(sscanf(buff, "%u", &temp) == 1)
                    {
                        // THOM TODO: check if valid start time (> 0, not too far in the future)

                        sys_data.start_pump_time = read_hib_rtcc() + temp*60;
                        sys_samp.start_pump_cntdown = temp*60;        // cntdown is in seconds
                        prn_nl(); uprintf("Pump start delay is %d minutes, pump starts at ", temp);
                        print_time_t_Time(sys_data.start_pump_time);
                    }
                }
            }
            else
            {
                // response == NO, enter datetime
                // get user delay until datetime
                usersTime = getDateAndTime();

                if(usersTime)
                {
                    prn_nl(); uprintf("Pump will start at: ");
                    print_time_t_Time(usersTime);           // print timedate
                    if(sys_data.GMT) uprintf(" GMT");
                    else uprintf(" Local");


                    sys_data.start_pump_time = usersTime;       // seconds since 1980
                    sys_samp.start_pump_cntdown = usersTime - read_hib_rtcc();    // in seconds
                    prn_nl(); uprintf("Pump delay %d minutes", sys_samp.start_pump_cntdown/60); prn_nl(); 


                    //uprintf("\r\n\r\nNote: Daylight Savings not observed. Use GMT when this will be an issue.\r\n");
                }

            }

        } // if response==YES, enter a delayed pump start
        else
        {
            // No entry of delayed start
            sys_data.start_pump_time = 0;
        }

    }

    //
    if (askForNotes)
    {
        enter_deployment_note(30);
    }

    // Begin deployment...
    sys_samp.current_sample = 0;    // Reset sample number
    sys_samp.state = DEPLOYED;          // assume RTC periodic wakeup sample period, unless period=0

    if(sys_data.sampling_period == 0)
    {
        prn_nl(); prn_nl(); uprintf("Deployed in polled mode, space to wake, then ts ..."); prn_nl(); prn_nl();         // THOM, polled mode  7 May 2018  (COMMAND)
        //sys_samp.state = COMMAND;
        sys_samp.state = IDLE;
    }

    if( print_write_metadata_header(current_time, 1, 1))  // const time_t currentTime, uint32_t printFlg, uint32_t writeFlg
    {
        // Error writing file (returns 1)
        sys_samp.state = IDLE;      // New 12 Apr 2021
        fram_sys_samp_store();  // do this in sleep...
        prn_nl(); prn_nl(); uprintf("File system error, check SD Card, Aborting deployment, sleeping in IDLE state"); prn_nl(); 
        wait_consoleTx();
        sleep(IDLE, 0);
        return;     // File access error, abort deployment
    }

    //uprintf("\nHit a key, then type 'stop' to end deployment\n");

    prn_nl(); prn_nl(); uprintf("Starting deployment...       Hit a key, then type 'stop' to end deployment.");

    if(sys_data.pump1_ontime > 0)
        uprintf("  Ctrl-X <Enter> to terminate pump cycle");

    prn_nl();    // THOM New 07 Jun 2018 - added crlf, updated to prn_nl 21 Aug 2024

    //BUG 28 Dec 2021 - reported by Yui, data header is not written (due to missing f_close in print_write_data_header)
    print_write_data_header(1,1);   // print and write data header

    wait_consoleTx();

    fram_sys_samp_store();

    // JLD - its ok heading into this
    // state_dump("DEPLOY_START");

    if(sys_samp.state == DEPLOYED)
    {
        sleep(DEPLOYED, sys_samp.nextWakeUp);   // Sleep with RTC wakeup, nextWakeUp is 'now' in sec (arg ignored, calc'd in sleep THOM TODO, fix this
    }
    else
    {
        sleep(IDLE, 0);         // Just IDLE and DEPLOYED (WAKEGPIO and WAKERTC)
    }

}

#if BOARD_MSC == 3
void takeSampleUsingMode(void)
{
    switch(apf_fram.sys_sensor_mode)
    {
        case ISUS_SENSOR_MODE :
            apf_take_sample_nitrate(sys_data.msc_op);          // per Dana Swift, APF11 release needs to operate MSC1 compatible
            break;
        case DURA_SENSOR_MODE :
            apf_take_sample_pH(sys_data.msc_op);          // per Dana Swift, APF11 release needs to operate MSC1 compatible, ts is 'silent sample', requires sl
            break;
        case DURAISUS_SENSOR_MODE :
            apf_take_sample_nitrate(sys_data.msc_op);          // per Dana Swift, APF11 release needs to operate MSC1 compatible
            prn_nl();
            apf_take_sample_pH(sys_data.msc_op);          // per Dana Swift, APF11 release needs to operate MSC1 compatible, ts is 'silent sample', requires sl
            break;
        case UNSELECTED_MODE :
            pump_and_sample(1,1);           // New v4, delay before sampling, pump sequencer, get_sample called from pump_and_sample();, board specifics handled in pump_and_sample
            break;
    }
}
#endif

/**************************************************************************************
 * deploy()
 * Accepts a deployment state argument that determines whether to start the deployment,
 * take a sample, or enter the command mode.
 ***************************************************************************************/
void deploy(deploy_state state, unsigned int wokebyserialFlg)
{
	switch(state)
	{
        case DEPLOYED:      // DEPLOY_AFTER_RTC_WAKEUP
            // RTC wakeup, take sample, compute next wakeup
            // ALIGN  Might have been woken by rs232, then timeout and drop thru here.   Causes sampling.  adding a global flag
            // if data is aligned, need to compute if this is time to sample or return to sleep
            // Note: if the wakeup happens with in the serial timeout (in exec_command(5)), then a sample is skipped unless getUserInputTimeLimit() in user_io.c checks for the proper sample time and does the sample there (big HACK)
            // Fix 30 Dec 2021
            if(wokebyserialFlg == 0)
            {
                // Take sample
                #if BOARD_MSC == 3
                takeSampleUsingMode();
                #else
                pump_and_sample(1,1);           // New v4, delay before sampling, pump sequencer, get_sample called from pump_and_sample();, board specifics handled in pump_and_sample
                #endif
            }

            compute_nextWakeUp();       // result in sys_samp.nextWakeUp
            sleep(DEPLOYED, sys_samp.nextWakeUp);   // Sleep until woken by RTC or keystroke
            break;

      //  case  DEPLOY_AFTER_CONSOLE_WAKEUP:
      //      compute_nextWakeUp();       // result in sys_samp.nextWakeUp
      //      sleep(DEPLOYED, sys_samp.nextWakeUp);   // Sleep until woken by RTC or keystroke
      //      break;


        case IDLE:      // should NOT ever need to handle IDLE being passed to DEPLOY 12 Apr 2021   Case not handled in old code
        default:
            prn_nl(); prn_nl(); uprintf("Deploy() Should NOT be IDLE, check arg error"); prn_nl(); prn_nl(); 
            wait_consoleTx();
            sleep(IDLE, 0);
            break;

	}

}



void state_dump(char *tag)
{
    time_t current_time;

    dbg_flag = 1;

    dbg_printf("\r\n\r\n%s\r\n", tag);

    if(sys_samp.state == DEPLOYED)
    {
        dbg_printf("DEPOYED\r\n");
    }
#ifdef NOCODE
    if( sys_samp.state == COMMAND)
    {
        dbg_printf("COMMAND\r\n");
    }
#endif
    if( sys_samp.state == IDLE)
    {
        dbg_printf("IDLE\r\n");
    }

    current_time = read_hib_rtcc();

    dbg_printf("Now %lu, next %lu, period %ly\r\n", (unsigned int)current_time, sys_samp.nextWakeUp, sys_data.sampling_period);
}

// called from tm4c123gh6pXX_startup_ccs.c
void faultISR_handler(void)
{
    prn_nl(); prn_nl();  uprintf("Crash MoSoS..., entering FaultISR infinite loop.  Hit Reset and report problem");  prn_nl(); prn_nl();
    wait_consoleTx();
    delay_msec_noDog(2000);

    while(1)
    {
        //let the watchdog reset the processor (1.6s)
    }
}



/*
* Commemorating the contributions:
*   Ken Johnson
*   Luke Colleti
*   Todd Martz
*   Rob Glatts, rglatts@ucsd.edu (from 4/2014)
*   Yui Takeshita, Scripps, MBARI (from 2018)
*   Taylor Wirth, twirth@ucsd.edu (from 4/2015)
*   David Muller, davehmuller@gmail.com
*   Germán Alfaro, alfaro.germanevera@gmail.com
*   Thom Maughan
*   Irene Hu
*   Aadithya 'Aadu' Prakash, aadu@uw.edu, passed away 28 Mar 2025
*

*/
const char* contrib_arr[] =
{
  "Easter Ellen Cupp, Scripps, Pioneer Biological Oceanographer",
  "Ken Johnson, MLML, MBARI (from 1990)",
  "Luke Colleti, MLML, MBARI (from 1990)",
  "Todd Martz MBARI, Scripps (from 2010)",
  "Rob Glatts, Scripps (from 4/2014)",
  "Yui Takeshita, Scripps, MBARI (from 2018)",
  "Taylor Wirth, Scripps (from 4/2015)",
  "David Muller, Scripps",
  "Germán Alfaro, Scripps",
  "Thom Maughan, HP, TI, MBARI (from Jan 2008, CarbonLab 2018, ChemSensors Feb 19, 2024) embedded software engineer, MoSoS developer",
  "Irene Hu, MBARI (from 2021), Sylvester the TuxCat operator, MSC3 Electrical Engineer",
  "Josh Plant, MBARI (from 2012), gold star Chem Sensors Lab manager",
  "Jessica Metter, MBARI (from 2023), gold star Chem Sensors Calibration and Python Test engineer",
  "Ben Davis, MBARI (from 2019), gold star Chem Sensors Module Mass Production",
  "Carole Sakamoto",
  "Hans Jannasch",
  "Bob Herlien, Digital Research, MBARI, CPM for Motorola 68K processor, CPM68k became PicoDos on Persistor CF2 used in MSC1",
  "Ginger Elrod",
  "Jose Rosal",
  "Jim Montgomery",
  "Chris Beebe",
  "Phil Bresnahan",
  "Aadithya 'Aadu' Prakash, Univ of Washington, died on the job 28 Mar 2025",
  "Dana Swift, Univ of Washington",
  "Rick Rupan, Univ of Washington (from 2001)",
  "Jacob Schloss, Suburban Marine (from 2024)",
  "Jim Davis, HP, DaviSoft (2025)",
  "Joe Warren, MBARI",
  "Ben Werb, MBARI",
  "Peter Walz, MBARI (from 2017)",
  "Gene Massion, MBARI",
  "Estanislao Cucunuchi, Native American",
  "Mariana Bif, MBARI",
  "Chris Scholin, MBARI",
  "Colonel Sanders, Kentucky Fried Chicken",
  "Pete Peterson, HP Fort Collins",
  "Julie Packard, Monterey Bay Aquarium, MBARI",
  "Lucille Packard, HP",
  "David Packard, HP, MBARI",
  "William Hewlett, HP, MBARI",
  "Flora Hewlett, HP",
  0
};
