using System; using System.Threading; using Microsoft.SPOT; using System.IO; using System.IO.Ports; namespace YOUR_NAMESPACE_HERE { /// /// Demonstrates XModem Receive and Send features. /// /// SUGGESTED PROCEDURE: /// /// Step 1: /// Determine if you want the modem to operate as if it were receiving/sending a "small" file (one that fits in your device's memory) /// or whether you want the modem to operate as if it were receiving/sending a "large" file (one that exceeds your device's memory). /// Set PretendFileFitsRAM = True for "small" file. /// Set PretendFileFitsRAM = False for "large" file. /// /// Step 2: /// Determine if you want to use XModem-Checksum, XModem-CRC, or XModem-1K. Specify the desired variant when instantiating the /// XMODEM object. /// /// Step 3: /// Using the PC terminal program of your choice, SEND a file to your FEZ using the chosen XModem protocol. /// The file should be small enough to fit inside your FEZ's memory (purely for demonstration purposes). /// In the real world, the file can be of any size, but this demo assumes that the file is small so that different /// features can be demonstrated. /// /// Step 4: /// After a 30 second delay (customizable with the WaitPeriodMilliseconds variable), this demo will turn around and send the /// file back to your PC. Instruct your PC terminal program to RECEIVE a file using your selected XModem protocol before /// this happens. /// /// Step 5: /// Examine the file that was echoed back to your PC and verify that its contents match the original file. /// NOTE: XMODEM may add one or more SUB padding bytes (byte decimal 26, looks like a right-arrow in /// Notepad) to the end of a file in order to produce a uniform packet length. This is a limitation of the XModem /// protocol itself, since packets are a fixed size and must be padded if the data file is shorter than a whole /// packet. XMODEM.TrimPaddingBytesFromEnd() can be used to remove trailing padding bytes. /// public class Program { // Instantiate serial port that the XModem object will use public static SerialPort COMPort = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One); // Instantiate the XModem object, specifying the particular XModem variant to use public static XMODEM Modem = new XMODEM(COMPort, XMODEM.Variants.XModem1K); // Data structures for data-received and data-to-send public static MemoryStream DataMemoryStream = new MemoryStream(); // Grows dynamically public static byte[] DataArray = new byte[0]; public static void Main() { bool PretendFileFitsRAM = true; int WaitPeriodMilliseconds = 30000; Debug.Print("Send a file using the chosen XModem protocol now."); if (PretendFileFitsRAM == true) { // Simulates a "small" file that can fit in device memory. ReceiveFileWhole(); Debug.Print("Waiting for 30 seconds. Instruct your PC to Receive a file using your chosen XModem protocol."); Thread.Sleep(WaitPeriodMilliseconds); Debug.Print("The file will now be echoed back to the PC."); SendFileWhole(); } else { // Simulates a "large" file that exceeds device memory. ReceiveFilePieces(); Debug.Print("Waiting for 30 seconds. Instruct your PC to Receive a file using your chosen XModem protocol."); Thread.Sleep(WaitPeriodMilliseconds); Debug.Print("The file will now be echoed back to the PC."); SendFilePieces(); } // The unreachable block below simply demonstrates additional features if (false) { // Cancels an active file transfer and informs the other party of the cancellation Modem.CancelFileTransfer(); // Non-standard data packet sizes can be implemented by redefining the nominal sizes for "1024" or "128" byte packets Modem.Packet1024NominalSize = 2048; // Redefine "1024"-sized packets to be 2048 bytes instead Modem.Packet128NominalSize = 300; // Redefine "128"-sized packets to be 300 bytes instead // XMODEM sender officially sends EOT to indicate end-of-file. However, you can specify a different byte value // to send upon reaching the end-of-file Modem.EndOfFileByteToSend = Modem.EOF; // The end-of-file byte can also be specified via the XMODEM constructor // When sending data, if the available data bytes fall short of the required packet size, the Sender pads the packet // with padding bytes. This is normally SUB, but a different byte value can be specified. This can also be specified // via the XMODEM constructor. Modem.PaddingByte = 32; // SPACE is the new padding byte // Byte values can also be arbitrarily redefined. Here is a small sampling: Modem.ACK = 14; Modem.NAK = 28; Modem.CAN = 96; Modem.SUB = 32; // Another way to redefine the padding byte // If using XMODEM to receive data, the Receiver sends a byte to tell the Sender when to start sending data. // It sends this byte repeatedly a certain number of times. For XMODEM-1K/XMODEM-CRC transfers, the byte sent // is normally the letter C. The official specification stipulates that if the Sender still has not sent the first packet // after the maximum number of file initiation bytes have been sent, the Receiver should fall back to the XMODEM-Checksum // protocol and send NAK instead. This implementation follows this behavior, but this can be disabled. Modem.ReceiverFallBackAllowed = false; // Default is True // CUSTOM TIMEOUTS, RETRIES, ETC. CAN BE SPECIFIED: // The number of milliseconds a Receiver waits in between successive sending of the file initiation byte Modem.ReceiverFileInitiationRetryMillisec = 1000; // 1 second // The total number of file initiation bytes a Receiver sends before it either times out or falls back // to an older protocol Modem.ReceiverFileInitiationMaxAttempts = 60; // 1 minute timeout // While Receiving data, if the Sender suddenly goes silent, this is the maximum amount of time a Receiver will wait // before prompting the Sender to resend the packet by transmitting NAK Modem.ReceiverTimeoutMillisec = 5000; // 5 seconds // The maximum number of consecutive times a Receiver will request a packet to be repeated, otherwise it terminates Modem.ReceiverMaxConsecutiveRetries = 3; // The number of consecutive CAN bytes a Sender or Receiver must RECEIVE in order to cancel the file transfer Modem.NumCancellationBytesRequired = 2; // The number of consecutive CAN bytes a Sender or Receiver SENDS if the user cancels the file transfer Modem.NumCancellationBytesToSend = 8; // The maximum number of consecutive retries that a Sender will attempt to resend a packet, otherwise it terminates Modem.MaxSenderRetries = 3; // When sending data, if the Sender does not receive an ACK or NAK from the Receiver, it will resend the packet // after this amount of time has elapsed without a response Modem.SenderPacketRetryTimeoutMillisec = 60000; // 1 minute // When sending data, if the Sender has heard absolutely nothing from the Receiver during this amount of time, // the transfer is aborted Modem.SendInactivityTimeoutMillisec = 120000; // 2 minutes } } /// /// Demonstrates a file being received in its entirety and stored in a single data object. /// This should only be used for small files that can fit in available RAM. /// 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!"); } } /// /// Demonstrates a file being received packet-by-packet. /// This is the recommended technique for receiving very large files that cannot fit entirely in memory. /// public static void ReceiveFilePieces() { // Subscribe to the PacketReceived event. This event is raised every time a valid data packet // is received by the modem. Modem.PacketReceived += new XMODEM.PacketReceivedEventHandler(Modem_PacketReceived); // 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. Modem.Receive(); // After the receive session has terminated, you may examine the termination reason to determine if the file transfer // stopped because of an error and respond appropriately. if (Modem.TerminationReason == XMODEM.TerminationReasonEnum.EndOfFile) { Debug.Print("PIECEMEAL FILE RECEIVE SUCCESSFUL!"); } else { // Something went wrong during the transfer. You may examine the termination reason and respond accordingly. Debug.Print("PIECEMEAL FILE RECEIVE FAILED!"); } } /// /// XMODEM.PacketReceived event handler. /// /// The XMODEM object that raised the event. /// /// The current valid data packet that has just been received. /// Only pure data is included. XModem overhead (header bytes, checksums, etc.) are omitted. /// /// /// Boolean flag that indicates if this packet is the final packet expected. /// True if the end-of-file byte was received, and the current packet is therefore the last one. /// False if the end-of-file byte has not been received, and more packets are still expected. /// private static void Modem_PacketReceived(XMODEM sender, byte[] packet, bool endOfFileDetected) { // The PacketReceived event is only raised once an incoming packet has been validated and confirmed // to be error-free (within the limits of the error-checking scheme associated with a particular // XModem variant). if (endOfFileDetected == false) { // A packet was received but more are expected after it. // Do something useful with the newly-received chunk of data, such as writing it to persistent storage, storing it in an // intermediate data structure, parsing it, or passing it to the GHI.Premium.System.SystemUpdate.Load() method, etc.... // If you choose to receive a file piece-by-piece, that means the file being transferred is so large that it cannot be stored // entirely in RAM. Presumably, your process allows you to still do useful work with each packet as they come in, without // having to store all packets in memory. // ***** EXAMPLE ONLY ***** // In this demo, we will add each packet to a MemoryStream. However, you likely will not do this in real life, // since the file may be so large that it will be pointless to store it all in RAM. The received data is being stored // in a MemoryStream simply so it can be regurgitated later as part of this demo. DataMemoryStream.Write(packet, 0, packet.Length); } else { // The final packet in the file has been reached. // 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. packet = Modem.TrimPaddingBytesFromEnd(packet); // Now do something useful with the final packet and perform whatever cleanup is necessary since this is the last // packet in the file. DataMemoryStream.Write(packet, 0, packet.Length); // Convert the example MemoryStream into a byte array so that it can be sent later as part of this demo. DataArray = DataMemoryStream.ToArray(); } } /// /// Demonstrates a file being sent in its enirety in one big lump. This is for files /// that are small enough to fit inside a single object in RAM. /// public static void SendFileWhole() { int numBytesSuccessfullySent = Modem.Send(DataArray); if (numBytesSuccessfullySent == DataArray.Length && Modem.TerminationReason == XMODEM.TerminationReasonEnum.EndOfFile) Debug.Print("LUMP FILE SEND SUCCESSFUL!"); else Debug.Print("LUMP FILE SEND FAILED!"); } /// /// Demonstrates a very large file being read piecemeal from a source (such as persistent storage), and subsequently /// being sent piece by piece. This is for files that are so large that they cannot be stored entirely in RAM. /// 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 = 400; // 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 < DataArray.Length) { int numFileBytesRemaining = DataArray.Length - 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); // 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!"); } } }