/*
 * mms_spec.c   (ported from MSC1, Persistor CF2 cronin.c)
 *
 *  Created on: Feb 3, 2024
 *      Author: tm   Ported from from Luke Colleti CF2 persistor code, per Yui private handoff of source Jan 29, 2024
 *      Note: Luke suddenly retired 16 Feb 16 2024 and Ken Johnson asked for Thom's help at around 1pm on Feb 16 at Ocean Sciences in New Orleans
 */


#include "board_util.h"
#include "system.h"
#include "sleep.h"
#include "string.h"
#include "uartstdio.h"
#include "user_io.h"
#include "microsd.h"
#include "fatfs/source/ff.h"
#include "uart3.h"
#include "serial.h"
#include "sample.h"
#include "config.h"
#include "test.h"
#include "i2c_pcal64xx.h"
#include "i2c_bme280.h"
#include "i2c_ina260.h"
#include "i2c_fram.h"
#include "apf_init.h"
#include "apf_util.h"
#include "apf.h"
#include "apf_math.h"
#include "apf_numbr.h"
#include "apf_util.h"
#include "mms_test.h"
#include "mms_spec.h"
#include "serial_cmd_fxn.h"
#include "main.h"

#define SPECDELAY_MS 50 


//LampPwrOff => mms_lampPwrOff

extern unsigned char uart3_prn_buf[];
extern uint8_t pcal6416_shadow_port0;   // defd in i2c_pcal64xx.c
extern uint8_t pcal6416_shadow_port1;

struct mmsDriver mms;

uint16_t SpecData[260];                 //IH uses in apf_test.c (removed static)
uint16_t DarkData[260];

static double FitData[FITVALUES];
double AbsData[260];                    //IH uses in apf_test.c
static double AbsFitData[260];
static double ResidualsData[260];

int mmsDbgFlg = 0;      //=1 to enable printf debug statements

void mms_isusLampTest(void)
{
    uprintf("mms_isusLampTest in mms_spec.c Imp Es Nada"); prn_nl();
}

void mms_dev_test(int testNum)  // dev test
{
    // char input;
    int indx, i;
    int retVal;
    unsigned char rxBuf[128];

    //char   *ScanTypes[] = {"D", "S"};
    char   ebuf[ERRBUFSIZE];
    char   SpecCmd[128];
    int    loop;
    uint16_t  val1, val2;
    uint32_t bufIndx;


    if(testNum == 0)
    {
        mms_rts(1);   //is this rts = inactive

        mms_specPwrOn();   // mms_open (mms_uart_init, pcal6416 bit on) and wait cts

        uprintf("Sending V for version request to MMS Dig\r\n");
        while(1)
        {
            mms_putc('V');
            mms_putc(0x0d);

            retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);   // if( SerGetsTmout(abuf, sizeof(abuf), GET_ACK_TIMEOUT) > 0 )
            if(retVal >= 0)
                uprintf("rx %d: %s\r\n", retVal, rxBuf);  // DEBUG
            else
                uprintf("rx timed out\r\n");


            // any key on console to aport
            if( UARTRxBytesAvail() )
            {
                // input =
                get_key();
                break;
            }

            delay_msec(500);
        }
    //#endif
    }
    //----------------------------------------------------

    if(testNum == 1)    // this is a cut down version of mms_scanSpectra, WORKS 9/11/2024
    {
        mms_rts(1);   //is this rts = inactive

        mms_specPwrOn();    //mms_uart_init, mms_open, mms_rts(1)

        uprintf("dev ver of mms_scanSpectra (menu E)");

        //negate RTS output  << what does this mean?  not asserted?   when is rts set low
        //PinSet( 15 );
        mms_rts(1);


        //set spec trigger mode to "MBARI CUSTOM"
        //TUPuts(tup, "E5\r" );
        mms_tx_str("E5\r");
        delay_msec( 50 ); // was 50, appears to return a string
        // E5 returns the version string (not in CF2 code).   Can just delay and flushRx APFTODO
        retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);   //cr lf cr
        if(retVal >= 0)
        {
            //rx 25: MBARI v2.00; 2024-01-24
            uprintf("E5 rx %d: %s\r\n", retVal, rxBuf);  // DEBUG
            mms_flushRx();  // flush the cr
        }
        else
        {
            uprintf("E5 rx timed out waiting for version string response from E5\r\n");
        }


        //set spec scan buffer depth
        //sprintf(SpecCmd, "R%hu\r", scan_num );
        //mms_tx_str( SpecCmd );
        mms_tx_str("R1\r");
        delay_msec( 50 ); // was 50, some serial chars, maybe?
        retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);
        if(retVal >= 0)
        {
            //rx 3:
            uprintf("R1 rx %d: %s\r\n", retVal, rxBuf);  // DEBUG
        }
        else
        {
            uprintf("R1 rx timed out waiting for version string response from E5\r\n");
        }


        //set spec integration period
        sprintf(SpecCmd, "I%lu\r", apf_eeprom.isus_spec_period );
        mms_tx_str( (unsigned char *) SpecCmd );
        //mms_tx_str("I300\r");
        delay_msec( 50 );

        //for( loop=0; loop<scan_num; loop++ )
        for( loop=0; loop<1; loop++ )
        {

            //HIH6130_ReadTempHumidity( &sysTemperature, &sysHumidity );
            //APFTODO this hangs: bme280_read_data();   // why read humidity in the loop?

            apf_fram.isus_sample_time = read_hib_rtcc();

            //start "automatic" scanning
            mms_tx_str( "S\r" );

            // wait for SPEC_CTS to go lo (active), SPEC_CTS_TIMEOUT timeout 500msec
            retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);  //500msec
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                sprintf(ebuf, "Scan Timeout, Spectrometer CTS never asserted");
                LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
                mms_close();  // power off spec   NEW 9 Sep 2024
                return; //( TIMEOUT );
            }

            uprintf("1 cts went low 195\r\n");


            //wait for SPEC_CTS to return back high (released)

            retVal = mms_wait_cts(1, 15000);          // SPEC_INT_TIMEOUT timeout 15seconds
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
                LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
                mms_close();  // power off spec   NEW 9 Sep 2024
                return; //( TIMEOUT );
            }
            uprintf("1 cts went hi 283\r\n");


            //get data, one byte at a time, using RTS/CTS handshaking
            //TUPuts( tup, "g\r" );
            mms_tx_str( "g\r" );

            // wait for SPEC_CTS to go lo (active), timeout 500msec
            retVal = mms_wait_cts(0, 500);
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                sprintf(ebuf, "Scan Timeout, Spectrometer CTS never asserted");
                LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
                mms_close();  // power off spec   NEW 9 Sep 2024
                return; //( TIMEOUT );
            }

            uprintf("1 cts went lo 269\r\n");


            // MMS_UV Spectometer sends pixel 256 (400nm) first and pixel 1 (200nm) last.
            // Load data into array in the opposite oder, i.e., SpecData[0] = 200nm
            //TURxFlush( tup );
            mms_flushRx();
            bufIndx = 0;
            for( i = 255; i >= 0; i-- )
            {

                //PinClear( 15 );  //assert RTS output
                mms_rts(0);
                //val1 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get high byte of value */
                val1 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout in msec
                //PinSet( 15 );    //negate RTS output
                mms_rts(1);

                //Delay10us();
                delay_usec(10);

                //PinClear( 15 );  //assert RTS output
                mms_rts(0);      //assert RTS output
                //val2 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get low  byte of value */
                val2 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout in msec, if timeout, return is greater than 255
                //PinSet( 15 );    //negate RTS output
                mms_rts(1);      //negate RTS output

                //if( (val1 == -1) || (val2 == -1) )
                if( (val1 > 255) || (val2 > 255) )
                {
                    //printf("Data Timeout, cell = %d\n", i);
                    sprintf(ebuf, "Data Timeout, Data Byte Read");
                    LogErrorMsg(ebuf, (int)SPEC_DAT_READ_ERROR, apf_eeprom.sys_message_enable);
                    mms_close();  // power off spec   NEW 9 Sep 2024
                    return; //( TIMEOUT );
                }
                //uprintf("loop rx 2byte data from spec 327\r\n");

                prnBuf[bufIndx] = val1;
                bufIndx++;
                prnBuf[bufIndx] = val2;
                bufIndx++;
                //Data[i+(loop*256)] = ((val1 << 8) & 0xFF00) | (val2 & 0x00FF);

                //if( i == 255 ) //set first clocked pixel value to zero
                    //Data[i+(loop*256)] = 0;

            } // for

            prnBuf[510] = 0;
            prnBuf[511] = 0;

            //wait for SPEC_CTS to return back high (released)
            retVal = mms_wait_cts(1, 500);
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
                LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
                mms_close();  // power off spec   NEW 9 Sep 2024
                return; //( TIMEOUT );
            }

            uprintf("1 cts went hi 352\r\n");

        } //for( loop=0; loop<1; loop++ )

        // next step is to printf the output to the console

    }
    //----------------------------------------------------

    if(testNum == 2)
    {
        uprintf("attach loop back DB9 connector(rx to tx (2-3)) (rts to cts (7-8))");  prn_nl();
        uprintf("  success is on followed by off many times"); prn_nl();
        for(indx=0; indx<3000; indx++)
        {
            //mms_putc('U');      // for scope trigger
            mms_rts(1);
            retVal = mms_wait_cts(1, 500);
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                uprintf("timed out waiting for cts to go hi"); prn_nl();
            }
            //led_red_on();
            //led_green_on();
            delay_msec(25);

            //mms_putc('U');      // for scope trigger
            mms_rts(0);
            retVal = mms_wait_cts(0, 500);
            if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
            {
                uprintf("timed out waiting for cts to go lo"); prn_nl();
            }
            //led_red_off();
            //led_green_off();
            delay_msec(25);


            if( UARTRxBytesAvail() )
            {
                // input =
                get_key();
                uprintf("console key terminated loop"); prn_nl();
                break;
            }
        }
    }




    mms_close();
}

