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


FILE *OpenApfLogFile( uchar select )
{
	char ebuf[ERRBUFSIZE];
	FILE   *fptr;
//fpos_t fsize;

	//check if ERROR.LOG has grown too big...
	CheckFileSize( ERRORLOG_FILE );

	memset(logfname, '\0', sizeof(logfname));

	//check if FLASH card is going to run out of space soon
	pdcfinfo("C:", &size, &avail);
	if( avail < 5000L ) {
		if( sys_last_error != (int)FLASH_CARD_FULL ) {
			sprintf(ebuf, "FLASH Card is Full with %ld Bytes Remaining", avail);
			LogErrorMsg(ebuf, (int)FLASH_CARD_FULL,sys_message_enable);
		}
		return( FNULL );
	}

	if( select == ISUS_DATA_FILE ) {
		if( sys_por_ctr < 9999 )
			sprintf(logfname,"C:\\ISUS\\PROF%04lu.log", sys_por_ctr);
		else
			sprintf(logfname,"C:\\ISUS\\PROF9999.log", sys_por_ctr);
	}
	else if( select == DURA_DATA_FILE ) {
		if( sys_por_ctr < 9999 )
			sprintf(logfname,"C:\\DURA\\PROF%04lu.log", sys_por_ctr);
		else
			sprintf(logfname,"C:\\DURA\\PROF9999.log", sys_por_ctr);
	}

	fptr = fopen(logfname, "a+");
	if(fptr != FNULL)
		return(fptr);
	else{
		sprintf(ebuf, "File Open Failure on file: %s", logfname);
		LogErrorMsg(ebuf, (int)FILE_OPEN_FAILURE, sys_message_enable);
		return( FNULL );
	}

}

