using System; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Threading; 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 StringBuilder columnTypes = 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 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; public static event SendLineDelegate SendConsoleLine; //public static StateBase[] states; public static StringBuilder CurrentState { get { return _currentState;} set { _currentState.Clear().Append(value); } } 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 } private static StringBuilder[] columnName = new StringBuilder[(int)EngrLiterals.ColumnNums.lastColumn]; private static StringBuilder[] columnType = new StringBuilder[(int)EngrLiterals.ColumnNums.lastColumn]; private static readonly StringBuilder _currentState = new StringBuilder(32); private static Register resetControllerStatus = new Register(0xFFFFFE04); public static bool init(StringBuilder currentstate) { CurrentState = currentstate; var sdStorage = SDStorage.Instance; //Add default event handler for SendConsoleLine event SendConsoleLine += debugLine; //Add event handler for SafeEncoding Notifications SafeEncoding.Notify += EncoderNotification; for (int i = 0; i < (int)EngrLiterals.ColumnNums.lastColumn; i++) { columnName[i] = new StringBuilder(32); columnType[i] = new StringBuilder(32); } EngrLiterals.CreateEngrHeaderLines(columnName, columnType); columnNames.Clear(); columnTypes.Clear(); for (int i = 0; i < (int)EngrLiterals.ColumnNums.lastColumn; i++) { columnNames.Append(columnName[i]); columnNames.Append(Literals.tabChar); columnTypes.Append(columnType[i]); columnTypes.Append(Literals.tabChar); } columnNames.Append(Literals.crlfString); columnTypes.Append(Literals.crlfString); for (int i = 0; i < sbFileNames.Length; i++) sbFileNames[i] = new StringBuilder(128); sdVol = new VolumeInfo("\\SD"); SDMounted = true; CreateDirName(); openNewEngFile(); 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); writeToColumns(sbTemp); printVolInfo(); /* TODO move out of engineering logger 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(1, sbTemp); return (true); } else { sbTemp.Clear(); sbTemp.Append("***ERROR: Engr Logger Failed to Initialize"); EngrLogger.writeToColumns(1, sbTemp); return (false); } } public static void BTSendLine(StringBuilder msg) { if (SendConsoleLine != null) SendConsoleLine(msg); } private static int lastValueIndex; private static int columnNum; private static int valueNum; public static void Comment(string msg) { writeToColumns(msg); } public static void Comment(int btVerbosity, string msg) { writeToColumns(btVerbosity, 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 //This overload does not write to the BT module [MethodImpl(MethodImplOptions.Synchronized)] public static void writeToColumns(params object[] values) { writeToColumns(BTVerbositySetPoint, values); } //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 //This overload does write to the BT module with appropriate verbosity setting //Verbosity = 0: no BT output at all //Verbosity = 1: Basic messages are always sent, minimum verbosity //Verbosity = 5: Maximum verbosity private const int BTVerbositySetPoint = 1; [MethodImpl(MethodImplOptions.Synchronized)] public static void writeToColumns(int BTVerbosity, params object[] values) { if (values == null) return; 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)EngrLiterals.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)EngrLiterals.ColumnNums.dateTime) { timeNow = (DateTime)values[valueNum + 1]; sbWrite.Append(timeNow.ToString(FmtDate)); sbWrite.Append(token); sbWrite.Append(timeNow.ToString(FmtTime)); } else if (columnNum == (int)EngrLiterals.ColumnNums.state) { sbWrite.Append(CurrentState); } else if (columnNum == (int)EngrLiterals.ColumnNums.Msc) { sbWrite.Append(values[valueNum + 1]); } else if (columnNum == (int)EngrLiterals.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)EngrLiterals.ColumnNums.CTDSalinity) || (columnNum == (int)EngrLiterals.ColumnNums.TOpt) || (columnNum == (int)EngrLiterals.ColumnNums.IModem) || (columnNum == (int)EngrLiterals.ColumnNums.GPS) || (columnNum == (int)EngrLiterals.ColumnNums.FLBB) || (columnNum == (int)EngrLiterals.ColumnNums.OCRText) || (columnNum == (int)EngrLiterals.ColumnNums.pumpMotor) ) 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', ' '); if (MissionConfig.DebuggerVerbosityLevel > 3) 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(Encoding.UTF8.GetBytes(sbWrite.ToString()), writeArray, sbWrite.Length); try { engrLogFile.Write(writeArray, 0, sbWrite.Length); if (BTVerbosity >= BTVerbositySetPoint) { if (SendConsoleLine != null) SendConsoleLine(sbWrite); } } catch { sbTemp.Clear(); sbTemp.Append("[ERROR] No File Open: "); sbTemp.Append(sbWrite); if (SendConsoleLine != null) SendConsoleLine(sbTemp); Debug.Print(sbTemp.ToString()); } } else { sbTemp.Clear(); sbTemp.Append("No SD: "); sbTemp.Append(sbWrite); if (SendConsoleLine != null) SendConsoleLine(sbTemp); Debug.Print(sbTemp.ToString()); } } catch { sbTemp.Clear(); sbTemp.Append("[ERROR] in EngrLogger.WriteToColumns "); sbTemp.Append(sbWrite); sbWrite.Clear(); sbWrite.Append(sbTemp); if (SendConsoleLine != null) 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(EngrLiterals.ColumnNums.comment, sbTemp); sbTemp.Clear(); sbTemp.Append("Volume Free Space: "); sbTemp.Append(sdVol.TotalFreeSpace.ToString()); writeToColumns(EngrLiterals.ColumnNums.comment, sbTemp); } private const int numFilesToGet = 20; public static int numFilesOnDisk; 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"); 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(); if (SendConsoleLine != null) SendConsoleLine(sbTemp); } catch { writeToColumns("[ERROR] in getDir get FileInfo"); } } } else { sbTemp.Clear(); sbTemp.Append("No files on disk!"); Debug.Print(sbTemp.ToString()); sbTemp.AppendLine(); if (SendConsoleLine !=null) SendConsoleLine(sbTemp); } } public static void closeEngrLogFile() { if (engFileOpen) { engrLogFile.Flush(); engrLogFile.Close(); sdVol.FlushAll(); } engFileOpen = false; } public static StringBuilder fileName = new StringBuilder(128); public static StringBuilder dirName = new StringBuilder(128); public static int dirNameCount = 1; public static StringBuilder fileNameBase = new StringBuilder(128); public static void openNewEngFile() { if (engFileOpen) { closeEngrLogFile(); } timeNow = DateTime.Now; try { fileName.Clear(); fileName.Append(dirName); fileName.Append(MissionConfig.PlatformName); fileName.Append('.'); fileName.Append((SV.ProfileNum+1).ToString("d3")); fileNameBase.Clear(); fileNameBase.Append(fileName); fileName.Append(".eng"); engrLogFile = new FileStream(fileName.ToString(), FileMode.OpenOrCreate, FileAccess.Write); if (!engrLogFile.CanWrite) { throw new IOException("Cannot Open ENG File:" + fileName); } Thread.Sleep(100); lock (loggerLock) { engrLogFile.Write(Encoding.UTF8.GetBytes(columnNames.ToString()), 0, columnNames.Length); engrLogFile.Write(Encoding.UTF8.GetBytes(columnTypes.ToString()), 0, columnTypes.Length); } //if (SendConsoleLine !=null) SendConsoleLine(columnNames); sbTemp.Clear(); sbTemp.Append("Opened new engr log file: "); sbTemp.Append(fileName); writeToColumns(sbTemp); engFileOpen = true; } catch (Exception e) { sbTemp.Clear().Append(e);//TODO P1 Does this need to set recovery mode if (SendConsoleLine != null) SendConsoleLine(sbTemp); engFileOpen = false; } } private static void CreateDirName() { timeNow = DateTime.Now; dirName.Clear(); dirName.Append(sdVol.RootDirectory); dirName.Append("\\data\\"); dirName.Append(timeNow.Year.ToString()); dirName.Append("-"); dirName.Append(timeNow.Month.ToString()); dirName.Append("-"); dirName.Append(timeNow.Day.ToString()); dirName.Append("T"); dirName.Append(timeNow.Hour.ToString()); dirName.Append("-"); dirName.Append(timeNow.Minute.ToString()); dirName.Append("-"); dirName.Append(timeNow.Second.ToString()); dirName.Append("\\"); if (Directory.Exists(dirName.ToString())) { //This should never happen given it takes more than 1 second for the program to get here dirName.Append(dirNameCount.ToString()); dirName.Append("\\"); dirNameCount++; if (dirNameCount > 10) { throw new IOException("Too many directories with same name:" + dirName); } Directory.CreateDirectory(dirName.ToString()); } else Directory.CreateDirectory(dirName.ToString()); } private static void EncoderNotification(StringBuilder sb) { writeToColumns(sb); } } }