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 { /// /// Singleton Class for employment of the MSC class /// 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()); } } } /// /// Class: MSC /// File: MSC.cs /// Summary: Device driver for the ISUS/DuraFet Multi Sensor Controller (MSC) /// Author: Eric Martin /// Date: 01NOV2017 /// public class MSC { private MotherBoard motherboard = MotherBoard.Instance; /// /// Deterimines which sensor model the MSC will use when sampling (see TS command) /// public enum SampleMode { NoSensor = 0, ISUS, DuraFET, } /// /// Hardware configuration values for MSC configuration /// 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 }; /// /// if you send another command after an ack, the previous command is negated! /// this plus wfaTimeout must get greater than 3 seconds... /// private readonly TimeSpan _cmdWaitSpanPostAck = new TimeSpan(0, 0, 0, 0, 00); //was 1.4 /// /// Timeout between successive issuance of data on the serial port. the msc is not asynchronous. /// private readonly TimeSpan _cmdWaitSpanReg = new TimeSpan(0, 0, 0, 0, 250); private readonly NativeUART _port; private readonly StringBuilder _sbTemp; /// /// Timeout Before Ack gets sent /// private readonly TimeSpan _wfaTimeout = new TimeSpan(0, 0, 0, 0, 0); // Timeout before ack gets sent short 1.6 /// /// Timeout for 1000 bytes or so to come in with the config file, as its multiple lines, you just have to wait. /// private readonly TimeSpan _wfCfgTimeout = new TimeSpan(0, 0, 0, 10, 0); /// /// Timeout to wait for a longer request (too many bytes) /// private readonly TimeSpan _wfLongTimeout = new TimeSpan(0, 0, 0, 60, 0); /// /// Timeout to wait for a sample request (too many bytes) /// private readonly TimeSpan _wfT1T2Timeout = new TimeSpan(0, 0, 0, 10, 0); /// /// Default Timeout length for any transaction pushed through waitforresponse /// 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] "; /// /// constructor /// /// id used for logging purposes /// /// /// /// /// /// millisecond timeout /// currently unused /// /// 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; } } /// /// Current mode for commands that require a mode be set before interaction. /// See the MSC users manual. /// 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 /// /// Send a command for the msc to take a sample. Must have a /// mode set prior. /// public void TakeSample() { CopyCommand(CmdTakeSample); WriteOutBuffer(); _expectedResponse = ResponseCmd.TakeSampleDat; } /// /// Send a command to request the last sample /// record. Mode must be set /// 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; } } /// /// Issue the command for the MSC to suspend itself, /// going into sleep mode. /// public void Suspend() { CopyCommand(CmdSleep); WriteOutBuffer(); _expectedResponse = ResponseCmd.Sleep; } /// /// Send the OFF command and would typically /// be sent at the end of a profile. /// public void Shutdown() { CopyCommand(CmdOff); WriteOutBuffer(); _expectedResponse = ResponseCmd.Off; } /// /// Sent prior to deployment, this resets all /// stored values. /// 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); } /// /// Collects the time from the micro and sends /// it to the msc. /// public void SetRTC() { _rtcPosixTime = Utils.SecondsSinceEpoch; CopyCommand(CmdRtc); AddComma(); AddField(_rtcPosixTime); WriteOutBuffer(); _expectedResponse = ResponseCmd.Rtc; } /// /// Send a command to the MSC to retrieve the clock value. /// public void ReqRTC() { CopyCommand(CmdRtc); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.Rtc; } /// /// Set the stored values of the ctd /// /// seconds since jan 1, 1970 /// temp in deg c /// salinity in pss /// depth in decibars pressure 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); } /// /// Sends a T1/T2 Command with CTD data /// 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); } /// /// Send a message with the class-stored CTD values /// public void SendCTD() { SendCtdT1T2(CmdCtd, ResponseCmd.Ctd); } /// /// Sends a message requestion the stored CTD values in dBar, degC and pss /// public void ReqCTD() { CopyCommand(CmdCtd); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.Ctd; } /// /// Set the values to be sent to the MSC for the fit windows /// /// /// public void SetFitWindow(short wlBegin, short wlEnd) { _fitWlBegin = wlBegin; _fitWlEnd = wlEnd; } /// /// Transmit stored window values to the MSC /// public void SendFitWindow() { CopyCommand(CmdFit); AddComma(); AddField(_fitWlBegin); AddComma(); AddField(_fitWlEnd); WriteOutBuffer(); _expectedResponse = ResponseCmd.Fit; } /// /// Tranmist a request for the current fit window values /// public void ReqFitWindow() { CopyCommand(CmdFit); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.Fit; } /// /// Set the stored values to be sent to the MSC for spectra /// /// /// public void SetSpectra(short wlBegin, short wlEnd) { _spectraWlBegin = wlBegin; _spectraWlEnd = wlEnd; } /// /// Transmit a command to set the Spectra window, must reply ACK to complete command /// public void SendSpectra() { CopyCommand(CmdSpectra); AddComma(); AddField(_spectraWlBegin); AddComma(); AddField(_spectraWlEnd); WriteOutBuffer(); _expectedResponse = ResponseCmd.Spectra; } /// /// Request the spectra /// public void ReqSpectra() { CopyCommand(CmdSpectra); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.Spectra; } /// /// Store a value to be transmitted for the bake command /// /// todo don't know the units public void SetBakeDuration(short duration) { _bakeDuration = duration; } /// /// Send the bake command, along with duration, requires and ack /// public void Bake() { CopyCommand(CmdBake); AddComma(); AddField(_bakeDuration); _expectedResponse = ResponseCmd.Bake; } /// /// Request a single error message /// public void ReqErrorMessage() { CopyCommand(CmdSendError); WriteOutBuffer(); _expectedResponse = ResponseCmd.SendError; } /// /// Transmit the stored mode setting, /// public void SendMode() { CopyCommand(CmdMode); AddComma(); AddField((uint) CurrentMode); WriteOutBuffer(); _expectedResponse = ResponseCmd.Mode; } /// /// Request the current mode /// public void ReqMode() { CopyCommand(CmdMode); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.Mode; } #endregion #region Simple Get Commands (Single and Dual Chars, no ,?// /// /// Transmit command to wake up the MSC from low-current modes, responds with ACK /// public void Wake() { WriteBytes(CmdWake); /*W*/ //Thread.Sleep(250); //WriteBytes(CmdWake); /*W*/ _expectedResponse = ResponseCmd.NoCmd; } /// /// Request config data for the MSC, which mode must be set /// public void ReqSensorConfig() { WriteBytes(CmdIsusDuraConfig); /*C*/ _expectedResponse = ResponseCmd.IsusDuraConfig; } /// /// Request number of sensor errrors, mode must be set /// public void ReqErrorCount() { WriteBytes(CmdError); /*E*/ _expectedResponse = ResponseCmd.ErrorCounter; } public void ReqSendErrorCount() { CopyCommand(CmdSendError); AddRequest(); WriteOutBuffer(); _expectedResponse = ResponseCmd.SendError; } /// /// Request power on and system reset counter values and event time /// public void ReqPowerCount() { WriteBytes(CmdPower); /*P*/ _expectedResponse = ResponseCmd.PowerCounter; } /// /// Get MSC sample counter value, the total number of sample by either sensor, /// depends on the mode command /// public void ReqMscSampleCount() { WriteBytes(CmdSample); /*S*/ _expectedResponse = ResponseCmd.SampleCounter; } /// /// Get the time in seconds that it took the MSC to collect its last sample. /// public void ReqSampleTime() { WriteBytes(CmdTime); /*T*/ _expectedResponse = ResponseCmd.TimeOfLastSample; } /// /// Get the MSC configuration, which defines what modes it supports. /// public void ReqMscConfig() { WriteBytes(CmdMscConfig); /*Q*/ _expectedResponse = ResponseCmd.MscSensorConfig; } /// /// Dummy Request Transmits nothing, waiting for a full timeout. /// 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 /// /// Sequencer intended to be used during sampling /// /// /// /// Sequencer to collect a sample and retrieve the values for both pH and N3 /// GM 2018 May 16 /// /// /// /// 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; /// /// Sequencer to collect a sample and retrieve the values. This is for one mode /// at present. Assumes the MSC is already awake. /// /// /// 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; /// /// Sequence to retrieve config settings from the msc, and if /// enabled, also set the time. /// /// true if the sequence should also set /// the time of the MSC based on the system time. /// /// 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; /// /// Sequence to download all errors in the error log, and todo dump /// them to the science file. /// /// /// 0 running, -1 failed, 1 done/success 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; } /// /// 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 /// /// 0 Still Running, -1 Failed, 1 Done/Success 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; /// /// Simple sequencer for setting the spectra with an input value, /// that only get set on the first visit to the function /// /// /// /// 0 running, -1 Failed, 1 Done/Success 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; /// /// Simple sequence to wake up the sensor and send it new /// window values, set only on the first call to the loop. /// /// /// /// 0 running, -1 failed, 1 success/done 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; /// /// checks a waiting for response state and takes common actions on /// the input sequence counter /// /// /// /// 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; } } /// /// augments CheckStateAndAdvance by adding in a retry capability /// to sustain multiple timeouts /// /// /// /// /// 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; } } /// /// Delegate for public functions that get passed to sequencer functions. /// public delegate void SendCommand(); private WfrState _wfrState = WfrState.NotWaiting; private ResponseCmd _expectedResponse; private DateTime _wfrStartTime; //private bool _wfaRunning; /// /// Tool for non-block sequential access to build serial /// command action sequences. /// /// /// /// 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 */