int LogErrorMsg(char *emsg, int ecode, uchar outp)
{

	if( (ecode <= -100) && (ecode >= -199) ) {
		sys_error_time = RTCGetTime( NULL, NULL );
		tp = localtime(&sys_error_time);
		DS3234_WrSRAM(SYS_LASTERRTIME_PTR, sizeof(ulong), (ulong*)&sys_error_time);

		++sys_error_ctr;
		DS3234_WrSRAM(SYS_ERRORCTR_PTR, sizeof(ulong), (ulong*)&sys_error_ctr);

		sys_last_error=ecode;
		DS3234_WrSRAM(SYS_LASTERROR_PTR, sizeof(int), (int*)&sys_last_error);
	}
	else if( (ecode <= -200) && (ecode >= -299) ) {
		isus_error_time = RTCGetTime( NULL, NULL );
		tp = localtime(&isus_error_time);
		DS3234_WrSRAM(ISUS_LASTERRTIME_PTR, sizeof(ulong), (ulong*)&isus_error_time);

		++isus_error_ctr;
		DS3234_WrSRAM(ISUS_ERRORCTR_PTR, sizeof(ulong), (ulong*)&isus_error_ctr);

		isus_last_error=ecode;
		DS3234_WrSRAM(ISUS_LASTERROR_PTR, sizeof(int), (int*)&isus_last_error);
	}
	else if( (ecode <= -300) && (ecode >= -399) ) {
		dura_error_time = RTCGetTime( NULL, NULL );
		tp = localtime(&dura_error_time);
		DS3234_WrSRAM(DURA_LASTERRTIME_PTR, sizeof(ulong), (ulong*)&dura_error_time);

		++dura_error_ctr;
		DS3234_WrSRAM(DURA_ERRORCTR_PTR, sizeof(ulong), (ulong*)&dura_error_ctr);

		dura_last_error=ecode;
		DS3234_WrSRAM(DURA_LASTERROR_PTR, sizeof(int), (int*)&dura_last_error);
	}
	else {
		current_time = RTCGetTime( NULL, NULL );
		tp = localtime(&current_time);
	}


	if( (err_file = fopen("C:\\ERROR.LOG", "a+")) == FNULL ) {
		sys_error_time = RTCGetTime( NULL, NULL );
		tp = localtime(&sys_error_time);
		DS3234_WrSRAM(SYS_LASTERRTIME_PTR, sizeof(ulong), (ulong*)&sys_error_time);

		++sys_error_ctr;
		DS3234_WrSRAM(SYS_ERRORCTR_PTR, sizeof(ulong), (ulong*)&sys_error_ctr);

		sys_last_error = (int)FILE_OPEN_FAILURE;
		DS3234_WrSRAM(SYS_LASTERROR_PTR, sizeof(int), (int*)&sys_last_error);
		return( ERROR );
	}

	fprintf(err_file, "E, %02d/%02d/%02d %02d:%02d:%02d, %d, %s\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec, ecode, emsg);

	fclose(err_file);

	if( outp == TRUE )
		printf("%02d/%02d/%02d %02d:%02d:%02d, %d, %s\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec, ecode, emsg);

	return( OK );

}

long CalcRecsPending(uchar select)
{
	char   *sbuf;
	char   *FileName[] = {"C:\\OFFLOAD.DAT", "C:\\ERROR.LOG"};
	FILE   *fptr;
	long ctr;


	if( ( select < 0 ) || ( select > 1 ) )
		return( ERROR );

	if( (fptr = fopen(FileName[select], "r")) == FNULL)
		return( OK );

	if( (sbuf = calloc((size_t)DATBUFSIZE, sizeof(char))) == NULL ) {
		fclose(fptr);
		return( ERROR );
	}

	ctr = 0L;
	while( (fgets(sbuf, (size_t)DATBUFSIZE, fptr) != NULL) && (!feof(fptr)) ) {
		++ctr;
	}

	free(sbuf);
	fclose(fptr);

	return( ctr );

}

int SendRecords(uchar select, fpos_t *fpsrc)
{
	char   *sbuf;
	char   *FileName[] = {"C:\\OFFLOAD.DAT", "C:\\ERROR.LOG"};
	FILE   *fptr;
	fpos_t fptmp;


	if( ( select < 0 ) || ( select > 1 ) )
		return( ERROR );

	if( (fptr = fopen(FileName[select], "r")) == FNULL)
		return( ERROR );

	//set file pos to passed value
	if( *fpsrc != 0 ) {
		if( fsetpos(fptr, fpsrc) != OK ) {
			fclose(fptr);
			return( ERROR );
		}
	}

	if( (sbuf = calloc((size_t)DATBUFSIZE, sizeof(char))) == NULL ) {
		fclose(fptr);
		return( ERROR );
	}

	if( fgets(sbuf, (size_t)DATBUFSIZE, fptr) != NULL ) {
		SerPuts(sbuf);
		fflush(stdout);
	}

	free(sbuf);

	//read current file pos
	fgetpos(fptr, fpsrc);

	//read EOF file pos
	fseek(fptr, 0L, SEEK_END);
	fptmp=ftell(fptr);

	fclose(fptr);

	//does the current file pos and EOF match?
	if( *fpsrc == fptmp ) {
		remove( FileName[select] );
		SerPuts("EOF\n");
		fflush(stdout);
		return( TRUE );
	}

	return( OK );

}

int CheckFileSize(uchar select)
{
	char   *ebuf;
	char   *FileName[] = {"C:\\OFFLOAD.DAT", "C:\\ERROR.LOG"};
	FILE   *fptr;
	fpos_t fsize;


	if( ( select < 0 ) || ( select > 1 ) )
		return( ERROR );

	if( (fptr = fopen(FileName[select], "r")) == FNULL)
		return( OK );

	//read EOF file pos
	if( fseek(fptr, 0L, SEEK_END) != OK ) {
		fclose(fptr);
		return( ERROR );
	}

	fsize=ftell(fptr);

	fclose(fptr);

	if( select == 0 ) {
		if( fsize > (fpos_t)sys_sf_limit*1000L ) {
			remove("C:\\OFFLOAD.DAT");
			if( (ebuf = calloc((size_t)ERRBUFSIZE, sizeof(char))) == NULL )
				return( ERROR );
			sprintf(ebuf, "OFFLOAD.DAT Too Big %ld Bytes - File Removed", fsize);
			LogErrorMsg(ebuf, (int)OFFLOAD_FILE_TOO_BIG,sys_message_enable);
			free(ebuf);
			return( TRUE );
		}
		else
			return( OK );
	}

	else if( select == 1 ) {
		if( fsize > (fpos_t)sys_ef_limit*1000L ) {
			remove("C:\\ERROR.LOG");
			if( (ebuf = calloc((size_t)ERRBUFSIZE, sizeof(char))) == NULL )
				return( ERROR );
			sprintf(ebuf, "ERROR.LOG Too Big %ld Bytes - File Removed", fsize);
			LogErrorMsg(ebuf, (int)ERROR_FILE_TOO_BIG, sys_message_enable);
			free(ebuf);
			return( TRUE );
		}
		else
			return( OK );
	}

	else{
		return( ERROR );
	}

}

int LoadIsusECoefs(double *Lambda, double **Ecoefs, double *Zeros, double *Tcoefs, double *CalTemp, char *CalDate)
{
	FILE *coef_file;
	char *srchptr, *dataptr;
	char data[512];
	int i, j, ret;
	int got_date, month, day, year;
	int got_temp;
	double sw_calib_temp;

	//hard code Tcoefs for now...
	for(i = 0; i < TCOEFS; i++) {
		if(i == 0)
			Tcoefs[i] = 1.1500276;
		else if(i == 1)
			Tcoefs[i] = 0.0284;
		else if(i == 2)
			Tcoefs[i] = -0.3101349;
		else if(i == 3)
			Tcoefs[i] = 0.001222;
		else
			Tcoefs[i] = 0.0;
	}
	
	/* open Extinction Coefficients and Zero Scan file */
	coef_file = fopen("C:\\ISUS\\ECOEFZER.CAL", "r");
	if(coef_file == FNULL)
		return( -1 );

	got_date = got_temp = FALSE;

	// starting at 0 we end at 256, the count we want...
	for(i = 0; i < 256; ) {
		if(fgets(data, sizeof(data), coef_file) == NULL) {
			printf("\n  NULL ret in Ext. Coef / Zero Data!\n");
			return( -2 );
		}
		else{
			if(got_date == FALSE) {
				srchptr = memchr(data, 'H', 1);
				if(srchptr != NULL) {
					srchptr=srchptr+2;
					ret = sscanf(srchptr,"%d/%d/%d,,,,\n",&month, &day, &year);
					if(ret == 3) {
						got_date = TRUE;
						sprintf(CalDate,"%02d/%02d/%02d", month, day, year);
						continue;
					}
				}
			}
			if(got_temp == FALSE) {
				srchptr = memchr(data, 'H', 1);
				if(srchptr != NULL) {
					srchptr=srchptr+2;
					ret = sscanf(srchptr,"CalTemp,%lf,,,\n",&sw_calib_temp);
					if(ret == 1) {
						got_temp = TRUE;
						*CalTemp = sw_calib_temp;
						continue;
					}
				}
			}
			srchptr = memchr(data, 'E', 1);
			if(srchptr != NULL) {
				++i;
				j=0;
				dataptr = strtok(srchptr, ",");
				while ((dataptr = strtok(NULL, ",")) != NULL) {
					
					switch( j ){
						
						case 0:
							if(Lambda != NULL)									//skip value, if Lambda array has not already been initialized via ZCoef values
								Lambda[i] = strtod(dataptr, NULL);
							break;
							
						case 1:
							Ecoefs[i][0] = strtod(dataptr, NULL);
							break;
							
						case 2:
							Ecoefs[i][1] = strtod(dataptr, NULL);
							break;
	
						case 3:
							Ecoefs[i][2] = strtod(dataptr, NULL);
							break;
							
						case 4:
							Zeros[i] = strtod(dataptr, NULL);
							break;	
							
						default:
							break;
							
					}
					++j;
				}

				if(j != 5) {
					printf("\n  Bad data at %d in ECoef File!\n", i);
					printf("%s\n", srchptr);
					fclose(coef_file);
					return( -3 );
				}
			}
		}
	} //end for loop

	if(got_date == FALSE)
		sprintf(CalDate, "NODATEFOUND");

	if(got_temp == FALSE)
		*CalTemp = 20.0;

	fclose(coef_file);
	return(i);

}

int LoadIsusZCoefs(double *ZeissCoefs)
{
	FILE   *coef_file;
	char   *coef_ptr;
	char buff[150];
	int i;

	/* open Zeiss Coefficients file */
	coef_file = fopen("C:\\ISUS\\ZCOEFDAT.CAL", "r");
	if(coef_file == FNULL) {
		//printf("\n  Can't open ZCOEFDAT.CAL!\n");
		return( -1 );
	}
	else{
		i=0;
		do {
			fgets(buff, sizeof(buff), coef_file);
			if( (strchr(buff,'/')) || (strchr(buff,'*')) )
				continue;
			coef_ptr = strstr(buff,"C");
			if(coef_ptr != NULL) {
				coef_ptr += 3;
				if( (strtod(coef_ptr, NULL) != 0.0) && (i < 5) ) {
					ZeissCoefs[i] = strtod(coef_ptr, NULL);
					i++;
				}
			}
		} while( !feof(coef_file) );
		if(i == 3) {
			ZeissCoefs[3]=0.0; //clear possible residual from an "new" spec with 3 coefs
			ZeissCoefs[4]=0.0; //clear possible residual from an "new" spec with 3 coefs
		}
		if(i == 4)
			ZeissCoefs[4]=0.0;  //clear possible residual from an "old" spec with 5 coefs
	} // end else

	fclose(coef_file);
	return(i);      //should return 5 (MMS-UV) or 4 (MMS-UV-II) or 3-4 (MMS-UV-II NEW MODELS)

}

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

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

	fclose(coef_file);

	return(i);      //should return 4

}

/******************************************************************************\
**	pdx_access		POSIX-like access funtion
**
**	access modes (_A_RDONLY, _A_HIDDEN,...) defined in <dirent.h>
\******************************************************************************/

int pdx_access(const *path, int amode)
{
	struct stat stbuf;

	if (stat(path, &stbuf) == -1)
		return -1;

	return (stbuf.st_attr & amode) ? 0 : -1;

}               //____ pdx_access() ____//


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

ushort SerGetsTmout(char *buf, ushort buflen, ushort seconds)
{
	register int c;
	register ushort nchars;
	RTCTimer TmOut;

	RTCCountdownTimerSetup( &TmOut, (1000000L * seconds) );         // set timeout

	nchars = 0;
	buf[0] = '\0';

	while(  RTCCountdownTimeout(&TmOut) != true ) {
		if ( SCIRxQueuedCount() ) {
			switch( c = SCIRxGetByte( false ) ) {
			case '\n':
			case '\r':
				if(nchars > 0)
					return( nchars );
				else
					break;

			default:
				buf[nchars++] = (char)c;
				buf[nchars] = '\0';
				if ( nchars >= (buflen-1) )
					return( nchars );
				break;
			} // end switch
		} // end if
	} // end while

	return( nchars );

}

int SerPuts(char *str)
{

	while (*str) {
		if (*str == '\n')
			SCITxPutChar('\r');
		SCITxPutChar(*str++);
	}

	return( TRUE );

}

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

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

	SCIRxFlush();            // Flush the UART buffer

	return strlen(buffer);   // Return the length

}

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

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

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

	return(string);
}

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

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

	strupr(buf);

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

}

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

