using System;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using SensorModules;
using SWModules;
using HWModules;
using Math = System.Math;

namespace SensorModules
{
    /// <summary>
    ///     Singleton Class for employment of the MSC class
    /// </summary>
    public sealed class MSC1 : MSC
    {
        private static MSC1 _instance;

        private MSC1()
            : base("MSC1", "COM5", 9600, Parity.None, 8, StopBits.One, 500, 500, false, 10)
        {
        }

        public static MSC1 Instance
        {
            get { return _instance ?? (_instance = new MSC1()); }
        }

        
    }

    /// <summary>
    ///     Class: MSC
    ///     File: MSC.cs
    ///     Summary: Device driver for the ISUS/DuraFet Multi Sensor Controller (MSC)
    ///     Author: Eric Martin
    ///     Date: 01NOV2017
    /// </summary>
    public class MSC
    {
        private MotherBoard motherboard = MotherBoard.Instance;

        /// <summary>
        ///     Deterimines which sensor model the MSC will use when sampling (see TS command)
        /// </summary>
        public enum SampleMode
        {
            NoSensor = 0,
            ISUS,
            DuraFET,
        }

        /// <summary>
        /// Hardware configuration values for MSC configuration
        /// </summary>
        public enum SensorConfig
        {
            NoSensors = 1,
            IsusOnly,
            DuraFetOnly,
            IsusAndDuraFet
        }

        private const byte CmdWake = 0x57; //W
        private const byte CmdIsusDuraConfig = 0x43; //C
        private const byte CmdError = 0x45; //E
        private const byte CmdPower = 0x50; //P
        private const byte CmdSample = 0x53; //S
        private const byte CmdTime = 0x54; //T
        private const byte CmdMscConfig = 0x51; //Q
        private const double Tolerance = 0.1;

        // Long Commands
        private static readonly byte[] RespAck = {0x41, 0x43, 0x4B, 0x0A};
        private static readonly byte[] RespNak = {0x4E, 0x41, 0x4B};
        private static readonly byte[] RespCmd = {0x43, 0x4D, 0x44}; //CMD
        private static readonly byte[] RespDat = {0x44, 0x41, 0x54}; //DAT
        private static readonly byte[] RespE = {0x45}; //E
        private static readonly byte[] RespSE = {0x53, 0x45}; //SE
        private static readonly byte[] RespSamp = {0x30, 0x78}; //0x
        private static readonly byte[] RespEof = {0x45, 0x4F, 0x46}; //EOF
        private static readonly byte[] RespCfg = {0x48}; //H

        private static readonly byte[] CmdTakeSample = {0x54, 0x53}; //TS
        private static readonly byte[] CmdSendLast = {0x53, 0x4C}; //SL
        private static readonly byte[] CmdRtc = {0x52, 0x54, 0x43}; //RTC
        private static readonly byte[] CmdSleep = {0x53, 0x4C, 0x50}; //SLP
        private static readonly byte[] CmdCtd = {0x43, 0x54, 0x44}; //CTD
        private static readonly byte[] CmdFit = {0x46, 0x49, 0x54}; //FIT
        private static readonly byte[] CmdBake = {0x42, 0x41, 0x4b, 0x45}; //BAKE
        private static readonly byte[] CmdSendError = {0x53, 0x45}; //SE
        private static readonly byte[] CmdMode = {0x4d, 0x4f, 0x44, 0x45}; //MODE
        private static readonly byte[] CmdSpectra = {0x53, 0x50, 0x45, 0x43, 0x54, 0x52, 0x41}; //SPECTRA
        private static readonly byte[] CmdOff = {0x4F, 0x46, 0x46}; //OFF
        private static readonly byte[] CmdInit = {0x49, 0x4E, 0x49, 0x54}; //INIT
        private static readonly byte[] CmdT1 = {0x54, 0x31}; //T1
        private static readonly byte[] CmdT2 = {0x54, 0x32}; //T2
        private static readonly byte[] CmdT3 = {0x54, 0x33}; //T3
        private static readonly byte[] CR = { 0x0A };


        /// <summary>
        ///     if you send another command after an ack, the previous command is negated!
        ///     this plus wfaTimeout must get greater than 3 seconds...
        /// </summary>
        private readonly TimeSpan _cmdWaitSpanPostAck = new TimeSpan(0, 0, 0, 0, 00); //was 1.4

        /// <summary>
        ///     Timeout between successive issuance of data on the serial port. the msc is not asynchronous.
        /// </summary>
        private readonly TimeSpan _cmdWaitSpanReg = new TimeSpan(0, 0, 0, 0, 250);

        private readonly NativeUART _port;

        private readonly StringBuilder _sbTemp;

        /// <summary>
        ///     Timeout Before Ack gets sent
        /// </summary>
        private readonly TimeSpan _wfaTimeout = new TimeSpan(0, 0, 0, 0, 0); // Timeout before ack gets sent short 1.6

        /// <summary>
        ///     Timeout for 1000 bytes or so to come in with the config file, as its multiple lines, you just have to wait.
        /// </summary>
        private readonly TimeSpan _wfCfgTimeout = new TimeSpan(0, 0, 0, 10, 0);

        /// <summary>
        ///     Timeout to wait for a longer request (too many bytes)
        /// </summary>
        private readonly TimeSpan _wfLongTimeout = new TimeSpan(0, 0, 0, 60, 0);

        /// <summary>
        ///     Timeout to wait for a sample request (too many bytes)
        /// </summary>
        private readonly TimeSpan _wfT1T2Timeout = new TimeSpan(0, 0, 0, 10, 0);

        /// <summary>
        ///     Default Timeout length for any transaction pushed through waitforresponse
        /// </summary>
        private readonly TimeSpan _wfrTimeout = new TimeSpan(0, 0, 0, 3, 0);//shortest possible 3

        private short _bakeDuration;

        private TimeSpan _cmdWaitSpan;
        private static float _ctdDepth;
        private static uint _ctdPosixTime;
        private static float _ctdSalinity;
        private static float _ctdTemperature;
        private static float _lastSentCtdDepth;
        private static uint _lastSentCtdPosixTime;
        private static float _lastSentCtdSalinity;
        private static float _lastSentCtdTemperature;
        private uint _errorCount;
        private uint _errorLastTime;
        private short _errorMessages;

        private byte[] _field;
        private short _fitWlBegin;
        private short _fitWlEnd;
        private ResponseCmd _lastCommand;

        private ResponseCode _lastResponse;
        private bool _needToSendAck;
        private byte[] _outBytes;
        private int _outLength;
        private byte[] _fieldBytes;


        private byte[] _parseBuff = new byte[128];

        private uint _powerCycleCount;
        private uint _powerLastResetTime;
        private uint _powerResetCount;
        private uint _rtcPosixTime;

        private uint _sampleCount;
        private uint _sampleLastTime;
        private SensorConfig _sensorConfig;
        private short _spectraWlBegin;
        private short _spectraWlEnd;
        private double _timeOfLastCollectionSecs;

        private readonly string msgHeader = "[MSC] ";

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="id">id used for logging purposes</param>
        /// <param name="portName"></param>
        /// <param name="baudRate"></param>
        /// <param name="parity"></param>
        /// <param name="dataBits"></param>
        /// <param name="stopBits"></param>
        /// <param name="readTimeout">millisecond timeout</param>
        /// <param name="writeTimeout">currently unused</param>
        /// <param name="useSTX"></param>
        /// <param name="inSTX"></param>
        public MSC(string id, string portName,
            int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout,
            int writeTimeout, bool useSTX, byte inSTX)
        {
            //var motherBoard = MotherBoard.Instance;
            //motherBoard.Enable12V();
            //motherBoard.EnableChannelPower(7);

            _port = new NativeUART(id, QRecordType.MSC, ProcessMessage, portName, baudRate, parity, dataBits,
                    stopBits, readTimeout, writeTimeout, useSTX, inSTX);
            
            _port.Open();

            _port.DiscardInBuffer();
            _port.DiscardOutBuffer();


            _outBytes = new byte[128];
            _outLength = 0;

            _fieldBytes = new byte[128];

            //Private Variable Inits
            _sbTemp = new StringBuilder();
            _cmdWaitSpan = _cmdWaitSpanReg;
        }