void mms_test_menu(void)
{
    time_t timer;
    char input;

    while(1)
    {
        prn_nl(); prn_nl();
        uprintf("MMS Spec Menu:"); prn_nl();
        //uprintf("1 -- mms_dev_test(1), hack of mms_scanSpectra()"); prn_nl();
        uprintf("2 -- mms_lampPwrOnUsingSpec()"); prn_nl();
        uprintf("3 -- mms_IsusLampTest()"); prn_nl();
        uprintf("4 -- mms_bake(10)"); prn_nl();
        uprintf("5 -- mms_takeIsusSample() - T1 command"); prn_nl();
        uprintf("6 -- mms_check_mmsDig_version"); prn_nl();
        uprintf("7 -- isusTest_menu_E"); prn_nl();
        uprintf("8 -- mms_scanSpectra()"); prn_nl();
        uprintf("9 -- Exit to Main Menu"); prn_nl(); prn_nl();
        uprintf("Enter Selection [9]: ");

        UARTFlushRx();
        timer = MENU_TIMEOUT_SEC + read_hib_rtcc();

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

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


        switch(input)
        {

            case '1':
                //mms_dev_test(1);  // dev test spec,  essentially mms_scanSpectra
                break;

            case '2':
                //SpecData is 256*upto 200 in CF2.  Limit this to 4 or less  Also def DarkData
                mms_lampPwrOnUsingSpec(SpecData, FALSE);
                break;

            case '3':
                mms_IsusLampTest();   // mms_test.c
                break;

            case '4':
                mms_bake_handler(10);
                break;

            case '5':
                //dbuf was 2048 in CF2...
//                mms_takeIsusSample(prnBuf, &apf.acquire_time, apf_fram.CTD_Time, apf_fram.CTD_Temp, apf_fram.CTD_Salinity, apf_fram.CTD_Depth);           //mms_dev_test(2);
                mms_takeIsusSample(&apf.acquire_time, apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth, 1);  // txFlg is true          //mms_dev_test(2);
                break;

            case '6':
                mms_check_mmsDig_version();
                break;

            case '7':
                isusTest_menu_E();
                break;

            case '8':
                //mms_scanSpectra(FILE *fptr, uint16_t scan_num, uint16_t *Data, unsigned char ScanType )
                //dat_file =
                mms_scanSpectra(1, SpecData, INTENSITY_SCAN, 1, 0);     //print, no write
                break;

            default: //Exit
                prn_nl();
                uprintf("Press enter to bring up main menu"); prn_nl();
                return;
        }

    }


}





// routines for operating the MMS UV Spectrometer.   Includes FiberLite fxn

//int LampPwrOnUsingSpec( TUPort *tup, ushort *Data, uchar ShowRef )
int mms_lampPwrOnUsingSpec( uint16_t *Data, uint8_t ShowRef )
{
    char ebuf[ERRBUFSIZE];
    uint32_t  scan_period;
    uint16_t scan_min, scan_max;
    int    indx;  //was i
    float fSysVolt, fSysAmp, fLampVolt, fLampAmp;

    //RTCTimer TmOut;

    uint32_t tick_ms_delta, tick_ms_start;

    if(mmsDbgFlg==1) { uprintf("mms_lampPwrOnUsingSpec in mms_spec.c"); prn_nl(); }

    if( apf.isus_lamp_pwr == TRUE )   //APFTODO, clean out unnecessary MSC1 code
        return( TRUE );

    if( apf.isus_spec_pwr != TRUE )
    {
        sprintf(ebuf, "Powerup Failure, Lamp requires spectrometer to be powered");
        LogErrorMsg(ebuf, (int)LAMP_PWR_ON_FAILURE, apf_eeprom.sys_message_enable);
        if( ShowRef == TRUE )
        {
            uprintf("  %s", ebuf); prn_nl();
        }
        return( ERROR );
    }

    if( ShowRef == TRUE ) 
	{
        uprintf("  RefLimit = %d", apf_eeprom.isus_ref_limit); prn_nl();
    }

    //    pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_PWR_ON, HIGH);  APFTODO, make a function, mms_lamp_pwr(1)
    pcal6416_shadow_port0 |= LAMP_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    apf_fram.isus_lampOn_ctr++;         //count the number of times the lamp is turned on
    apf.isus_lampStart_msec = Timer_1ms();

    //DelaySecs( 1 );
    delay_sec(1);

    //NEW, per the MSCInterfaceSpec, check if voltage is below ISUS_V_MIN, if low (spec was 8.0v, but that has been raised to 10v after a stir)
    ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );        //system voltage and current

    if(fSysVolt < sys_data.isus_vin_min_volt)      //ISUS_SYS_V_MIN)
    {
        if(mmsDbgFlg==1) { uprintf("DBG: Input Voltage, %.2f v, is too low, aborting ISUS sampling.  Current = %.3f a", fSysVolt, fSysAmp);  prn_nl(); }
        //lamp power off
        pcal6416_shadow_port0 &= ~LAMP_PWR_ON;
        pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

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

        return( ERROR );
    }

    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, (LAMP_DU_ENABLE | LAMP_W_ENABLE | LAMP_SHUT_ENABLE), HIGH);
    pcal6416_shadow_port0 |= (LAMP_DU_ENABLE | LAMP_W_ENABLE | LAMP_SHUT_ENABLE);
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    //DelayMilliSecs( 500 );
    delay_msec(500);

    indx = 0;
    scan_max = 0;
    scan_period = 250;

    //RTCCountdownTimerSetup(&TmOut, APF_LAMP_IGNITE_TIMEOUT);  APF_LAMP_IGNITE_WAIT_10000MS
    //if(mmsDbgFlg == 1) { uprintf("RTCCountdown...mms_lampPwrOnUsingSpec in mms_spec.c");  prn_nl();  }

    // IH adding fields to this
    if( ShowRef == TRUE || mmsDbgFlg == 1) {
        uprintf("  #, IntTime(ms), Max, SysV(V), SysI(A), LampV(V), LampI(A)"); prn_nl();
    }

    tick_ms_start = Timer_1ms();
    do
    {
        // Note LAMP_W is already on per the above
        //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, HIGH);
        pcal6416_shadow_port0 |= LAMP_W_ENABLE;
        pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
        //DelayMilliSecs( 100 );
        delay_msec(100);

        // IH 4/17/25
        ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );
        ina260_lamp_voltageCurrent( &fLampVolt, &fLampAmp );

        // IH 9/25/25 adding check for return value; otherwise it just returns 0s w/o informing user of error
        // not sure why mms_singleScanSpectra exists when we already have mms_scanSpectra
        if( mms_singleScanSpectra( scan_period, Data ) != TRUE ) {
            if(mmsDbgFlg == 1) { uprintf("mms_singleScanSpectra fail in lampPwrOnUsingSpec, ret err");  prn_nl();  }
            // this function does not turn on or turn off; calling function should handle turnoffs
            return( ERROR );
        }
        ShortVectorMinMax( 100, Data, &scan_min, &scan_max );

        //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, LOW);
        pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
        pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
        //DelayMilliSecs( 100 );
        delay_msec(100);

        indx++;

        if( ShowRef == TRUE || mmsDbgFlg == 1) {
            uprintf("  %d, %d, %d, %6.3f, %6.3f, %6.3f, %6.3f", indx, scan_period, scan_max, fSysVolt, fSysAmp, fLampVolt, fLampAmp); prn_nl();
        }

        scan_period = scan_period + (unsigned long)(indx*50);

        if( (scan_max < apf_eeprom.isus_ref_limit) && (indx > 4) )
        {
            LampCloseShutter();
            LampOpenShutter();
        }


        tick_ms_delta = (Timer_1ms() - tick_ms_start);
        if(tick_ms_delta >= APF_LAMP_IGNITE_WAIT_10000MS)       //10sec
        {
            //INA219_ReadVoltageCurrent( &volts, &amps );
            ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );
            //if(mmsDbgFlg == 1) { uprintf("Powerup Failure, Du Lamp could not turn on, RefLimit = %hu, VSYS = %.3f V, ISYS = %6.3f A", apf_eeprom.isus_ref_limit, fSysVolt, fSysAmp); prn_nl(); }

            sprintf(ebuf, "Powerup Failure, Du Lamp could not turn on, RefLimit = %hu, VSYS = %6.3f V, ISYS = %6.3f A/r/n", apf_eeprom.isus_ref_limit, fSysVolt, fSysAmp);
            LogErrorMsg(ebuf, (int)LAMP_PWR_ON_FAILURE, apf_eeprom.sys_message_enable);
            if( ShowRef == TRUE || mmsDbgFlg == 1) 
			{
                uprintf("%s", ebuf); prn_nl();
            }
            return( TIMEOUT );
        }

    } while( (scan_max < apf_eeprom.isus_ref_limit) );

    DataMean = (float)scan_max;
    DataStdDev = (float)scan_min;

    apf.isus_lamp_pwr = TRUE;

    if(mmsDbgFlg == 1) { uprintf("mms_lampPwrOnUsingSpec() done, good"); prn_nl(); }

    return( TRUE );

}



void LampOpenShutter(void)
{
    if(mmsDbgFlg == 1) { uprintf("LampOpenShutter not supported"); prn_nl(); }
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_SHUT_ENABLE, HIGH);
    //DelayMilliSecs( 500 );
    delay_msec(500);
}

void LampCloseShutter(void)
{
    if(mmsDbgFlg == 1) { uprintf("LampCloseShutter not supported"); prn_nl(); }
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_SHUT_ENABLE, LOW);
    //DelayMilliSecs( 500 );
    delay_msec(500);
}

int RefPwrOn(void)
{
    if(mmsDbgFlg == 1) { uprintf("RefPwrOn not supported"); prn_nl(); }
    apf.isus_ref_pwr = TRUE;

    return( TRUE );
}