int SyncNextSample( void )
{
//char   ebuf[ERRBUFSIZE];
//ulong  secsperday=86400;
	double delta;

	// where are we now
	current_time = RTCGetTime( NULL, NULL );

	//no sample taken yet!
	if( (isus_reset_flag == TRUE) && (dura_reset_flag == TRUE) ) {
		alarm_time = current_time + ((ulong)sys_apf_tmout * 3600); //hours to live
		return( 1 );
	}
	else{
		delta = difftime(alarm_time, current_time);

		//if delta is too far positive (in the future) then reset
		//if delta is too far negative missedSleeps in main() will take care of it

		if( delta > (double)sys_apf_tmout * 3600.0 )
			alarm_time = current_time + ((ulong)sys_apf_tmout * 3600);  //hours to live
		return( 2 );
	}

}

void CheckRtcDelta(void)
{
	char ebuf[ERRBUFSIZE];
	double delta;

	/* get the current times from CF2 and Dallas DS3234 */
	CF2_secs    = RTCGetTime( NULL, NULL );
	DS3234_secs = DS3234_GetTime();

	if( fabs((delta=difftime(CF2_secs, DS3234_secs))) > 5.0 ) {
		// update CF2 RTC with DS3234 RTC value, which has a much better drift rate
		RTCSetTime(DS3234_secs, 0);
		sprintf(ebuf, "RTC Absolute Delta (CF2 minus DS3234) exceeded 5 secs - delta = %+.1lf", delta);
		LogErrorMsg(ebuf, (int)RTC_DELTA_EXCEEDED, sys_message_enable);
	}

}

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

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

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

}

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

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

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

