using System; using System.IO; using System.IO.Ports; using System.Text; using System.Threading; using SWModules; namespace HWModules { public sealed class BTConsole { public NativeUART IOPort; private BTConsole() { IOPort = new NativeUART("BTconsole", processMessage, "COM4", 115200, Parity.None, 8, StopBits.One, 5000, 20, true, 36); } private static BTConsole instance; public static BTConsole Instance { get { instance = instance ?? (instance = new BTConsole()); return instance; } } //TODO Need to replace XModem file transfer with Eric's ZModem file transfer //private XModem Modem = new XModem(IOPort, XModem.Variants.XModem1K); //private StructQueue.qStruct qS = new StructQueue.qStruct(); //public event QueueItem EnqueueItem; private MessageQueue _messageQueue; private QRecord _msgQEntry; public bool IsOpen { get { return IOPort.IsOpen; } } public static bool BTSend = true; public void init() { if (!IOPort.IsOpen) { IOPort.Open(); } IOPort.DiscardInBuffer(); IOPort.DiscardOutBuffer(); IOPort.DataReceived += new SerialDataReceivedEventHandler(consolePortDataReceived); //qS.byteArray = new byte[StructQueue.byteArrayWidth]; _messageQueue = MessageQueue.Instance; _msgQEntry = new QRecord(processMessage) {type = QRecordType.BTConsole}; } private static StringBuilder uploadStartComment = new StringBuilder(128); private static readonly byte[] baFUS = Encoding.UTF8.GetBytes("File Upload Successful"); private static readonly byte[] baFUF = Encoding.UTF8.GetBytes("File Upload Failed"); public static byte[] DataArray = new byte[0]; private static FileStream uploadFile; private static int uploadFileLength = 0; private static int maxFileSize = 524288; public void detachEventHandler() { //Detach the "regular" console port event handler so the XModem class attach its event handler IOPort.DataReceived -= consolePortDataReceived; } private static readonly byte[] baWaiting = Encoding.UTF8.GetBytes("Waiting 30 seconds. Setup your PC to receive a file using XModem-1K protocol"); private static readonly byte[] baDoneWaitingWhole = Encoding.UTF8.GetBytes("Starting XMODEM whole file upload"); private static readonly byte[] baDoneWaitingPieces = Encoding.UTF8.GetBytes("Starting XMODEM file upload in pieces"); public void uploadDataFile(StringBuilder uploadFileName) { EngrLogger.writeToColumns("Starting uploadDataFile"); uploadFile = new FileStream(uploadFileName.ToString(), FileMode.Open, FileAccess.Read); uploadFileLength = (int)uploadFile.Length; //Detach the "regular" console port event handler so the XModem class attach its event handler IOPort.DataReceived -= consolePortDataReceived; SendLine(baWaiting, baWaiting.Length); Enqueue(baWaiting, baWaiting.Length); Thread.Sleep(30000); if (uploadFileLength < maxFileSize) { DataArray = new byte[uploadFileLength]; uploadFile.Read(DataArray, 0, uploadFileLength); SendLine(baDoneWaitingWhole,baDoneWaitingWhole.Length); Enqueue(baDoneWaitingWhole, baDoneWaitingWhole.Length); //SendFileWhole(); } else { SendLine(baDoneWaitingPieces, baDoneWaitingPieces.Length); Enqueue(baDoneWaitingPieces, baDoneWaitingPieces.Length); //SendFilePieces(); } uploadFile.Close(); Array.Clear(DataArray, 0, DataArray.Length); //Re-attach "regular" console port event handler IOPort.DataReceived += consolePortDataReceived; } //TODO P0 make sure all the buffers from SBDWT, through the queue and through this are large enough //TODO P0 change recovery mode so it creates a minimum length message for SBD and everything else protected static StringBuilder sbTemp = new StringBuilder(2048); private static byte[] byteTemp = new byte[2048]; public void SendLine(StringBuilder sbLineToSend) { if (BTSend) { sbTemp.Clear(); sbTemp.Append(sbLineToSend); sbTemp.AppendLine(); Array.Clear(byteTemp, 0, byteTemp.Length); Array.Copy(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), byteTemp, sbTemp.Length); IOPort.Write(byteTemp, 0, byteTemp.Length); } } public void SendLine(byte[] baLineToSend, int nbytes) { if (BTSend) { IOPort.Write(baLineToSend, 0, nbytes); } } public void CloseConsolePort() { IOPort.Close(); } private static double simPressure = 0.0; public static bool DirectoryValid { get; set; } public static int uploadEngrFileNum = -1; private static readonly StringBuilder sbConsoleCommand = new StringBuilder(32); private static StringBuilder sbCommand = new StringBuilder(64); private static StringBuilder sbInBytes = new StringBuilder(256); private static byte[] commandBytes = new byte[64]; private static readonly byte[] LF = new byte[] { 13 }; private static readonly byte[] dollarSign = new byte[] { (byte)'$' }; //private static byte[] returnedByteArray = new byte[StructQueue.byteArrayWidth]; private static byte[] sbdwtByteArray = new byte[1024]; private static byte[] sbdwtHeader = new byte[9] { (byte)65, (byte)84, (byte)43, (byte)83, (byte)66, (byte)68, (byte)87, (byte)84, (byte)61 }; // AT+SBDWT= private static byte[] sbdrtHeader = UTF8Encoding.UTF8.GetBytes("SBDRT:"); private static byte[] sbdRecoveryTrue = UTF8Encoding.UTF8.GetBytes("$RECOVERYTRUE"); private static byte[] sbdRecoveryFalse = UTF8Encoding.UTF8.GetBytes("$RECOVERYFALSE"); private static byte[] sbdGo = UTF8Encoding.UTF8.GetBytes("$GO"); private static byte[] sbdMaxProfiles = UTF8Encoding.UTF8.GetBytes("$MAXPROFILES"); private static byte[] sbdMissionTimeout = UTF8Encoding.UTF8.GetBytes("$MISSIONTIMEOUT"); private static byte[] sbdParkTime = UTF8Encoding.UTF8.GetBytes("$PARKTIME"); private static byte[] sbdParkPressure = UTF8Encoding.UTF8.GetBytes("$PARKPRESSURE"); public void checkForConsoleCommands(byte[] inBytes) { sbInBytes.Clear(); sbInBytes.Append(UTF8Encoding.UTF8.GetChars(inBytes)); sbTemp.Clear(); sbTemp.Append("Received SBD Command: "); sbTemp.Append(UTF8Encoding.UTF8.GetChars(inBytes).ToString()); for (int i = 0; i < 10; i++) { sbCommand.Clear(); sbInBytes.Clear(); if (GHI.Utilities.Arrays.Contains(inBytes, dollarSign) >= 0) { try { if (Utils.getField(inBytes, (byte)';', i, ref commandBytes) > 0) { Array.Copy(LF, 0, commandBytes, Array.IndexOf(commandBytes, 0), 1); //processConsoleCommand needs LF terminated commands sbCommand.Append(UTF8Encoding.UTF8.GetChars(commandBytes)); // //Debug.Print("commandBytes: " + sbCommand.ToString() + ":End of command"); } else break; if (GHI.Utilities.Arrays.Contains(commandBytes, sbdRecoveryTrue) >= 0) { EngrLogger.writeToColumns("Received SBD: RecoveryTrue"); processConsoleCommand(UTF8Encoding.UTF8.GetBytes("$RECOVERYTRUE\r\n")); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdRecoveryFalse) >= 0) { EngrLogger.writeToColumns("Received SBD: RecoveryFalse"); processConsoleCommand(UTF8Encoding.UTF8.GetBytes("$RECOVERYFALSE\r\n")); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdGo) >= 0) //TODO P3 Add a don't go command { EngrLogger.writeToColumns("Received SBD: Go"); processConsoleCommand(UTF8Encoding.UTF8.GetBytes("$GO\r\n")); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdMaxProfiles) >= 0) { EngrLogger.writeToColumns("Received SBD: MaxProfiles"); processConsoleCommand(commandBytes); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdMissionTimeout) >= 0) { EngrLogger.writeToColumns("Received SBD: MissionTimeout"); processConsoleCommand(commandBytes); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdParkTime) >= 0) { EngrLogger.writeToColumns("Received SBD: ParkTime"); processConsoleCommand(commandBytes); } else if (GHI.Utilities.Arrays.Contains(commandBytes, sbdParkPressure) >= 0) { EngrLogger.writeToColumns("Received SBD: ParkPressure"); processConsoleCommand(commandBytes); } } catch { EngrLogger.writeToColumns("Error processing SBD checkForConsoleCommands"); } } } } //TODO P1 seem to have to send commands twice to get them processesed public void processConsoleCommand(byte[] consoleCommandMessage) { const byte byteToken = 44; int tokenIndex = -1; double argValue = -1; //Ignore command if it doesn't have a CR if (Array.IndexOf(consoleCommandMessage, 13) >= 0) { BTConsole.BTSend = true; sbTemp.Clear(); sbTemp.Append("Processing Console Command: "); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, 0, Array.IndexOf(consoleCommandMessage, 13))); EngrLogger.writeToColumns(sbTemp); //Check if the command has an argument tokenIndex = Array.IndexOf(consoleCommandMessage, byteToken); //If it has an argument, process it here if (tokenIndex > 0) { sbTemp.Clear(); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, tokenIndex + 1, (Array.IndexOf(consoleCommandMessage, 13) - tokenIndex - 1))); try { argValue = double.Parse(sbTemp.ToString()); sbTemp.Clear(); sbTemp.Append("Console arg value = "); sbTemp.Append(argValue.ToString()); //Debug.Print(sbTemp.ToString()); } catch { sbTemp.Clear(); sbTemp.Append("Error parsing console command argument: "); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, 0, (Array.IndexOf(consoleCommandMessage, 13) - 1))); EngrLogger.writeToColumns(sbTemp); } sbTemp.Clear(); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, 1, tokenIndex - 1)); switch (sbTemp.ToString().ToUpper()) { case "SIMP": simPressure = argValue; sbTemp.Clear(); sbTemp.Append("Sim P = "); sbTemp.Append(simPressure.ToString()); //Debug.Print(sbTemp.ToString()); break; case "UPENGR": SV.UploadEngrFile = true; uploadEngrFileNum = (int)argValue; break; case "MAXPROFILES": if (((int)argValue > 0) && ((int)argValue < 200)) { sbTemp.Clear(); sbTemp.Append("Setting Max Number of Profiles to: "); sbTemp.Append(((float)argValue).ToString()); EngrLogger.writeToColumns(sbTemp.ToString()); SV.MaxProfiles = (int)argValue; } else EngrLogger.writeToColumns("MaxProfiles value out of range"); break; case "MISSIONTIMEOUT": if (((int)argValue > 0) && ((int)argValue < 120)) { SV.MissionTimeoutTS = new TimeSpan(0, (int)argValue, 0, 0); SV.MissionTimedOut = false; sbTemp.Clear(); sbTemp.Append("Set MissionTimeout hours to: "); sbTemp.Append(SV.MissionTimeoutTS.ToString()); EngrLogger.writeToColumns(sbTemp.ToString()); } else EngrLogger.writeToColumns("MissionTimeout value out of range"); break; case "PARKTIME": if (((int)argValue > 0) && ((int)argValue < (2 * 24 * 3600))) { Mission.descendTable[0].parkTime = new TimeSpan(0, 0, (int)argValue, 0); sbTemp.Clear(); sbTemp.Append("Park time minutes to: "); sbTemp.Append(Mission.descendTable[0].parkTime.ToString()); EngrLogger.writeToColumns(sbTemp.ToString()); } else EngrLogger.writeToColumns("ParkTime value out of range"); break; case "PARKPRESSURE": if (((float)argValue > 4) && ((float)argValue < 300)) { sbTemp.Clear(); sbTemp.Append("Setting Park Pressure to: "); sbTemp.Append(((float)argValue).ToString()); EngrLogger.writeToColumns(sbTemp.ToString()); Mission.descendTable[0].pressure = (float)argValue; } else EngrLogger.writeToColumns("ParkPressure value out of range"); break; } } else //process commands without an argument { sbTemp.Clear(); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, 1, (Array.IndexOf(consoleCommandMessage, 13) - 1))); switch (sbTemp.ToString().ToUpper()) { case "GETDIR": EngrLogger.getDirectory(); DirectoryValid = true; break; case "GO": SV.SurfaceOpsGo = true; break; case "RECOVERYTRUE": SV.RecoveryMode = true; sbTemp.Clear(); sbTemp.Append("Recovery mode set true"); EngrLogger.writeToColumns(sbTemp); break; case "RECOVERYFALSE": SV.RecoveryMode = false; sbTemp.Clear(); sbTemp.Append("Recovery mode set false"); EngrLogger.writeToColumns(sbTemp); break; case "STARTDOG": sbTemp.Clear(); sbTemp.Append("Starting Watchdog"); EngrLogger.writeToColumns(sbTemp); WatchDog.startWatchdog(); break; case "FAKEMTSBD": NAL_A3LAR.fakeMTSBD(); break; case "EXIT": SV.CurrentState = CPFStates.exit; // EngrLogger.CurrentState = cpfStatesArray[(int)SV.CurrentState].stateName; break; default: sbTemp.Clear(); sbTemp.Append("Invalid command, try again"); SendLine(sbTemp); break; } } } else { sbTemp.Clear(); sbTemp.Append("Received non-CR terminated Console Command: "); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consoleCommandMessage, 0, (Array.IndexOf(consoleCommandMessage, 0) - 1))); EngrLogger.writeToColumns(sbTemp); } sbConsoleCommand.Clear(); sbConsoleCommand.Append("NONE"); } public void processMessage(QRecord qRecord) { processConsoleCommand(qRecord.byteArray); } private static byte[] consolePortReadBytes = new byte[64]; private static byte[] consolePortInBytes = new byte[128]; private static byte[] header = UTF8Encoding.UTF8.GetBytes("CON"); private static byte[] message = new byte[256]; private static byte STX = 36; //Start of text = "$" private static byte ETX = 10; //End of text = LF private static int messageNum = 0; private static int consolePortBytesToRead = 0; private static int consolePortInBytesIndex = 0; private void consolePortDataReceived(object sender, SerialDataReceivedEventArgs e) { consolePortBytesToRead = IOPort.BytesToRead; try { IOPort.Read(consolePortReadBytes, 0, consolePortBytesToRead); } catch { //Debug.Print("Console Read Exception"); //TODO P2 do something smarter here } for (int i = 0; i < consolePortBytesToRead; i++) { consolePortInBytes[consolePortInBytesIndex] = consolePortReadBytes[i]; if (consolePortInBytesIndex == 0) //Check to make sure first character is = STX { if (consolePortInBytes[0] == STX) //only move on to byte index 1 after we get a STX { consolePortInBytesIndex = consolePortInBytesIndex + 1; } } else { if (consolePortInBytes[consolePortInBytesIndex] == ETX) { Array.Clear(_msgQEntry.byteArray, 0, _msgQEntry.byteArray.Length); Enqueue(consolePortInBytes, consolePortInBytesIndex); sbTemp.Clear(); sbTemp.Append(UTF8Encoding.UTF8.GetChars(consolePortInBytes)); //Debug.Print(sbTemp.ToString()); consolePortInBytesIndex = 0; messageNum = messageNum + 1; Array.Clear(consolePortInBytes, 0, consolePortInBytes.Length); Array.Clear(consolePortReadBytes, 0, consolePortReadBytes.Length); } else { consolePortInBytesIndex = consolePortInBytesIndex + 1; } } } } //private void PrepQS() //{ //qS.timeStamp = DateTime.Now; //qS.state = SV.CurrentState; //qS.qRecordType = StructQueue.QRecordType.BTConsole; //_msgQEntry.Update(); //} private void Enqueue(byte[] msg, int msglen) { //PrepQS(); _msgQEntry.Update(); Array.Copy(msg, _msgQEntry.byteArray, msglen); //EnqueueItem(qS); _messageQueue.Enqueue(_msgQEntry); } //public virtual bool SendFileWhole() //{ // int numBytesSuccessfullySent = 0; // bool success = false; // numBytesSuccessfullySent = Modem.Send(DataArray); // if (numBytesSuccessfullySent == DataArray.Length && Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile) // { // SendLine(baFUS, baFUS.Length); // Enqueue(baFUS, baFUS.Length); // success = true; // } // else // { // SendLine(baFUF, baFUF.Length); // Enqueue(baFUF, baFUF.Length); // success = false; // } // return (success); //} //public virtual void SendFilePieces() //{ // // In this example, this is the number of bytes to read from the file during each pass, to be packaged for sending. // // This value is completely arbitrary. It may be as small as 1 byte all the way up to a very large value (within device // // memory capacity). This is absolutely independent of XModem packet size. The XMODEM.AddToOutboundPacket() method // // accepts arrays of any size and packetizes arguments automatically, whether they are larger or smaller than // // the chosen packet size. // int desiredBlockSize = maxFileSize; // // Tracks our position in the file // int fileOffset = 0; // // Instruct the modem to begin the send process. // // We MUST call this method first before calling XMODEM.AddToOutboundPacket() otherwise an exception will be thrown. // Modem.Send(); // // Loop until all bytes have been read from "persistent storage". // while (fileOffset < uploadFileLength) // { // int numFileBytesRemaining = uploadFileLength - fileOffset; // // Determine the number of bytes that we can pull from the source file to populate the current block. // int numBytesToPull; // if (numFileBytesRemaining >= desiredBlockSize) // { // // There are enough remaining bytes in the file that we can fill a block completely // numBytesToPull = desiredBlockSize; // } // else // { // // The number of bytes remaining is smaller than the desired block size, so only // // use the available number // numBytesToPull = numFileBytesRemaining; // } // // Instantiate the block that will hold the bytes from persistent storage // byte[] block = new byte[numBytesToPull]; // // Pretend that we are pulling the data from a file in persistent storage // //Array.Copy(DataArray, fileOffset, block, 0, block.Length); // //Do this to read from a file instead of pretending // uploadFile.Seek(fileOffset, SeekOrigin.Begin); // uploadFile.Read(block, 0, numBytesToPull); // // Add the current block of data to the outbound packet. This is a blocking method call. // // If the modem still does not have enough bytes for a packet, the fresh data is added to the pending packet, // // and this method returns immediately. // // If the modem has collected enough data for one or more packets, it transmits each packet and blocks // // until transmission is complete. Note that this process can be relatively quick if the Receiver responds // // quickly, but may be slow if packets have to be re-transmitted or the Receiver does not respond, requiring // // timeouts to expire. // int numBytesSuccessfullySentDuringThisCall = Modem.AddToOutboundPacket(block); // // Determine if the latest packet has been transmitted successfully, or whether an error or timeout has // // resulted in the file transfer being cancelled. // if (Modem.TerminationReason == XModem.TerminationReasonEnum.TransferStillActiveNotTerminated) // { // // So far so good, so proceed normally // fileOffset += numBytesToPull; // } // else // { // // Something bad happened while attempting to send the latest packet // //Debug.Print("PIECEMEAL FILE SEND FAILED!"); // return; // } // } // // Once all bytes have been packetized, we should inform the Receiver that the file is complete. // // The EndFile() method automatically transmits any pending packets followed by the end-of-file byte. // // We MUST do this to finish the file-send process properly. This is a blocking method call // // which blocks until the Receiver acknowledges the end-of-file. // Modem.EndFile(); // // Examine the ultimate outcome of our file send attempt // if (Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile) // //Debug.Print("PIECEMEAL FILE SEND SUCCESSFUL!"); // else // //Debug.Print("PIECEMEAL FILE SEND FAILED!"); //} } }