int RefPwrOff(void)
{
    if(mmsDbgFlg == 1) { uprintf("RefPwrOff not supported"); prn_nl(); }
    apf.isus_ref_pwr = FALSE;

    return( TRUE );
}

int mms_lampPwrOn( void )   //was LampPwrOn
{

    if( apf.isus_lamp_pwr == TRUE )
        return( TRUE );


    //pcal6416_write_port_bit(uint8_t regNum, uint8_t bitMask, uint32_t state)
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_PWR_ON, 1);
    pcal6416_shadow_port0 |= LAMP_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    apf_fram.isus_lampOn_ctr++;         //count the number of times the lamp is turned on

    delay_sec( 1 );


    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, (LAMP_DU_ENABLE | LAMP_W_ENABLE | LAMP_SHUT_ENABLE), 1);
    pcal6416_shadow_port0 |= LAMP_DU_ENABLE | LAMP_W_ENABLE | LAMP_SHUT_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    delay_msec( 500 );
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, 0);
    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 500 );
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, 1);
    pcal6416_shadow_port0 |= LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 500 );
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, 0);
    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 500 );
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, 1);
    pcal6416_shadow_port0 |= LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 500 );
    //pcal6416_write_port_bit(PCAL6416A_OUT_PORT0, LAMP_W_ENABLE, 0);
    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 500 );

    apf.isus_lampStart_msec = Timer_1ms();
    apf.isus_lamp_pwr = TRUE;
    apf.isus_lamp_tungsten = FALSE;

    return( TRUE );
}


int mms_lampPwrOff( void )      // was LampPwrOff
{
    pcal6416_shadow_port0 &= ~LAMP_DU_ENABLE;
    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_shadow_port0 &= ~LAMP_SHUT_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    delay_msec( 250 );

    pcal6416_shadow_port0 &= ~LAMP_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    // compute how long the lamp was on and add it to the total lamp on time
    uint32_t elapsedOnTime = Timer_1ms() - apf.isus_lampStart_msec;
    apf_fram.isus_lampOn_mSec += elapsedOnTime;

    apf.isus_lamp_pwr = FALSE;
    apf.isus_lamp_tungsten = FALSE;
    apf.isus_lamp_deuterium = FALSE;

    return( TRUE );

}

int mms_lamp12vPwrOn( void )      // was LampPwrOff
{
    pcal6416_shadow_port0 |= LAMP_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    apf.isus_lamp_pwr = TRUE;

    return( TRUE );
}

int mms_lamp12vPwrOff( void )      // was LampPwrOff
{

    pcal6416_shadow_port0 &= ~LAMP_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    pcal6416_shadow_port0 &= ~LAMP_DU_ENABLE;
    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_shadow_port0 &= ~LAMP_SHUT_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    // if the lamp had been powered on and deuterium had been on, then count the time
    if(apf.isus_lamp_deuterium)
    {
        // compute how long the lamp was on and add it to the total lamp on time
        uint32_t elapsedOnTime = Timer_1ms() - apf.isus_lampStart_msec;
        apf_fram.isus_lampOn_mSec += elapsedOnTime;
    }


    apf.isus_lamp_pwr = FALSE;
    apf.isus_lamp_tungsten = FALSE;
    apf.isus_lamp_deuterium = FALSE;

    apf.isus_lamp_pwr = FALSE;

    return( TRUE );
}

int mms_lampDeuteriumPwrOn( void )      // was LampPwrOff
{
    pcal6416_shadow_port0 |= LAMP_DU_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    if(apf.isus_lamp_pwr == FALSE)
    {
        mms_lamp12vPwrOn();
    }

    apf.isus_lampStart_msec = Timer_1ms();
    apf.isus_lamp_deuterium = TRUE;

    return( TRUE );
}

int mms_lampDeuteriumPwrOff( void )      // was LampPwrOff
{

    pcal6416_shadow_port0 &= ~LAMP_DU_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    apf.isus_lamp_deuterium = FALSE;

    // compute how long the lamp was on and add it to the total lamp on time
    uint32_t elapsedOnTime = Timer_1ms() - apf.isus_lampStart_msec;
    apf_fram.isus_lampOn_mSec += elapsedOnTime;

    return( TRUE );
}

int mms_lampTungstenPwrOn( void )      // was LampPwrOff
{
    pcal6416_shadow_port0 |= LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    if(apf.isus_lamp_pwr == FALSE)
    {
        mms_lamp12vPwrOn();
    }

    apf.isus_lamp_tungsten = TRUE;

    return( TRUE );
}

int mms_lampTungstenPwrOff( void )      // was LampPwrOff
{

    pcal6416_shadow_port0 &= ~LAMP_W_ENABLE;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    apf.isus_lamp_tungsten = FALSE;

    return( TRUE );
}


//int SpecPwrOn( TUPort **tup )
int mms_specPwrOn(void)
{
    int retVal;

// #define MMS_ERRBUFSIZE      256
    // char errbuf[MMS_ERRBUFSIZE];        // 256

    if( apf.isus_spec_pwr == TRUE )
        return( TRUE );

    //Turn on Spec Pwr
    mms_open();     //pcal6416 SPEC_PWR_ON, enable rs232 xcvr, init uart3, calls pcal6416_spec_pwr(TRUE)

    //wait for SPEC_CTS to go low (asserted)
    //it never will if spectrometer isn't connected as the RS232 receiver output is high via internal pull-down on its input
    retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);      // 500msec
    if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
    {
        if(mmsDbgFlg == 1) { uprintf("DEBUG msg: Powerup Timeout, Spectrometer CTS never asserted"); prn_nl(); }
        mms_specPwrOff();       //added 19 May 2025
        LogErrorMsg("Powerup Timeout, Spectrometer CTS never asserted", (int)SPEC_PWR_ON_FAILURE, apf_eeprom.sys_message_enable);
        return (TIMEOUT);
    }

    //wait for SPEC_CTS to return back high (released)
    retVal = mms_wait_cts(1, SPEC_CTS_WAIT_500MS);      // 500msec
    if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
    {
        if(mmsDbgFlg == 1) { uprintf("DEBUG msg: Powerup Timeout, Spectrometer CTS never released");  prn_nl(); }// APF todo, comment this out
        mms_specPwrOff();       //added 19 May 2025
        LogErrorMsg("Powerup Timeout, Spectrometer CTS never released", (int)SPEC_PWR_ON_FAILURE, apf_eeprom.sys_message_enable);
        return (TIMEOUT);
    }

    //set SPEC_RTS high (released state)
    //PinSet( 15 );
    mms_rts(1);

    //Delay1ms();
    delay_msec(1);

    //UARTFlushRx();      mms_rtssh( *tup );
    mms_flushRx();

    apf.isus_spec_pwr = TRUE;

    // Start the lamp on elapsed timer.
    apf.isus_lampStart_msec = Timer_1ms();

    return( TRUE );

}


//int SpecPwrOff( TUPort **tup )
int mms_specPwrOff(void)
{

    mms_flushRx();

    // PinClear( 15 );
    mms_rts(0);         //THOM, APFTODO, isnt this activating RTS?

    mms_enable_rs232_xcvr(0);   // off

	pcal6416_spec_pwr(FALSE);

    return( TRUE );
}




//int ScanSpectra( TUPort *tup, FILE *fptr, ushort scan_num, ushort *Data, uchar ScanType )
//int mms_scanSpectra(FILE *fptr, uint16_t scan_num, uint16_t *Data, unsigned char ScanType )
int mms_scanSpectra(uint16_t scan_num, uint16_t *Data, unsigned char ScanType, int printFlg, int writeFlg )
{
    char   *ScanTypes[] = {"D", "S"};
    char   ebuf[ERRBUFSIZE];
    char   SpecCmd[128];
    int    loop;
    short  val1, val2;
    struct tm *t;
    int    retVal;
    int indx;
    unsigned char rxBuf[128];
    uint32_t tickStart, ticks;
    uint32_t strPtr;

    //mmsDbgFlg = 1;  //do this in take_Isus
//#ifdef STOPCAPPINGSCANNUMTOONE  (putting it back to cap 4/4/2024)
    // IH commented thsi out 4/16/25 for Ben's code which needs to take multiple dark spectra
    // I calloc'ed a dynamic array for the data
#ifdef NOCODE	
    if(scan_num > 1)
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra(), scan_num is limited to 1"); prn_nl(); }
        scan_num = 1;
    }
