//#define USB_STORAGE //enable if using USB memory using System; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using GHI.IO.Storage; using GHI.Processor; using Microsoft.SPOT; using Microsoft.SPOT.IO; using SWModules; namespace HWModules { // Create a Delegate for Sending Lines (likely to the bluetooth or a console port.) public delegate void SendLineDelegate(StringBuilder sb); public static class EngrLogger { private static StringBuilder columnNames = new StringBuilder(); //private static SDStorage sdStorage; public static VolumeInfo sdVol; private static FileStream engrLogFile; private static StringBuilder sbTemp = new StringBuilder(4096); private static StringBuilder sbWrite = new StringBuilder(4096); private static StringBuilder sbPrefix = new StringBuilder(4096); private static object loggerLock = new object(); private static byte[] writeArray = new byte[4096]; private static DateTime timeNow; private static char token = '\t'; public static bool engFileOpen { get; private set; } private static bool SDMounted = false; public static event SendLineDelegate SendConsoleLine; //public static StateBase[] states; public static StringBuilder CurrentState { get; set; } private static void debugLine(StringBuilder sb) { //This method is only required as a place holder until the desired method is attached (delegated) to the SendConsoleLine event } public enum ColumnNums : int { //First the prefix for all records dateTime = 0, state, comment, //Then the verbosity level 0 stuff pressure, bellowsPosition, batteryBusVolts, batteryBusAmps, //CTD Stuff CTDTemp, CTDSalinity, SBE41Pressure, SBE41LPFPressure, SBE41LPFVelocity, SBE41DiffVelocity, SBE41Diff2Velocity, SBE41Slope, CTDSamplePeriod, //PTH Stuff LPS331Pressure, LPS331Temperature, SHT21Humidity, SHT21Temperature, //PID stuff PID_SP, PID_PV, PID_y1, PID_y2, PID_I, PID_v, PID_u, PID_uCPS, PID_h, //Science data FLBB, TOpt, TPhase, RPhase, NO3, pH, OCR, //GPS Stuff GPS, //Iridium Modem Stuff IModem, Msc, lastColumn, } private static StringBuilder[] columnName = new StringBuilder[(int)ColumnNums.lastColumn]; private static Register resetControllerStatus = new Register(0xFFFFFE04); public static bool init(StringBuilder currentstate) { engFileOpen = false; CurrentState = currentstate; var sdStorage = SDStorage.Instance; //Add default event handler for SendConsoleLine event SendConsoleLine += new SendLineDelegate(debugLine); for (int i = 0; i < (int)ColumnNums.lastColumn; i++) columnName[i] = new StringBuilder(32); columnName[(int)ColumnNums.dateTime].Append("$\tDate\tTime"); columnName[(int)ColumnNums.state].Append("State"); columnName[(int)ColumnNums.comment].Append("Comment"); columnName[(int)ColumnNums.pressure].Append("Pressure"); columnName[(int)ColumnNums.bellowsPosition].Append("Bel Pos"); columnName[(int)ColumnNums.batteryBusVolts].Append("Batt Volt"); columnName[(int)ColumnNums.GPS].Append("GPS"); columnName[(int)ColumnNums.IModem].Append("Irid Modem"); columnName[(int)ColumnNums.LPS331Pressure].Append("LPS331 Press"); columnName[(int)ColumnNums.LPS331Temperature].Append("LPS311 Temp"); columnName[(int)ColumnNums.SHT21Humidity].Append("SHT21 Humid"); columnName[(int)ColumnNums.SHT21Temperature].Append("SHT21 Temp"); columnName[(int)ColumnNums.CTDTemp].Append("CTD Temp"); columnName[(int)ColumnNums.CTDSalinity].Append("CTD Sal"); columnName[(int)ColumnNums.FLBB].Append("FLBB"); columnName[(int)ColumnNums.TOpt].Append("Topt"); columnName[(int)ColumnNums.TPhase].Append("Tphase"); columnName[(int)ColumnNums.RPhase].Append("Rphase"); columnName[(int)ColumnNums.NO3].Append("NO3"); columnName[(int)ColumnNums.pH].Append("pH"); columnName[(int)ColumnNums.OCR].Append("OCR"); columnName[(int)ColumnNums.SBE41Pressure].Append("Raw Press"); columnName[(int)ColumnNums.SBE41LPFPressure].Append("LPF Press"); columnName[(int)ColumnNums.SBE41LPFVelocity].Append("LPF Vel"); columnName[(int)ColumnNums.SBE41DiffVelocity].Append("Diff Vel"); columnName[(int)ColumnNums.SBE41Diff2Velocity].Append("Diff 2 Vel"); columnName[(int)ColumnNums.SBE41Slope].Append("Slope of LPF Press."); columnName[(int)ColumnNums.CTDSamplePeriod].Append("CTD Sample Period"); columnName[(int)ColumnNums.PID_SP].Append("PID SP"); columnName[(int)ColumnNums.PID_PV].Append("PID PV"); columnName[(int)ColumnNums.PID_y1].Append("PID Y1"); columnName[(int)ColumnNums.PID_y2].Append("PID Y2"); columnName[(int)ColumnNums.PID_I].Append("PID I"); columnName[(int)ColumnNums.PID_v].Append("PID v"); columnName[(int)ColumnNums.PID_u].Append("PID u"); columnName[(int)ColumnNums.PID_uCPS].Append("PID uCPS"); columnName[(int)ColumnNums.PID_h].Append("PID h"); columnName[(int)ColumnNums.batteryBusAmps].Append("Batt Amps"); columnName[(int) ColumnNums.Msc].Append("Msc"); columnNames.Clear(); for (int i = 0; i < (int)ColumnNums.lastColumn; i++) { columnNames.Append(columnName[i]); columnNames.Append(Literals.tabChar); } columnNames.Append(Literals.crlfString); for (int i = 0; i < sbFileNames.Length; i++) sbFileNames[i] = new StringBuilder(128); sdVol = new VolumeInfo("\\SD"); SDMounted = true; startNewEngAndSciFiles(); uint resetControllerStatusValue = resetControllerStatus.Value; sbTemp.Clear(); sbTemp.Append("Reset Controller Status Register Value = "); sbTemp.Append(resetControllerStatusValue.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("File Name = "); sbTemp.Append(fileName.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("Volume Total Size: "); sbTemp.Append(sdVol.TotalSize.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("Volume Free Space: "); sbTemp.Append(sdVol.TotalFreeSpace.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID K = "); sbTemp.Append(BuoyancyEngine.PlatVel.K.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID Ti = "); sbTemp.Append(BuoyancyEngine.PlatVel.Ti.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID Td = "); sbTemp.Append(BuoyancyEngine.PlatVel.Td.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID Tf = "); sbTemp.Append(BuoyancyEngine.PlatVel.Tf.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID Tt = "); sbTemp.Append(BuoyancyEngine.PlatVel.Tt.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID B = "); sbTemp.Append(BuoyancyEngine.PlatVel.B.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID uLow = "); sbTemp.Append(BuoyancyEngine.PlatVel.uLow.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID uHigh = "); sbTemp.Append(BuoyancyEngine.PlatVel.uHigh.ToString()); writeToColumns(sbTemp); sbTemp.Clear(); sbTemp.Append("PlatVel PID h = "); sbTemp.Append(BuoyancyEngine.PlatVel.h.ToString()); writeToColumns(sbTemp); if (SDMounted) { sbTemp.Clear(); sbTemp.Append("Engr Logger Initialized"); EngrLogger.writeToColumns(sbTemp); return (true); } else return (false); } private static int lastValueIndex = 0; private static int columnNum = 0; private static int valueNum = 0; private static StringBuilder sbTemp2 = new StringBuilder(512); public static void Comment(string msg) { writeToColumns(msg); } private const string FmtDate = "dd-MMM-yyyy"; private const string FmtTime = "HH:mm:ss.fff"; //If only one entry is in value array passed to his method, it gets treated as a comment and this method prepends date, time and state //All CR/LF are removed from the data until just before writing to SD card [MethodImpl(MethodImplOptions.Synchronized)] public static void writeToColumns(params object[] values) { try { timeNow = DateTime.Now; sbWrite.Clear(); sbWrite.Append("$"); sbWrite.Append(token); if (values.Length == 1) //If there is only one argument in the object list, it is a comment and only a comment { sbWrite.Append(timeNow.ToString(FmtDate)); sbWrite.Append(token); sbWrite.Append(timeNow.ToString(FmtTime)); sbWrite.Append(token); sbWrite.Append(CurrentState); sbWrite.Append(token); sbWrite.Append(values[0]); } else { lastValueIndex = values.Length - 1; try { for (columnNum = 0; columnNum < (int)ColumnNums.lastColumn; columnNum++) { for (valueNum = 0; valueNum < lastValueIndex; valueNum = valueNum + 2) { if ((int)values[valueNum] == columnNum) { //TODO P3 need to do a better job formatting values to minimize length if (columnNum == (int)ColumnNums.dateTime) { timeNow = (DateTime)values[valueNum + 1]; sbWrite.Append(timeNow.ToString(FmtDate)); sbWrite.Append(token); sbWrite.Append(timeNow.ToString(FmtTime)); } else if (columnNum == (int)ColumnNums.state) { sbWrite.Append(CurrentState); } else if (columnNum == (int)ColumnNums.Msc) { sbWrite.Append(values[valueNum + 1]); } else if (columnNum == (int)ColumnNums.comment) //TODO P2 Need to do the formatting in the instrument class, this is too generic sbWrite.Append(values[valueNum + 1]); else if ((columnNum == (int)ColumnNums.CTDSalinity) || (columnNum == (int)ColumnNums.TOpt) || (columnNum == (int)ColumnNums.IModem) || (columnNum == (int)ColumnNums.GPS) || (columnNum == (int)ColumnNums.FLBB) || (columnNum == (int)ColumnNums.OCR)) sbWrite.Append(SafeEncoding.GetChars((byte[])values[valueNum + 1])); else { if (values[valueNum+1] is double) sbWrite.Append(((double)values[valueNum + 1]).ToString("f6")); else if (values[valueNum+1] is int) sbWrite.Append((int)values[valueNum+1]); } break; } } sbWrite.Append(token); } } catch { sbWrite.Append(timeNow.ToString(FmtDate)); sbWrite.Append(token); sbWrite.Append(timeNow.ToString(FmtTime)); sbWrite.Append(token); sbWrite.Append(SV.CurrentState); sbWrite.Append(token); sbWrite.Append("Error putting values in columns in EngrLogger.writeToColumns"); } } //Replace all CRLF including possible terminating CRLF since //Debug.Print adds its own sbWrite.Replace('\r', ' '); sbWrite.Replace('\n', ' '); Debug.Print(sbWrite.ToString()); //Add in CRLF for log files //sbWrite.Append("\r\n"); terminateSBWrite(); //this was an attempt to make the file smaller by not writing unused tabs at the end of every line. It may have thrown an exception if (SDMounted) { Array.Clear(writeArray, 0, writeArray.Length); Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWrite.ToString()), writeArray, sbWrite.Length); try { engrLogFile.Write(writeArray, 0, sbWrite.Length); SendConsoleLine(sbWrite); } catch { sbTemp.Clear(); sbTemp.Append("Error writing to Engineering log file: "); sbTemp.Append(sbWrite); SendConsoleLine(sbTemp); Debug.Print(sbTemp.ToString()); } } else { sbTemp.Clear(); sbTemp.Append("No SD: "); sbTemp.Append(sbWrite); SendConsoleLine(sbTemp); Debug.Print(sbTemp.ToString()); } } catch { sbTemp.Clear(); sbTemp.Append("Error in EngrLogger.WriteToColumns "); sbTemp.Append(sbWrite); sbWrite.Clear(); sbWrite.Append(sbTemp); SendConsoleLine(sbTemp); Debug.Print(sbTemp.ToString()); } } private static void terminateSBWrite() { for (int i = sbWrite.Length - 1; i > 0; i--) { if (sbWrite[i] == '\t') sbWrite.Remove(i, 1); else break; } sbWrite.Append("\r\n"); } private static void printVolInfo() { sbTemp.Clear(); sbTemp.Append("Volume Total Size: "); sbTemp.Append(sdVol.TotalSize.ToString()); writeToColumns(ColumnNums.comment, sbTemp); sbTemp.Clear(); sbTemp.Append("Volume Free Space: "); sbTemp.Append(sdVol.TotalFreeSpace.ToString()); writeToColumns(ColumnNums.comment, sbTemp); } private const int numFilesToGet = 20; public static int numFilesOnDisk = 0; public static StringBuilder[] sbFileNames = new StringBuilder[numFilesToGet]; public static void getDirectory() { sbTemp.Clear(); sbTemp.Append(sdVol.RootDirectory); sbTemp.Append("\\data\\"); String[] fileNames = Directory.GetFiles(sbTemp.ToString()); sbTemp.Clear(); sbTemp.Append("Processing GETDIR Console message"); EngrLogger.writeToColumns(sbTemp); numFilesOnDisk = Directory.GetFiles(sdVol.RootDirectory).Length; int fileID = numFilesToGet - 1; int lastFile = numFilesOnDisk - numFilesToGet; if (lastFile < 0) lastFile = 0; if (numFilesOnDisk > 0) { for (int i = lastFile; i < numFilesOnDisk; i++) { try { fileID = numFilesOnDisk - (i + 1); FileInfo fileInfo = new FileInfo(fileNames[i]); sbFileNames[fileID].Clear(); sbFileNames[fileID].Append(fileNames[i]); sbTemp.Clear(); sbTemp.Append("File ID = " + fileID.ToString() + " File Name = " + fileInfo.Name + " Creation Date = " + fileInfo.CreationTime.ToString() + " Length = " + fileInfo.Length.ToString() + " FileNum = " + i.ToString()); Debug.Print(sbTemp.ToString()); sbTemp.AppendLine(); SendConsoleLine(sbTemp); fileID--; } catch { EngrLogger.writeToColumns("Error in getDir get FileInfo"); } } } else { sbTemp.Clear(); sbTemp.Append("No files on disk!"); Debug.Print(sbTemp.ToString()); sbTemp.AppendLine(); SendConsoleLine(sbTemp); } } public static void closeFile() { if (engFileOpen) { engrLogFile.Close(); } engFileOpen = false; } public static int startNewEngAndSciFiles() { if (engFileOpen) { sbTemp.Clear(); sbTemp.Append("Closing old .eng file"); EngrLogger.writeToColumns(sbTemp); EngrLogger.closeFile(); } openNewEngFile(); sbTemp.Clear(); sbTemp.Append("Opened new engr log file: "); sbTemp.Append(EngrLogger.fileName); EngrLogger.writeToColumns(sbTemp); EngrLogger.writeToColumns("Closing old .msg file"); SciLogger.closeMsgFile(); SciLogger.openNewMsgFile(); EngrLogger.writeToColumns("Opened new .msg file"); EngrLogger.writeToColumns("Closing old .log file"); SciLogger.closeLogFile(); SciLogger.openNewLogFile(); EngrLogger.writeToColumns("Opened new .log file"); SV.UploadLastEngrFile = true; return (1); } public static StringBuilder fileName = new StringBuilder(128); public static StringBuilder fileNameBase = new StringBuilder(128); public static void openNewEngFile() { if (engFileOpen) closeFile(); timeNow = DateTime.Now; try { //Check for Data Directory, create if neccessary if (!Directory.Exists(sdVol.RootDirectory + "\\data\\")) Directory.CreateDirectory(sdVol.RootDirectory + "\\data\\"); fileName.Clear(); fileName.Append(sdVol.RootDirectory); fileName.Append("\\data\\"); fileName.Append(missionConfig.platformName); fileName.Append('.'); fileName.Append((SV.ProfileNum+1).ToString("d3")); fileName.Append('.'); //TODO P3 probably need to get rid of the rest of this for production deployments fileName.Append(timeNow.Year.ToString()); fileName.Append("-"); fileName.Append(timeNow.Month.ToString()); fileName.Append("-"); fileName.Append(timeNow.Day.ToString()); fileName.Append("T"); fileName.Append(timeNow.Hour.ToString()); fileName.Append("-"); fileName.Append(timeNow.Minute.ToString()); fileName.Append("-"); fileName.Append(timeNow.Second.ToString()); fileNameBase.Clear(); fileNameBase.Append(fileName); fileName.Append(".eng"); engrLogFile = new FileStream(fileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write); if (engrLogFile.CanWrite) { engFileOpen = true; Thread.Sleep(1000); lock (loggerLock) { engrLogFile.Write(UTF8Encoding.UTF8.GetBytes(columnNames.ToString()), 0, columnNames.Length); } SendConsoleLine(columnNames); Debug.Print(columnNames.Replace(Literals.crlfString, Literals.spaceString).ToString()); } else { sbTemp.Clear(); sbTemp.Append("Error opening EngrLogger"); SendConsoleLine(sbTemp); engFileOpen = false; //TODO P1 Does this need to set recovery mode? } } catch (Exception e) { sbTemp.Clear(); sbTemp.Append("Error opening EngrLogger"); SendConsoleLine(sbTemp); } finally { engFileOpen = false; } } } }