using System;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using SWModules;
using Math = System.Math;
namespace HWModules
{
///
/// 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[] 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[] 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, 5, 0);
///
/// Timeout to wait for a sample request (too many bytes)
///
private readonly TimeSpan _wfLongTimeout = new TimeSpan(0, 0, 0, 60, 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(EngrLogger.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,
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
private static int _msAckSleep = 1000;
protected void ProcessMessage(QRecord record)
{
_lastResponse = GetResponseCode(record.byteArray);
//EngrLogger.writeToColumns(EngrLogger.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(EngrLogger.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.Config: //todo log this in a science log
LastCommand = ResponseCmd.IsusDuraConfig;
//EngrLogger.writeToColumns(EngrLogger.ColumnNums.Msc,msgHeader + UtfString(record.byteArray));
break;
case ResponseCode.Eof:
LastCommand = ResponseCmd.Eof;
break;
case ResponseCode.Unknown:
default:
//EngrLogger.writeToColumns(EngrLogger.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:
ReqLastRecord();
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(EngrLogger.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, 14, ref _fieldBytes) != 0)
{
SciLogger.postSample(SciLogger.Instruments.Ph,_fieldBytes);
_sbTemp.Clear();
_sbTemp.Append("[MSC] Post: ");
_sbTemp.Append(UTF8Encoding.UTF8.GetChars(_fieldBytes));
EngrLogger.writeToColumns(_sbTemp);
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: ");
_sbTemp.Append(UTF8Encoding.UTF8.GetChars(_fieldBytes));
EngrLogger.writeToColumns(_sbTemp);
return true;
}
return false;
}
//EngrLogger.writeToColumns(EngrLogger.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(EngrLogger.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)
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.writeToColumns(EngrLogger.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(EngrLogger.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(EngrLogger.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(EngrLogger.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(EngrLogger.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(EngrLogger.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(EngrLogger.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(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.writeToColumns(EngrLogger.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(EngrLogger.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));
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(EngrLogger.ColumnNums.Msc,"[MSC] NAK RXD: " + UtfString(_parseBuff));
EngrLogger.writeToColumns("[MSC] 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.writeToColumns(EngrLogger.ColumnNums.Msc, msgHeader + UtfString(bytes));
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();
//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;
}
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
//TODO P1 I changed this to static so I could call it from SBE41CTD.parse, is that right?
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);
}
///
/// Send a message with the class-stored CTD values
///
public void SendCTD()
{
CopyCommand(CmdCtd);
AddComma();
AddField(_lastSentCtdPosixTime);
AddComma();
AddField(_lastSentCtdTemperature);
AddComma();
AddField(_lastSentCtdSalinity);
AddComma();
AddField(_lastSentCtdDepth);
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*/
//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;
}
///
/// 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)
{
_pHandN3ModeNum = 0;
EngrLogger.writeToColumns("Staring PH and NO3 sample sequence");
}
}
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:
//TODO P1 need to send CTD command to MSC before mode command
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(EngrLogger.ColumnNums.Msc, "[MSC] SequencePHandN3 critical failure" + e);
EngrLogger.writeToColumns("[MSC] SequencePHandN3 critical failure" + 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;
//kill some time, send a lot of wakes until we get a response
/*case 3:
state = WaitForResponse(DummyReq, _wfrTimeout);
if (state != WfrState.Waiting)
++_sampleSequence;
return 0;
//state = WaitForResponse(ReqMode, _wfrTimeout);
//return CheckStateAndAdvance(state, ref _sampleSequence);
*/
//request sample
case 3:
state = WaitForResponse(TakeSample, _wfLongTimeout);
return CheckGoodSampleState(state, ref _sampleSequence);
//Get data
/*case 4:
state = WaitForResponse(ReqLastRecord, _wfrTimeout);
//todo Log the Data if it came.
retval = CheckStateAndAdvance(state, ref _sampleSequence);
if (state == WfrState.GotResponse) goto case 5;
return retval;*/
//Cleanup and Exit
case 4:
_sampleSequence = -1;
//EngrLogger.writeToColumns(EngrLogger.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(EngrLogger.ColumnNums.Msc, "[MSC] Sample():" + e);
EngrLogger.writeToColumns("[MSC] Sample():" + e);
return -1;
}
}
public void ResetSequencers()
{
EngrLogger.Comment("[MSC] Resetting Sequencers");
_initSequence = -1;
_ctdSequence = -1;
_dlSequence = -1;
_fitSequence = -1;
_sampleSequence = -1;
_pHandN3ModeNum = -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.writeToColumns(EngrLogger.ColumnNums.Msc,"[MSC] 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("[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;
///
/// 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(EngrLogger.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)
{
//Debug.Print("Timed out CheckStateAndTryAgain");
--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(EngrLogger.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(EngrLogger.ColumnNums.Msc, "[MSC] Timeout for Command" + sendCommand.Method.Name);
EngrLogger.writeToColumns("[MSC] 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
}
}
/*
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
*/