#endif	
//#endif
    if ( !( (ScanType == 0) || (ScanType == 1)) )
    // if( ( ScanType < 0 ) || ( ScanType > 1 ) )
    {
        if(mmsDbgFlg == 1) { uprintf("Scan type error %d", (int)ScanType ); prn_nl(); }
        return( ERROR );
    }


    if(ScanType == DARKCURRENT_SCAN)
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra: Dark Current Scan"); prn_nl(); }
    }
    else
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra: Fiberlite UV Scan"); prn_nl(); }
    }

    tickStart = Timer_1ms();
    wdog_pet();  //

    // THOM added the specPwrOn... and set of rts=1
	mms_rts(1);   //is this rts = inactive
    if(mmsDbgFlg == 1) { uprintf("spec power on"); prn_nl(); }
    mms_specPwrOn();        //APFTODO - CF2 code requires separate step to turn on specPwr, doing this in scanSpectra now 10 Sep 2024

    if( apf.isus_spec_pwr != TRUE )
    {
      if(mmsDbgFlg == 1) { uprintf("apf.isus_spec_pwr != TRUE, scan stopped"); prn_nl(); }
      return( ERROR );
    }
  
    //negate RTS output  (already done in specPwrOn())
    //PinSet( 15 );
    mms_rts(1);   //rts = inactive gpio=1 means rs232 rts is -5v

	//set spec trigger mode to "MBARI CUSTOM"
    //TUPuts(tup, "E5\r" );
	//DelayMilliSecs( 50 );
    mms_tx_str("E5\r");
    delay_msec( SPECDELAY_MS ); // was 50, appears to return a string
    // E5 returns the version string (get is not in CF2 code).   Can just delay and flushRx APFTODO, adding rx_str for now
    retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);   //cr lf cr  500ms
    if(retVal >= 0)
    {
        //rx 25: MBARI v2.00; 2024-01-24
        if(mmsDbgFlg == 1) { uprintf("E5 rx %d good: %s", retVal, rxBuf);  prn_nl();  }// DEBUG
        mms_flushRx();  // flush the cr
    }
    else
    {
        if(mmsDbgFlg == 1) { uprintf("E5 rx timed out waiting for version string response from E5"); prn_nl(); }
    }	
	
    wdog_pet();  //

	//set spec scan buffer depth
	//scan_num = 1;  //DEBUG APFTOD
	sprintf(SpecCmd, "R%u\r", scan_num );
    if(mmsDbgFlg == 1) {uprintf(SpecCmd, "DBG: scan num to specCmd: R%u", scan_num ); prn_nl();}
    mms_tx_str( (unsigned char *) SpecCmd );   // APFTODO, change this to allow scan >= 1
    //mms_tx_str("R1\r");
    delay_msec( SPECDELAY_MS ); // was 50, some serial chars, maybe?
    retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);   //500msec
    if(retVal >= 0)
    {
        //rx 3:
        if(mmsDbgFlg == 1) { uprintf("R%u good rx %d: %s", scan_num, retVal, rxBuf);  prn_nl(); }// DEBUG
    }
    else
    {
        if(mmsDbgFlg == 1) { uprintf("R1 rx timed out"); prn_nl(); }
    }	
	
	//set spec integration period
	//sprintf(SpecCmd, "I%lu\r", isus_spec_period );
	//TUPuts( tup, SpecCmd );
	//DelayMilliSecs( 50 );	
	    //set spec integration period
    sprintf(SpecCmd, "I%lu\r", apf_eeprom.isus_spec_period );
    mms_tx_str( (unsigned char *) SpecCmd );		// APFTODO
    if(mmsDbgFlg == 1) { uprintf("DBG: mms_spec.c mms_scanSpec I%lu", apf_eeprom.isus_spec_period );  prn_nl(); }
    //mms_tx_str("I500\r");           //APFTODO 500 is hard coded, need to pass in apf_eeprom.isus_spec_period
    delay_msec( SPECDELAY_MS );

    wdog_pet();  //

    // CF2 had this call in the scan loop
    // IH 9/18/25 for timing tests, moving this out of this loop - NOT PERMANENT FIX
    //bme280_getValues(&apf.sys_humidity, &apf.sys_temperature, &apf.sys_pressure);


    for( loop=0; loop<scan_num; loop++ )
    //for( loop=0; loop<1; loop++ )  //APFTODO - make it scan_num
	{
	 
        //isus_sample_time = RTCGetTime( NULL, NULL );            //get time at start of data collection
        apf_fram.isus_sample_time = read_hib_rtcc();       //APFTODO //get time at start of data collection
	
        //start "automatic" scanning
        //TUPuts( tup, "S\r" );
		mms_tx_str( "S\r" );	  
	
		//wait for SPEC_CTS to go low (asserted)
        // wait for SPEC_CTS to go lo (active), SPEC_CTS_TIMEOUT timeout 500msec
        retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);
        if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
        {
            if(mmsDbgFlg == 1) { uprintf("ERR: Scan Timeout, Spectrometer CTS never asserted"); prn_nl(); }

            sprintf(ebuf, "Scan Timeout, Spectrometer CTS never asserted");
            LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
            //mms_close();  // power off spec   NEW 9 Sep 2024

            return( TIMEOUT );
        }

		if(mmsDbgFlg == 1) { uprintf("0 cts went low 1089"); prn_nl(); }

        //wait for SPEC_CTS to return back high (released)
        retVal = mms_wait_cts(1, SPEC_INT_WAIT_15000MS);          // SPEC_INT_TIMEOUT timeout 15seconds
        if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
        {
            if(mmsDbgFlg == 1) { uprintf("ERR: Scan Timeout, Spectrometer CTS never released"); prn_nl(); }
            sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
            LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
            //mms_close();  // power off spec   NEW 9 Sep 2024
            return( TIMEOUT );
        }
		if(mmsDbgFlg == 1) { uprintf("1 cts went hi 1100");  prn_nl();  } //DEBUG APFTODO<<<<<<<<<<<<<<<<

        //get data, one byte at a time, using RTS/CTS handshaking
        //TUPuts( tup, "g\r" );
        mms_tx_str( "g\r" );
	
        //wait for SPEC_CTS to go low (asserted)
        // wait for SPEC_CTS to go lo (active), timeout 500msec
        retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);
        if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
        {
            if(mmsDbgFlg == 1) { uprintf("ERR: Data Timeout, Spectrometer CTS never asserted, mms_scanSpectra()"); prn_nl(); }
            sprintf(ebuf, "Data Timeout, Spectrometer CTS never asserted");
            LogErrorMsg(ebuf, (int)SPEC_CTS_DATA_ERROR, apf_eeprom.sys_message_enable);
            //mms_close();  // power off spec   NEW 9 Sep 2024
            return( TIMEOUT );
        }
	  
	  if(mmsDbgFlg == 1) { uprintf("0 cts went lo 1117"); prn_nl(); }
	  
	  // MMS_UV Spectometer sends pixel 256 (400nm) first and pixel 1 (200nm) last.
	  // Load data into array in the opposite order, i.e., SpecData[0] = 200nm
        //TURxFlush( tup );
        mms_flushRx();

        //for( indx = 255; indx >= 0; indx-- )
        for( indx=255; indx>=0; indx-- )     //indx ends up trying to count negative from 0 bug 21 Oct 2024, indx changed to type int, all is well
        {
            wdog_pet();  //

            //PinClear( 15 );  //assert RTS output
            mms_rts(0);
            //val1 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get high byte of value */
            val1 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout 500 msec


            //PinSet( 15 );    //negate RTS output
            mms_rts(1);

            //Delay10us();
            delay_usec(10);

            //PinClear( 15 );  //assert RTS output
            mms_rts(0);      //assert RTS output
            //val2 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get low  byte of value */
            val2 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout in msec, if timeout, return is greater than 255
            //PinSet( 15 );    //negate RTS output
            mms_rts(1);      //negate RTS output

            //uprintf("[%03u]v1 %03x, v2 %03x. ", indx, val1, val2);  //DEBUG
            //wait_consoleTx();

            //if( (val1 == -1) || (val2 == -1) )
            if( (val1 > 255) || (val2 > 255) )
            {
                //printf("Data Timeout, cell = %d\n", i);
                if(mmsDbgFlg == 1) { uprintf("ERR: Data Timeout, Data Byte of 256 spec shorts"); prn_nl(); }
                sprintf(ebuf, "Data Timeout, Data Byte Read");
                LogErrorMsg(ebuf, (int)SPEC_DAT_READ_ERROR, apf_eeprom.sys_message_enable);
                //mms_close();  // power off spec   NEW 9 Sep 2024
                return( TIMEOUT );
            }

            if((indx>=0) && (indx<=255))
            {
                Data[indx+(loop*256)] = ((val1 << 8) & 0xFF00) | (val2 & 0x00FF);
            }

            if( indx == 255 ) //set first clocked pixel value to zero
                Data[indx+(loop*256)] = 0;
        }
		
	   //wait for SPEC_CTS to return back high (released), all data has been sent!
        //wait for SPEC_CTS to return back high (released)
        retVal = mms_wait_cts(1, SPEC_DAT_WAIT_500MS);
        if(retVal < 0)  // TIMEOUT is -2 and invalid input is -1
        {
            if(mmsDbgFlg == 1) { uprintf("ERR: Scan Timeout, Spectrometer CTS never released"); prn_nl(); }
            sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
            LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
            //mms_close();  // power off spec   NEW 9 Sep 2024
            return( TIMEOUT );
        }

      if( ScanType == DARKCURRENT_SCAN )
          ShortVectorMeanStdDev(100, &Data[(loop*256)], &DataMean, &DataStdDev);
      else
          ShortVectorMeanStdDev(256-1, &Data[(loop*256)], &DataMean, &DataStdDev);

      apf_fram.isus_sample_time = read_hib_rtcc();
      t = localtime(&apf_fram.isus_sample_time);

      strPtr = 0;
      //strPtr += sprintf((char*)&prnBuf[strPtr], "%s,%02d/%02d/%02d %02d:%02d:%02d,%.2f,%.2f,%.2f,", ScanTypes[ScanType], t->tm_mon+1, t->tm_mday, t->tm_year+1900, t->tm_hour, t->tm_min, t->tm_sec, sysTemperature, DataMean, DataStdDev);
      strPtr += sprintf((char*)&prnBuf[strPtr], "%s,%02d/%02d/%02d %02d:%02d:%02d,%.2f,%.2f,%.2f,", ScanTypes[ScanType], t->tm_mon+1, t->tm_mday, t->tm_year+1900, t->tm_hour, t->tm_min, t->tm_sec, apf.sys_temperature, DataMean, DataStdDev);
      for(indx=0; indx<256; indx++)
      {
          if(indx != 255)
              strPtr += sprintf((char*)&prnBuf[strPtr], "%hu,", Data[indx+(loop*256)]);
          else
              strPtr += sprintf((char*)&prnBuf[strPtr], "%hu", Data[indx+(loop*256)]);

          //uprintf("%02X%02X", (unsigned char)(Data[indx]/256), (unsigned char)(Data[indx] & 0x00ff));
      }
      strPtr += sprn_nl((char*)prnBuf, strPtr);


        ticks = (Timer_1ms() - tickStart);
        if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("scan spec    = %5u", ticks);  }


        if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )  // fix 6 Aug 2021
        {
        tickStart = Timer_1ms();

        appendIsusProfileDataToSDCard((char*)prnBuf);

        ticks = (Timer_1ms() - tickStart);
        if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("uSD write    = %5u", ticks);  }

        } // if sys_data.app_cfg[APPCFG_MICROSD_ENABLE]

        if(printFlg == 1) { UARTwrite((char *)prnBuf, strlen((char *)prnBuf)); }