        private ResponseCmd LastCommand
        {
            get { return _lastCommand; }
            set
            {
                _lastCommand = value;
                if (_wfrState == WfrState.Waiting && value == _expectedResponse)
                    _wfrState = WfrState.GotResponse;
            }
        }

        /// <summary>
        /// Current mode for commands that require a mode be set before interaction.
        /// See the MSC users manual. 
        /// </summary>
        public SampleMode CurrentMode { get; set; }

        private void SendAck()
        {
            //Respond with Generic ACK
            CopyCommand(RespAck);
            WriteOutBuffer();
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void WriteOutBuffer()
        {
            //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] TX: " + UtfString(_outBytes));
            EngrLogger.writeToColumns("[MSC] TX: " + UtfString(_outBytes));
            if (_port.IsOpen)
            {
                _port.Write(_outBytes, 0, _outLength);
                //_port.Write(CR,0,1);
            }
        }

        ~MSC()
        {
            if (_port.IsOpen)
                _port.Close();
        }

        private enum ResponseCode
        {
            Ack,
            Nak,
            Eof,
            Error,
            Sample,
            Config,
            SendError,
            Unknown
        }

        private enum ResponseCmd
        {
            IsusDuraConfig,
            ErrorCounter,
            PowerCounter,
            SampleCounter,
            TimeOfLastSample,
            MscSensorConfig,
            TakeSample,
            TakeSampleDat,
            TakeSampleCmd,
            SendLast,
            SendLastData,
            Sleep,
            Off,
            Init,
            Rtc,
            Ctd,
            Fit,
            Spectra,
            Bake,
            SendError,
            Mode,
            Unknown,
            Eof,
            NoCmd,
        }

        #region Input Processing

        private ResponseCode GetResponseCode(byte[] inBytes)
        {
            if (Utils.CompareArray(inBytes, 0, RespAck, 0, 3))
                return ResponseCode.Ack;
            if (Utils.CompareArray(inBytes, 0, RespNak, 0, 3))
                return ResponseCode.Nak;
            if (Utils.CompareArray(inBytes, 0, RespEof, 0, 3))
                return ResponseCode.Eof;
            if (Utils.CompareArray(inBytes, 0, RespE, 0, 1))
                return ResponseCode.Error;
            if (Utils.CompareArray(inBytes, 0, RespSE, 0, 2))
                return ResponseCode.SendError;
            if (Utils.CompareArray(inBytes, 0, RespSamp, 0, RespSamp.Length)) 
                return ResponseCode.Sample;
            if (Utils.CompareArray(inBytes, 0, RespCfg, 0, 1))
                return ResponseCode.Config;

            return ResponseCode.Unknown;
        }

        private ResponseCmd GetResponseCmd(byte[] inBytes)
        {
            //NOTE: Order is very important here, we check for the longest first.
            //      If we checked for the single chars first, we'd succeed, as we
            //      looking for a terminator to the token.

            if (inBytes[3] == 0x0D) //EOL
                return ResponseCmd.NoCmd;

            if (Utils.CompareArray(inBytes, 4, CmdSpectra, 0, CmdSpectra.Length))
                return ResponseCmd.Spectra;
            if (Utils.CompareArray(inBytes, 4, CmdMode, 0, CmdMode.Length))
                return ResponseCmd.Mode;
            if (Utils.CompareArray(inBytes, 4, CmdBake, 0, CmdBake.Length))
                return ResponseCmd.Bake;
            if (Utils.CompareArray(inBytes, 4, CmdFit, 0, CmdFit.Length))
                return ResponseCmd.Fit;
            if (Utils.CompareArray(inBytes, 4, CmdCtd, 0, CmdCtd.Length))
                return ResponseCmd.Ctd;
            if (Utils.CompareArray(inBytes, 4, CmdRtc, 0, CmdRtc.Length))
                return ResponseCmd.Rtc;
            if (Utils.CompareArray(inBytes, 4, CmdInit, 0, CmdInit.Length))
                return ResponseCmd.Init;
            if (Utils.CompareArray(inBytes, 4, CmdOff, 0, CmdOff.Length))
                return ResponseCmd.Off;
            if (Utils.CompareArray(inBytes, 4, CmdSleep, 0, CmdSleep.Length))
                return ResponseCmd.Sleep;
            if (Utils.CompareArray(inBytes, 4, CmdSendLast, 0, CmdSendLast.Length))
                return ResponseCmd.SendLast;
            if (Utils.CompareArray(inBytes, 4, CmdSendError, 0, CmdSendError.Length))
                return ResponseCmd.SendError;
            if (Utils.CompareArray(inBytes, 4, CmdTakeSample, 0, CmdTakeSample.Length))
            {
                //See if there's a DAT or CMD
                if (Utils.CompareArray(inBytes, 7, RespCmd, 0, 3)) //CMD
                    return ResponseCmd.TakeSampleCmd;
                if (Utils.CompareArray(inBytes, 7, RespDat, 0, 3)) //DAT
                    return ResponseCmd.TakeSampleDat;
                return ResponseCmd.TakeSample;
            }
            if (inBytes[4] == CmdMscConfig)
                return ResponseCmd.MscSensorConfig;
            if (inBytes[4] == CmdTime)
                return ResponseCmd.TimeOfLastSample;
            if (inBytes[4] == CmdSample)
                return ResponseCmd.SampleCounter;
            if (inBytes[4] == CmdPower)
                return ResponseCmd.PowerCounter;
            if (inBytes[4] == CmdError)
                return ResponseCmd.ErrorCounter;
            if (inBytes[4] == CmdIsusDuraConfig)
                return ResponseCmd.IsusDuraConfig;

            return ResponseCmd.Unknown;
        }

        #endregion

        #region Response Processing

        private static int _msAckSleep = 1000;
        protected void ProcessMessage(QRecord record)
        {
            _lastResponse = GetResponseCode(record.byteArray);

            //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] RX: " + UtfString(record.byteArray));
            EngrLogger.writeToColumns("[MSC] RX: " + UtfString(record.byteArray));

            switch (_lastResponse)
            {
                //ACK: Respond with ACK for Setting Change
                case ResponseCode.Ack:
                case ResponseCode.Nak:
                    //Categorize the response command
                    LastCommand = GetResponseCmd(record.byteArray);

                    if (LastCommand == ResponseCmd.NoCmd)
                    {
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] Got ACK");
                        //do nothing else
                        return;
                    }

                    if (LastCommand == ResponseCmd.Unknown)
                        return;

                    //Process the fields
                    if (ProcessResponse(record.byteArray, _lastResponse, LastCommand))
                    {

                        _needToSendAck = true;
                        _wfrStartTime = record.timeStamp;
                        
                    }

                    break;


                //Process Sample Data
                case ResponseCode.Sample:
                    LastCommand = ResponseCmd.SendLastData;
                    ProcessResponse(record.byteArray, _lastResponse, LastCommand);
                    break;

                //These are error messages from the ISUS or DuraFet
                case ResponseCode.Error:
                    LastCommand = ResponseCmd.SendError;
                    ProcessErrorRecord(record.byteArray, _lastResponse);
                    break;

                case ResponseCode.SendError:
                    LastCommand = ResponseCmd.SendError;
                    ProcessSendError(record.byteArray, _lastResponse);
                    break;

                case ResponseCode.Config: 
                    LastCommand = ResponseCmd.IsusDuraConfig;
                    if (CurrentMode == SampleMode.DuraFET)
                        SciLogger.writeToDuraFile(record.byteArray,record.length);
                    else if (CurrentMode == SampleMode.ISUS)
                        SciLogger.writeToIsusFile(record.byteArray, record.length);

                    break;
                case ResponseCode.Eof:
                    LastCommand = ResponseCmd.Eof;
                    break;
                case ResponseCode.Unknown:
                default:
                    //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(record.byteArray));
                    break;
            }
        }

