using System; using System.Collections; using System.IO; using System.Text; using GHI.Processor; using Microsoft.SPOT; using SWModules; using System.Threading; // ReSharper disable All namespace HWModules { public static class SciLogger { private static CTD1 ctd1; private static FLBB1 flbb1; private static MSC1 msc1; private static Optode1 optode1; private static Radiometer1 radiometer1; private static FileQueue _fileQ; private static QRecord _qRecord; private static FileStream msgFile; private static StringBuilder msgFileName = new StringBuilder(64); private static FileStream logFile; private static StringBuilder logFileName = new StringBuilder(64); public static StringBuilder CurrentState { get; set; } private static byte[] colNames = UTF8Encoding.UTF8.GetBytes("$ Comment\tDate Time\tpres (dBar)\ttemp (degC)\tsal (ppt)\tTopt (degC)\tTPhase (deg)\tRPhase (deg)\tpH (V)\tNO3 (V)\tFSig (counts)\tBbSig (counts)\tTSig (counts)\tOCR Ch1 (uW/cm2/nm)\tOCR Ch2\tOCR Ch3\tOCR Ch4\r\n"); private static StringBuilder sbTemp = new StringBuilder(2000); private static readonly byte[] CRLF = new byte[] { 13, 10 }; private const byte tab = (byte)Literals.tabChar; private static StringBuilder gpsPositionSB = new StringBuilder("Lon Lat numSats gpsFixTime TTFF:\t no fix"); /// /// Open a new file with given name /// /// Filename without suffix, .sci will be added. private static bool msgFileOpen = false; public static void openNewMsgFile() { EngrLogger.writeToColumns("Opening new msg file"); if (ctd1 == null) { ctd1 = CTD1.Instance; flbb1 = FLBB1.Instance; msc1 = MSC1.Instance; optode1 = Optode1.Instance; radiometer1 = Radiometer1.Instance; _fileQ = new FileQueue(); } if (msgFileOpen) { closeMsgFile(); msgFileOpen = false; } msgFileFooter.Clear(); msgFileFooter.Append("$ Profile details\r\n"); msgFileFooter.Append("$ Date Time\tComment\tPres (dBar)\tBel Pos (mm)\tBus Volt\tBus Cur (mA)\r\n"); cpData.Clear(); cpData.Append("$ CP Data\r\n"); msgFileName.Clear(); msgFileName.Append(EngrLogger.fileNameBase); msgFileName.Append(".msg"); try { msgFile = new FileStream(msgFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write); msgFileOpen = true; } catch { EngrLogger.writeToColumns("Error opening .msg File"); } } /// /// Closes the Science file, and enqueues it for transmission. /// public static void closeMsgFile() { if (msgFileOpen) { msgFile.Write(CRLF, 0, CRLF.Length); msgFile.Write(Encoding.UTF8.GetBytes(gpsPositionSB.ToString()), 0, gpsPositionSB.Length); msgFile.Write(CRLF, 0, CRLF.Length); writeMsgFooter(); EngrLogger.writeToColumns("Closing .msg File"); msgFile.Close(); msgFileOpen = false; } } private static bool logFileOpen = false; public static void openNewLogFile() { EngrLogger.writeToColumns("Opening new log file"); if (logFileOpen) { closeLogFile(); logFileOpen = false; } logFileName.Clear(); logFileName.Append(EngrLogger.fileNameBase); logFileName.Append(".log"); try { logFile = new FileStream(logFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write); logFileOpen = true; } catch { EngrLogger.writeToColumns("Error opening .log File"); } } public static void closeLogFile() { if (logFileOpen) { EngrLogger.writeToColumns("Closing .log File"); logFile.Close(); logFileOpen = false; } } /// /// Allow other objects to add lines to SciFile Footer /// private static StringBuilder msgFileFooter = new StringBuilder(1024); public static void appendToMsgFooter(StringBuilder newLine) { sbTemp.Clear(); sbTemp.Append("Append to msg footer: "); sbTemp.Append(newLine); EngrLogger.writeToColumns(sbTemp); msgFileFooter.Append(newLine); msgFileFooter.Append("\r\n"); } /// /// Write SciFile footer /// private static void writeMsgFooter() { EngrLogger.writeToColumns("Writing Footer to msg File"); msgFile.Write(Encoding.UTF8.GetBytes(msgFileFooter.ToString()), 0, msgFileFooter.Length); msgFile.Flush(); } /// /// Allow other objects to add lines to SciFile Header /// public static void appendToMsgHeader(StringBuilder newLine) { sbTemp.Clear(); sbTemp.Append("Append to msg header: "); sbTemp.Append(newLine); EngrLogger.writeToColumns(sbTemp); msgFileHeader.Append("$ "); msgFileHeader.Append(newLine); msgFileHeader.Append(Literals.crlfString); } private static StringBuilder msgFileHeader = new StringBuilder(1024); /// /// Write out the header to file /// public static void writeMsgHeader() { EngrLogger.writeToColumns("Writing Header to msg File"); msgFile.Write(SafeEncoding.UTF8.GetBytes(msgFileHeader.ToString()), 0, msgFileHeader.Length); msgFile.Write(colNames, 0, colNames.Length); msgFile.Flush(); } /// /// Append the cp data /// /// byte array containing the segment of CP data to be copied into the file. public static void appendCPData(byte[] inBytes) { //TODO P1 this should use safe encoding sbTemp.Clear(); sbTemp.Append(UTF8Encoding.UTF8.GetChars(inBytes)); cpData.Append(sbTemp); sbTemp.Clear(); sbTemp.Append("Appending CP Data: "); sbTemp.Append(UTF8Encoding.UTF8.GetChars(inBytes)); EngrLogger.writeToColumns(sbTemp); } private static StringBuilder cpData = new StringBuilder(2048); /// /// Write out all stored CP data to the file. /// public static void writeCPData() { EngrLogger.writeToColumns("Writing CP data to msg File"); appendCPData(UTF8Encoding.UTF8.GetBytes("$ End of CP data\r\n")); msgFile.Write(UTF8Encoding.UTF8.GetBytes(cpData.ToString()), 0, cpData.Length); msgFile.Flush(); } /// /// Sample types for byte array storage and output /// public enum Instrument { Ctd = 0, Optode, Ph, NO3, Flbb, Radiometer } /// /// Determine if an instrument should be sampled. /// /// /// /// private static bool sampleMe(Instrument instr, int instrList) { if (((int)System.Math.Pow((double)2, (double)instr) & instrList) > 0) return true; else return false; } private static StringBuilder ctdSB = new StringBuilder(32); private static StringBuilder optodeSB = new StringBuilder(64); private static StringBuilder phSB = new StringBuilder(16); private static StringBuilder no3SB = new StringBuilder(16); private static StringBuilder flbbSB = new StringBuilder(16); private static StringBuilder ocrSB = new StringBuilder(16); private static bool gotCTD = false; private static bool gotOptode = false; private static bool gotPH = false; private static bool gotNO3 = false; private static bool gotFLBB = false; private static bool gotRadiometer = false; public static bool getCTD = false; public static bool getOptode = false; public static bool getPH = false; public static bool waitForCTDThenGetPH = false; public static bool getNO3 = false; public static bool waitForCTDThenGetNO3 = false; public static bool getFLBB = false; public static bool getRadiometer = false; private static int missedCTD = 0; private static int missedOptode = 0; private static int missedPH = 0; private static int missedNO3 = 0; private static int missedFLBB = 0; private static int missedOCR = 0; public static bool sampleInProgress = false; private static StringBuilder sampleStartTimeStamp = new StringBuilder(32); /// /// /// //TODO P2 need to make sure SciLogger is not active in recovery mode public static void startNewSampleCycle(int instrumentList) { if ((SV.CurrentState == CPFStates.anchor) || (SV.CurrentState == CPFStates.park) || (SV.CurrentState == CPFStates.ascend) || (SV.CurrentState == CPFStates.surfaceOps) ) { EngrLogger.writeToColumns("Starting New SciLogger Sample Cycle"); ctdSB.Clear(); ctdSB.Append("NaN\tNaN\tNaN"); gotCTD = false; optodeSB.Clear(); optodeSB.Append("NaN\tNaN\tNaN"); gotOptode = false; phSB.Clear(); phSB.Append("NaN"); gotPH = false; no3SB.Clear(); no3SB.Append("NaN"); gotNO3 = false; flbbSB.Clear(); flbbSB.Append("NaN\tNaN\tNaN"); gotFLBB = false; ocrSB.Clear(); ocrSB.Append("NaN\tNaN\tNaN\tNaN"); gotRadiometer = false; sampleStartTimeStamp.Clear(); sampleStartTimeStamp.Append(DateTime.Now.ToString("yyyy-MMM-ddTHH:mm:ssZ")); if (sampleMe(Instrument.Ctd, instrumentList)) { getCTD = true; //Don't take a PTS sample in ascend mode because SBE41 is in CP mode and already outputting a PTS sample if (!SV.InCPMode) { ctd1.sendCRLF(); Thread.Sleep(1000); ctd1.sendPTS(); EngrLogger.writeToColumns("Sci Logger sample CTD pts"); } } else getCTD = false; if (sampleMe(Instrument.Optode, instrumentList)) { getOptode = true; optode1.doSample(); EngrLogger.writeToColumns("Sci Logger sample Optode"); } else getOptode = false; //TODO P2 need to create cases for pH only and NO3 only if (sampleMe(Instrument.Ph, instrumentList) || sampleMe(Instrument.NO3, instrumentList)) { waitForCTDThenGetPH = true; waitForCTDThenGetNO3 = true; EngrLogger.writeToColumns("Sci Logger set wait for CTD then sample pH and NO3 true"); } else { getPH = false; getNO3 = false; waitForCTDThenGetPH = true; waitForCTDThenGetNO3 = true; } if (sampleMe(Instrument.Flbb, instrumentList)) { getFLBB = true; flbb1.sendRunCmd(); EngrLogger.writeToColumns("Sci Logger sample FLBB"); } else getFLBB = false; if (sampleMe(Instrument.Radiometer, instrumentList)) { getRadiometer = true; radiometer1.sendPollSample(); EngrLogger.writeToColumns("Sci Logger sample Radiometer"); } else getRadiometer = false; if (getCTD || getFLBB || waitForCTDThenGetPH || getRadiometer || getOptode || waitForCTDThenGetNO3) sampleInProgress = true; } } public static void endSampleCycle() //TODO P1 Check if writeSample does exactly what this does only more correctly { EngrLogger.writeToColumns("Ending SciLogger Sample Cycle"); if(sampleInProgress) writeSample(); gotCTD = false; gotOptode = false; gotPH = false; gotNO3 = false; gotFLBB = false; gotRadiometer = false; getCTD = false; getOptode = false; getPH = false; getNO3 = false; waitForCTDThenGetPH = false; waitForCTDThenGetNO3 = false; getFLBB = false; getRadiometer = false; sampleInProgress = false; } /// /// Write key science data to file. /// public static void writeSample() { sbTemp.Clear(); if ((SV.CurrentState == CPFStates.anchor) || (SV.CurrentState == CPFStates.park) || (SV.CurrentState == CPFStates.surfaceOps) || (SV.CurrentState == CPFStates.ascend) || (SV.CurrentState == CPFStates.descend)) { EngrLogger.writeToColumns("Writing sample to msg file"); if (SV.CurrentState == CPFStates.anchor) sbTemp.Append("Anchor pt: "); else if (SV.CurrentState == CPFStates.park) sbTemp.Append("Park pt: "); else if (SV.CurrentState == CPFStates.surfaceOps) sbTemp.Append("Surface pt: "); else if (SV.CurrentState == CPFStates.ascend) sbTemp.Append("Ascend pt: "); else if (SV.CurrentState == CPFStates.descend) sbTemp.Append("Decend pt: "); sbTemp.Append(Literals.tabChar); sbTemp.Append(sampleStartTimeStamp); sbTemp.Append(Literals.tabChar); sbTemp.Append(ctdSB); sbTemp.Append(Literals.tabChar); sbTemp.Append(optodeSB); sbTemp.Append(Literals.tabChar); sbTemp.Append(phSB); sbTemp.Append(Literals.tabChar); sbTemp.Append(no3SB); sbTemp.Append(Literals.tabChar); sbTemp.Append(flbbSB); sbTemp.Append(Literals.tabChar); sbTemp.Append(ocrSB); EngrLogger.writeToColumns(sbTemp); sbTemp.Append("\r\n"); //Debug.Print("\r\n\r\n Writing this to msg file: " + sbTemp.ToString() + "\r\n\r\n"); msgFile.Write(Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length); msgFile.Flush(); //TODO P2 Eventually need to do something when missedXXX exceeds some value //Really need to do this inside a well executed commmand processor object instantiated in each instrument if (getCTD) { if (!gotCTD) { missedCTD++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed CTD message "); sbTemp.Append(missedCTD); EngrLogger.writeToColumns(sbTemp); } } if (getOptode) { if (!gotOptode) { missedOptode++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed Optode message "); sbTemp.Append(missedOptode); EngrLogger.writeToColumns(sbTemp); } } if (getPH) { if (!gotPH) { missedPH++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed PH message "); sbTemp.Append(missedPH); EngrLogger.writeToColumns(sbTemp); } } if (getNO3) { if (!gotNO3) { missedNO3++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed NO3 message "); sbTemp.Append(missedNO3); EngrLogger.writeToColumns(sbTemp); } } if (getFLBB) { if (!gotFLBB) { missedFLBB++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed FLBB message "); sbTemp.Append(missedFLBB); EngrLogger.writeToColumns(sbTemp); } } if (getRadiometer) { if (!gotRadiometer) { missedOCR++; sbTemp.Clear(); sbTemp.Append("ERROR: Missed OCR message "); sbTemp.Append(missedOCR); EngrLogger.writeToColumns(sbTemp); } } sampleInProgress = false; } } private static float postedCTDPressure = float.MaxValue; private static float postedCTDTemperature = float.MaxValue; private static float postedCTDSalinity = float.MaxValue; private static DateTime postedCTDTimestamp = new DateTime(2000, 1, 1); public static void setCTDValues(CTD1.PTSRecord ptsRecord) { postedCTDPressure = ptsRecord.pressure; postedCTDTemperature = ptsRecord.temperature; postedCTDSalinity = ptsRecord.salinity; postedCTDTimestamp = ptsRecord.timeStamp; } private static StringBuilder returnSB = new StringBuilder(32); public static void postSample(Instrument sample, StringBuilder sampleSB) { switch (sample) { case Instrument.Ctd: #region format CTD message if (!gotCTD) { if (waitForCTDThenGetNO3) { getNO3 = true; // pH and N3 sampling requires its own state machine so setting this SV tells doStateAction methods to run the appropriate MSC sampling sequence SV.SamplePHandN3 = true; waitForCTDThenGetNO3 = false; } if (waitForCTDThenGetPH) { getPH = true; // pH and N3 sampling requires its own state machine so setting this SV tells doStateAction methods to run the appropriate MSC sampling sequence SV.SamplePHandN3 = true; waitForCTDThenGetPH = false; } if( getPH || getNO3) msc1.SetCTDValues(Utils.dateTimeToPosixTime(postedCTDTimestamp), postedCTDTemperature, postedCTDSalinity, postedCTDPressure); ctdSB.Clear(); //TODO P0 probably should replace all GetChars with SafeEncoding get chars ctdSB.Append(sampleSB); gotCTD = true; } break; #endregion case Instrument.Flbb: #region format FLBB message flbbSB.Clear(); sbTemp.Clear(); sbTemp.Append(sampleSB); //Debug.Print("FLBB post: " + sbTemp.ToString()); //Get FL signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 3, ref returnSB); flbbSB.Append(returnSB); gotFLBB = true; getFLBB = false; } catch { EngrLogger.writeToColumns("Error parsing flbb Topt"); } flbbSB.Append(Literals.tabChar); //Get BB signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 5, ref returnSB); flbbSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing flbb TCPhase"); } flbbSB.Append(Literals.tabChar); //Get Temp signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 6, ref returnSB); flbbSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing flbb TCPhase"); } //Debug.Print("new flbbSB: " + flbbSB.ToString()); break; #endregion case Instrument.NO3: #region format NO3 message no3SB.Clear(); no3SB.Append(sampleSB); gotNO3 = true; break; #endregion case Instrument.Radiometer: #region format OCR message ocrSB.Clear(); sbTemp.Clear(); sbTemp.Append(sampleSB); //Debug.Print("ocr post: " + sbTemp.ToString()); //Get CH1 signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 1, ref returnSB); ocrSB.Append(returnSB); gotRadiometer = true; } catch { EngrLogger.writeToColumns("Error parsing ocr Ch1"); } ocrSB.Append(Literals.tabChar); //Get CH2 signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 2, ref returnSB); ocrSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing ocr Ch2"); } ocrSB.Append(Literals.tabChar); //Get CH3 signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 3, ref returnSB); ocrSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing ocr Ch3"); } ocrSB.Append(Literals.tabChar); //Get CH4 signal try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 4, ref returnSB); ocrSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing ocr Ch4"); } //Debug.Print("new ocrSB: " + ocrSB.ToString()); break; #endregion case Instrument.Optode: #region format Optode message optodeSB.Clear(); sbTemp.Clear(); sbTemp.Append(sampleSB); //Debug.Print("Optode post: " + sbTemp.ToString()); //Get Topt try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 3, ref returnSB); optodeSB.Append(returnSB); gotOptode = true; } catch { EngrLogger.writeToColumns("Error parsing optode Topt"); } optodeSB.Append(Literals.tabChar); //Get TCPhase try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 5, ref returnSB); optodeSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing optode TCPhase"); } optodeSB.Append(Literals.tabChar); //Get C2RPhase try { returnSB.Clear(); Utils.getField(sampleSB, Literals.commaChar, 7, ref returnSB); optodeSB.Append(returnSB); } catch { EngrLogger.writeToColumns("Error parsing optode C2RPhase"); } //Debug.Print("new optodeSB: " + optodeSB.ToString()); break; #endregion case Instrument.Ph: #region format pH message phSB.Clear(); phSB.Append(sampleSB); gotPH = true; break; #endregion default: throw new Exception("Unknown sample type sent"); } //If everything that wa supposed to post posted, write the sample bool everythingPosted = true; if (getCTD) { if (gotCTD) getCTD = false; else everythingPosted = false; } if (getFLBB) { if (gotFLBB) getFLBB = false; else everythingPosted = false; } if (getNO3) { if (gotNO3) getNO3 = false; else everythingPosted = false; } if (getRadiometer) { if (gotRadiometer) getRadiometer = false; else everythingPosted = false; } if (getOptode) { if (gotOptode) getOptode = false; else everythingPosted = false; } if (getPH) { if (gotPH) getPH = false; else everythingPosted = false; } if (everythingPosted) writeSample(); } public static void writeMsgComment(StringBuilder inComment) { sbTemp.Clear(); sbTemp.Append("$ "); sbTemp.Append(inComment); sbTemp.Append("\r\n"); //Debug.Print("msg Comment: "); //Debug.Print(sbTemp.ToString()); msgFile.Write(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length); msgFile.Flush(); } public static void writeToLog(StringBuilder inLine) { sbTemp.Clear(); sbTemp.Append(inLine); sbTemp.Append("\r\n"); //Debug.Print("log Comment: "); //Debug.Print(sbTemp.ToString()); logFile.Write(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), 0, sbTemp.Length); logFile.Flush(); } /// /// Print the current science file to the debugger /// public static void printLastMsgFile() { StreamReader sciFileStream; string fileLine; bool reopenFile; if (msgFileOpen) { closeMsgFile(); msgFileOpen = false; reopenFile = true; } else reopenFile = false; sciFileStream = new StreamReader(msgFileName.ToString()); while ((fileLine = sciFileStream.ReadLine()) != null) Debug.Print(fileLine); sciFileStream.Close(); if (reopenFile) msgFile = new FileStream(msgFileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write); } /// /// Enqueue's a filename in the FIFO queue that the rudics thread watches and attempts to upload from. /// /// Name Must be full path, and valid, file should not be open. public static void EnqueueFileForXfer(string name) { _qRecord.byteArray = Encoding.UTF8.GetBytes(name); _qRecord.Update(); // Add filename to transfer buffer _fileQ.Enqueue(_qRecord); sbTemp.Clear().Append("Enqueueing File for Transfer: ").Append(name); EngrLogger.writeToColumns(sbTemp); } /// /// Returns the last file closed as a QRecord, with closing time in the date field. /// /// Thrown when attempting to dequeue from empty queue public static QRecord DequeueLastFileClosed { get { return _fileQ.Dequeue(); } } private static bool numberOfCharsInByteArray(int n, char testChar, byte[] byteArray) { int numChars = 0; int index = -1; for (int i = 0; i < n; i++) { index = Array.IndexOf(byteArray, (byte)testChar, index + 1); if (index < 0) break; else numChars++; } if (numChars >= n) return true; else return false; } public static void updateLatLon(gpsUBlox.GPSPosition position) { gpsPositionSB.Clear(); gpsPositionSB.Append("Lon Lat numSats gpsFixTime TTFF:\t"); gpsPositionSB.Append(position.lonSB); gpsPositionSB.Append(Literals.tabChar); gpsPositionSB.Append(position.latSB); gpsPositionSB.Append(Literals.tabChar); gpsPositionSB.Append(position.gpsTimeOfFix); gpsPositionSB.Append(Literals.tabChar); gpsPositionSB.Append(position.gpsTTFF); gpsPositionSB.Append(Literals.tabChar); } } }