//------------------------


	} /* end loop */	

    //mmsDbgFlg = 0;
    if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra, done, good"); prn_nl(); }
    return( TRUE );

}





//int SingleScanSpectra( TUPort *tup, unsigned long scan_period, ushort *Data )
int mms_singleScanSpectra(uint32_t scan_period, uint16_t *Data )
{

    char   ebuf[ERRBUFSIZE];
    char   SpecCmd[128];
    int    retVal;
    int    indx;
    short  val1, val2;
    //RTCTimer TmOut;

    if( apf.isus_spec_pwr != TRUE )
      return( ERROR );

    //negate RTS output
    //PinSet( 15 );
    mms_rts(1);

    //set spec trigger mode to "MBARI CUSTOM"
    //TUPuts(tup, "E5\r" );
    mms_tx_str( "E5\r" );
    delay_msec( SPECDELAY_MS );

    //set spec scan buffer depth
    // for scan multiple, do this: sprintf(SpecCmd, "R%hu\r", scan_num );
    sprintf(SpecCmd, "R1\r");
    //TUPuts( tup, SpecCmd );
    mms_tx_str( (unsigned char *) SpecCmd );
    delay_msec( SPECDELAY_MS );

    //set spec integration period
    sprintf(SpecCmd, "I%lu\r", scan_period );
    //TUPuts( tup, SpecCmd );
    mms_tx_str( (unsigned char *) SpecCmd );
    delay_msec( SPECDELAY_MS );

    //start "automatic" scanning
    //TUPuts( tup, "S\r" );
    mms_tx_str( "S\r" );

    // wait for SPEC_CTS to go lo (active), SPEC_CTS_TIMEOUT timeout 500msec
    retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);
    if(retVal < 0)
    {
        sprintf(ebuf, "Scan Timeout, Spectrometer CTS never asserted");
        LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
        return( TIMEOUT );
    }


    //wait for SPEC_CTS to return back high (released)
    retVal = mms_wait_cts(1, SPEC_CTS_WAIT_500MS);
    if(retVal < 0)
    {
        sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
        LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
        return( TIMEOUT );
    }


    //get data, one byte at a time, using RTS/CTS handshaking
    //TUPuts( tup, "g\r" );
    mms_tx_str( "g\r" );

    // wait for SPEC_CTS to go lo (active), SPEC_CTS_TIMEOUT timeout 500msec
    retVal = mms_wait_cts(0, SPEC_CTS_WAIT_500MS);
    if(retVal < 0)
    {
        if(mmsDbgFlg == 1) { uprintf("Scan Timeout, Spectrometer CTS never asserted"); prn_nl(); }
        sprintf(ebuf, "Scan Timeout, Spectrometer CTS never asserted");
        LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
        return( TIMEOUT );
    }


    // MMS_UV Spectometer sends pixel 256 (400nm) first and pixel 1 (200nm) last.
    // Load data into array in the opposite oder, i.e., SpecData[0] = 200nm
    //TURxFlush( tup );
    mms_flushRx();
    for( indx = 255; indx >= 0; indx-- )  //BUG!!  indx must be int, not uint32_t
    {
        //uprintf("   indx = %d", indx); prn_nl();  //DEBUG

        //PinClear( 15 );  //assert RTS output
        mms_rts(0);  //assert RTS output
        //val1 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get high byte of value */
        val1 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout in msec, get high byte
        //PinSet( 15 );    //negate RTS output
        mms_rts(1);      //negate RTS output

        //Delay10us();
        delay_usec(10);

        //PinClear( 15 );  //assert RTS output
        mms_rts(0);  //assert RTS output
        //val2 = TURxGetByteWithTimeout( tup, SPEC_DAT_TIMEOUT );                     /* get low  byte of value */
        val2 = mms_rx_byte_timeout( SPEC_DAT_WAIT_500MS );              // timeout in msec, get lo byte
        //PinSet( 15 );    //negate RTS output
        mms_rts(1);      //negate RTS output

        if( (val1 == -1) || (val2 == -1) )
        {
            if(mmsDbgFlg == 1) { uprintf("Data Timeout, cell = %d", indx); prn_nl(); }
            sprintf(ebuf, "Data Timeout, Data Byte Read");
            LogErrorMsg(ebuf, (int)SPEC_DAT_READ_ERROR, apf_eeprom.sys_message_enable);
            return( TIMEOUT );
         }

        if((indx>=0) && (indx<=255))
            Data[indx] = ((val1 << 8) & 0xFF00) | (val2 & 0x00FF);

        if( indx == 255 )          // MMS_UV supported, dropping support for MMS_UV_VIS
            Data[indx] = 0;

#ifdef APFTODO
#if SPECTROMETER_TYPE == MMS_UV
        if( i == 255 )
            Data[i] = 0;
#elif SPECTROMETER_TYPE == MMS_UV_VIS
        if( i == 0 )
          Data[i] = Data[(i+1)];
#endif
#endif
    }

    //wait for SPEC_CTS to return back high (released), all data has been sent!
    retVal = mms_wait_cts(1, SPEC_CTS_WAIT_500MS);
    if(retVal < 0)
    {
        sprintf(ebuf, "Scan Timeout, Spectrometer CTS never released");
        LogErrorMsg(ebuf, (int)SPEC_CTS_SCAN_ERROR, apf_eeprom.sys_message_enable);
        return( TIMEOUT );
    }

    return( TRUE );

}


int test_takeIsusSample_timing(void)
{

    // call mms_takeIsusSample without timing
    sys_data.app_cfg[APPCFG_SAMPLING_DIAG] = 0;
    mms_takeIsusSample(&apf.acquire_time, apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth, 1);     //defd in mms_spec.c


    // call get_sample with timing
    sys_data.app_cfg[APPCFG_SAMPLING_DIAG] = 1;

    prn_nl(); uprintf(" ---  Sample Timing (msec) ---"); prn_nl();
    mms_takeIsusSample(&apf.acquire_time, apf.CTD_Time, apf.CTD_Temp, apf.CTD_Salinity, apf.CTD_Depth, 1);     //defd in mms_spec.c

    sys_data.app_cfg[APPCFG_SAMPLING_DIAG] = 0;

    return(0);
}

int test_takeIsusSample_loop(void)
{
    // char input;

    //print_config_data_fields();

    //print_data_header();     // defd in system.c

    while(1)
    {
        if( UARTRxBytesAvail() )        // replace wiht kbhit()
        {
            // input =
            getChar();

            break;

        }

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

    }

    return 1;
}




/*
 *  mms_takeIsusSample is function called by T1 serial command (TakeIsusSample) and TS command with mode,1
 *
 * Problem:  CF2 code has variables CTD_Time in scope of main, passed in as args to function with scope in function
 * was TakeIsusSample in CF2 authored by Luke Colletti.
 */
