using System; using System.IO; using System.Text; using System.IO.Ports; using System.Threading; using Microsoft.SPOT; namespace CPF { class BTConsole { private static SerialPort consolePort = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One); public static XModem Modem = new XModem(consolePort, XModem.Variants.XModem1K); private static StructQueue.qStruct qS = new StructQueue.qStruct(); public static bool BTSend = true; public static void OpenConsolePort() { if (!consolePort.IsOpen) { consolePort.Open(); } consolePort.DiscardInBuffer(); consolePort.DiscardOutBuffer(); consolePort.DataReceived += new SerialDataReceivedEventHandler(consolePortDataReceived); qS.byteArray = new byte[StructQueue.qRecordWidth]; } private static StringBuilder uploadStartComment = new StringBuilder(128); private static StringBuilder sbFUS = new StringBuilder("File Upload Successful"); private static StringBuilder sbFUF = new StringBuilder("File Upload Failed"); private static StringBuilder uploadStartPrefix = new StringBuilder(128); private static StringBuilder uploadPrefix2 = new StringBuilder(128); public static MemoryStream DataMemoryStream = new MemoryStream(); // Grows dynamically public static byte[] DataArray = new byte[0]; private static FileStream uploadFile; private static int uploadFileLength = 0; private static int maxFileSize = 524288; private static Object BTLock = new Object(); public static void detachEventHandler() { //Detach the "regular" console port event handler so the XModem class attach its event handler consolePort.DataReceived -= consolePortDataReceived; } private static StringBuilder sbWaiting = new StringBuilder("Waiting 30 seconds. Setup your PC to receive a file using XModem-1K protocol"); private static StringBuilder sbDoneWaitingWhole = new StringBuilder("Starting XMODEM whole file upload"); private static StringBuilder sbDoneWaitingPieces = new StringBuilder("Starting XMODEM file upload in pieces"); public static void uploadDataFile(StringBuilder uploadFileName) { Debug.Print("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 consolePort.DataReceived -= consolePortDataReceived; SendLine(sbWaiting); qS.timeStamp = DateTime.Now; qS.state = Program.CPFState; qS.qRecordType = StructQueue.QRecordType.BTConsole; Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWaiting.ToString()), qS.byteArray, sbWaiting.Length); lock (BTLock) { Program.sQueue.Enqueue(qS); } Thread.Sleep(30000); qS.timeStamp = DateTime.Now; qS.state = Program.CPFState; qS.qRecordType = StructQueue.QRecordType.BTConsole; if (uploadFileLength < maxFileSize) { DataArray = new byte[uploadFileLength]; uploadFile.Read(DataArray, 0, uploadFileLength); SendLine(sbDoneWaitingWhole); Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingWhole.ToString()), qS.byteArray, sbDoneWaitingWhole.Length); lock (BTLock) { Program.sQueue.Enqueue(qS); } SendFileWhole(); } else { SendLine(sbDoneWaitingPieces); Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingPieces.ToString()), qS.byteArray, sbDoneWaitingPieces.Length); lock (BTLock) { Program.sQueue.Enqueue(qS); } SendFilePieces(); } uploadFile.Close(); Array.Clear(DataArray, 0, DataArray.Length); //Re-attach "regular" console port event handler consolePort.DataReceived += consolePortDataReceived; } //public static void uploadDataFileWhole(StringBuilder uploadFileName) //{ // //The XModem SendFileWhole method expects the whole file to be in a byte array in memory // //There is a SendFilePieces for really big files // Debug.Print("Starting uploadDataFileWhole"); // uploadFile = new FileStream(uploadFileName.ToString(), FileMode.Open, FileAccess.Read); // uploadFileLength = (int)uploadFile.Length; // DataArray = new byte[uploadFileLength]; // uploadFile.Read(DataArray, 0, uploadFileLength); // //Detach the "regular" console port event handler so the XModem class attach its event handler // consolePort.DataReceived -= consolePortDataReceived; // SendLine(sbWaiting); // qS.timeStamp = DateTime.Now; // qS.state = Program.CPFState; // qS.qRecordType = StructQueue.QRecordType.BTConsole; // Array.Copy(UTF8Encoding.UTF8.GetBytes(sbWaiting.ToString()), qS.byteArray, sbWaiting.Length); // lock (BTLock) { Program.sQueue.Enqueue(qS); } // Thread.Sleep(30000); // SendLine(sbDoneWaitingWhole); // qS.timeStamp = DateTime.Now; // qS.state = Program.CPFState; // qS.qRecordType = StructQueue.QRecordType.BTConsole; // Array.Copy(UTF8Encoding.UTF8.GetBytes(sbDoneWaitingWhole.ToString()), qS.byteArray, sbDoneWaitingWhole.Length); // lock (BTLock) { Program.sQueue.Enqueue(qS); } // SendFileWhole(); // //Re-attach "regular" console port event handler // consolePort.DataReceived += consolePortDataReceived; //} private static StringBuilder sbTemp = new StringBuilder(512); private static byte[] byteTemp = new byte[1024]; public static void SendLine(StringBuilder sbLineToSend) { if (BTSend) { sbTemp.Clear(); sbTemp.Append(sbLineToSend); Array.Clear(byteTemp, 0, byteTemp.Length); Array.Copy(UTF8Encoding.UTF8.GetBytes(sbTemp.ToString()), byteTemp, sbTemp.Length); consolePort.Write(byteTemp, 0, byteTemp.Length); } } public static void CloseConsolePort() { consolePort.Close(); } private static byte[] consolePortReadBytes = new byte[64]; private static byte[] consolePortInBytes = new byte[16]; 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 static object consoleLock = new object(); private static void consolePortDataReceived(object sender, SerialDataReceivedEventArgs e) { consolePortBytesToRead = consolePort.BytesToRead; try { consolePort.Read(consolePortReadBytes, 0, consolePortBytesToRead); } catch { Debug.Print("Console Read Exception"); //TODO 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 = LF { if (consolePortInBytes[0] == STX) //only move on to byte index 1 after we get a LF { consolePortInBytesIndex = consolePortInBytesIndex + 1; } } else { if (consolePortInBytes[consolePortInBytesIndex] == ETX) { //Array.Clear(message, 0, message.Length); //Array.Copy(header, message, header.Length); //Array.Copy(consolePortInBytes, 1, message, header.Length, consolePortInBytesIndex); //lock (consoleLock) { Program.MessageQueue.Enqueue(message); } Array.Clear(qS.byteArray, 0, qS.byteArray.Length); Array.Copy(consolePortInBytes, qS.byteArray, consolePortInBytesIndex); qS.timeStamp = DateTime.Now; qS.state = Program.CPFState; qS.qRecordType = StructQueue.QRecordType.BTConsole; lock (consoleLock) { Program.sQueue.Enqueue(qS); } consolePortInBytesIndex = 0; messageNum = messageNum + 1; Array.Clear(consolePortInBytes, 0, consolePortInBytes.Length); Array.Clear(consolePortReadBytes, 0, consolePortReadBytes.Length); } else { consolePortInBytesIndex = consolePortInBytesIndex + 1; } } } } public static void ReceiveFileWhole() { // Passing an empty MemoryStream object to the XModem.Receive() method indicates that the user wishes to receive the entire // file in one big lump. NOTE: If using this technique, your device must have enough contiguous RAM to support the expected // file size. Contiguous RAM is the largest memory that can be allocated to a single object. It is usually much smaller // than a device's "total" RAM. // Start the XModem receive process. This method blocks until the transfer has terminated (either through successful // end-of-file, or due to errors such as timeouts, excessive retries, etc). You can examine the termination reason to // determine under what conditions the transfer has stopped. XModem.TerminationReasonEnum terminationReason = Modem.Receive(DataMemoryStream); if (terminationReason == XModem.TerminationReasonEnum.EndOfFile) { Debug.Print("LUMP FILE RECEIVE SUCCESSFUL!"); // Transfer successful, so convert MemoryStream to byte array DataArray = DataMemoryStream.ToArray(); // Because XModem packets are a fixed size, if the original transmitted file does not fill a packet completely, // padding bytes are added until the packet is the required size. The padding byte is usually the SUB symbol // (byte value 26). You may strip away these padding bytes if desired. This may be a problem, however, // if one or more SUB bytes dangling at the end is actually a legitimate part of the file (rare, but theoretically // possible). // In this example, we will assume that consecutive dangling SUB bytes are never a legitimate part of the file, // and we can strip them away so that the received file will match its original size. DataArray = Modem.TrimPaddingBytesFromEnd(DataArray); } else { // Something went wrong during the transfer. You may examine the termination reason and respond accordingly. Debug.Print("LUMP FILE RECEIVE FAILED!"); } } public static bool SendFileWhole() { int numBytesSuccessfullySent = 0; bool success = false; numBytesSuccessfullySent = Modem.Send(DataArray); if (numBytesSuccessfullySent == DataArray.Length && Modem.TerminationReason == XModem.TerminationReasonEnum.EndOfFile) { SendLine(sbFUS); qS.timeStamp = DateTime.Now; qS.state = Program.CPFState; qS.qRecordType = StructQueue.QRecordType.BTConsole; Array.Copy(UTF8Encoding.UTF8.GetBytes(sbFUS.ToString()), qS.byteArray, sbFUS.Length); lock (BTLock) { Program.sQueue.Enqueue(qS); } success = true; } else { SendLine(sbFUF); qS.timeStamp = DateTime.Now; qS.state = Program.CPFState; qS.qRecordType = StructQueue.QRecordType.BTConsole; Array.Copy(UTF8Encoding.UTF8.GetBytes(sbFUF.ToString()), qS.byteArray, sbFUF.Length); lock (BTLock) { Program.sQueue.Enqueue(qS); } success = false; } return (success); } public static 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!"); } } }