void DelaySecs( ushort secs )
{
	RTCDelayMicroSeconds( (ulong)secs * 1000000UL );
}

void DelayMilliSecs( ushort msecs )
{
	RTCDelayMicroSeconds( (ulong)msecs * 1000UL );
}

/*****************************************************************\
**	sleep		delay for absolute or relative (since last) time
**	returns:
**		0		ok
**		1		missed the last sleep interval
**		-1		overflowed the 1.2 hour limit
**	NOTE: THIS ROUTINE IS NOT REENTRANT !
\*****************************************************************/

short Sleep(ulong us, bool absolute)
{
	static RTCTimer rt;
	static ulong lasttime;
	ulong donetime;
	short error = 0;

	if (absolute)
	{
reset:
		RTCElapsedTimerSetup(&rt);
		lasttime = RTCElapsedTime(&rt);
		if (error)
			return error;
	}

	donetime = us + lasttime;
	if (donetime < us || donetime < lasttime)
	{
		error = -1;     // we'll overflowed the 1.2h limit
		goto reset;
	}

	lasttime = donetime;            // for next invocation
	if (donetime < RTCElapsedTime(&rt))
		return 1;       // we missed the next interval

	while ((lasttime = RTCElapsedTime(&rt)) < donetime)
		;

	return error;

}               //____ sleep() ____//


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

