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!");
}
}
}