// TakeIsusSample
//int mms_takeIsusSample(char *dbuf, float *atime, uint32_t CTD_Time, float CTD_Temp, float CTD_Salinity, float CTD_Depth)
int mms_takeIsusSample(float *atime, uint32_t CTD_Time, float CTD_Temp, float CTD_Salinity, float CTD_Depth, int printFlg)
{
    char    tmpBuf[8];
    uint16_t crc;     //was ushort
    int     ret;
    double ref_ch_mean, ref_ch_sd, dc_mean, dc_sd, sw_dc_mean;

    int strIndx, strSize;       //should this be unsigned int?
    uint32_t indx;
    struct tm *tp;
    char   errBuf[48];

    uint32_t tickStart, ticks, tickTot;
    uint32_t tick500start;

    tickTot = 0;

    tickStart = Timer_1ms();

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("fram get     = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    *atime = -1.0;

    //if( SpecPwrOn( &SpecPort ) != TRUE )
    //Energize the Spec
    if( mms_specPwrOn() != TRUE )
    {
        //apf.isus_sample_time = Timer_1ms(false) - samp_tickStart;
        return( ERROR );
    }

#ifdef OLDCODE //replace delay_msec(500) with load apfArray
    //DelayMilliSecs( 500 );
    delay_msec(500);   //APFTODO why wait 500msec??   Measure UV output after power on, this is likely too long
    //APFTODO load apfArray from FRAM in this delay slot of 500msec
#endif
    tick500start = Timer_1ms();
    fram_apfArray_refresh();            //load apfArray from FRAM, 312msec (i2c running at 100khz)
    //apf_copyEcoefsFileToData();         //must be done after loading apfArray, moved to fram_apfArray_refresh

    bme280_getValues(&apf.sys_humidity, &apf.sys_temperature, &apf.sys_pressure);       //new 13 May 2025  //~300msec

    if(mmsDbgFlg == 1) { uprintf("DBG: %u msec fram_apfArray_refresh", (Timer_1ms() - tick500start)); prn_nl(); }
    while(1)
    {
        indx = Timer_1ms() - tick500start;
        if( (indx) > 500)
        {
            break;
        }
        delay_msec(10);
    }
    if(mmsDbgFlg == 1) { uprintf("DBG: %u msec delay, aimed for 500msec", indx); prn_nl(); } //823msec BME280+ApfArray load

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("power on     = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    if(mmsDbgFlg == 1) { uprintf("calling mms_scanSpectra DARK scan, mms_takeIsusSample in mms_spec.c");  prn_nl();  }

    //if( ScanSpectra( SpecPort, dat_file, 1, DarkData, DARKCURRENT_SCAN ) != TRUE )
    //if( mms_scanSpectra( dat_file, 1, DarkData, DARKCURRENT_SCAN ) != TRUE )    //scan num is one
    if( mms_scanSpectra( 1, DarkData, DARKCURRENT_SCAN, 0, 1 ) != TRUE )    //scan num is one, dont print, write
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed, mms_takeIsusSample in mms_spec.c");  prn_nl();  }
        //LampPwrOff();
        mms_lampPwrOff();
        //SpecPwrOff( SpecPort );
        mms_specPwrOff();
        apf_fram.isus_sample_time = 0;
        //apf.isus_sample_time = Timer_1ms(false) - samp_tickStart;
        return( ERROR );
    }

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("scan dark    = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    dc_mean = DataMean;         //value set in ScanSpectra()
    dc_sd   = DataStdDev;       //value set in ScanSpectra()

    if(mmsDbgFlg == 1) { uprintf("calling mms_lampPwrOnUsingSpec");  prn_nl();  }

    ret = mms_lampPwrOnUsingSpec( SpecData, FALSE );   //LampPwrOnUsingSpec( SpecPort, SpecData, FALSE );
    if(ret == ERROR)  //added ret val check in case of low sys voltage (below 10.0v)
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed, mms_takeIsusSample in mms_spec.c");  prn_nl();  }
        //LampPwrOff();
        mms_lampPwrOff();
        //SpecPwrOff( SpecPort );
        mms_specPwrOff();

        return( ERROR );
    }

    ref_ch_mean = DataMean;     //value set in LampPwrOnUsingSpec() = MaxPixelInten
    ref_ch_sd   = DataStdDev;   //value set in LampPwrOnUsingSpec() = MinPixelInten

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("lamp on spec = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    if(mmsDbgFlg == 1) { uprintf("calling mms_scanSpectra FiberLite UV scan, mms_takeIsusSample in mms_spec.c");  prn_nl();  }

    //if( ScanSpectra( SpecPort, dat_file, 1, SpecData, INTENSITY_SCAN ) != TRUE )
    //if( mms_scanSpectra( dat_file, 1, SpecData, INTENSITY_SCAN ) != TRUE )
    if( mms_scanSpectra( 1, SpecData, INTENSITY_SCAN, 0, 1 ) != TRUE )   // dont print, write
    {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra fail, ret err");  prn_nl();  }

        mms_lampPwrOff();   //LampPwrOff();
        mms_specPwrOff( );  //SpecPwrOff( SpecPort );
        apf_fram.isus_sample_time = 0;

        return( ERROR );
    }

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("scan uv      = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    //apf.isus_sample_time = Timer_1ms(false) - samp_tickStart;

    //INA219_ReadVoltageCurrent( &sysVoltage, &sysCurrent );
    ina260_system_voltageCurrent( &apf.sys_voltage, &apf.sys_current );

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("ina260       = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    //LampPwrOff();
    mms_lampPwrOff();
    //SpecPwrOff( SpecPort );
    mms_specPwrOff();

    if(mmsDbgFlg == 1) { uprintf("power off lamp and spec, calculating...");  prn_nl();  }

    apf.isus_sal       = -1.0;      //initialize prior to fit
    apf.isus_no3       = -1.0;
    apf.isus_hs        = -1.0;
    apf.isus_err       = -1.0;

    // zero fill for human sanity
    memset(AbsData,       0, sizeof(AbsData));
    memset(AbsFitData,    0, sizeof(AbsFitData));
    memset(ResidualsData, 0, sizeof(ResidualsData));
    memset(FitData,       0, sizeof(FitData));

    int linfit_ret = 0;
    //uprintf("DBG: apfArray.Ecoefs = %u", apfArray.EcoefsCnt); prn_nl();   //APFTODO
    if( apfArray.EcoefsCnt == 256 )
    {
        if( apf_eeprom.isus_tcomp_mode == TRUE )
        {
            if( CTD_Time != 0 ) //time will be zero if CTD values are not passed
            {
                apf_XformSwEcoefs(apfArray.LambdaData, apfArray.TcompCoefs, apfArray.SwCalTemp, CTD_Temp);
            }
            else
            {
                apf_copySwEcoefsFileToData();
            }

            FitData[SALINITY]=0.0;

            if(apf.CTD_Time != 0)
            {
                if( ! apf_eeprom.isus_salfit_mode )
                {
                    FitData[SALINITY] = apf.CTD_Salinity;
                }
                else
                {
                    FitData[SALINITY] = 0.0;
                }
            }
        } //end tcomp if
        else
        {
            apf_copySwEcoefsFileToData(); //always start fresh
            FitData[SALINITY]=0.0; //always fit salinity if Br Tcomp isn't enabled
        }

        //CalcAbs() expects the ScanData to begin at 0 and the ZeroData and AbsData to begin at 1, confused, read on...
        CalcAbs(&apfArray.ZeroData[apf_eeprom.isus_pixel_beg-1], &SpecData[apf_eeprom.isus_pixel_beg-1], &AbsData[0], dc_mean, apf_eeprom.isus_fit_pixels);

        double *EcoefDataRowPtr[256+1];
        for(int jndx=0; jndx<=256; jndx++)
        {
           EcoefDataRowPtr[jndx] = &(apf_EcoefData[jndx][0]);
        }

        //LinFit() expects array data to begin at 1, therefore passed data values (Ecoef, Lambda and Abs) should point one back, Absorbance Data should start at AbsData[1], see CalcAbs(), above.
        linfit_ret = LinFit(&EcoefDataRowPtr[apf_eeprom.isus_pixel_beg-1], &apfArray.LambdaData[apf_eeprom.isus_pixel_beg-1], &AbsData[0], &AbsFitData[0], &ResidualsData[0], &FitData[0], apf_eeprom.isus_fit_pixels, apf_eeprom.isus_fit_concs, apf_eeprom.isus_bsl_model);

        if(0)
        {
            uprintf("Outside dump:\r\n");
            uprintf("isus_pixel_beg: %d\r\n",  apf_eeprom.isus_pixel_beg);
            uprintf("isus_pixel_end: %d\r\n",  apf_eeprom.isus_pixel_end);
            uprintf("isus_fit_pixels: %d\r\n", apf_eeprom.isus_fit_pixels);
            uprintf("\r\n");
            wait_consoleTx();

            uprintf("i_fit_pixels, EcoefData[i+apf_eeprom.isus_pixel_beg-1][0], EcoefData[i+apf_eeprom.isus_pixel_beg-1][1], EcoefData[i+apf_eeprom.isus_pixel_beg-1][2], lambda[i+apf_eeprom.isus_pixel_beg-1], specData[i+apf_eeprom.isus_pixel_beg-1], abs[i], absFit[i]\r\n");
            for(int i = 1; i <= apf_eeprom.isus_fit_pixels; i++)
            {
                const int i_offset = apf_eeprom.isus_pixel_beg - 1;

                double const * EcoefDataRow = EcoefDataRowPtr[i + i_offset];
                const double   lambda       = apfArray.LambdaData[i + i_offset];
                const int      spec         = SpecData[i+i_offset];
                const double   abs          = AbsData[i];
                const double   absFit       = AbsFitData[i];

                uprintf("%d, %e, %e, %e, %e, %d, %e, %e\r\n", i, EcoefDataRow[0], EcoefDataRow[1], EcoefDataRow[2], lambda, spec, abs, absFit);
                wait_consoleTx();
            }
            uprintf("\r\n");

            uprintf("i, LambdaData, EcoefData[0], EcoefData[1], EcoefData[2]\r\n");
            for(int i = 0; i <= 256; i++)
            {
                uprintf("%d, %e, %e, %e, %e\r\n", i, apfArray.LambdaData[i], EcoefDataRowPtr[i][0], EcoefDataRowPtr[i][1], EcoefDataRowPtr[i][2]);
                wait_consoleTx();
            }
            uprintf("\r\n");

            uprintf("i, DarkData, SpecData\r\n");
            for(int i = 0; i <= 255; i++)
            {
                uprintf("%d, %d, %d\r\n", i, DarkData[i], SpecData[i]);
                wait_consoleTx();
            }
            uprintf("\r\n");

            uprintf("i, ZeroData, AbsData, AbsFitData, ResidualsData\r\n");
            for(int i = 0; i <= 256; i++)
            {
                uprintf("%d, %e, %e, %e, %e\r\n", i, apfArray.ZeroData[i], AbsData[i], AbsFitData[i], ResidualsData[i]);
                wait_consoleTx();
            }
            uprintf("\r\n");

            for(int i = 0; i < FITVALUES; i++)
            {
                uprintf("FitData[%d]: %e\r\n", i, FitData[i]);
                wait_consoleTx();
            }
            uprintf("\r\n");
        }

        if( linfit_ret != TRUE )
        {
            snprintf(errBuf, sizeof(errBuf), "Spectra Fitting Error LinFit() ret = %d", linfit_ret);
            LogErrorMsg(errBuf, (int)NRC_FIT_FAILURE, apf_eeprom.sys_message_enable);

            apf.isus_sal       = -1.0;
            apf.isus_no3       = -1.0;
            apf.isus_hs        = -1.0;
            apf.isus_err       = -1.0;
        }
        else
        {
            apf.isus_sal = FitData[SALINITY];
            apf.isus_no3 = FitData[NITRATE];
            apf.isus_hs  = FitData[BISULFIDE];
            apf.isus_err = FitData[FIT_ERROR];
        }
    }

    //uprintf("DBG: mms_takeIsus apf.isus_no3 = %.2lf", apf.isus_no3); prn_nl();

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("calc NO3     = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    // apf_fram.isus_sample_time = read_hib_rtcc(); // the time is set in mms_scanSpectra
    tp = localtime(&apf_fram.isus_sample_time);
    //strftime(timestamp, sizeof(timestamp), "%m/%d/%Y %H:%M:%S", time_struct);        // change format of date/time per Yui  7 jun 2018
#ifdef PRNSPECWITHOUTLINFIT
    if(linfit_ret)
    {
#endif
        #define CRC_PRNSTRING_OFFSET    7
        strIndx = CRC_PRNSTRING_OFFSET;  //CRC_OFFSET=7
        for(indx=0; indx<CRC_PRNSTRING_OFFSET; indx++)
            prnBuf[indx] = ' ';

        //bug fix v5.1.8
        //strSize = sprintf((char*)&prnBuf[strIndx], "A,%02d/%02d/%02d %02d:%02d:%02d,%lu,%.2f,%.4f,%.4f,%lu,%lu,%lu,%lu,%.2f,%.2f,%.3f,%.3e,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.3le,%d,%d,",
        strSize = sprintf((char*)&prnBuf[strIndx], "A,%02d/%02d/%02d %02d:%02d:%02d,%lu,%.2f,%.4f,%.4f,%lu,%lu,%lu,%lu,%.2f,%lu,%.3f,%.3e,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.3le,%d,%d,",
                tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec,
                CTD_Time, CTD_Depth, CTD_Temp, CTD_Salinity,
                apf_fram.isus_sample_ctr, apf_fram.sys_wdog_ctr, apf_fram.isus_error_ctr, apf_fram.sys_error_ctr,
                //apf.sys_temperature, ((float)apf.sys_humidity), apf.sys_voltage, apf.sys_current,
                apf.sys_temperature, apf.sys_humidity, apf.sys_voltage, apf.sys_current,
                ref_ch_mean, ref_ch_sd, dc_mean, dc_sd,
                apf.isus_sal, apf.isus_no3, apf.isus_err, apf_eeprom.isus_datpixel_beg, apf_eeprom.isus_datpixel_end);
        strIndx += strSize;

        if( (apf_eeprom.isus_datpixel_beg > 0) && (apf_eeprom.isus_datpixel_end <= 256) )
        {
            for( indx = apf_eeprom.isus_datpixel_beg; indx <= apf_eeprom.isus_datpixel_end; indx++ )
            {
                //sprintf(tbuf+strlen(tbuf), "%04X", SpecData[i-1]);  //APFTODO, find a better way than strlen
                strSize = sprintf((char*)&prnBuf[strIndx], "%04X", SpecData[indx-1]);
                strIndx += strSize;  //should be 4
            }
        }

        sw_dc_mean = 0.0;
        for( indx = 0; indx < 5; indx++ )
            sw_dc_mean += (double)SpecData[indx];
        sw_dc_mean = sw_dc_mean/5.0;
        //strSize = sprintf(prnBuf+strlen(prnBuf), ",%.1lf", sw_dc_mean);
        strSize = sprintf((char*)&prnBuf[strIndx], ",%.1lf", sw_dc_mean);
        strIndx += strSize;
        apf_fram.sendlast_byteCnt = strIndx;

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

        for(indx=0; indx<CRC_PRNSTRING_OFFSET; indx++)
        {
            if(tmpBuf[indx] == 0)
                break;
            prnBuf[indx] = tmpBuf[indx];
        }
#ifdef PRNSPECWITHOUTLINFIT
    }
    else
    {
        prnBuf[0] = '\0';
        apf_fram.sendlast_byteCnt = 0;
    }
#endif
    apf_fram.isus_sample_ctr++;      // apf struct is persisted in FRAM

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("output       = %5u,   %5u", ticks, tickTot); }

    tickStart = Timer_1ms();

    apf_fram.isus_sampleCnt_perProfile++;

    // save persistent info to FRAM
    fram_apf_vars_store();
    //eeprom_apf_cfg_store   APFTODO, check of eeprom.<var> has changed, should not have, otherwise move var to fram struct

    //store formatted prnBuf to FRAM in the data buf dbuf section, this is for the SL command
    fram_apf_dbuf_store(&prnBuf[0], apf_fram.sendlast_byteCnt);

    ticks = (Timer_1ms() - tickStart);
    tickTot += ticks;
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("fram set     = %5u,   %5u", ticks, tickTot);  }
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("------------------------------");   }
    if(sys_data.app_cfg[APPCFG_SAMPLING_DIAG] == 1) { prn_nl(); uprintf("total (msec) =          %5u", tickTot);  prn_nl(); prn_nl(); }


    apf.acquire_time = (float)tickTot;
    apf.acquire_time /= 1000.0;

    apf.gotSample = TRUE;           //new 3 May 2025, was set in serial_cmd_fxn.c, moved here

    if(mmsDbgFlg == 1) { uprintf("apf.isus_sample_time = %u, apf.acquire_time = %.3f", apf_fram.isus_sample_time, apf.acquire_time); prn_nl();  }

#ifdef APFTODO //atime written in v5.0.27, not in v5.0.29 (likely ok, but chasing no3 not work)
   if(atime)
    {
        *atime = ((float)ticks) / 1000.0f;
    }
#endif	

    if(mmsDbgFlg == 1 || printFlg == 1) { UARTwrite((const char*)prnBuf, apf_fram.sendlast_byteCnt); prn_nl(); }

    mmsDbgFlg = 0;

    //return( (linfit_ret) ? (OK) : (ERROR) );
    return(OK);
}


//int TakeIsusPseudoSample(char *dbuf, float *atime, ulong CTD_Time, float CTD_Temp, float CTD_Salinity, float CTD_Depth)
//Not needed at this time (7 Sep 2024)
int mms_takeIsusPseudoSample(char *dbuf, float *atime, uint32_t CTD_Time, float CTD_Temp, float CTD_Salinity, float CTD_Depth)
{
    uprintf("mms_takeIsusPseudoSample in mms_spec.c, Imp es Nada"); prn_nl();

#ifdef APFTODO
    char   *tbuf;
    char   *ebuf;
    char   *tmbuf;
    ushort crc;
    int i;
    double ref_ch_mean, ref_ch_sd, dc_mean, dc_sd, sw_dc_mean;
    double isus_no3, isus_sal, isus_hs, isus_err;
    ldiv_t sdiv;
    RTCTimer ElpTm;

    *atime = -1.0;

    RTCElapsedTimerSetup( &ElpTm );

    HIH6130_ReadTempHumidity( &sysTemperature, &sysHumidity );

    dat_file = OpenApfLogFile( ISUS_DATA_FILE );
    if( dat_file != FNULL ) {
        if( isus_reset_flag == TRUE ) {
            OutputIsusHeader(dat_file);
            isus_reset_flag = FALSE;
        }
    }

    if( SpecPwrOn( &SpecPort ) != TRUE ) {
        if( dat_file != FNULL )
            f_close(dat_file);
        return( ERROR );
    }

    DelayMilliSecs( 500 );

    isus_sample_time = RTCGetTime( NULL, NULL );
    tp = localtime(&isus_sample_time);
    DelayMilliSecs( isus_spec_period );

    dc_mean   = 435.23;
    dc_sd     = 4.31;
    if( dat_file != FNULL ) {
        f_printf(dat_file,"D,%02d/%02d/%02d %02d:%02d:%02d,%.2f,%.2f,%.2f,", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec, sysTemperature, dc_mean, dc_sd);
        for(i = 1; i <= 256; i++) {
            if(i != 256) {
                if( (Zcoefs > 0) || (Ecoefs == 256) )
                    f_printf(dat_file, "%.1f,", LambdaData[i]);
                else
                    f_printf(dat_file, "%05d,", i);
            }
            else{
                if( (Zcoefs > 0) || (Ecoefs == 256) )
                    f_printf(dat_file, "%.1f\n", LambdaData[i]);
                else
                    f_printf(dat_file, "%05d\n", i);
            }
        } //end i loop
    } //end FNULL if


    LampPwrOn();

    isus_sample_time = RTCGetTime( NULL, NULL );
    tp = localtime(&isus_sample_time);
    DelayMilliSecs( isus_spec_period );

    ref_ch_mean = 1022.16;
    ref_ch_sd   = 1.67;
    if( dat_file != FNULL ) {
        f_printf(dat_file,"S,%02d/%02d/%02d %02d:%02d:%02d,%.2f,%.2f,%.2f,", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec,sysTemperature, ref_ch_mean, ref_ch_sd);
        for(i = 1; i <= 256; i++) {
            if(i != 256) {
                if( (Zcoefs > 0) || (Ecoefs == 256) )
                    f_printf(dat_file, "%.1f,", LambdaData[i]);
                else
                    f_printf(dat_file, "%05d,", i);
            }
            else{
                if( (Zcoefs > 0) || (Ecoefs == 256) )
                    f_printf(dat_file, "%.1f\n", LambdaData[i]);
                else
                    f_printf(dat_file, "%05d\n", i);
            }
        } //end i loop

        f_close(dat_file);
    } //end FNULL if

    INA219_ReadVoltageCurrent( &sysVoltage, &sysCurrent );

    LampPwrOff();
    SpecPwrOff( SpecPort );

    isus_sal       = 32.57;
    isus_no3       = 12.35;
    isus_hs        = -1.0;
    isus_err       = 0.0001245;

    if( CTD_Time != 0 ) {           //time will be zero if CTD values are not passed
        if( (isus_salfit_mode == FIX) && (CTD_Time != 0) )
            isus_sal=CTD_Salinity;
    }

    if( (tbuf = calloc((size_t)DATBUFSIZE, sizeof(char))) == NULL ) {
        if( (ebuf = calloc((size_t)ERRBUFSIZE, sizeof(char))) == NULL )
            return( ERROR );
        sprintf(ebuf, "CAN'T allocate TakeSample() tbuf memory");
        LogErrorMsg(ebuf, (int)MALLOC_FAILURE, sys_message_enable);
        free(ebuf);
        return( ERROR );
    }

    sprintf(tbuf, "A,%02d/%02d/%02d %02d:%02d:%02d,%lu,%.2f,%.4f,%.4f,%lu,%lu,%lu,%lu,%.2f,%.2f,%.3f,%.3e,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.2lf,%.3le,%d,%d,",
            tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec,
            CTD_Time, CTD_Depth, CTD_Temp, CTD_Salinity,
            isus_sample_ctr, sys_por_ctr, isus_error_ctr, sys_error_ctr,
            sysTemperature, sysHumidity, sysVoltage, sysCurrent,
            ref_ch_mean, ref_ch_sd, dc_mean, dc_sd,
            isus_sal, isus_no3, isus_err, isus_datpixel_beg, isus_datpixel_end);

    for( i = isus_datpixel_beg; i <= isus_datpixel_end; i++ ) {
        sprintf(tbuf+strlen(tbuf), "%04X", i);
    }

    sw_dc_mean = 411.35;
    sprintf(tbuf+strlen(tbuf), ",%.1lf", sw_dc_mean);

    crc = fCrc16Bit((const unsigned char *)tbuf);

    sprintf(dbuf,"0x%04X,%s", crc, tbuf);

    free(tbuf);

    ++isus_sample_ctr;
    DS3234_WrSRAM(ISUS_SAMPLECTR_PTR, sizeof(ulong), (ulong*)&isus_sample_ctr);
    DS3234_WrSRAM(ISUS_LASTSAMPTIME_PTR, sizeof(ulong), (ulong*)&isus_sample_time);

    sdiv = ldiv(RTCElapsedTime( &ElpTm ), 1000000L);
    if( (tmbuf = calloc((size_t)40, sizeof(char))) != NULL ) {
        sprintf(tmbuf, "%ld.%.6ld", sdiv.quot, sdiv.rem);
        *atime = atof(tmbuf);
        free(tmbuf);
    }
#endif
    return( TRUE );       // OK

}




//-------------- Uart Interface ------------------

/*
* mms_uart_init configures onchip uart3 peripheral, isr, buffers and sets baud rate
*         Does not turn on 5v output for MMS Digital
*
*/
void mms_uart_init(unsigned long mms_baud)
{
    uart3_init();      // pinmux for uart3

    uart3_setup(mms_baud, 16000000);

    uart3_echo_set(0);     // no echo
}

/*
* Apply 5v power to the MMS Digital board and prepare uart3 and driver state vars.
* Assumes mms_uart_init() is called.
*/
void mms_open(void)
{
    pcal6416_spec_pwr(TRUE);

    //wait for mms to power on (10msec from CF2)
    delay_msec(10);

    mms_uart_init(MMS_BAUD);

    // moved to mms_open
    mms_enable_rs232_xcvr(1);   // on    //revA is 'always on', revB needs GPIO  THOM TODO

    //mms.state = 0;
    //mms.bufIndx = 0;

    mms_flushRx();
}

/*
* 5v power off, rs232 xcvr off
*/
void mms_close(void)
{

    mms_enable_rs232_xcvr(0);   // on    //revA is 'always on'

    //spec pwr off
    pcal6416_shadow_port0 &= ~SPEC_PWR_ON;
    pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);

    //APF Todo shouldn't spec_rts = 1 at close even with xcvr off
    mms_rts(1);         // APF TODO, figure out what state to park rts

    //perhaps wait a bit before flushing

    mms_flushRx();      // flush in case of glitches

    apf.isus_spec_pwr = FALSE;  //
}

unsigned char mms_getc(void)
{
    unsigned char c;

    c = uart3_rx_byte();
    return c;
}

void mms_putc(unsigned char c)
{
    uart3_tx_byte(c);
}


uint32_t mms_get_cts(void)
{
    uint32_t state;

#if BOARD_MSC == 3
    #if defined(PART_TM4C1290NCPDT)
        state = MAP_GPIOPinRead(GPIO_PORTN_BASE, GPIO_PIN_5);   //SPEC_CTS
    #elif defined(PART_TM4C123GH6PM)
        state = MAP_GPIOPinRead(GPIO_PORTD_BASE, GPIO_PIN_6);   //SPEC_CTS
    #else
        #error "Unknown part"
    #endif
#else
    #error "Unknown board"
#endif

    if(state > 0)
      state = 1;

    return(state);
}




// MMS spec power is controlled by i2c addressable latch, pcal6416, uart3 is used for spec serial, rts/cts MSC2:
int mms_enable_rs232_xcvr(unsigned int state)
{
    if(state == 0)
    {
        // transceiver off
        pcal6416_shadow_port0 &= ~SPEC_232_EN;
        pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    }
    else
    {
        // transceiver on
        pcal6416_shadow_port0 |= SPEC_232_EN;
        pcal6416_write_byte(PCAL6416A_OUT_PORT0, pcal6416_shadow_port0);
    }
    return(state);
}

int mms_rts(unsigned int state)
{
    const uint8_t val = (state) ? (0xFFU) : (0x00U);

#if BOARD_MSC == 3
    #if defined(PART_TM4C1290NCPDT)
        MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_4, val);
    #elif defined(PART_TM4C123GH6PM)
        MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_7, val);
    #else
        #error "Unknown part"
    #endif