        private bool ProcessResponse(byte[] p, ResponseCode code, ResponseCmd command)
        {
            switch (command)
            {
                case ResponseCmd.ErrorCounter:
                    return ProcessErrorCounterResponse(p, code);
                case ResponseCmd.PowerCounter:
                    return ProcessPowerCounterResponse(p, code);
                case ResponseCmd.SampleCounter:
                    return ProcessSampleCounterResponse(p, code);
                case ResponseCmd.TimeOfLastSample:
                    return ProcessTimeOfLastSampleCollection(p, code);
                case ResponseCmd.MscSensorConfig:
                    return ProcessMscSensorConfiguration(p, code);
                case ResponseCmd.TakeSample:
                    return ProcessTakeSample(p, code);
                case ResponseCmd.TakeSampleDat:
                    return ProcessTakeSampleDat(p, code);
                case ResponseCmd.SendLast:
                    return ProcessSendLast(p, code);
                case ResponseCmd.SendLastData:
                    return ProcessSendLastData(p, code);
                case ResponseCmd.Rtc:
                    return ProcessRtc(p, code);
                case ResponseCmd.Ctd:
                    return ProcessCtd(p, code);
                case ResponseCmd.Fit:
                    return ProcessFit(p, code);
                case ResponseCmd.Spectra:
                    return ProcessSpectra(p, code);
                case ResponseCmd.Bake:
                    return ProcessBake(p, code);
                case ResponseCmd.SendError:
                    return ProcessSendError(p, code);
                case ResponseCmd.Mode:
                    return ProcessMode(p, code);
                case ResponseCmd.Sleep:
                case ResponseCmd.Off:
                case ResponseCmd.Init:
                default:
                    return false;
            }
        }

        private bool ProcessTakeSampleDat(byte[] p, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    //WaitForResponse(ReqLastRecord,_wfrTimeout);
                    return false;

                case ResponseCode.Nak:
                    //The other error message came through, something like InvalidMode...
                    return false;
                default:
                    return false;
            }
        }

        private static readonly byte comma = 0x2C; //comma

        private bool ProcessSendLastData(byte[] bytes, ResponseCode code)
        {
            //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] Data: " + UtfString(bytes));
            EngrLogger.writeToColumns("[MSC] Data: " + UtfString(bytes));

            //Determine if the record is ph or nitrate
            var res = Utils.getField(bytes, comma, 1, ref _fieldBytes);

            if (res == 0)
                return false;

            if (_fieldBytes[0] == (byte) 'p' && _fieldBytes[1] == (byte) 'H')
            {
                if (Utils.getField(bytes, comma, 16, ref _fieldBytes) != 0)     //Note: field 14 (starting at 0) is computed pH.  This is currently a dummy field and we really want to post Vrs field 16 (starting at 0)
                {
                    SciLogger.postSample(SciLogger.Instruments.Ph,_fieldBytes);
                    _sbTemp.Clear();
                    _sbTemp.Append("[MSC] Post pH: ");
                    _sbTemp.Append(SafeEncoding.GetChars(_fieldBytes));
                    EngrLogger.writeToColumns(_sbTemp);
                    SciLogger.writeToDuraFile(bytes);
                    return true;
                }

                return false;
            }
       
            
            if (_fieldBytes[0] == (byte)'A')
            {
                if (Utils.getField(bytes, comma, 20, ref _fieldBytes) != 0)
                {
                    SciLogger.postSample(SciLogger.Instruments.NO3,_fieldBytes);
                    _sbTemp.Clear();
                    _sbTemp.Append("[MSC] Post NO3: ");
                    _sbTemp.Append(SafeEncoding.GetChars(_fieldBytes));
                    EngrLogger.writeToColumns(_sbTemp);
                    SciLogger.writeToIsusFile(bytes);
                    return true;
                }

                return false;
            }

            //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] [ERROR] Unknown data message received.");
            EngrLogger.writeToColumns("[MSC] [ERROR] Unknown data message received.");
            return false;

        }

        private bool ProcessMode(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    //Determine if its an inquiry
                    if (bytes[8] == 0x3F)
                        return false;
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        if (CurrentMode != (SampleMode) uint.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }

                    return true;
                case ResponseCode.Nak:
                    // Get the error message
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }



        private bool ProcessSendError(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    //This command only responds with a ? inquiry
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) <= 0)
                        return false;
                    try
                    {
                        _errorCount = uint.Parse(UtfString(_parseBuff));
                    }
                    catch
                    {
                        _errorCount = 0;
                        return false;
                    }
                    finally
                    {

                        EngrLogger.writeToColumns("[MSC] ERRORS AVAILABLE: "+_errorCount);
                        SciLogger.WriteErrorCount(_errorCount, CurrentMode);
                    }

                    return false;

                default:
                    return false;
            }
        }

        private bool ProcessBake(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        if (_bakeDuration != short.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }

                    return true;
                case ResponseCode.Nak:
                    // Get the error message
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessSpectra(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    //Determine if its an inquiry
                    if (bytes[11] == 0x3F)
                        return false;

                    // If its not an inquiry, we need to send something back to confirm the setting.
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        if (_spectraWlBegin != short.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }
                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)

                    {
                        if (_spectraWlEnd != short.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }

                    return true;
                case ResponseCode.Nak:
                    // Get the error message
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessFit(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    //Determine if its an inquiry
                    if (bytes[7] == 0x3F)
                        return false;

                    // If its not an inquiry, we need to send something back to confirm the setting.
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        if (_fitWlBegin != short.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }
                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)

                    {
                        if (_fitWlEnd != short.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }

                    return true;
                case ResponseCode.Nak:
                    // Get the error message
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessCtd(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    //Determine if its an inquiry
                    if (bytes[7] == 0x3F)
                        return false;

                    // If its not an inquiry, we need to send something back to confirm the setting.
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        if (_lastSentCtdPosixTime != uint.Parse(UtfString(_parseBuff))) return false;
                    }
                    else
                    {
                        return false;
                    }

                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)
                    {
                        if (Math.Abs(_lastSentCtdTemperature - double.Parse(UtfString(_parseBuff))) > Tolerance) return false;
                    }
                    else
                    {
                        return false;
                    }
                    if (Utils.getField(bytes, 0x2C, 4, ref _parseBuff) > 0)
                    {
                        if (Math.Abs(_lastSentCtdSalinity - double.Parse(UtfString(_parseBuff))) > Tolerance) return false;
                    }
                    else
                    {
                        return false;
                    }
                    if (Utils.getField(bytes, 0x2C, 5, ref _parseBuff) > 0)
                    {
                        if (Math.Abs(_lastSentCtdDepth - double.Parse(UtfString(_parseBuff))) > Tolerance) return false;
                    }
                    else
                    {
                        return false;
                    }

                    return true;

                case ResponseCode.Nak:
                    // Get the error message
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }


        private bool ProcessRtc(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    //Determine if its an inquiry
                    if (bytes[7] == 0x3F)
                        return false;

                    // If its not an inquiry, we need to send something back to confirm the setting.
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        var rtcSeconds = uint.Parse(UtfString(_parseBuff));
                        if (rtcSeconds == _rtcPosixTime)
                            return true;
                    }
                    return false;

                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,msgHeader + UtfString(_parseBuff));
                        EngrLogger.writeToColumns(msgHeader + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
            //Determine if its an inquiry
        }

        private bool ProcessSendLast(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    //TODO I don't think the sensor data will xmit with a header, need a sensor to verify
                    return false;
                case ResponseCode.Nak:
                    //TODO I guess we'll log it...
                    return false;
                default:
                    return false;
            }
        }


        private bool ProcessTakeSample(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.CompareArray(bytes, 7, RespCmd, 0, 3)) //CMD
                        return false;
                    if (Utils.CompareArray(bytes, 7, RespDat, 0, 3)) //DAT
                        return false;
                    
                    //Garbled Message?
                    return false;

                case ResponseCode.Nak:
                    if (Utils.CompareArray(bytes, 7, RespDat, 0, 3)) //DAT
                        return false;

                    //The other error message came through, something like InvalidMode...
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessMscSensorConfiguration(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        _sensorConfig = (SensorConfig) byte.Parse(UtfString(_parseBuff));
                    return false;
                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] NAK RXD: " + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessTimeOfLastSampleCollection(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        _timeOfLastCollectionSecs = double.Parse(UtfString(_parseBuff));
                    return false;
                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] NAK RXD: " + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private string UtfString(byte[] bytes)
        {
            _sbTemp.Clear().Append(SafeEncoding.GetChars(bytes));
            return _sbTemp.ToString();
        }

        private bool ProcessSampleCounterResponse(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        _sampleCount = uint.Parse(UtfString(_parseBuff));
                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)
                        _sampleLastTime = uint.Parse(UtfString(_parseBuff));
                    return false;
                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] NAK RXD: " + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessPowerCounterResponse(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:

                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        _powerCycleCount = uint.Parse(UtfString(_parseBuff));
                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)
                        _powerResetCount = uint.Parse(UtfString(_parseBuff));
                    if (Utils.getField(bytes, 0x2C, 4, ref _parseBuff) > 0)
                        _powerLastResetTime = uint.Parse(UtfString(_parseBuff));
                    return false;
                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] NAK RXD: " + UtfString(_parseBuff));
                    return false;
                default:
                    return false;
            }
        }

        private bool ProcessErrorCounterResponse(byte[] bytes, ResponseCode code)
        {
            switch (code)
            {
                case ResponseCode.Ack:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                    {
                        _errorCount = uint.Parse(UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] ERRORS AVAILABLE: "+_errorCount);
                    }
                    else _errorCount = 0;

                    SciLogger.WriteErrorCount(_errorCount, CurrentMode);

                    if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0)
                        _errorLastTime = uint.Parse(UtfString(_parseBuff));

                    return false; //no response required.

                case ResponseCode.Nak:
                    if (Utils.getField(bytes, 0x2C, 2, ref _parseBuff) > 0)
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
                        EngrLogger.writeToColumns("[MSC] NAK RXD: " + UtfString(_parseBuff));
                    return false;

                default:
                    return false;
            }
        }

        private bool ProcessErrorRecord(byte[] bytes, ResponseCode code)
        {
            if (code != ResponseCode.Error)
                return false;

            SciLogger.writeErrorRecord(bytes, CurrentMode);

            EngrLogger.writeToColumns(msgHeader + UtfString(bytes));
            return true;
        }

        #endregion

        #region Longer Form Commands with inputs

        /// <summary>
        /// Send a command for the msc to take a sample. Must have a 
        /// mode set prior. <see cref="SendMode"/>
        /// </summary>
        public void TakeSample()
        {
            CopyCommand(CmdTakeSample);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.TakeSampleDat;
        }

        /// <summary>
        /// Send a command to request the last sample
        /// record. Mode must be set <see cref="SendMode"/>
        /// </summary>
        public void ReqLastRecord()
        {
            CopyCommand(CmdSendLast); /*SL*/
            WriteOutBuffer();
            switch (CurrentMode)
            {
                case SampleMode.ISUS:
                case SampleMode.DuraFET:
                    _expectedResponse = ResponseCmd.SendLastData;
                    break;
                case SampleMode.NoSensor:
                    //do nothing
                    break;
            }
        }

        /// <summary>
        /// Issue the command for the MSC to suspend itself,
        /// going into sleep mode.
        /// </summary>
        public void Suspend()
        {
            CopyCommand(CmdSleep);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Sleep;
        }

        /// <summary>
        /// Send the OFF command and would typically
        /// be sent at the end of a profile. 
        /// </summary>
        public void Shutdown()
        {
            CopyCommand(CmdOff);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Off;
        }

        /// <summary>
        /// Sent prior to deployment, this resets all
        /// stored values.
        /// </summary>
        public void InitMsc()
        {
            CopyCommand(CmdInit);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Init;
        }

        public void powerUp()
        {
            EngrLogger.writeToColumns("[MSC] Powering up MSC");
            motherboard.EnableChannelPower(MotherBoard.ChannelNames.MSCChannel);
            _port.DiscardInBuffer();
            _port.DiscardOutBuffer();

        }

        public void powerDown()
        {
            EngrLogger.writeToColumns("[MSC] Powering down MSC");
            motherboard.DisableChannelPower(MotherBoard.ChannelNames.MSCChannel);
        }



        /// <summary>
        /// Collects the time from the micro and sends
        /// it to the msc.
        /// </summary>
        public void SetRTC()
        {
            _rtcPosixTime = Utils.SecondsSinceEpoch;
            CopyCommand(CmdRtc);
            AddComma();
            AddField(_rtcPosixTime);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Rtc;
        }

        /// <summary>
        /// Send a command to the MSC to retrieve the clock value. 
        /// </summary>
        public void ReqRTC()
        {
            CopyCommand(CmdRtc);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Rtc;
        }


        /// <summary>
        /// Set the stored values of the ctd 
        /// </summary>
        /// <param name="posixTime">seconds since jan 1, 1970</param>
        /// <param name="temperature">temp in deg c</param>
        /// <param name="salinity">salinity in pss</param>
        /// <param name="depth">depth in decibars pressure</param>
        public void SetCTDValues(uint posixTime, float temperature, float salinity, float depth)
        {
            _ctdPosixTime = posixTime;
            _ctdTemperature = temperature;
            _ctdSalinity = salinity;
            _ctdDepth = depth;

            _sbTemp.Clear();
            _sbTemp.Append("Setting MSC time and CTD Values: ");
            _sbTemp.Append(_ctdPosixTime.ToString());
            _sbTemp.Append(Literals.commaChar);
            _sbTemp.Append(_ctdSalinity.ToString("f2"));
            _sbTemp.Append(Literals.commaChar);
            _sbTemp.Append(_ctdTemperature.ToString("f3"));
            _sbTemp.Append(Literals.commaChar);
            _sbTemp.Append(_ctdDepth.ToString("f2"));
            EngrLogger.writeToColumns(_sbTemp);
        }

        /// <summary>
        /// Sends a T1/T2 Command with CTD data
        /// </summary>
        private void SendCtdT1T2(byte[] cmd, ResponseCmd response)
        {
            CopyCommand(cmd);
            AddComma();
            AddField(_lastSentCtdPosixTime);
            AddComma();
            AddField(_lastSentCtdTemperature);
            AddComma();
            AddField(_lastSentCtdSalinity);
            AddComma();
            AddField(_lastSentCtdDepth);
            WriteOutBuffer();
            _expectedResponse = response;
        }

        public void SendT1() { SendCtdT1T2(CmdT1, ResponseCmd.SendLastData); }
        public void SendT2() { SendCtdT1T2(CmdT2, ResponseCmd.SendLastData); }
        public void SendT3() { SendCtdT1T2(CmdT3, ResponseCmd.SendLastData); }

        /// <summary>
        /// Send a message with the class-stored CTD values
        /// </summary>
        public void SendCTD() { SendCtdT1T2(CmdCtd, ResponseCmd.Ctd); }

        /// <summary>
        /// Sends a message requestion the stored CTD values in dBar, degC and pss
        /// </summary>
        public void ReqCTD()
        {
            CopyCommand(CmdCtd);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Ctd;
        }

        /// <summary>
        /// Set the values to be sent to the MSC for the fit windows
        /// </summary>
        /// <param name="wlBegin"></param>
        /// <param name="wlEnd"></param>
        public void SetFitWindow(short wlBegin, short wlEnd)
        {
            _fitWlBegin = wlBegin;
            _fitWlEnd = wlEnd;
        }

        /// <summary>
        /// Transmit stored window values to the MSC
        /// </summary>
        public void SendFitWindow()
        {
            CopyCommand(CmdFit);
            AddComma();
            AddField(_fitWlBegin);
            AddComma();
            AddField(_fitWlEnd);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Fit;
        }

        /// <summary>
        /// Tranmist a request for the current fit window values
        /// </summary>
        public void ReqFitWindow()
        {
            CopyCommand(CmdFit);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Fit;
        }

        /// <summary>
        /// Set the stored values to be sent to the MSC for spectra 
        /// </summary>
        /// <param name="wlBegin"></param>
        /// <param name="wlEnd"></param>
        public void SetSpectra(short wlBegin, short wlEnd)
        {
            _spectraWlBegin = wlBegin;
            _spectraWlEnd = wlEnd;
        }

        /// <summary>
        /// Transmit a command to set the Spectra window, must reply ACK to complete command
        /// </summary>
        public void SendSpectra()
        {
            CopyCommand(CmdSpectra);
            AddComma();
            AddField(_spectraWlBegin);
            AddComma();
            AddField(_spectraWlEnd);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Spectra;
        }

        /// <summary>
        /// Request the spectra
        /// </summary>
        public void ReqSpectra()
        {
            CopyCommand(CmdSpectra);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Spectra;
        }

        /// <summary>
        /// Store a value to be transmitted for the bake command
        /// </summary>
        /// <param name="duration">todo don't know the units</param>
        public void SetBakeDuration(short duration)
        {
            _bakeDuration = duration;
        }

        /// <summary>
        /// Send the bake command, along with duration, requires and ack
        /// </summary>
        public void Bake()
        {
            CopyCommand(CmdBake);
            AddComma();
            AddField(_bakeDuration);
            _expectedResponse = ResponseCmd.Bake;
        }

        /// <summary>
        /// Request a single error message
        /// </summary>
        public void ReqErrorMessage()
        {
            CopyCommand(CmdSendError);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.SendError;
        }

        /// <summary>
        /// Transmit the stored mode setting, <see cref="CurrentMode"/>
        /// </summary>
        public void SendMode()
        {
            CopyCommand(CmdMode);
            AddComma();
            AddField((uint) CurrentMode);
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Mode;
        }

        /// <summary>
        /// Request the current mode <see cref="SampleMode"/>
        /// </summary>
        public void ReqMode()
        {
            CopyCommand(CmdMode);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.Mode;
        }

        #endregion

        #region Simple Get Commands (Single and Dual Chars, no ,?//

        /// <summary>
        /// Transmit command to wake up the MSC from low-current modes, responds with ACK
        /// </summary>
        public void Wake()
        {
            WriteBytes(CmdWake); /*W*/
            //Thread.Sleep(250);
            //WriteBytes(CmdWake); /*W*/
            _expectedResponse = ResponseCmd.NoCmd;
        }

        /// <summary>
        /// Request config data for the MSC, which mode must be set <see cref="SendMode"/>
        /// </summary>
        public void ReqSensorConfig()
        {
            WriteBytes(CmdIsusDuraConfig); /*C*/
            _expectedResponse = ResponseCmd.IsusDuraConfig;
        }

        /// <summary>
        /// Request number of sensor errrors, mode must be set <see cref="SendMode"/>
        /// </summary>
        public void ReqErrorCount()
        {
            WriteBytes(CmdError); /*E*/
            _expectedResponse = ResponseCmd.ErrorCounter;
        }

        public void ReqSendErrorCount()
        {
            CopyCommand(CmdSendError);
            AddRequest();
            WriteOutBuffer();
            _expectedResponse = ResponseCmd.SendError;
        }

        /// <summary>
        /// Request power on and system reset counter values and event time
        /// </summary>
        public void ReqPowerCount()
        {
            WriteBytes(CmdPower); /*P*/
            _expectedResponse = ResponseCmd.PowerCounter;
        }

        /// <summary>
        /// Get MSC sample counter value, the total number of sample by either sensor,
        /// depends on the mode command <see cref="SendMode"/>
        /// </summary>
        public void ReqMscSampleCount()
        {
            WriteBytes(CmdSample); /*S*/
            _expectedResponse = ResponseCmd.SampleCounter;
        }

        /// <summary>
        /// Get the time in seconds that it took the MSC to collect its last sample. 
        /// </summary>
        public void ReqSampleTime()
        {
            WriteBytes(CmdTime); /*T*/
            _expectedResponse = ResponseCmd.TimeOfLastSample;
        }

        /// <summary>
        /// Get the MSC configuration, which defines what modes it supports.
        /// </summary>
        public void ReqMscConfig()
        {
            WriteBytes(CmdMscConfig); /*Q*/
            _expectedResponse = ResponseCmd.MscSensorConfig;
        }

        /// <summary>
        /// Dummy Request Transmits nothing, waiting for a full timeout.
        /// </summary>
        public void DummyReq()
        {
            return;
        }

        #endregion

        #region OutBuffer Data Functions

        private void ClearOutBuffer()
        {
            ClearBuffer(ref _outBytes);
        }

        private static void ClearBuffer(ref byte[] inBytes)
        {
            Array.Clear(inBytes, 0, inBytes.Length);
        }

        private void AddRequest()
        {
            AddComma();
            _outBytes[_outLength++] = 0x3F; //?
        }

        private void AddComma()
        {
            _outBytes[_outLength++] = 0x2C; //,
        }

        private void AddField(short value)
        {
            _field = Encoding.UTF8.GetBytes(value.ToString());
            CopyField();
        }

        private void CopyField()
        {
            Array.Copy(_field, 0, _outBytes, _outLength, _field.Length);
            _outLength += _field.Length;
        }

        private void AddField(uint value)
        {
            _field = Encoding.UTF8.GetBytes(value.ToString());
            CopyField();
        }

        private void AddField(float value)
        {
            _field = Encoding.UTF8.GetBytes(value.ToString("F4"));
            CopyField();
        }


        private void CopyCommand(byte[] cmd)
        {
            Array.Clear(_outBytes, 0, _outBytes.Length);
            Array.Copy(cmd, _outBytes, cmd.Length);
            _outLength = cmd.Length;
        }

        private void WriteBytes(byte inByte)
        {
            Array.Clear(_outBytes, 0, _outBytes.Length);
            _outBytes[0] = inByte;
            _outLength = 1;
            WriteOutBuffer();
        }

        //private void WriteBytes(byte byte0, byte byte1)
        //{
        //    _outBytes[0] = byte0;
        //    _outBytes[1] = byte1;
        //    _outLength = 2;
        //    WriteOutBuffer();
        //}

        #endregion

        #region Public Driver Actions

        /// <summary>
        ///     Sequencer intended to be used during sampling
        /// </summary>
        /// 

        /// <summary>
        /// Sequencer to collect a sample and retrieve the values for both pH and N3
        /// GM 2018 May 16
        /// <see cref="SendMode"/>
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>

        private int _pHandN3ModeNum = -1;

        public void startPHandNO3SampleSequence()
        {
            if (_pHandN3ModeNum >= 0) return;
            _pHandN3ModeNum = 0;
            _wfrState = WfrState.NotWaiting;
            EngrLogger.writeToColumns("Starting PH and NO3 sample sequence");
        }

        private int _T1T2SampleState = -1;

        public void startT1T2SampleSequence()
        {
            if (_T1T2SampleState >= 0) return;
            _T1T2SampleState = 0;
            _wfrState = WfrState.NotWaiting;
            EngrLogger.Comment("[MSC] Starting PH and NO3 Sample Sequence, using T1/T2");
        }

        public int SequenceT1T2Samples()
        {
            try
            {
                if (_T1T2SampleState < 0)
                    return -1;

                int ret;
                switch (_T1T2SampleState)
                {
                    case 0 :
                        EngrLogger.Comment("[MSC] Starting ISUS Sample");
                        _T1T2SampleState++;
                        goto case 1;
                    
                    case 1:
                        SetLastSentCTDValues();
                        _T1T2SampleState++;
                        goto case 2;

                    case 2:
                        ret = SequenceSampleT1T2(SampleMode.ISUS);
                        if (ret == 0) return ret;
                        if (ret == 1)
                        {
                            _T1T2SampleState++;
                            goto case 3;
                        }
                        goto default;

                    case 3:
                        EngrLogger.writeToColumns("[MSC] Starting pH sample");
                        _T1T2SampleState++;
                        goto case 4;

                    case 4:
                        ret = SequenceSampleT1T2(SampleMode.DuraFET);
                        if (ret == 0) return ret;
                        if (ret == 1)
                        {
                            _T1T2SampleState++;
                            goto case 5;
                        }
                        goto default;
                    
                    case 5:
                        //Suspend();
                        _T1T2SampleState = -1;
                        return 1;
                    
                    default:
                        //Error Condition, cleanup and exit.
                        EngrLogger.writeToColumns("[MSC] [ERROR] Failed sample sequence, sequence value: " +
                                                  _T1T2SampleState);
                        _T1T2SampleState = -1;
                        return -1;
                }
            }
            catch (Exception e)
            {
                _sampleSequence = -1;
                _T1T2SampleState = -1;
                EngrLogger.writeToColumns("[MSC] [ERROR] SequencePHandN3 critical failure" + e);
                return -1;
            }
        }

        public int SequencePHandNO3Samples()
        {
            try
            {
                if (_pHandN3ModeNum < 0)
                    return -1;

                int ret;
                switch (_pHandN3ModeNum)
                {
                    //Init 
                    case 0:
                        EngrLogger.writeToColumns("[MSC] Starting ISUS sample");
                        _pHandN3ModeNum++;
                        goto case 1;
                    case 1:
                        ret = SequenceSendCtdValues();
                        if (ret == 0) return ret;
                        if (ret == 1)
                        {
                            _pHandN3ModeNum++;
                            goto case 2;
                        }
                        goto default;

                    case 2:
                        ret = SequenceSample(SampleMode.ISUS);
                        if (ret == 0) return ret;
                        if (ret == 1)
                        {
                            _pHandN3ModeNum++;
                            goto case 3;
                        }
                        goto default;

                    case 3:
                        EngrLogger.writeToColumns("[MSC] Starting pH sample");
                        _pHandN3ModeNum++;
                        goto case 4;
                    case 4:
                        ret = SequenceSample(SampleMode.DuraFET);
                        if (ret == 0) return ret;
                        if (ret == 1)
                        {
                            _pHandN3ModeNum++;
                            goto case 5;
                        }
                        goto default;
                    case 5:
                        //Suspend();
                        _pHandN3ModeNum = -1;
                        return 1;
                    default:
                        //Error Condition, cleanup and exit.
                        EngrLogger.writeToColumns("[MSC] [ERROR] Failed sample sequence, sequence value: "+_pHandN3ModeNum.ToString());
                        _pHandN3ModeNum = -1;
                        return -1;
                }
            }
            catch (Exception e)
            {
                _sampleSequence = -1;
                _pHandN3ModeNum = -1;
                //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] SequencePHandN3 critical failure" + e);
                EngrLogger.writeToColumns("[MSC] SequencePHandN3 critical failure" + e);
                return -1;
            }
        }

        private short _sampleT1T2Sequence;

        public int SequenceSampleT1T2(SampleMode mode)
        {
            WfrState state;
            short retval = 0;
            SendCommand sampleModeCommand;
            if (mode == SampleMode.ISUS)
                sampleModeCommand = SendT1;
            else if (mode == SampleMode.DuraFET)
                sampleModeCommand = SendT2;
            else
                return 1;

            if (_sampleT1T2Sequence < 0) _sampleT1T2Sequence= 0;
            try
            {
                switch (_sampleT1T2Sequence)
                {
                    //Init 
                    case 0:
                        _remainingAttempts = MaxAttempts;
                        _sampleT1T2Sequence = 1;
                        goto case 1;

                    //Make Sure its awake
                    case 1:
                        state = WaitForResponse(Wake, _wfrTimeout);
                        retval =  CheckStateAndTryAgain(state, ref _sampleT1T2Sequence, ref _remainingAttempts);
                        if (state == WfrState.GotResponse) goto case 2;
                        return retval;

                    //Request Sample and Expect data
                    case 2:
                        state = WaitForResponse(sampleModeCommand, _wfT1T2Timeout);
                        retval = CheckStateAndAdvance(state, ref _sampleT1T2Sequence);
                        if (state == WfrState.GotResponse) goto case 3;
                        return retval;
                    //Cleanup and Exit
                    case 3:
                        _sampleT1T2Sequence = -1;
                        EngrLogger.writeToColumns("[MSC] Done Sampling.");
                        return 1;
                    default:
                        //Error Condition, cleanup and exit.
                        _sampleT1T2Sequence = -1;
                        return -1;
                }
            }
            catch (Exception e)
            {
                _sampleT1T2Sequence = -1;
                EngrLogger.writeToColumns("[MSC] Sample():" + e);
                return -1;
            }
        }

        private short _sampleSequence;

        /// <summary>
        /// Sequencer to collect a sample and retrieve the values. This is for one mode
        /// at present. Assumes the MSC is already awake. <see cref="SendMode"/>
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>
        public int SequenceSample(SampleMode mode)
        {
            WfrState state;
            short retval = 0;
            if (_sampleSequence < 0) _sampleSequence = 0;
            try
            {
                switch (_sampleSequence)
                {
                    //Init 
                    case 0:
                        _remainingAttempts = MaxAttempts;
                        _sampleSequence = 2;
                        goto case 2;

                    //Make Sure its awake
                    case 1:
                        state = WaitForResponse(Wake, _wfrTimeout);
                        retval =  CheckStateAndTryAgain(state, ref _sampleSequence, ref _remainingAttempts);
                        if (state == WfrState.GotResponse) goto case 2;
                        return retval;
                    //set mode 
                    case 2:
                        CurrentMode = mode;
                        state = WaitForResponse(SendMode, _wfrTimeout);
                        
                        retval = CheckStateAndAdvance(state, ref _sampleSequence);

                        if (state == WfrState.GotResponse) goto case 3;
                        return retval;
                    //request sample
                    case 3:
                        state = WaitForResponse(TakeSample, _wfLongTimeout);
                        return CheckGoodSampleState(state, ref _sampleSequence);

                    //Get data
                    case 4:
                        state = WaitForResponse(ReqLastRecord, _wfrTimeout);
                        retval = CheckStateAndAdvance(state, ref _sampleSequence);
                        if (state == WfrState.GotResponse) goto case 5;
                        return retval;
                    //Cleanup and Exit
                    case 5:
                        _sampleSequence = -1;
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] Done Sampling.");
                        EngrLogger.writeToColumns("[MSC] Done Sampling.");
                        return 1;
                    default:
                        //Error Condition, cleanup and exit.
                        _sampleSequence = -1;
                        return -1;
                }
            }
            catch (Exception e)
            {
                _sampleSequence = -1;
                //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] Sample():" + e);
                EngrLogger.writeToColumns("[MSC] Sample():" + e);
                return -1;
            }
        }

        public void ResetSequencers()
        {
            EngrLogger.Comment("[MSC] Resetting Sequencers");
            _configSequence = -1;
            _ctdSequence = -1;
            _dlSequence = -1;
            _fitSequence = -1;
            _sampleSequence = -1;
            _sampleT1T2Sequence = -1;
            _pHandN3ModeNum = -1;
            _simpleSequence = -1;
            _wfrState = WfrState.NotWaiting;
        }

        public void StartGetConfigsSequencer()
        {
            if (_configSequence >= 0) return;

            _configSequence = 0;
            _wfrState = WfrState.NotWaiting;

            EngrLogger.Comment("[MSC] Starting config data collection.");
        }

        private short _configSequence;
        private short _remainingAttempts;
        private const short MaxAttempts = 10;

        /// <summary>
        /// Sequence to retrieve config settings from the msc, and if
        /// enabled, also set the time. 
        /// </summary>
        /// <param name="alsoSetRtc">true if the sequence should also set
        /// the time of the MSC based on the system time.
        /// </param>
        /// <returns></returns>
        public int SequenceGetConfigs(bool alsoSetRtc=false)
        {
            WfrState state;

            switch (_configSequence)
            {
                //Wake 
                case 0:
                    _configSequence++;
                    _remainingAttempts = MaxAttempts;
                    goto case 1;
                //Make Sure its awake
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _configSequence, ref _remainingAttempts);
                //Check if we're setting the time
                case 2:
                    if (alsoSetRtc)
                    {
                        _configSequence = 3;
                        goto case 3;
                    }
                    else
                    {
                        _configSequence = 4;
                        goto case 4;
                    }
                //Set the time
                case 3:
                    _remainingAttempts = MaxAttempts;
                    state = WaitForResponse(SetRTC, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _configSequence);

                // Set the mode to ISUS
                case 4:
                    CurrentMode = SampleMode.ISUS;
                    state = WaitForResponse(SendMode, _wfrTimeout);
                    return CheckStateAndAdvance(state, ref _configSequence);

                //Get the ISUS Configuration and put it into the profile
                case 5:
                    state = WaitForResponse(ReqSensorConfig, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _configSequence);

                //wait for the config to roll in. takes about 5 seconds
                case 6:
                    if (DateTime.Now - _timeLastSeqSuccess < _wfCfgTimeout)
                        return 0;
                    _configSequence++;
                    _remainingAttempts = 3;
                    goto case 7;
                //now get the errors
                case 7:
                    state = WaitForResponse(ReqSendErrorCount, _wfCfgTimeout);
                    return CheckStateAndAdvance(state, ref _configSequence);
                //check error count
                case 8:
                    _configSequence++;
                    if (_errorCount == 0)
                        _configSequence++; // skip the download
                    return 0;
                case 9:
                    state = WaitForResponse(ReqErrorMessage, _wfrTimeout);
                    //stay here until we get an eof...
                    if (state == WfrState.TimedOut)
                        _configSequence++;
                    // unfortunately the EOF come unsolicited, so we need to watch out for it,
                    // which is not a problem since the timeout here also ends the cycle. 
                    if (_lastResponse == ResponseCode.Eof) {
                        _configSequence++;
                    }
                    return 0;

                //Make Sure its awake
                case 10:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _configSequence, ref _remainingAttempts);

                // Set the mode to DuraFet
                case 11:
                    CurrentMode = SampleMode.DuraFET;
                    state = WaitForResponse(SendMode, _wfrTimeout);
                    if (CheckStateAndAdvance(state, ref _configSequence) < 0)
                        goto default;
                    return 0;

                //Get the ISUS Configuration and put it into the profile
                case 12:
                    state = WaitForResponse(ReqSensorConfig, _wfLongTimeout);
                    if (CheckStateAndAdvance(state, ref _configSequence) < 0)
                        goto default;
                    return 0;

                //wait for the config to roll in. takes about 3 seconds
                case 13:
                    if (DateTime.Now - _timeLastSeqSuccess < _wfCfgTimeout) return 0;
                    _configSequence++ ;
                    _remainingAttempts = 3;
                    return 0;
                //now get the errors
                case 14:
                    state = WaitForResponse(ReqSendErrorCount, _wfCfgTimeout);
                    return CheckStateAndAdvance(state, ref _configSequence);

                //check error count
                case 15:
                    _configSequence++;
                    if (_errorCount == 0)
                    {
                        _configSequence = -1;
                        return 1; // skip the download

                    }
                    return 0;
                case 16:
                    state = WaitForResponse(ReqErrorMessage, _wfrTimeout);
                    //stay here until we get an eof...
                    if (state == WfrState.TimedOut)
                        goto default;
                    // unfortunately the EOF come unsolicited, so we need to watch out for it,
                    // which is not a problem since the timeout here also ends the cycle. 
                    if (_lastResponse == ResponseCode.Eof) {
                        _configSequence++;
                        return 1;
                    }
                    return 0;
                    
                default:
                    _configSequence = -1;
                    return -1;
            }
        }

        private short _dlSequence = -1;

        /// <summary>
        /// Sequence to download all errors in the error log, and todo dump 
        /// them to the science file. 
        /// 
        /// </summary>
        /// <returns>0 running, -1 failed, 1 done/success</returns>
        public int SequenceDownloadErrors()
        {
            WfrState state;
            if (_dlSequence < 0)
                _dlSequence = 0;
            switch (_dlSequence)
            {
                //setup
                case 0:
                    _remainingAttempts = MaxAttempts;
                    _dlSequence++;
                    goto case 1;
                //attempt to wake up the unit
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _dlSequence, ref _remainingAttempts);

                //StartDownloading
                case 2:

                    state = WaitForResponse(ReqErrorMessage, _wfrTimeout);
                    //stay here until we get an eof...
                    if (state == WfrState.GotResponse)
                    {
                        if (_lastResponse == ResponseCode.Eof)
                        {
                            _dlSequence = -1;
                            return 1;
                        }
                        //Debug.Print("[MSC] Got Response: " + _lastResponse);
                        return 0;
                    }
                    if (state == WfrState.TimedOut)
                        goto default;
                    // unfortunately the EOF come unsolicited, so we need to watch out for it,
                    // which is not a problem since the timeout here also ends the cycle. 
                    if (_lastResponse == ResponseCode.Eof) {
                        _dlSequence = -1;
                        return 1;
                    }
                    return 0;

                default:
                    _dlSequence = -1;
                    return -1;
            }
        }
        
        private void SetLastSentCTDValues() {
            _lastSentCtdPosixTime = _ctdPosixTime;
            _lastSentCtdSalinity = _ctdSalinity;
            _lastSentCtdTemperature = _ctdTemperature;
            _lastSentCtdDepth = _ctdDepth;
        }

        /// <summary>
        ///     Sequencer to wake up and send a set of CTD values to the MSC, the input values
        ///     only will be set on the first visit to this sequencer
        /// </summary>
        /// <returns>0 Still Running, -1 Failed, 1 Done/Success</returns>
        public int SequenceSendCtdValues()
        {
            WfrState state;
            if (_ctdSequence < 0)
            {
                SetLastSentCTDValues();
                
                _sbTemp.Clear();
                _sbTemp.Append("[MSC] Starting MSC Sequence CTDValues (time, press, temp, salinity) ");
                _sbTemp.Append(_lastSentCtdPosixTime.ToString());
                _sbTemp.Append(", ");
                _sbTemp.Append(_lastSentCtdDepth.ToString("f2"));
                _sbTemp.Append(", ");
                _sbTemp.Append(_lastSentCtdTemperature.ToString("f3"));
                _sbTemp.Append(", ");
                _sbTemp.Append(_lastSentCtdSalinity.ToString("f2"));
                EngrLogger.writeToColumns(_sbTemp);

                SetLastSentCTDValues();
                _ctdSequence = 0;
            }

            switch (_ctdSequence)
            {
                //setup
                case 0:
                    _remainingAttempts = MaxAttempts;
                    _ctdSequence++;
                    goto case 1;
                //attempt to wake up the unit
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _ctdSequence, ref _remainingAttempts);

                //send the ctd values
                case 2:
                    state = WaitForResponse(SendCTD, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _ctdSequence);

                //all done
                case 3:
                    _ctdSequence = -1;
                    return 1;

                default:
                    _ctdSequence = -1;
                    return -1;
            }
        }

        private short _ctdSequence = -1;
        public int SequenceSimple(SendCommand sendCommand)
        {
            WfrState state;
            if (_simpleSequence < 0)
            {
                
                _sbTemp.Clear();
                _sbTemp.Append("[MSC] Starting MSC Sequence"); 
                EngrLogger.writeToColumns(_sbTemp);
                _simpleSequence = 0;
            }

            switch (_simpleSequence)
            {
                //setup
                case 0:
                    _remainingAttempts = MaxAttempts;
                    _simpleSequence++;
                    goto case 1;
                //attempt to wake up the unit
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _simpleSequence, ref _remainingAttempts);

                //send the init command 
                case 2:
                    state = WaitForResponse(sendCommand, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _simpleSequence);

                //all done
                case 3:
                    _simpleSequence = -1;
                    return 1;

                default:
                    _simpleSequence = -1;
                    return -1;
            }
        }
        private short _simpleSequence = -1;
        /// <summary>
        ///     Simple sequencer for setting the spectra with an input value,
        ///     that only get set on the first visit to the function
        /// </summary>
        /// <param name="wlBegin"></param>
        /// <param name="wlEnd"></param>
        /// <returns>0 running, -1 Failed, 1 Done/Success</returns>
        public int SequenceSendSpectraValues(short wlBegin, short wlEnd)
        {
            WfrState state;
            if (_spectraSequence < 0)
            {
                SetSpectra(wlBegin,wlEnd);
                _spectraSequence = 0;
            }

            switch (_spectraSequence)
            {
                //setup
                case 0:
                    _remainingAttempts = MaxAttempts;
                    _spectraSequence++;
                    goto case 1;
                //attempt to wake up the unit
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _spectraSequence, ref _remainingAttempts);

                //send the ctd values
                case 2:
                    state = WaitForResponse(SendSpectra, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _spectraSequence);

                //all done
                case 3:
                    _spectraSequence = -1;
                    return 1;

                default:
                    _spectraSequence = -1;
                    return -1;
            }
        }

        private short _spectraSequence = -1;

        /// <summary>
        /// Simple sequence to wake up the sensor and send it new
        /// window values, set only on the first call to the loop.
        /// </summary>
        /// <param name="wlBegin"></param>
        /// <param name="wlEnd"></param>
        /// <returns>0 running, -1 failed, 1 success/done</returns>
        public int SequenceSendFitValues(short wlBegin, short wlEnd)
        {
            WfrState state;
            if (_fitSequence < 0)
            {
                SetFitWindow(wlBegin,wlEnd);
                _fitSequence = 0;
            }

            switch (_fitSequence)
            {
                //setup
                case 0:
                    _remainingAttempts = MaxAttempts;
                    _fitSequence++;
                    goto case 1;
                //attempt to wake up the unit
                case 1:
                    state = WaitForResponse(Wake, _wfrTimeout);
                    return CheckStateAndTryAgain(state, ref _fitSequence, ref _remainingAttempts);

                //send the ctd values
                case 2:
                    state = WaitForResponse(SendFitWindow, _wfLongTimeout);
                    return CheckStateAndAdvance(state, ref _fitSequence);

                //all done
                case 3:
                    _fitSequence = -1;
                    return 1;

                default:
                    _fitSequence = -1;
                    return -1;
            }
        }

        private short _fitSequence = -1;

        private DateTime _timeLastSeqSuccess;

        /// <summary>
        /// checks a waiting for response state and takes common actions on
        /// the input sequence counter
        /// </summary>
        /// <param name="state"></param>
        /// <param name="sequence"></param>
        /// <returns></returns>
        public short CheckStateAndAdvance(WfrState state, ref short sequence)
        {
            switch (state)
            {
                case WfrState.GotResponse:
                    sequence++;
                    _timeLastSeqSuccess = DateTime.Now; //added for sequence waiting on long data records
                    return 0;
                case WfrState.TimedOut:
                    //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] [ERROR] Failed Sample on Sequence #" + sequence);
                    EngrLogger.writeToColumns("[MSC] [ERROR] Failed Sample on Sequence #" + sequence);
                    sequence = -1;
                    return -1;
                default:
                    return 0;
            }
        }

        /// <summary>
        /// augments CheckStateAndAdvance by adding in a retry capability 
        /// to sustain multiple timeouts
        /// </summary>
        /// <param name="state"></param>
        /// <param name="sequence"></param>
        /// <param name="remainingAttempts"></param>
        /// <returns></returns>
        public short CheckStateAndTryAgain(WfrState state, ref short sequence, ref short remainingAttempts)
        {
            if (state == WfrState.GotResponse)
                return CheckStateAndAdvance(state, ref sequence);
            if (state != WfrState.Waiting)
                --remainingAttempts;
            if (remainingAttempts == 0)
            {
                sequence = -1;
                return -1;
            }
            return 0;
        }

        private int CheckGoodSampleState(WfrState state, ref short sequence)
        {
            switch (state)
            {
                case WfrState.GotResponse:
                    if (_lastResponse == ResponseCode.Ack)
                    {
                        sequence++;
                        return 0;
                    }
                    sequence += 2;
                    return 0;
                case WfrState.TimedOut:
                    //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc,"[MSC] ERROR Failed Sample on Sequence #" + _sampleSequence);
                    EngrLogger.writeToColumns("[MSC] ERROR Failed Sample on Sequence #" + _sampleSequence);
                    sequence = 0;
                    return -1;
                default:
                    return 0;
            }
        }


        private bool NeedToWait
        {
            get { return DateTime.Now - _wfrStartTime < _cmdWaitSpan; }
        }

        /// <summary>
        /// Delegate for public functions that get passed to sequencer functions.
        /// </summary>
        public delegate void SendCommand();

        private WfrState _wfrState = WfrState.NotWaiting;
        private ResponseCmd _expectedResponse;
        private DateTime _wfrStartTime;
        //private bool _wfaRunning;

        /// <summary>
        /// Tool for non-block sequential access to build serial
        /// command action sequences.
        /// </summary>
        /// <param name="sendCommand"></param>
        /// <param name="timeOut"></param>
        /// <returns></returns>
        public WfrState WaitForResponse(SendCommand sendCommand, TimeSpan timeOut)
        {
            switch (_wfrState)
            {
                case WfrState.NotWaiting:

                    if (NeedToWait)
                        return 0;
                    //the MSC does not respond well, so it needs to always wait a bit between initial calls to WFR

                    _wfrStartTime = DateTime.Now;
                    sendCommand();
                    _cmdWaitSpan = _cmdWaitSpanReg;
                    _wfrState = WfrState.Waiting;
                    return WfrState.Waiting;

                case WfrState.Waiting:

                    if (DateTime.Now - _wfrStartTime > timeOut)
                    {
                        //EngrLogger.writeToColumns(EngrLiterals.ColumnNums.Msc, "[MSC] Timeout for Command" + sendCommand.Method.Name);
                        EngrLogger.writeToColumns("[MSC] Timeout for Command" + sendCommand.Method.Name);
                        goto default;
                    }

                    return (int) WfrState.Waiting;

                case WfrState.GotResponse:

                    if (_needToSendAck)
                    {
                        //if (!_wfaRunning)
                        //{
                            //_wfrStartTime = DateTime.Now;
                            //_wfaRunning = true;
                        //}
                        if (DateTime.Now - _wfrStartTime > _wfaTimeout)
                        {
                            SendAck();
                            _wfrStartTime = DateTime.Now;
                            _cmdWaitSpan = _cmdWaitSpanPostAck;
                            _needToSendAck = false;
                            //_wfaRunning = false;
                        }
                        else
                        {
                            return WfrState.Waiting;
                        }
                    }
                    _wfrState = WfrState.NotWaiting;
                    return WfrState.GotResponse;

                default:
                    _wfrState = WfrState.NotWaiting;
                    _lastResponse = ResponseCode.Unknown;
                    return WfrState.TimedOut;
            }
        }

        #endregion
    }
}

