using System; using System.IO.Ports; using System.Runtime.CompilerServices; using System.Text; using Microsoft.SPOT; using SWModules; using Math = System.Math; /* * TODO List for MSC Driver - EJM * - incorporate items to be put into the structured queue * - unfortunately some messages will have to be immediately handled and responded to, that's in testing. * - * */ namespace HWModules { /// /// Singleton Class for employment of the MSC class /// public sealed class MSC1 : MSC { private static MSC1 _instance; private MSC1() : base("MSC1", "COM7", 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 { /// /// 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}; 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[] 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 string msgHeader = "MSC: "; /// /// 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, 1, 400); // /// /// 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 ExarUart _port; private readonly StringBuilder _sbTemp; /// /// Timeout Before Ack gets sent /// private readonly TimeSpan _wfaTimeout = new TimeSpan(0, 0, 0, 1, 600); // Timeout before ack gets sent /// /// 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, 5, 0); /// /// Timeout to wait for a sample request (too many bytes) /// private readonly TimeSpan _wfLongTimeout = new TimeSpan(0, 0, 0, 45, 0); /// /// Default Timeout length for any transaction pushed through waitforresponse /// private readonly TimeSpan _wfrTimeout = new TimeSpan(0, 0, 0, 1, 0); private short _bakeDuration; private TimeSpan _cmdWaitSpan; private float _ctdDepth; private uint _ctdPosixTime; private float _ctdSalinity; private float _ctdTemperature; 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[] _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; /// /// 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 ExarUart(id, ProcessMessage, portName, baudRate, parity, dataBits, stopBits, readTimeout, writeTimeout, useSTX, inSTX); _port.Open(); _outBytes = new byte[128]; _outLength = 0; //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.Comment(msgHeader + "OUT: " + UtfString(_outBytes)); if (_port.IsOpen) _port.Write(_outBytes, 0, _outLength); } ~MSC() { if (_port.IsOpen) _port.Close(); } private enum ResponseCode { Ack, Nak, Eof, Error, Sample, Config, 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, 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 protected void ProcessMessage(QRecord record) { _lastResponse = GetResponseCode(record.byteArray); EngrLogger.Comment(msgHeader + "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.Comment("MSC: Got ACK"); //do nothing else return; } if (LastCommand == ResponseCmd.Unknown) return; //Process the fields if (ProcessResponse(record.byteArray, _lastResponse, LastCommand)) _needToSendAck = true; 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.Config: //todo log this in a science log LastCommand = ResponseCmd.IsusDuraConfig; //EngrLogger.Comment(msgHeader + UtfString(record.byteArray)); break; case ResponseCode.Eof: LastCommand = ResponseCmd.Eof; break; case ResponseCode.Unknown: default: //EngrLogger.Comment(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.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 ProcessSendLastData(byte[] bytes, ResponseCode code) { EngrLogger.Comment(msgHeader + "Data: " + UtfString(bytes)); return true; } 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.Comment(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) if (_errorMessages != short.Parse(UtfString(_parseBuff))) return false; return false; case ResponseCode.Nak: 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.Comment(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.Comment(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.Comment(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 (_ctdPosixTime != uint.Parse(UtfString(_parseBuff))) return false; } else { return false; } if (Utils.getField(bytes, 0x2C, 3, ref _parseBuff) > 0) { if (Math.Abs(_ctdTemperature - double.Parse(UtfString(_parseBuff))) > Tolerance) return false; } else { return false; } if (Utils.getField(bytes, 0x2C, 4, ref _parseBuff) > 0) { if (Math.Abs(_ctdSalinity - double.Parse(UtfString(_parseBuff))) > Tolerance) return false; } else { return false; } if (Utils.getField(bytes, 0x2C, 5, ref _parseBuff) > 0) { if (Math.Abs(_ctdDepth - 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.Comment(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.Comment(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.Comment(msgHeader + "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.Comment(msgHeader + "NAK RXD: " + UtfString(_parseBuff)); return false; default: return false; } } private string UtfString(byte[] bytes) { _sbTemp.Clear().Append(Encoding.UTF8.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.Comment(msgHeader + "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.Comment(msgHeader + "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)); 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.Comment(msgHeader + "NAK RXD: " + UtfString(_parseBuff)); return false; default: //TODO Log it? return false; } } private bool ProcessErrorRecord(byte[] bytes, ResponseCode code) { if (code != ResponseCode.Error) return false; //TODO Dump it to the science log EngrLogger.Comment(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(); //TODO DON"T KNOW UNTIL I GET A SENSOR... switch (CurrentMode) { case SampleMode.ISUS: //TODO dont' know yet. _expectedResponse = ResponseCmd.SendLastData; break; 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; } /// /// 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 SetCTD(uint posixTime, float temperature, float salinity, float depth) { _ctdPosixTime = posixTime; _ctdTemperature = temperature; _ctdSalinity = salinity; _ctdDepth = depth; } /// /// Send a message with the class-stored CTD values /// public void SendCTD() { CopyCommand(CmdCtd); AddComma(); AddField(_ctdPosixTime); AddComma(); AddField(_ctdTemperature); AddComma(); AddField(_ctdSalinity); AddComma(); AddField(_ctdDepth); WriteOutBuffer(); _expectedResponse = 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*/ _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; } /// /// 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; } #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 = 0; public int SequencePHandN3Samples() { try { switch (PHandN3ModeNum) { //Init case 0: EngrLogger.writeToColumns("Starting ISUS sample"); //goto case 1; goto case 2; //Just for initial testing case 1: if (SequenceSample(SampleMode.ISUS) == 1) { PHandN3ModeNum = 2; } return 0; case 2: EngrLogger.writeToColumns("Starting pH sample"); //goto case 3; goto case 4; //Just for initial testing case 3: if (SequenceSample(SampleMode.DuraFET) == 1) { PHandN3ModeNum = 4; } return 0; case 4: PHandN3ModeNum = 0; //TODO P1: Doesn't the + eventually fire up the garbage collector? EngrLogger.Comment(msgHeader + "Done Sampling."); return 1; default: //Error Condition, cleanup and exit. return -1; } } catch (Exception e) { _sampleSequence = 0; EngrLogger.Comment(msgHeader + "Sample():" + e); return -1; } } private short _sampleSequence; /// /// Sequencer to collect a sample and retrieve the values. This is for one mode /// at present. /// /// /// public int SequenceSample(SampleMode mode) { WfrState state; if (_sampleSequence < 0) _sampleSequence = 0; try { switch (_sampleSequence) { //Init case 0: _remainingAttempts = MaxAttempts; _sampleSequence++; goto case 1; //Make Sure its awake case 1: state = WaitForResponse(Wake, _wfrTimeout); return CheckStateAndTryAgain(state, ref _sampleSequence, ref _remainingAttempts); //set mode case 2: CurrentMode = mode; state = WaitForResponse(SendMode, _wfrTimeout); return CheckStateAndAdvance(state, ref _sampleSequence); //kill some time, send a lot of wakes until we get a response case 3: state = WaitForResponse(ReqMode, _wfrTimeout); return CheckStateAndAdvance(state, ref _sampleSequence); //request sample case 4: state = WaitForResponse(TakeSample, _wfLongTimeout); return CheckGoodSampleState(state, ref _sampleSequence); //Get data case 5: state = WaitForResponse(ReqLastRecord, _wfrTimeout); //todo Log the Data if it came. return CheckStateAndAdvance(state, ref _sampleSequence); //Cleanup and Exit case 6: _sampleSequence = -1; EngrLogger.Comment(msgHeader + "Done Sampling."); return 1; default: //Error Condition, cleanup and exit. _sampleSequence = -1; return -1; } } catch (Exception e) { _sampleSequence = 0; EngrLogger.Comment(msgHeader + "Sample():" + e); return -1; } } private short _initSequence; private short _remainingAttempts; private const short MaxAttempts = 10; /// /// Sequencer to initialize a new sample profile. /// /// public int SequenceInitializeProfile() { WfrState state; if (_initSequence < 0) _initSequence = 0; switch (_initSequence) { //Wake case 0: _initSequence++; _remainingAttempts = MaxAttempts; goto case 1; //Make Sure its awake case 1: state = WaitForResponse(Wake, _wfrTimeout); return CheckStateAndTryAgain(state, ref _initSequence, ref _remainingAttempts); //Set the time case 2: _remainingAttempts = MaxAttempts; state = WaitForResponse(SetRTC, _wfLongTimeout); return CheckStateAndAdvance(state, ref _initSequence); // Set the mode to ISUS case 3: CurrentMode = SampleMode.ISUS; state = WaitForResponse(SendMode, _wfrTimeout); return CheckStateAndAdvance(state, ref _initSequence); //Get the ISUS Configuration and put it into the profile case 4: state = WaitForResponse(ReqSensorConfig, _wfLongTimeout); return CheckStateAndAdvance(state, ref _initSequence); //wait for the config to roll in. takes about 5 seconds case 5: if (DateTime.Now - _timeLastSeqSuccess < _wfCfgTimeout) return 0; _initSequence++; goto case 6; //Make Sure its awake case 6: state = WaitForResponse(Wake, _wfrTimeout); return CheckStateAndTryAgain(state, ref _initSequence, ref _remainingAttempts); // Set the mode to DuraFet case 7: CurrentMode = SampleMode.DuraFET; state = WaitForResponse(SendMode, _wfrTimeout); if (CheckStateAndAdvance(state, ref _initSequence) < 0) goto default; return 0; //Get the ISUS Configuration and put it into the profile case 8: state = WaitForResponse(ReqSensorConfig, _wfLongTimeout); if (CheckStateAndAdvance(state, ref _initSequence) < 0) goto default; return 0; //wait for the config to roll in. takes about 3 seconds case 9: if (DateTime.Now - _timeLastSeqSuccess < _wfCfgTimeout) return 0; _initSequence = -1; EngrLogger.Comment(msgHeader + "Done Profile Initialization"); return 1; default: _initSequence = -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("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; } } /// /// 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(uint utcSeconds, float temperatureDegC, float salinityPss, float depthDBar) { WfrState state; if (_ctdSequence < 0) { SetCTD(utcSeconds,temperatureDegC,salinityPss,depthDBar); _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; /// /// 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.Comment(msgHeader + "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.Comment(msgHeader + "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.Comment(msgHeader + "Timeout for Command" + sendCommand.Method.Name); goto default; //todo GM implements this with a post failure counter...why??? } 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 } }