void OutputIsusHeader(FILE *fptr)
{
	int i;

	if(fptr == FNULL)
		return;

	pdcfinfo("C:", &size, &avail);
	DS1683_RdElapsedTimer( &isus_lamp_elp );
	DS1683_RdEventCounter( &isus_lamp_evt );
	hdiv = ldiv(isus_lamp_elp, 3600L);
	mdiv = ldiv(hdiv.rem, 60L);

	fprintf(fptr, "H, Datalogger Serial Num , SN:%04hu\n", sys_serial_num );
	fprintf(fptr, "H, Application      Name , %s\n", strupr(bfile) );
	fprintf(fptr, "H, App Build        Date , %s, %s\n", bdate, btime);
	tp = localtime( &sys_reset_time );
	fprintf(fptr, "H, Power Up Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	current_time = RTCGetTime( NULL, NULL );
	tp = localtime( &current_time );
	fprintf(fptr, "H, Current  Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	tp = localtime( &alarm_time );
	fprintf(fptr, "H, Alarm    Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	fprintf(fptr, "H, Power Active  Timeout , %hu\n", sys_apf_tmout);
	fprintf(fptr, "H, Persistor CF2-1M Info , S/N:%ld - BIOS:%d.%02d - PicoDOS:%d.%02d\n", BIOSGVT.CFxSerNum, BIOSGVT.BIOSVersion, BIOSGVT.BIOSRelease, BIOSGVT.PICOVersion, BIOSGVT.PICORelease);
	fprintf(fptr, "H, Compact FLASH    Size , %ld\n", size);
	fprintf(fptr, "H, Compact FLASH    Left , %.2f%%\n", (float)(((float)avail/(float)size)*100.0));
	fprintf(fptr, "H, Power Cycle   Counter , %lu\n", sys_por_ctr);
	fprintf(fptr, "H, System Reset  Counter , %lu\n", sys_reset_ctr);

	fprintf(fptr, "H, System Error  Counter , %lu\n", sys_error_ctr);
	fprintf(fptr, "H, Last System     Error , %d\n",  sys_last_error);
	tp = localtime( &sys_error_time );
	fprintf(fptr, "H, Last System ErrorTime , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

	fprintf(fptr, "H, ISUS  Error   Counter , %lu\n", isus_error_ctr);
	fprintf(fptr, "H, Last ISUS       Error , %d\n", isus_last_error);
	tp = localtime( &isus_error_time );
	fprintf(fptr, "H, Last ISUS Error  Time , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

	fprintf(fptr, "H, ISUS Sample   Counter , %lu\n", isus_sample_ctr);
	tp = localtime( &isus_sample_time );
	fprintf(fptr, "H, Last Sample Rec. Time , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

	fprintf(fptr, "H, Fiberlite    Odometer , %02ld:%02ld:%02ld\n", hdiv.quot, mdiv.quot, mdiv.rem);
	fprintf(fptr, "H, Fiberlite      Cycles , %hu\n", isus_lamp_evt);
	fprintf(fptr, "H, Spec Intensity LampON , %hu\n", isus_ref_limit);
	fprintf(fptr, "H, Spec Integration Time , %lu\n", isus_spec_period);
	fprintf(fptr, "H, Spec Pre  scans taken , %hu\n", isus_pre_scans);
	fprintf(fptr, "H, Spec Lite scans taken , %hu\n", isus_scan_num);
	fprintf(fptr, "H, Spec Dark scans taken , %hu\n", isus_dark_num);
	if(Zcoefs <= 0)
		fprintf(fptr, "H, Zceof File    Error   , ZCoefs = %d\n", Zcoefs);
	if(Ecoefs != 256)
		fprintf(fptr, "H, Eceof File    Error   , ECoefs = %d\n", Ecoefs);
	else
		fprintf(fptr, "H, Calibration      Date , %s\n", CalDate);
	fprintf(fptr, "H, Sw Calibration   Temp , %.2lf\n", SwCalTemp);
	fprintf(fptr, "H, Wavelength Fit  Range , %.3f <-> %.3f\n", LambdaData[isus_pixel_beg], LambdaData[isus_pixel_end]);
	fprintf(fptr, "H, Pixel Fit       Range , %d <-> %d\n", isus_pixel_beg, isus_pixel_end);
	fprintf(fptr, "H, Fitted Concentrations , %d\n", isus_fit_concs);
	fprintf(fptr, "H, Baseline        Model , ");
	if(isus_bsl_model == 1)
		fprintf(fptr, "(ax + b)\n");
	if(isus_bsl_model == 2)
		fprintf(fptr, "(ax^2 + bx + c)\n");
	if(isus_bsl_model == 3)
		fprintf(fptr, "exp(ax + b)\n");
	if(isus_bsl_model == 4)
		fprintf(fptr, "exp(ax^2 + bx + c)\n");
	fprintf(fptr, "H, Br Temp  Compensation , ");
	if(isus_tcomp_mode == TRUE)
		fprintf(fptr, "ENABLED\n");
	else
		fprintf(fptr, "DISABLED\n");
	fprintf(fptr, "H, Bromide Term in Model , ");
	if(isus_salfit_mode == TRUE)
		fprintf(fptr, "will be FIT (to absorbance data)\n");
	else
		fprintf(fptr, "will be FIXED (to external CTD value)\n");

	if(Zcoefs > 0) {
		fprintf(fptr, "H, Zeiss Coefficient Vals, ");
		for(i = 0; i < 5; i++) {
			if(i != 4)
				fprintf(fptr,"%e,", ZeissCoefs[i]);
			else
				fprintf(fptr,"%e\n", ZeissCoefs[i]);
		}
	} //end Zcoefs if

}

void OutputDuraHeader(FILE *fptr)
{

	if(fptr == FNULL)
		return;

	pdcfinfo("C:", &size, &avail);

	fprintf(fptr, "H, Datalogger Serial Num , SN:%04hu\n", sys_serial_num );
	fprintf(fptr, "H, Application      Name , %s\n", strupr(bfile) );
	fprintf(fptr, "H, App Build        Date , %s, %s\n", bdate, btime);
	tp = localtime(&sys_reset_time);
	fprintf(fptr, "H, Power Up Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	current_time = RTCGetTime( NULL, NULL );
	tp = localtime(&current_time);
	fprintf(fptr, "H, Current  Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	tp = localtime(&alarm_time);
	fprintf(fptr, "H, Alarm    Time   (GMT) , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	fprintf(fptr, "H, Power Active  Timeout , %hu\n", sys_apf_tmout);
	fprintf(fptr, "H, Persistor CF2-1M Info , S/N:%ld - BIOS:%d.%02d - PicoDOS:%d.%02d\n", BIOSGVT.CFxSerNum, BIOSGVT.BIOSVersion, BIOSGVT.BIOSRelease, BIOSGVT.PICOVersion, BIOSGVT.PICORelease);
	fprintf(fptr, "H, Compact FLASH    Size , %ld\n", size);
	fprintf(fptr, "H, Compact FLASH    Left , %.2f%%\n", (float)(((float)avail/(float)size)*100.0));
	fprintf(fptr, "H, Power Cycle   Counter , %lu\n", sys_por_ctr);
	fprintf(fptr, "H, System Reset  Counter , %lu\n", sys_reset_ctr);

	fprintf(fptr, "H, System Error  Counter , %lu\n", sys_error_ctr);
	fprintf(fptr, "H, Last System     Error , %d\n",  sys_last_error);
	tp = localtime( &sys_error_time );
	fprintf(fptr, "H, Last System ErrorTime , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

	fprintf(fptr, "H, DURA  Error   Counter , %lu\n", dura_error_ctr);
	fprintf(fptr, "H, Last DURA       Error , %d\n", dura_last_error);
	tp = localtime( &dura_error_time );
	fprintf(fptr, "H, Last DURA Error  Time , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);

	fprintf(fptr, "H, DURA Sample   Counter , %lu\n", dura_sample_ctr);
	tp = localtime( &dura_sample_time );
	fprintf(fptr, "H, Last Sample Rec. Time , %02d/%02d/%02d %02d:%02d:%02d\n", tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
	fprintf(fptr, "H, AtoD Reads per Sample , %hu\n", dura_atod_readings);
	fprintf(fptr, "H, AtoD Reading    Delay , %hu\n", dura_atod_delay);
	fprintf(fptr, "H, Samps per AtoD  Recal , %lu\n", dura_samps_per_cal);

}

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

	spec_was_on = isus_spec_pwr;
	
	ret = SpecPwrOn( &SpecPort );

	if( ret == TRUE ) {
		TUPuts( SpecPort, "V\r");
		nchars = TUGetsTmout(SpecPort, SpecFirmware, sizeof(SpecFirmware), 2);
		if( nchars > 0 ) {
			if( (srchptr=strstr(SpecFirmware, "MBARI")) == NULL )
				sprintf(SpecFirmware, "Can't find MBARI!");
		}
	}
	else if( ret == TIMEOUT )
		sprintf(SpecFirmware, "Power On Timeout!");
	else if( ret == ERROR )
		sprintf(SpecFirmware, "TPU UART Error!");
	else
		sprintf(SpecFirmware, "Mystery Power On Error!");

	if( spec_was_on == FALSE )
		SpecPwrOff( SpecPort );

	printf("\n\n  MBARI Serial Number : %04hu\n", sys_serial_num);

	printf("\n  CF2 App: %s Date: %s %s\n", strupr(bfile), bdate, btime);
	printf("  CF2 S/N: %ld - BIOS:%d.%02d - PicoDOS:%d.%02d\n", BIOSGVT.CFxSerNum, BIOSGVT.BIOSVersion, BIOSGVT.BIOSRelease,
	       BIOSGVT.PICOVersion, BIOSGVT.PICORelease);

	pdcfinfo("C:", &size, &avail);
	printf("  CF Card: Size=%ld Avail=%.2f%%\n\n", size, (float)(((float)avail/(float)size)*100.0));

	DisplayTimes();

	INA219_ReadVoltageCurrent(&volts, &amps);
	printf("\n  System Batt Voltage = %.3f\n", volts);
	printf("  System Load Current = %.2e\n", amps);

	HIH6130_ReadTempHumidity(&temp, &humi);
	dew_point=calc_dewpoint(humi, temp);
	printf("  Housing Temperature = %+06.2fC = %+06.2fF\n",temp, CtoF(temp) );
	printf("  Housing    Humidity = %5.1f%%  = %5.1fC (DP)\n", humi, dew_point);

	printf("  Power Cycle   Count = %lu\n", sys_por_ctr);
	printf("  System Reset  Count = %lu\n", sys_reset_ctr);

	printf("  System Error  Count = %lu\n", sys_error_ctr);
	printf("  Last System   Error = %d\n",  sys_last_error);
	printf("  Last Error     Time = %s", ctime( &sys_error_time) );

	printf("  ISUS Error    Count = %lu\n", isus_error_ctr);
	printf("  Last ISUS     Error = %d\n",  isus_last_error);
	printf("  Last Error     Time = %s", ctime( &isus_error_time) );

	printf("  DURA Error    Count = %lu\n", dura_error_ctr);
	printf("  Last DURA     Error = %d\n",  dura_last_error);
	printf("  Last Error     Time = %s", ctime( &dura_error_time) );

	DS1683_RdElapsedTimer( &isus_lamp_elp );
	DS1683_RdEventCounter( &isus_lamp_evt );
	hdiv = ldiv(isus_lamp_elp, 3600L);
	mdiv = ldiv(hdiv.rem, 60L);
	printf("  Fiberlite  Odometer = %02ld:%02ld:%02ld = %lu secs\n", hdiv.quot, mdiv.quot, mdiv.rem, isus_lamp_elp);
	printf("  Fiberlite    Cycles = %hu\n", isus_lamp_evt);

	if( srchptr != NULL )
		printf("\n  Spectrometer Status = %s\n", srchptr);
	else
		printf("\n  Spectrometer Status = %s\n", SpecFirmware);

	if( Zcoefs > 0 )
		printf("\n  Zeiss Coef File Found %d coefs loaded.\n", Zcoefs);
	else
		printf("\n  Zeiss Coef File Not Found!\n");

	if( Ecoefs > 0 )
		printf("  Ecoef File Found %d coefs loaded.\n", Ecoefs);
	else
		printf("  Ecoef File Not Found!\n");
	
#if DURA_SENSOR_TYPE == DURA_III
    if( Tcoefs > 0 )
      printf("\n  Thermistor Coef File Found %d coefs loaded.\n", Tcoefs);
    else
      printf("\n  Thermistor Coef File Not Found!\n");

    if( CalVals == TRUE )
      printf("\n  DuraFET III EEPROM Calibration Values Loaded.\n");
    else
      printf("\n  DuraFET III EEPROM Calibration Values COULD NOT BE Loaded.\n");
#endif
  
}

void DisplayTimes(void)
{
	time_t loc_time;
	double diff;

	/* get the current times from CF2 and Dallas DS3234 */
	CF2_secs    = RTCGetTime( NULL, NULL );
	DS3234_secs = DS3234_GetTime();

	loc_time = CF2_secs - (long)(sys_tzoffset*3600L);

	printf("  CF2 Power Up  (GMT): %s", ctime(&sys_reset_time) );

	printf("  CF2 RTC Time  (GMT): %s", ctime(&CF2_secs) );

	printf("  Adjusted Local Time: %s", ctime(&loc_time) );

	printf("  TZ Offset (GMT - LOCAL)  : %+.2lf hrs.\n", (difftime(CF2_secs, loc_time) / 3600.0));

	diff = difftime(CF2_secs, DS3234_secs);
	printf("  RTC delta (CF2 - DS3234) : %+.2lf secs.\n", diff);

}

void DisplayAlarmTime( void )
{
	printf("\n  Last ISUS Samp Time = %s", ctime(&isus_sample_time) );
	printf("\n  Last DURA Samp Time = %s", ctime(&dura_sample_time) );
	printf("\n  Current Wakeup Time = %s", ctime(&alarm_time) );
	current_time = alarm_time - (sys_tzoffset * 3600);
	printf("  Adjusted Local Time = %s", ctime(&current_time) );

}

void DisplayChipSelectSettings(void)
{
	int i;
	ushort type, size;
	ushort CSPAR0_, CSPAR1_, CSBASE_DATA, CSOPTION_DATA;
	ulong addr;
	ulong CSBASE_ADDR, CSOPTION_ADDR;

	CSPAR0_ = (ushort) *((vushort *)0xFFFA44);
	CSPAR1_ = (ushort) *((vushort *)0xFFFA46);

	printf("\n CS Pin Assignment Reg 0 = 0x%0.4X\n", CSPAR0_);
	printf(" CS Pin Assignment Reg 1 = 0x%0.4X\n", CSPAR1_);
	for(i = 0; i <= 5; i++) {
		type = (CSPAR0_ & (0x000C << i*2)) >> 2*(i+1);
		printf(" CS%02d Pin Assignment = %d\n", i, type);
	}
	for(i = 0; i <= 4; i++) {
		type = (CSPAR1_ & (0x0003 << i*2)) >> i*2;
		printf(" CS%02d Pin Assignment = %d\n", i+6, type);
	}

	CSBASE_ADDR   = 0xFFFA4C;
	CSOPTION_ADDR = 0xFFFA4E;

	for(i = 0; i <= 10; i++) {
		CSBASE_DATA   = (ushort) *((vushort *)(CSBASE_ADDR + (ulong)(i*4)));
		CSOPTION_DATA = (ushort) *((vushort *)(CSOPTION_ADDR + (ulong)(i*4)));
		addr = (ulong)(CSBASE_DATA & 0xFFF8) << 8;
		size = (CSBASE_DATA & 0x0007);
		printf(" CS%02d Base Addr = 0x%0.8lX, Block Size = %d and Option Values = 0x%0.4X\n", i, addr, size, CSOPTION_DATA);
	}

}

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

	tmptr = localtime(&ptime);

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

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

}

void DisplayLambdaInfo(FILE *fptr)
{
	int i;

	if(fptr == FNULL)
		return;

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

}

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

	temp = ByteVal;

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

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

	printf("b\n");

}


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

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

	ret = 0;

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

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

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

}

void XformSwEcoefs(double **Ecoefs, double **XformedEcoefs, double *Lambda, double *Tcoefs, double CalTemp, double SamTemp)
{
	int i;
	double L_210, dAbsAtCalT, dAbsAtSamT;

	for(i = 1; i <= 256; i++) {

		if( Ecoefs[i][0] != 0.0 ) {
			L_210 = Lambda[i] - 210.0;

			dAbsAtCalT  = ( Tcoefs[0] + Tcoefs[1] * CalTemp ) *
			              exp( (Tcoefs[2] + Tcoefs[3] * CalTemp) * L_210 );

			dAbsAtSamT  = ( Tcoefs[0] + Tcoefs[1] * SamTemp ) *
			              exp( (Tcoefs[2] + Tcoefs[3] * SamTemp) * L_210 );

			XformedEcoefs[i][0] = Ecoefs[i][0] * ( dAbsAtSamT / dAbsAtCalT );
		}
		else{
			XformedEcoefs[i][0] = Ecoefs[i][0];
		}

		//printf("%lf, %le, %le, %lf, %lf\n", Lambda[i], Ecoefs[i][0], XformedEcoefs[i][0], CalTemp, SamTemp);
	} //end for loop

}

/* Luke's Linear Method, nice and simple!

   void XformSwEcoefs(double **Ecoefs, double **XformedEcoefs, double SolutionRefTemp, double SolutionTemp)
   {
   int  i;
   double deltaTemp;

    deltaTemp = SolutionRefTemp - SolutionTemp;

    for(i = 1; i <= 256; i++) {
      if(Ecoefs[i][0] != 0.0)
        XformedEcoefs[i][0] = Ecoefs[i][0] - (deltaTemp * Ecoefs[i][2]);
      else
        XformedEcoefs[i][0] = Ecoefs[i][0];
      printf("%le, %le, %le, %lf\n", Ecoefs[i][0], XformedEcoefs[i][0], Ecoefs[i][2], deltaTemp);
      }

   }

 */

void InitEcoefMatrix(double **SrcEcoefs)
{
	int i;

	for(i = 1; i <= 256; i++) {
		SrcEcoefs[i][0] = 0.0;
		SrcEcoefs[i][1] = 0.0;
		SrcEcoefs[i][2] = 0.0;
	}

}

void CopyEcoefs(double **SrcEcoefs, double **DstEcoefs)
{
	int i;

	for(i = 1; i <= 256; i++) {
		DstEcoefs[i][0] = SrcEcoefs[i][0];
		DstEcoefs[i][1] = SrcEcoefs[i][1];
		DstEcoefs[i][2] = SrcEcoefs[i][2];
	}

}

void CopySwEcoefs(double **SrcEcoefs, double **DstEcoefs)
{
	int i;

	for(i = 1; i <= 256; i++) {
		DstEcoefs[i][0] = SrcEcoefs[i][0];
	}

}


void InitLambdaArray(double *Lambda, double *ZeissCoefs)
{
	double p;
	double ZC0 = ZeissCoefs[0];
	double ZC1 = ZeissCoefs[1];
	double ZC2 = ZeissCoefs[2];
	double ZC3 = ZeissCoefs[3];
	double ZC4 = ZeissCoefs[4];

// both of these are 10 times slower than below. The pow() seems to be the issue.
//    for( i = 1; i <= 256; i++ ){
//      Lambda[i] = CellNumToLambda(i, ZeissCoefs);
//      Lambda[i] = ZC0 + ZC1*i + ZC2*pow(i, 2.0) + ZC3*pow(i, 3.0) + ZC4*pow(i, 4.0);
//      }

	for( p = 1.0; p <= 256.0; p++ ) {
		Lambda[(int)p] = ZC0 + ZC1*p + ZC2*p*p + ZC3*p*p*p + ZC4*p*p*p*p;
		//printf("%.0lf, %lf\n", p, Lambda[(int)p]);
	}

}

int MatchPixelsToLambda(double *Lambda, int lambda_beg, int lambda_end, ushort *pixel_beg, ushort *pixel_end)
{
	ushort i;
	double min1, min2;
	double delta1, delta2;

	min1=min2=delta1=delta2=10.0;

	for(i = 1; i <= 256; i++) {
		delta1 = fabs((double)(Lambda[i]-lambda_beg));
		delta2 = fabs((double)(Lambda[i]-lambda_end));
		if(delta1 < min1) {
			min1 = delta1;
			*pixel_beg = i;
		}
		if(delta2 < min2) {
			min2 = delta2;
			*pixel_end = i;
		}
	} //end for

	return( TRUE );

}

double CellNumToLambda(int cell, double *ZeissCoefs)
{
	double lambda = 0.0;

	lambda = ZeissCoefs[0] + ZeissCoefs[1]*cell
	         + ZeissCoefs[2]*pow(cell, 2.0)
	         + ZeissCoefs[3]*pow(cell, 3.0)
	         + ZeissCoefs[4]*pow(cell, 4.0);

	return( lambda );

}

void FloatVectorMinMax(ushort end, float data[], float *min, float *max)
{
	ushort i;

	*max = data[0];
	*min = data[0];

	for(i = 0; i < end; i++) {
		if(data[i] > *max)
			*max = data[i];
		else if(data[i]< *min)
			*min = data[i];
	}

}

void FloatVectorMeanStdDev(ushort end, float data[], float *mean, float *stdev)
{
	ushort i;
	float sum, stdvar;

	sum = stdvar = 0.0;
	*mean = *stdev = 0.0;

	for(i = 0; i < end; i++)
		sum += data[i];
	*mean = (sum/end);

	for(i = 0; i < end; i++)
		stdvar += pow((data[i] - *mean), 2.0);
	stdvar /= (end - 1);
	*stdev = sqrt(stdvar);

}

void ShortVectorMinMax(ushort end, ushort data[], ushort *min, ushort *max)
{
	ushort i;

	*max = data[0];
	*min = data[0];

	for(i = 0; i < end; i++) {
		if(data[i] > *max)
			*max = data[i];
		else if(data[i]< *min)
			*min = data[i];
	}

}

void ShortVectorMeanStdDev(ushort end, ushort data[], float *mean, float *stdev)
{
	ushort i;
	float sum, stdvar;

	sum = stdvar = 0.0;
	*mean = *stdev = 0.0;

	for(i = 0; i < end; i++)
		sum += (float)data[i];
	*mean = (sum/end);

	for(i = 0; i < end; i++)
		stdvar += pow((data[i] - *mean), 2.0);
	stdvar /= (end - 1);
	*stdev = sqrt(stdvar);

}

float CtoF(float degC)
{
	return( 9.0/5.0 * degC + 32.0 );
}

/**
 * Evaluates p(x) for a polynom p.
 * Calculates the value of polynom p at x accordings to
 * Horners schema.
 * @param p polynom.
 * @param x value to be inserted into the polynom.
 * @return calculated polynom value.
 */
double poly(double x, int degree, double p[])
{
	double retval = 0.0;
	int i;

	for (i = degree; i >= 0; i--)
		retval = retval * x + p[i];
	return retval;
}

/* Conversion from resistance to temperature.
 * Calculates and returns temperature for given resistance.
 * @param t resistance (in Ohm).
 * @return corresponding temperature.
 */
double rtot(double r, double a[])
{
	double ti;

	ti = poly(log(r), 3, a);
	ti = 1.0 / ti + TABS;
	return ti;
}

/**
   /* Conversion from temperature to resistance.
 * Calculates and returns resistance for given temperature.
 * @param t temperature (in degree Celsius).
 * @return corresponding resistance.
 */
double ttor(double t, double a[])
{
	double r;
	double u, v, p, q, b, c, d;

	t = t - TABS;
	d = (a[0] - 1.0 / t) / a[3];
	c = a[1] / a[3];
	b = a[2] / a[3];
	q = 2.0 / 27.0 * b * b * b - 1.0 / 3.0 * b * c + d;
	p = c - 1.0 / 3.0 * b * b;
	v = -pow(q / 2.0 + sqrt(q * q / 4.0 + p * p * p / 27.0), 1.0 / 3.0);
	u =   pow(-q / 2.0 + sqrt(q * q / 4.0 + p * p * p / 27.0), 1.0 / 3.0);
	r  = exp(u + v - b / 3.0);
	return r;
}

ushort fCrc16Bit(const unsigned char *msg)
{
	ushort crc = 0xffff;

	while (*msg) {
		crc = updcrc((0xff & *msg), crc);
		msg++;
	}

	crc = updcrc(0,updcrc(0,crc));

	return( (crc & 0xffff) );

}