#else
    #error "Unknown board"
#endif

    return(state);
}


int mms_rxBytesAvail(void)
{
    return(uart3_rx_bytes_avail());
}

/*
* mms_rx_byte_timeout
*   timeout in millisec
*   return > 255 if timeout
*   return unsigned char (0 to 255), cast to uchar
*/
uint32_t mms_rx_byte_timeout(uint32_t timeout_ms)
{
    unsigned char uc;
    uint32_t ticks;
    uint32_t retVal;

    const uint32_t tickStart = Timer_1ms();

    while(1)
    {
        if( mms_rxBytesAvail() )
        {
            uc = uart3_rx_byte();
            retVal = (uint32_t)uc;
            return(retVal);
        }

        ticks = (Timer_1ms() - tickStart);
        if(ticks > timeout_ms)
        {
            retVal = 256;       // any number greater than 255 is an error
            return(retVal);
        }

    }

}

//void mms_rx_flush(void)
void mms_flushRx(void)
{
    uart3_rx_bufr_flush();
}

//void mms_tx_flush(bool bDiscard)
void mms_flushTx(bool bDiscard)
{
    uart3_tx_bufr_flush(bDiscard);
}

/*
* pass in null term string of size less than 254
*/
void mms_tx_str(unsigned char *strBuf)
{
    uart3_write((const char*)strBuf, strlen((const char*)strBuf));
}