/*
FOR PH Data Records:
ID  RECORD TYPE         SAMPLE DATA
1	RECORD_16BIT_CRC	0x4EC7
2	RECORD_DATA_TYPE	pH
3	DURAFET_TIMESTAMP (GMT)	11/21/2011 19:04
4	CTD_TIMESTAMP (1970 EPOCH SECS) 	1321898656
5	CTD_PRESSURE (D BAR)	-5
6	CTD_TEMPERATURE (DEG C)	20.123
7	CTD_SALINITY (PSS)	34.456
8	SAMPLE_COUNTER	1716
9	POWER_CYCLE_COUNTER	112
10	ERROR_COUNTER	443
11	HOUSING_TEMP (DEG C)	24.94
12	HOUSING_REL_HUMIIDITY (%)	33.87
13	DURAFET_SUPPLY_VOLTAGE (V)	12.34
14	DURAFET_SUPPLY_CURRENT (A)	0.052
15	DURAFET_COMPUTED_pH_VALUE (pH)	7.1234
16	DURAFET_BACKUP_BATT_VOLTAGE (V)	3.25
17	DURAFET_REFERENCE_ELECTRODE_VRS_VOLTAGE (V)	1.900084
18	DURAFET_REFERENCE_ELECTRODE_VRS_STDDEV	0.000022
19	DURAFET_COUNTER_ELECTRODE_VK_VOLTAGE (V)	-0.908396
20	DURAFET_COUNTER_ELECTRODE_VK_STDDEV	0.000434
21	DURAFET_COUNTER_ELECTRODE_IK_CURRENT (A)	6.04E-09
22	DURAFET_SUBSTRATE_IB_CURRENT (A)	-1.57E-09

FOR NO3 Data
ID  RECORD TYPE          SAMPLE DATA
1	RECORD_16BIT_CRC	 0xE0B6
2	RECORD_DATA_TYPE	A
3	ISUS_TIMESTAMP (GMT)	4/14/2007 2:58
4	CTD_TIMESTAMP (1970 EPOCH SECS) 	0
5	CTD_PRESSURE (D BAR)	-1
6	CTD_TEMPERATURE (DEG C)	-1
7	CTD_SALINITY (PSS)	-1
8	SAMPLE_COUNTER	268
9	POWER_CYCLE_COUNTER	1
10	ERROR_COUNTER	1
11	ENDCAP_SEAWATER_TEMPERATURE (DEG C)	18.19
12	HOUSING_TEMP (DEG C)	23.34
13	HOUSING_REL_HUMIIDITY (%)	34.52
14	ISUS_BATT_VOLTAGE (V)	0.01
15	ISUS_CURRENT (A)	0.635
16	REF_CH_MEAN	1102.79
17	REF_CH_STDEV	2.39
18	DARK_CURRENT_MEAN	938.89
19	DARK_CURRENT_STDEV	6.23
20	ISUS_SALINITY (PSS)	31.21
21	ISUS_NITRATE (uM)	27.48
22	ISUS_ABSORBANCE_FIT_RESIDUALS (RMS)	1.44E-03
23	FIT PIXEL_BEG	33
24	FIT PIXEL_END	63
25	PACKED_HEX_INTENSITY_SPECTRA	0701079D085B092009F90ADC0BDD0CFC0E370F88110512A41470165D187A1AAA1CF91F45219723C225C4277A286029892A8D2AD02AB12A4229A228DB2805
26 (NEW ITEM)	Seawater Dark Current (avg of first five intensity pixels)	910.23


*/