void mms_tx_buf(unsigned char *strBuf, unsigned int bufLen)
{
    uart3_write((const char*)strBuf, bufLen);
}



/*
* return size of TX buffer (for error checking)
*/
int mms_tx_buf_size(void)
{
    return(UART3_TX_BUFFER_SIZE);
}

/*
* return size of RX buffer (for error checking)
*/
int mms_rx_buf_size(void)
{
    return(UART3_RX_BUFFER_SIZE);
}

/*
* pass in address of buffer, terminator char, and timeout in msec
* return -1 for timeout or rx byte cnt
*/
int mms_rx_str(unsigned char *rxPtr, unsigned char termChar, uint32_t timeout_msec)
{
    int indx;
    uint32_t val1;
    unsigned char uc;

    indx = 0;

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

            if(uc == termChar)     //cr = 0x0d
            {
                *rxPtr = 0x00;       // null terminate
                indx++;
                return(indx);
            }

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


int mms_wait_cts(int state, uint32_t timeout)
{
    //int timeOutFlg;
    int retVal;
    uint32_t tick_ms_delta;

    if((state < 0) || (state > 1))
        return(-1);     // invalid input

    // wait for SPEC_CTS to go lo (active)
    //timeout in 500msec
    const uint32_t tick_ms_start = Timer_1ms();

    while(1)
    {
        retVal = mms_get_cts();
        if(retVal == state)
            return(state);  //0 or 1

        wdog_pet();

        tick_ms_delta = (Timer_1ms() - tick_ms_start);
        if(tick_ms_delta > timeout) // TODO this looks like it should be a > ? (JS @ 20250415)
        {
            return(TIMEOUT); // -2
        }

    }

}

int mms_check_mmsDig_version(void)
{
    int retVal;
    int retVal2;
    // char input;
    unsigned int indx;
    unsigned char rxBuf[64];

    mms_specPwrOn();

    if(mmsDbgFlg == 1) { uprintf("Sending V for version request to MMS Dig");  prn_nl(); }

    retVal2 = 0;
    for(indx=0; indx<3; indx++)     // read the version 3 times
    {
        mms_putc('V');
        mms_putc(0x0d);

        retVal = mms_rx_str(&rxBuf[0], CR, SPEC_DAT_WAIT_500MS);
        if(retVal >= 0)
        {
            uprintf("rx %d: %s\r\n", retVal, rxBuf);  // DEBUG
            retVal2++;
        }
        else
        {
            uprintf("rx timed out\r\n");
            retVal2--;
        }


        // any key on console to aport
        if( UARTRxBytesAvail() )
        {
            // input =
            get_key();
            break;
        }

        uprintf("DBG: remove delay in mms_check_mmsDig_version, figure out why first V doesnt work" );   prn_nl(); // DEBUG
        delay_msec(500);
    }

    mms_close();  // power off spec

    return(retVal2);
}



float mms_calc_NO3(void)
{
    uprintf("Calling mms_takeIsusSample");  prn_nl();

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

    uprintf("NO3 = %.2lf", apf.isus_no3); prn_nl();

    return (apf.isus_no3);
}





/*******************************************************************************
** Summary  : Routines to access Zeiss MMS-UV Spectrometer fitted with Cronin PIC based RS232 interface
** Filename : cronin.c
** Author   : Luke Coletti
** Project  :
** Version  : 1.0
** Compiler : MetroWerks Code Warrior v8.3
** Created  : 12/15/14
** Archived :
*******************************************************************************
** Modification History:
** 01/02/15 : CF2 Port, LJC
*******************************************************************************/
